Commit b3993755 authored by Eric W. Biederman's avatar Eric W. Biederman Committed by Luis Henriques

userns: Add a knob to disable setgroups on a per user namespace basis

commit 9cc46516 upstream.

- 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.
Signed-off-by: default avatar"Eric W. Biederman" <ebiederm@xmission.com>
Signed-off-by: default avatarLuis Henriques <luis.henriques@canonical.com>
parent 713f555c
...@@ -2526,6 +2526,57 @@ static const struct file_operations proc_projid_map_operations = { ...@@ -2526,6 +2526,57 @@ static const struct file_operations proc_projid_map_operations = {
.llseek = seq_lseek, .llseek = seq_lseek,
.release = proc_id_map_release, .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 */ #endif /* CONFIG_USER_NS */
static int proc_pid_personality(struct seq_file *m, struct pid_namespace *ns, static int proc_pid_personality(struct seq_file *m, struct pid_namespace *ns,
...@@ -2634,6 +2685,7 @@ static const struct pid_entry tgid_base_stuff[] = { ...@@ -2634,6 +2685,7 @@ static const struct pid_entry tgid_base_stuff[] = {
REG("uid_map", S_IRUGO|S_IWUSR, proc_uid_map_operations), REG("uid_map", S_IRUGO|S_IWUSR, proc_uid_map_operations),
REG("gid_map", S_IRUGO|S_IWUSR, proc_gid_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("projid_map", S_IRUGO|S_IWUSR, proc_projid_map_operations),
REG("setgroups", S_IRUGO|S_IWUSR, proc_setgroups_operations),
#endif #endif
#ifdef CONFIG_CHECKPOINT_RESTORE #ifdef CONFIG_CHECKPOINT_RESTORE
REG("timers", S_IRUGO, proc_timers_operations), REG("timers", S_IRUGO, proc_timers_operations),
...@@ -2969,6 +3021,7 @@ static const struct pid_entry tid_base_stuff[] = { ...@@ -2969,6 +3021,7 @@ static const struct pid_entry tid_base_stuff[] = {
REG("uid_map", S_IRUGO|S_IWUSR, proc_uid_map_operations), REG("uid_map", S_IRUGO|S_IWUSR, proc_uid_map_operations),
REG("gid_map", S_IRUGO|S_IWUSR, proc_gid_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("projid_map", S_IRUGO|S_IWUSR, proc_projid_map_operations),
REG("setgroups", S_IRUGO|S_IWUSR, proc_setgroups_operations),
#endif #endif
}; };
......
...@@ -17,6 +17,10 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */ ...@@ -17,6 +17,10 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */
} extent[UID_GID_MAP_MAX_EXTENTS]; } extent[UID_GID_MAP_MAX_EXTENTS];
}; };
#define USERNS_SETGROUPS_ALLOWED 1UL
#define USERNS_INIT_FLAGS USERNS_SETGROUPS_ALLOWED
struct user_namespace { struct user_namespace {
struct uid_gid_map uid_map; struct uid_gid_map uid_map;
struct uid_gid_map gid_map; struct uid_gid_map gid_map;
...@@ -27,6 +31,7 @@ struct user_namespace { ...@@ -27,6 +31,7 @@ struct user_namespace {
kuid_t owner; kuid_t owner;
kgid_t group; kgid_t group;
unsigned int proc_inum; unsigned int proc_inum;
unsigned long flags;
/* Register of per-UID persistent keyrings for this namespace */ /* Register of per-UID persistent keyrings for this namespace */
#ifdef CONFIG_PERSISTENT_KEYRINGS #ifdef CONFIG_PERSISTENT_KEYRINGS
...@@ -63,6 +68,8 @@ extern struct seq_operations proc_projid_seq_operations; ...@@ -63,6 +68,8 @@ extern 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_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_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_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); extern bool userns_may_setgroups(const struct user_namespace *ns);
#else #else
......
...@@ -51,6 +51,7 @@ struct user_namespace init_user_ns = { ...@@ -51,6 +51,7 @@ struct user_namespace init_user_ns = {
.owner = GLOBAL_ROOT_UID, .owner = GLOBAL_ROOT_UID,
.group = GLOBAL_ROOT_GID, .group = GLOBAL_ROOT_GID,
.proc_inum = PROC_USER_INIT_INO, .proc_inum = PROC_USER_INIT_INO,
.flags = USERNS_INIT_FLAGS,
#ifdef CONFIG_PERSISTENT_KEYRINGS #ifdef CONFIG_PERSISTENT_KEYRINGS
.persistent_keyring_register_sem = .persistent_keyring_register_sem =
__RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem), __RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem),
......
...@@ -100,6 +100,11 @@ int create_user_ns(struct cred *new) ...@@ -100,6 +100,11 @@ int create_user_ns(struct cred *new)
ns->owner = owner; ns->owner = owner;
ns->group = group; 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); set_cred_user_ns(new, ns);
#ifdef CONFIG_PERSISTENT_KEYRINGS #ifdef CONFIG_PERSISTENT_KEYRINGS
...@@ -839,6 +844,84 @@ static bool new_idmap_permitted(const struct file *file, ...@@ -839,6 +844,84 @@ static bool new_idmap_permitted(const struct file *file,
return false; 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 userns_may_setgroups(const struct user_namespace *ns)
{ {
bool allowed; bool allowed;
...@@ -848,6 +931,8 @@ bool userns_may_setgroups(const struct user_namespace *ns) ...@@ -848,6 +931,8 @@ bool userns_may_setgroups(const struct user_namespace *ns)
* the user namespace has been established. * the user namespace has been established.
*/ */
allowed = ns->gid_map.nr_extents != 0; allowed = ns->gid_map.nr_extents != 0;
/* Is setgroups allowed? */
allowed = allowed && (ns->flags & USERNS_SETGROUPS_ALLOWED);
mutex_unlock(&userns_state_mutex); mutex_unlock(&userns_state_mutex);
return allowed; return allowed;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment