diff options
author | Josh Boyer <jwboyer@fedoraproject.org> | 2014-12-18 16:17:32 -0500 |
---|---|---|
committer | Josh Boyer <jwboyer@fedoraproject.org> | 2014-12-18 16:17:37 -0500 |
commit | 363cd71ff322e841027b4ec4436004e0bad98be0 (patch) | |
tree | 514fe687849316608792f5fdfb2def33ea7c7f99 /userns-Add-a-knob-to-disable-setgroups-on-a-per-user.patch | |
parent | 672002c87d3b969328359938ad9dbe7d5074dfff (diff) | |
download | kernel-363cd71ff322e841027b4ec4436004e0bad98be0.tar.gz kernel-363cd71ff322e841027b4ec4436004e0bad98be0.tar.xz kernel-363cd71ff322e841027b4ec4436004e0bad98be0.zip |
CVE-2014-8989 userns can bypass group restrictions (rhbz 1170684 1170688)
Diffstat (limited to 'userns-Add-a-knob-to-disable-setgroups-on-a-per-user.patch')
-rw-r--r-- | userns-Add-a-knob-to-disable-setgroups-on-a-per-user.patch | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/userns-Add-a-knob-to-disable-setgroups-on-a-per-user.patch b/userns-Add-a-knob-to-disable-setgroups-on-a-per-user.patch new file mode 100644 index 000000000..a55381706 --- /dev/null +++ b/userns-Add-a-knob-to-disable-setgroups-on-a-per-user.patch @@ -0,0 +1,280 @@ +From: "Eric W. Biederman" <ebiederm@xmission.com> +Date: Tue, 2 Dec 2014 12:27:26 -0600 +Subject: [PATCH] userns: Add a knob to disable setgroups on a per user + namespace basis + +- Expose the knob to user space through a proc file /proc/<pid>/setgroups + + A value of "deny" means the setgroups system call is disabled in the + current processes user namespace and can not be enabled in the + future in this user namespace. + + A value of "allow" means the segtoups system call is enabled. + +- Descendant user namespaces inherit the value of setgroups from + their parents. + +- A proc file is used (instead of a sysctl) as sysctls currently do + not allow checking the permissions at open time. + +- Writing to the proc file is restricted to before the gid_map + for the user namespace is set. + + This ensures that disabling setgroups at a user namespace + level will never remove the ability to call setgroups + from a process that already has that ability. + + A process may opt in to the setgroups disable for itself by + creating, entering and configuring a user namespace or by calling + setns on an existing user namespace with setgroups disabled. + Processes without privileges already can not call setgroups so this + is a noop. Prodcess with privilege become processes without + privilege when entering a user namespace and as with any other path + to dropping privilege they would not have the ability to call + setgroups. So this remains within the bounds of what is possible + without a knob to disable setgroups permanently in a user namespace. + +Cc: stable@vger.kernel.org +Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com> +--- + fs/proc/base.c | 53 ++++++++++++++++++++++++++ + include/linux/user_namespace.h | 7 ++++ + kernel/user.c | 1 + + kernel/user_namespace.c | 85 ++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 146 insertions(+) + +diff --git a/fs/proc/base.c b/fs/proc/base.c +index 772efa45a452..7dc3ea89ef1a 100644 +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -2464,6 +2464,57 @@ static const struct file_operations proc_projid_map_operations = { + .llseek = seq_lseek, + .release = proc_id_map_release, + }; ++ ++static int proc_setgroups_open(struct inode *inode, struct file *file) ++{ ++ struct user_namespace *ns = NULL; ++ struct task_struct *task; ++ int ret; ++ ++ ret = -ESRCH; ++ task = get_proc_task(inode); ++ if (task) { ++ rcu_read_lock(); ++ ns = get_user_ns(task_cred_xxx(task, user_ns)); ++ rcu_read_unlock(); ++ put_task_struct(task); ++ } ++ if (!ns) ++ goto err; ++ ++ if (file->f_mode & FMODE_WRITE) { ++ ret = -EACCES; ++ if (!ns_capable(ns, CAP_SYS_ADMIN)) ++ goto err_put_ns; ++ } ++ ++ ret = single_open(file, &proc_setgroups_show, ns); ++ if (ret) ++ goto err_put_ns; ++ ++ return 0; ++err_put_ns: ++ put_user_ns(ns); ++err: ++ return ret; ++} ++ ++static int proc_setgroups_release(struct inode *inode, struct file *file) ++{ ++ struct seq_file *seq = file->private_data; ++ struct user_namespace *ns = seq->private; ++ int ret = single_release(inode, file); ++ put_user_ns(ns); ++ return ret; ++} ++ ++static const struct file_operations proc_setgroups_operations = { ++ .open = proc_setgroups_open, ++ .write = proc_setgroups_write, ++ .read = seq_read, ++ .llseek = seq_lseek, ++ .release = proc_setgroups_release, ++}; + #endif /* CONFIG_USER_NS */ + + static int proc_pid_personality(struct seq_file *m, struct pid_namespace *ns, +@@ -2572,6 +2623,7 @@ static const struct pid_entry tgid_base_stuff[] = { + REG("uid_map", S_IRUGO|S_IWUSR, proc_uid_map_operations), + REG("gid_map", S_IRUGO|S_IWUSR, proc_gid_map_operations), + REG("projid_map", S_IRUGO|S_IWUSR, proc_projid_map_operations), ++ REG("setgroups", S_IRUGO|S_IWUSR, proc_setgroups_operations), + #endif + #ifdef CONFIG_CHECKPOINT_RESTORE + REG("timers", S_IRUGO, proc_timers_operations), +@@ -2913,6 +2965,7 @@ static const struct pid_entry tid_base_stuff[] = { + REG("uid_map", S_IRUGO|S_IWUSR, proc_uid_map_operations), + REG("gid_map", S_IRUGO|S_IWUSR, proc_gid_map_operations), + REG("projid_map", S_IRUGO|S_IWUSR, proc_projid_map_operations), ++ REG("setgroups", S_IRUGO|S_IWUSR, proc_setgroups_operations), + #endif + }; + +diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h +index 8d493083486a..9f3579ff543d 100644 +--- a/include/linux/user_namespace.h ++++ b/include/linux/user_namespace.h +@@ -17,6 +17,10 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */ + } extent[UID_GID_MAP_MAX_EXTENTS]; + }; + ++#define USERNS_SETGROUPS_ALLOWED 1UL ++ ++#define USERNS_INIT_FLAGS USERNS_SETGROUPS_ALLOWED ++ + struct user_namespace { + struct uid_gid_map uid_map; + struct uid_gid_map gid_map; +@@ -27,6 +31,7 @@ struct user_namespace { + kuid_t owner; + kgid_t group; + unsigned int proc_inum; ++ unsigned long flags; + + /* Register of per-UID persistent keyrings for this namespace */ + #ifdef CONFIG_PERSISTENT_KEYRINGS +@@ -63,6 +68,8 @@ extern const struct seq_operations proc_projid_seq_operations; + extern ssize_t proc_uid_map_write(struct file *, const char __user *, size_t, loff_t *); + extern ssize_t proc_gid_map_write(struct file *, const char __user *, size_t, loff_t *); + extern ssize_t proc_projid_map_write(struct file *, const char __user *, size_t, loff_t *); ++extern ssize_t proc_setgroups_write(struct file *, const char __user *, size_t, loff_t *); ++extern int proc_setgroups_show(struct seq_file *m, void *v); + extern bool userns_may_setgroups(const struct user_namespace *ns); + #else + +diff --git a/kernel/user.c b/kernel/user.c +index 4efa39350e44..2d09940c9632 100644 +--- a/kernel/user.c ++++ b/kernel/user.c +@@ -51,6 +51,7 @@ struct user_namespace init_user_ns = { + .owner = GLOBAL_ROOT_UID, + .group = GLOBAL_ROOT_GID, + .proc_inum = PROC_USER_INIT_INO, ++ .flags = USERNS_INIT_FLAGS, + #ifdef CONFIG_PERSISTENT_KEYRINGS + .persistent_keyring_register_sem = + __RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem), +diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c +index 44a555ac6104..6e80f4c1322b 100644 +--- a/kernel/user_namespace.c ++++ b/kernel/user_namespace.c +@@ -100,6 +100,11 @@ int create_user_ns(struct cred *new) + ns->owner = owner; + ns->group = group; + ++ /* Inherit USERNS_SETGROUPS_ALLOWED from our parent */ ++ mutex_lock(&userns_state_mutex); ++ ns->flags = parent_ns->flags; ++ mutex_unlock(&userns_state_mutex); ++ + set_cred_user_ns(new, ns); + + #ifdef CONFIG_PERSISTENT_KEYRINGS +@@ -839,6 +844,84 @@ static bool new_idmap_permitted(const struct file *file, + return false; + } + ++int proc_setgroups_show(struct seq_file *seq, void *v) ++{ ++ struct user_namespace *ns = seq->private; ++ unsigned long userns_flags = ACCESS_ONCE(ns->flags); ++ ++ seq_printf(seq, "%s\n", ++ (userns_flags & USERNS_SETGROUPS_ALLOWED) ? ++ "allow" : "deny"); ++ return 0; ++} ++ ++ssize_t proc_setgroups_write(struct file *file, const char __user *buf, ++ size_t count, loff_t *ppos) ++{ ++ struct seq_file *seq = file->private_data; ++ struct user_namespace *ns = seq->private; ++ char kbuf[8], *pos; ++ bool setgroups_allowed; ++ ssize_t ret; ++ ++ /* Only allow a very narrow range of strings to be written */ ++ ret = -EINVAL; ++ if ((*ppos != 0) || (count >= sizeof(kbuf))) ++ goto out; ++ ++ /* What was written? */ ++ ret = -EFAULT; ++ if (copy_from_user(kbuf, buf, count)) ++ goto out; ++ kbuf[count] = '\0'; ++ pos = kbuf; ++ ++ /* What is being requested? */ ++ ret = -EINVAL; ++ if (strncmp(pos, "allow", 5) == 0) { ++ pos += 5; ++ setgroups_allowed = true; ++ } ++ else if (strncmp(pos, "deny", 4) == 0) { ++ pos += 4; ++ setgroups_allowed = false; ++ } ++ else ++ goto out; ++ ++ /* Verify there is not trailing junk on the line */ ++ pos = skip_spaces(pos); ++ if (*pos != '\0') ++ goto out; ++ ++ ret = -EPERM; ++ mutex_lock(&userns_state_mutex); ++ if (setgroups_allowed) { ++ /* Enabling setgroups after setgroups has been disabled ++ * is not allowed. ++ */ ++ if (!(ns->flags & USERNS_SETGROUPS_ALLOWED)) ++ goto out_unlock; ++ } else { ++ /* Permanently disabling setgroups after setgroups has ++ * been enabled by writing the gid_map is not allowed. ++ */ ++ if (ns->gid_map.nr_extents != 0) ++ goto out_unlock; ++ ns->flags &= ~USERNS_SETGROUPS_ALLOWED; ++ } ++ mutex_unlock(&userns_state_mutex); ++ ++ /* Report a successful write */ ++ *ppos = count; ++ ret = count; ++out: ++ return ret; ++out_unlock: ++ mutex_unlock(&userns_state_mutex); ++ goto out; ++} ++ + bool userns_may_setgroups(const struct user_namespace *ns) + { + bool allowed; +@@ -848,6 +931,8 @@ bool userns_may_setgroups(const struct user_namespace *ns) + * the user namespace has been established. + */ + allowed = ns->gid_map.nr_extents != 0; ++ /* Is setgroups allowed? */ ++ allowed = allowed && (ns->flags & USERNS_SETGROUPS_ALLOWED); + mutex_unlock(&userns_state_mutex); + + return allowed; +-- +2.1.0 + |