summaryrefslogtreecommitdiffstats
path: root/userns-Add-a-knob-to-disable-setgroups-on-a-per-user.patch
diff options
context:
space:
mode:
authorJosh Boyer <jwboyer@fedoraproject.org>2014-12-18 16:17:32 -0500
committerJosh Boyer <jwboyer@fedoraproject.org>2014-12-18 16:17:37 -0500
commit363cd71ff322e841027b4ec4436004e0bad98be0 (patch)
tree514fe687849316608792f5fdfb2def33ea7c7f99 /userns-Add-a-knob-to-disable-setgroups-on-a-per-user.patch
parent672002c87d3b969328359938ad9dbe7d5074dfff (diff)
downloadkernel-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.patch280
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
+