]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
fs: symlink restrictions on sticky directories
authorKees Cook <keescook@chromium.org>
Thu, 3 May 2012 05:43:28 +0000 (15:43 +1000)
committerStephen Rothwell <sfr@canb.auug.org.au>
Thu, 3 May 2012 05:46:20 +0000 (15:46 +1000)
A longstanding class of security issues is the symlink-based
time-of-check-time-of-use race, most commonly seen in world-writable
directories like /tmp.  The common method of exploitation of this flaw is
to cross privilege boundaries when following a given symlink (i.e.  a root
process follows a symlink belonging to another user).  For a likely
incomplete list of hundreds of examples across the years, please see:
http://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=/tmp

The solution is to permit symlinks to only be followed when outside a
sticky world-writable directory, or when the uid of the symlink and
follower match, or when the directory owner matches the symlink's owner.

Some pointers to the history of earlier discussion that I could find:

 1996 Aug, Zygo Blaxell
  http://marc.info/?l=bugtraq&m=87602167419830&w=2
 1996 Oct, Andrew Tridgell
  http://lkml.indiana.edu/hypermail/linux/kernel/9610.2/0086.html
 1997 Dec, Albert D Cahalan
  http://lkml.org/lkml/1997/12/16/4
 2005 Feb, Lorenzo Hernández García-Hierro
  http://lkml.indiana.edu/hypermail/linux/kernel/0502.0/1896.html
 2010 May, Kees Cook
  https://lkml.org/lkml/2010/5/30/144

Past objections and rebuttals could be summarized as:

 - Violates POSIX.
   - POSIX didn't consider this situation and it's not useful to follow
     a broken specification at the cost of security.
 - Might break unknown applications that use this feature.
   - Applications that break because of the change are easy to spot and
     fix. Applications that are vulnerable to symlink ToCToU by not having
     the change aren't. Additionally, no applications have yet been found
     that rely on this behavior.
 - Applications should just use mkstemp() or O_CREATE|O_EXCL.
   - True, but applications are not perfect, and new software is written
     all the time that makes these mistakes; blocking this flaw at the
     kernel is a single solution to the entire class of vulnerability.
 - This should live in the core VFS.
   - This should live in an LSM. (https://lkml.org/lkml/2010/5/31/135)
 - This should live in an LSM.
   - This should live in the core VFS. (https://lkml.org/lkml/2010/8/2/188)

This patch is based on the patch in Openwall and grsecurity, along with
suggestions from Al Viro.  I have added a sysctl to enable the protected
behavior, documentation, and an audit notification.

[akpm@linux-foundation.org: move sysctl_protected_sticky_symlinks declaration into .h]
Signed-off-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Ingo Molnar <mingo@elte.hu>
Cc: Matthew Wilcox <matthew@wil.cx>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Rik van Riel <riel@redhat.com>
Cc: Federica Teodori <federica.teodori@googlemail.com>
Cc: Lucian Adrian Grijincu <lucian.grijincu@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Eric Paris <eparis@redhat.com>
Cc: Randy Dunlap <rdunlap@xenotime.net>
Cc: Dan Rosenberg <drosenberg@vsecurity.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Documentation/sysctl/fs.txt
fs/Kconfig
fs/namei.c
include/linux/fs.h
kernel/sysctl.c

index 88fd7f5c8dcd61307171b3af852541d06a984380..01daa8049323c8a868a001bc4c2f4a0df98587b2 100644 (file)
@@ -32,6 +32,7 @@ Currently, these files are in /proc/sys/fs:
 - nr_open
 - overflowuid
 - overflowgid
+- protected_symlinks
 - suid_dumpable
 - super-max
 - super-nr
@@ -157,6 +158,26 @@ The default is 65534.
 
 ==============================================================
 
+protected_symlinks:
+
+A long-standing class of security issues is the symlink-based
+time-of-check-time-of-use race, most commonly seen in world-writable
+directories like /tmp. The common method of exploitation of this flaw
+is to cross privilege boundaries when following a given symlink (i.e. a
+root process follows a symlink belonging to another user). For a likely
+incomplete list of hundreds of examples across the years, please see:
+http://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=/tmp
+
+When set to "0", symlink following behavior is unrestricted.
+
+When set to "1" symlinks are permitted to be followed only when outside
+a sticky world-writable directory, or when the uid of the symlink and
+follower match, or when the directory owner matches the symlink's owner.
+
+This protection is based on the restrictions in Openwall and grsecurity.
+
+==============================================================
+
 suid_dumpable:
 
 This value can be used to query and set the core dump mode for setuid
index f95ae3a027f38dd3173d5c766eeeb0ecda58d2be..f54855b031095921c84d4aba9900d17035ecf93c 100644 (file)
@@ -276,4 +276,38 @@ endif # NETWORK_FILESYSTEMS
 source "fs/nls/Kconfig"
 source "fs/dlm/Kconfig"
 
+config PROTECTED_SYMLINKS
+       bool "Evaluate vulnerable symlink conditions"
+       default y
+       help
+         A long-standing class of security issues is the symlink-based
+         time-of-check-time-of-use race, most commonly seen in
+         world-writable directories like /tmp. The common method of
+         exploitation of this flaw is to cross privilege boundaries
+         when following a given symlink (i.e. a root process follows
+         a malicious symlink belonging to another user).
+
+         Enabling this adds the logic to examine these dangerous symlink
+         conditions. Whether or not the dangerous symlink situations are
+         allowed is controlled by PROTECTED_SYMLINKS_ENABLED.
+
+config PROTECTED_SYMLINKS_ENABLED
+       depends on PROTECTED_SYMLINKS
+       bool "Disallow symlink following in sticky world-writable dirs"
+       default y
+       help
+         Solve ToCToU symlink race vulnerablities by permitting symlinks
+         to be followed only when outside a sticky world-writable directory,
+         or when the uid of the symlink and follower match, or when the
+         directory and symlink owners match.
+
+         When PROC_SYSCTL is enabled, this setting can also be controlled
+         via /proc/sys/kernel/protected_symlinks.
+
+config PROTECTED_SYMLINKS_ENABLED_SYSCTL
+       depends on PROTECTED_SYMLINKS
+       int
+       default "1" if PROTECTED_SYMLINKS_ENABLED
+       default "0"
+
 endmenu
index b591dc414071d1226eb8a1b132d955ac669f179f..0322318f2fd03b205c36314620df2ad35ffd3c4a 100644 (file)
@@ -623,10 +623,84 @@ static inline void put_link(struct nameidata *nd, struct path *link, void *cooki
        path_put(link);
 }
 
+#ifdef CONFIG_PROTECTED_SYMLINKS
+int sysctl_protected_symlinks __read_mostly =
+       CONFIG_PROTECTED_SYMLINKS_ENABLED_SYSCTL;
+
+/**
+ * may_follow_link - Check symlink following for unsafe situations
+ * @dentry: The inode/dentry of the symlink
+ * @nameidata: The path data of the symlink
+ *
+ * In the case of the protected_symlinks sysctl being enabled,
+ * CAP_DAC_OVERRIDE needs to be specifically ignored if the symlink is
+ * in a sticky world-writable directory. This is to protect privileged
+ * processes from failing races against path names that may change out
+ * from under them by way of other users creating malicious symlinks.
+ * It will permit symlinks to be followed only when outside a sticky
+ * world-writable directory, or when the uid of the symlink and follower
+ * match, or when the directory owner matches the symlink's owner.
+ *
+ * Returns 0 if following the symlink is allowed, -ve on error.
+ */
+static inline int
+may_follow_link(struct dentry *dentry, struct nameidata *nameidata)
+{
+       int error = 0;
+       const struct inode *parent;
+       const struct inode *inode;
+       const struct cred *cred;
+
+       if (!sysctl_protected_symlinks)
+               return 0;
+
+       /* Allowed if owner and follower match. */
+       cred = current_cred();
+       inode = dentry->d_inode;
+       if (cred->fsuid == inode->i_uid)
+               return 0;
+
+       /* Check parent directory mode and owner. */
+       spin_lock(&dentry->d_lock);
+       parent = dentry->d_parent->d_inode;
+       if ((parent->i_mode & (S_ISVTX|S_IWOTH)) == (S_ISVTX|S_IWOTH) &&
+           parent->i_uid != inode->i_uid) {
+               error = -EACCES;
+       }
+       spin_unlock(&dentry->d_lock);
+
+#ifdef CONFIG_AUDIT
+       if (error) {
+               struct audit_buffer *ab;
+
+               ab = audit_log_start(current->audit_context,
+                                    GFP_KERNEL, AUDIT_AVC);
+               audit_log_format(ab, "op=follow_link action=denied");
+               audit_log_format(ab, " pid=%d comm=", current->pid);
+               audit_log_untrustedstring(ab, current->comm);
+               audit_log_d_path(ab, " path=", &nameidata->path);
+               audit_log_format(ab, " name=");
+               audit_log_untrustedstring(ab, dentry->d_name.name);
+               audit_log_format(ab, " dev=");
+               audit_log_untrustedstring(ab, inode->i_sb->s_id);
+               audit_log_format(ab, " ino=%lu", inode->i_ino);
+               audit_log_end(ab);
+       }
+#endif
+       return error;
+}
+#else
+static inline int
+may_follow_link(struct dentry *dentry, struct nameidata *nameidata)
+{
+       return 0;
+}
+#endif
+
 static __always_inline int
-follow_link(struct path *link, struct nameidata *nd, void **p)
+follow_link(struct path *link, struct nameidata *nd, void **p, bool sensitive)
 {
-       int error;
+       int error = 0;
        struct dentry *dentry = link->dentry;
 
        BUG_ON(nd->flags & LOOKUP_RCU);
@@ -645,7 +719,10 @@ follow_link(struct path *link, struct nameidata *nd, void **p)
        touch_atime(link);
        nd_set_link(nd, NULL);
 
-       error = security_inode_follow_link(link->dentry, nd);
+       if (sensitive)
+               error = may_follow_link(link->dentry, nd);
+       if (!error)
+               error = security_inode_follow_link(link->dentry, nd);
        if (error) {
                *p = ERR_PTR(error); /* no ->put_link(), please */
                path_put(&nd->path);
@@ -1351,7 +1428,7 @@ static inline int nested_symlink(struct path *path, struct nameidata *nd)
                struct path link = *path;
                void *cookie;
 
-               res = follow_link(&link, nd, &cookie);
+               res = follow_link(&link, nd, &cookie, false);
                if (!res)
                        res = walk_component(nd, path, &nd->last,
                                             nd->last_type, LOOKUP_FOLLOW);
@@ -1743,7 +1820,8 @@ static int path_lookupat(int dfd, const char *name,
                        void *cookie;
                        struct path link = path;
                        nd->flags |= LOOKUP_PARENT;
-                       err = follow_link(&link, nd, &cookie);
+
+                       err = follow_link(&link, nd, &cookie, true);
                        if (!err)
                                err = lookup_last(nd, &path);
                        put_link(nd, &link, cookie);
@@ -2404,7 +2482,8 @@ static struct file *path_openat(int dfd, const char *pathname,
                }
                nd->flags |= LOOKUP_PARENT;
                nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
-               error = follow_link(&link, nd, &cookie);
+
+               error = follow_link(&link, nd, &cookie, true);
                if (unlikely(error))
                        filp = ERR_PTR(error);
                else
index 99cbb1f6520630c7c182974632b2ee694a24b9cb..546c2b39d8df72ed50bd94e19f9ff13da186e321 100644 (file)
@@ -427,6 +427,7 @@ extern unsigned long get_max_files(void);
 extern int sysctl_nr_open;
 extern struct inodes_stat_t inodes_stat;
 extern int leases_enable, lease_break_time;
+extern int sysctl_protected_symlinks;
 
 struct buffer_head;
 typedef int (get_block_t)(struct inode *inode, sector_t iblock,
index 4ab11879aeb4d15baa3d8d25fa753725084ed809..2cbace33ab1f65e209fc5e6bb7e043ae13283a50 100644 (file)
@@ -1492,6 +1492,17 @@ static struct ctl_table fs_table[] = {
                .child          = epoll_table,
        },
 #endif
+#endif
+#ifdef CONFIG_PROTECTED_SYMLINKS
+       {
+               .procname       = "protected_symlinks",
+               .data           = &sysctl_protected_symlinks,
+               .maxlen         = sizeof(int),
+               .mode           = 0600,
+               .proc_handler   = proc_dointvec_minmax,
+               .extra1         = &zero,
+               .extra2         = &one,
+       },
 #endif
        {
                .procname       = "suid_dumpable",