CHROMIUM: security: Yama: add link restrictions
authorKees Cook <keescook@chromium.org>
Wed, 30 Nov 2011 22:20:13 +0000 (14:20 -0800)
committerGrant Grundler <grundler@google.com>
Thu, 24 May 2012 22:16:44 +0000 (15:16 -0700)
Add symlink and hardlink restrictions that have shown real-world security
benefits, along with sysctl knobs to control them.

BUG=chromium-os:22137
TEST=x86-alex build, boot, suite_Smoke passes, logging_UserCrash passes,
 security_SymlinkRestrictions, security_HardlinkRestrictions,
 security_ptraceRestriction.

Change-Id: I983e711f2f7c74b2f30d632b9fea4761637523e9
Signed-off-by: Kees Cook <keescook@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/12407
Reviewed-by: Olof Johansson <olofj@chromium.org>
[ 3.4-rc5: added many #include to fix missing types --grundler]

Documentation/security/Yama.txt
include/linux/pid_namespace.h
include/linux/security.h
kernel/pid.c
kernel/pid_namespace.c
security/security.c
security/yama/Kconfig
security/yama/yama_lsm.c

index a9511f1..442fac4 100644 (file)
@@ -5,10 +5,54 @@ any other LSM).
 
 Yama is controlled through sysctl in /proc/sys/kernel/yama:
 
+- protected_sticky_symlinks
+- protected_nonaccess_hardlinks
 - ptrace_scope
 
 ==============================================================
 
+protected_sticky_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.
+
+==============================================================
+
+protected_nonaccess_hardlinks:
+
+Hardlinks can be abused in a similar fashion to symlinks in sticky
+world-writable directories, but their weakness is not limited to
+just that scenario. For example, if /etc and /home are on the same
+partition, a regular user can create a hardlink to /etc/shadow in their
+home directory. While it retains the original owner and permissions,
+it is possible for privileged programs that are otherwise symlink-safe
+to mistakenly access the file through its hardlink. Additionally, a very
+minor untraceable quota-bypassing local denial of service is possible by
+an attacker exhausting disk space by filling a world-writable directory
+with hardlinks.
+
+When set to "0", hardlink creation behavior is unrestricted.
+
+When set to "1", hardlinks cannot be created to files that a given user
+would be unable to read and write originally, or are otherwise sensitive.
+
+This protection is based on the restrictions in Openwall and grsecurity.
+
+==============================================================
+
 ptrace_scope:
 
 As Linux grows in popularity, it will become a larger target for
index b067bd8..42e5aac 100644 (file)
@@ -34,6 +34,11 @@ struct pid_namespace {
        gid_t pid_gid;
        int hide_pid;
        int reboot;     /* group exit code if this pidns was rebooted */
+#ifdef CONFIG_SECURITY_YAMA
+       int ptrace_scope;
+       int protected_sticky_symlinks;
+       int protected_nonaccess_hardlinks;
+#endif
 };
 
 extern struct pid_namespace init_pid_ns;
index 673afbb..4acf709 100644 (file)
@@ -3021,5 +3021,47 @@ static inline void free_secdata(void *secdata)
 { }
 #endif /* CONFIG_SECURITY */
 
+#if CONFIG_SECURITY_YAMA
+extern int yama_ptrace_access_check(struct task_struct *child,
+                                   unsigned int mode);
+extern int yama_path_link(struct dentry *old_dentry, struct path *new_dir,
+                         struct dentry *new_dentry);
+extern int yama_inode_follow_link(struct dentry *dentry,
+                                 struct nameidata *nameidata);
+extern void yama_task_free(struct task_struct *task);
+extern int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3,
+                          unsigned long arg4, unsigned long arg5);
+#else
+static inline int yama_ptrace_access_check(struct task_struct *child,
+                                          unsigned int mode);
+{
+       return 0;
+}
+
+static inline int yama_path_link(struct dentry *old_dentry,
+                                struct path *new_dir,
+                                struct dentry *new_dentry);
+{
+       return 0;
+}
+
+static inline int yama_inode_follow_link(struct dentry *dentry,
+                                        struct nameidata *nameidata);
+{
+       return 0;
+}
+
+static inline void yama_task_free(struct task_struct *task);
+{
+}
+
+static inline int yama_task_prctl(int option, unsigned long arg2,
+                                 unsigned long arg3, unsigned long arg4,
+                                 unsigned long arg5)
+{
+       return -ENOSYS;
+}
+#endif /* CONFIG_SECURITY_YAMA */
+
 #endif /* ! __LINUX_SECURITY_H */
 
index 3f198bc..0c2a01a 100644 (file)
@@ -80,6 +80,8 @@ struct pid_namespace init_pid_ns = {
        .child_reaper = &init_task,
 #ifdef CONFIG_SECURITY_YAMA
        .ptrace_scope = 1,
+       .protected_sticky_symlinks = 1,
+       .protected_nonaccess_hardlinks = 1,
 #endif
 };
 EXPORT_SYMBOL_GPL(init_pid_ns);
index 3035d09..5b8a6c9 100644 (file)
@@ -104,6 +104,10 @@ static struct pid_namespace *create_pid_namespace(struct pid_namespace *parent_p
 
 #ifdef CONFIG_SECURITY_YAMA
        ns->ptrace_scope = parent_pid_ns->ptrace_scope;
+       ns->protected_sticky_symlinks =
+               parent_pid_ns->protected_sticky_symlinks;
+       ns->protected_nonaccess_hardlinks =
+               parent_pid_ns->protected_nonaccess_hardlinks;
 #endif
 
        return ns;
index bf619ff..837beb4 100644 (file)
@@ -132,6 +132,10 @@ int __init register_security(struct security_operations *ops)
 
 int security_ptrace_access_check(struct task_struct *child, unsigned int mode)
 {
+       int rc;
+       rc = yama_ptrace_access_check(child, mode);
+       if (rc)
+               return rc;
        return security_ops->ptrace_access_check(child, mode);
 }
 
@@ -400,8 +404,12 @@ int security_path_symlink(struct path *dir, struct dentry *dentry,
 int security_path_link(struct dentry *old_dentry, struct path *new_dir,
                       struct dentry *new_dentry)
 {
+       int rc;
        if (unlikely(IS_PRIVATE(old_dentry->d_inode)))
                return 0;
+       rc = yama_path_link(old_dentry, new_dir, new_dentry);
+       if (rc)
+               return rc;
        return security_ops->path_link(old_dentry, new_dir, new_dentry);
 }
 
@@ -515,8 +523,12 @@ int security_inode_readlink(struct dentry *dentry)
 
 int security_inode_follow_link(struct dentry *dentry, struct nameidata *nd)
 {
+       int rc;
        if (unlikely(IS_PRIVATE(dentry->d_inode)))
                return 0;
+       rc = yama_inode_follow_link(dentry, nd);
+       if (rc)
+               return rc;
        return security_ops->inode_follow_link(dentry, nd);
 }
 
@@ -719,6 +731,7 @@ int security_task_create(unsigned long clone_flags)
 
 void security_task_free(struct task_struct *task)
 {
+       yama_task_free(task);
        security_ops->task_free(task);
 }
 
@@ -834,6 +847,10 @@ int security_task_wait(struct task_struct *p)
 int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
                         unsigned long arg4, unsigned long arg5)
 {
+       int rc;
+       rc = yama_task_prctl(option, arg2, arg3, arg4, arg5);
+       if (rc != -ENOSYS)
+               return rc;
        return security_ops->task_prctl(option, arg2, arg3, arg4, arg5);
 }
 
index 51d6709..1a5d1c1 100644 (file)
@@ -7,7 +7,8 @@ config SECURITY_YAMA
        help
          This selects Yama, which extends DAC support with additional
          system-wide security settings beyond regular Linux discretionary
-         access controls. Currently available is ptrace scope restriction.
-         Further information can be found in Documentation/security/Yama.txt.
+         access controls. Currently available are symlink, hardlink, and
+         ptrace scope restrictions. Further information can be found in
+         Documentation/security/Yama.txt.
 
          If you are unsure how to answer this question, answer N.
index 5bd7a05..2bedb31 100644 (file)
 #include <linux/ptrace.h>
 #include <linux/prctl.h>
 #include <linux/ratelimit.h>
+#include <linux/pid.h>         /* init_pid_ns */
+#include <linux/sched.h>       /* current and other task stuff */
+#include <linux/stat.h>                /* for S_ISLNK and friends */
+#include <linux/pid_namespace.h>
+#include <linux/dcache.h>
+#include <linux/fs.h>          /* for struct inode */
 
 static int ptrace_scope = 1;
 
@@ -95,7 +101,7 @@ static void yama_ptracer_del(struct task_struct *tracer,
  * yama_task_free - check for task_pid to remove from exception list
  * @task: task being removed
  */
-static void yama_task_free(struct task_struct *task)
+void yama_task_free(struct task_struct *task)
 {
        yama_ptracer_del(task, task);
 }
@@ -111,7 +117,7 @@ static void yama_task_free(struct task_struct *task)
  * Return 0 on success, -ve on error.  -ENOSYS is returned when Yama
  * does not handle the given option.
  */
-static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3,
+int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3,
                           unsigned long arg4, unsigned long arg5)
 {
        int rc;
@@ -238,7 +244,7 @@ static int ptracer_exception_found(struct task_struct *tracer,
  *
  * Returns 0 if following the ptrace is allowed, -ve on error.
  */
-static int yama_ptrace_access_check(struct task_struct *child,
+int yama_ptrace_access_check(struct task_struct *child,
                                    unsigned int mode)
 {
        int rc;
@@ -270,13 +276,63 @@ static int yama_ptrace_access_check(struct task_struct *child,
        return rc;
 }
 
-static struct security_operations yama_ops = {
-       .name =                 "yama",
+/**
+ * yama_inode_follow_link - check for symlinks in sticky world-writeable dirs
+ * @dentry: The inode/dentry of the symlink
+ * @nameidata: The path data of the symlink
+ *
+ * In the case of the protected_sticky_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 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.
+ *
+ * Returns 0 if following the symlink is allowed, -ve on error.
+ */
+int yama_inode_follow_link(struct dentry *dentry,
+                                 struct nameidata *nameidata)
+{
+       int rc = 0;
+       const struct inode *parent;
+       const struct inode *inode;
+       const struct cred *cred;
 
-       .ptrace_access_check =  yama_ptrace_access_check,
-       .task_prctl =           yama_task_prctl,
-       .task_free =            yama_task_free,
-};
+       if (!current->nsproxy->pid_ns->protected_sticky_symlinks)
+               return 0;
+
+       /* if inode isn't a symlink, don't try to evaluate blocking it */
+       inode = dentry->d_inode;
+       if (!S_ISLNK(inode->i_mode))
+               return 0;
+
+       /* owner and follower match? */
+       cred = current_cred();
+       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) {
+               rc = -EACCES;
+       }
+       spin_unlock(&dentry->d_lock);
+
+       if (rc) {
+               char name[sizeof(current->comm)];
+               printk_ratelimited(KERN_NOTICE "non-matching-uid symlink "
+                       "following attempted in sticky world-writable "
+                       "directory by %s (fsuid %d != %d)\n",
+                       get_task_comm(name, current),
+                       cred->fsuid, inode->i_uid);
+       }
+
+       return rc;
+}
 
 static int yama_generic_permission(struct inode *inode, int mask)
 {
@@ -343,7 +399,44 @@ struct ctl_path yama_sysctl_path[] = {
        { }
 };
 
+static void *get_pid_data(ctl_table *table)
+{
+       char *which = table->data;
+       struct pid_namespace *pid_ns = current->nsproxy->pid_ns;
+       which = (which - (char *)&init_pid_ns) + (char *)pid_ns;
+       return which;
+}
+
+static int proc_pid_dointvec_minmax(ctl_table *table, int write,
+       void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+       struct ctl_table pid_table;
+
+       memcpy(&pid_table, table, sizeof(pid_table));
+       pid_table.data = get_pid_data(table);
+
+       return proc_dointvec_minmax(&pid_table, write, buffer, lenp, ppos);
+}
+
 static struct ctl_table yama_sysctl_table[] = {
+       {
+               .procname       = "protected_sticky_symlinks",
+               .data           = &init_pid_ns.protected_sticky_symlinks,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_pid_dointvec_minmax,
+               .extra1         = &zero,
+               .extra2         = &one,
+       },
+       {
+               .procname       = "protected_nonaccess_hardlinks",
+               .data           = &init_pid_ns.protected_nonaccess_hardlinks,
+               .maxlen         = sizeof(int),
+               .mode           = 0644,
+               .proc_handler   = proc_pid_dointvec_minmax,
+               .extra1         = &zero,
+               .extra2         = &one,
+       },
        {
                .procname       = "ptrace_scope",
                .data           = &ptrace_scope,
@@ -359,14 +452,8 @@ static struct ctl_table yama_sysctl_table[] = {
 
 static __init int yama_init(void)
 {
-       if (!security_module_enable(&yama_ops))
-               return 0;
-
        printk(KERN_INFO "Yama: becoming mindful.\n");
 
-       if (register_security(&yama_ops))
-               panic("Yama: kernel registration failed.\n");
-
 #ifdef CONFIG_SYSCTL
        if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table))
                panic("Yama: sysctl registration failed.\n");