From c4a4d603796c727b9555867571f89483be9c565e Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 16 Nov 2011 23:15:31 -0800 Subject: userns: Use cred->user_ns instead of cred->user->user_ns Optimize performance and prepare for the removal of the user_ns reference from user_struct. Remove the slow long walk through cred->user->user_ns and instead go straight to cred->user_ns. Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- kernel/user_namespace.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'kernel/user_namespace.c') diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 3b906e98b1d..f084083a0fd 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -90,7 +90,7 @@ uid_t user_ns_map_uid(struct user_namespace *to, const struct cred *cred, uid_t { struct user_namespace *tmp; - if (likely(to == cred->user->user_ns)) + if (likely(to == cred->user_ns)) return uid; @@ -112,7 +112,7 @@ gid_t user_ns_map_gid(struct user_namespace *to, const struct cred *cred, gid_t { struct user_namespace *tmp; - if (likely(to == cred->user->user_ns)) + if (likely(to == cred->user_ns)) return gid; /* Is cred->user the creator of the target user_ns -- cgit v1.2.3-70-g09d2 From 0093ccb68f3753c0ba4d74c89d7e0f444b8d6123 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 16 Nov 2011 21:52:53 -0800 Subject: cred: Refcount the user_ns pointed to by the cred. struct user_struct will shortly loose it's user_ns reference so make the cred user_ns reference a proper reference complete with reference counting. Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- include/linux/cred.h | 2 +- kernel/cred.c | 8 +++----- kernel/user_namespace.c | 8 +++++--- security/keys/process_keys.c | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) (limited to 'kernel/user_namespace.c') diff --git a/include/linux/cred.h b/include/linux/cred.h index d12c4e475c1..2c60ec80267 100644 --- a/include/linux/cred.h +++ b/include/linux/cred.h @@ -146,7 +146,7 @@ struct cred { void *security; /* subjective LSM security */ #endif struct user_struct *user; /* real user ID subscription */ - struct user_namespace *user_ns; /* cached user->user_ns */ + struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */ struct group_info *group_info; /* supplementary groups for euid/fsgid */ struct rcu_head rcu; /* RCU deletion hook */ }; diff --git a/kernel/cred.c b/kernel/cred.c index 97b36eeca4c..7a0d8066988 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -148,6 +148,7 @@ static void put_cred_rcu(struct rcu_head *rcu) if (cred->group_info) put_group_info(cred->group_info); free_uid(cred->user); + put_user_ns(cred->user_ns); kmem_cache_free(cred_jar, cred); } @@ -303,6 +304,7 @@ struct cred *prepare_creds(void) set_cred_subscribers(new, 0); get_group_info(new->group_info); get_uid(new->user); + get_user_ns(new->user_ns); #ifdef CONFIG_KEYS key_get(new->thread_keyring); @@ -412,11 +414,6 @@ int copy_creds(struct task_struct *p, unsigned long clone_flags) goto error_put; } - /* cache user_ns in cred. Doesn't need a refcount because it will - * stay pinned by cred->user - */ - new->user_ns = new->user->user_ns; - #ifdef CONFIG_KEYS /* new threads get their own thread keyrings if their parent already * had one */ @@ -676,6 +673,7 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon) atomic_set(&new->usage, 1); set_cred_subscribers(new, 0); get_uid(new->user); + get_user_ns(new->user_ns); get_group_info(new->group_info); #ifdef CONFIG_KEYS diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index f084083a0fd..58bb8781a77 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -24,7 +24,7 @@ static struct kmem_cache *user_ns_cachep __read_mostly; */ int create_user_ns(struct cred *new) { - struct user_namespace *ns; + struct user_namespace *ns, *parent_ns = new->user_ns; struct user_struct *root_user; int n; @@ -57,8 +57,10 @@ int create_user_ns(struct cred *new) #endif /* tgcred will be cleared in our caller bc CLONE_THREAD won't be set */ - /* root_user holds a reference to ns, our reference can be dropped */ - put_user_ns(ns); + /* Leave the reference to our user_ns with the new cred */ + new->user_ns = ns; + + put_user_ns(parent_ns); return 0; } diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 70febff06da..447fb7618ff 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -858,7 +858,7 @@ void key_replace_session_keyring(void) new-> sgid = old-> sgid; new->fsgid = old->fsgid; new->user = get_uid(old->user); - new->user_ns = new->user_ns; + new->user_ns = get_user_ns(new->user_ns); new->group_info = get_group_info(old->group_info); new->securebits = old->securebits; -- cgit v1.2.3-70-g09d2 From aeb3ae9da9b50a386b22af786d19b623e8d9f0fa Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 16 Nov 2011 21:59:43 -0800 Subject: userns: Add an explicit reference to the parent user namespace I am about to remove the struct user_namespace reference from struct user_struct. So keep an explicit track of the parent user namespace. Take advantage of this new reference and replace instances of user_ns->creator->user_ns with user_ns->parent. Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- include/linux/user_namespace.h | 1 + kernel/user_namespace.c | 13 ++++++------- security/commoncap.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'kernel/user_namespace.c') diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index faf467944ba..dc2d85a7637 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -12,6 +12,7 @@ struct user_namespace { struct kref kref; struct hlist_head uidhash_table[UIDHASH_SZ]; + struct user_namespace *parent; struct user_struct *creator; struct work_struct destroyer; }; diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 58bb8781a77..c15e533d6bc 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -45,6 +45,7 @@ int create_user_ns(struct cred *new) } /* set the new root user in the credentials under preparation */ + ns->parent = parent_ns; ns->creator = new->user; new->user = root_user; new->uid = new->euid = new->suid = new->fsuid = 0; @@ -60,8 +61,6 @@ int create_user_ns(struct cred *new) /* Leave the reference to our user_ns with the new cred */ new->user_ns = ns; - put_user_ns(parent_ns); - return 0; } @@ -72,10 +71,12 @@ int create_user_ns(struct cred *new) */ static void free_user_ns_work(struct work_struct *work) { - struct user_namespace *ns = + struct user_namespace *parent, *ns = container_of(work, struct user_namespace, destroyer); + parent = ns->parent; free_uid(ns->creator); kmem_cache_free(user_ns_cachep, ns); + put_user_ns(parent); } void free_user_ns(struct kref *kref) @@ -99,8 +100,7 @@ uid_t user_ns_map_uid(struct user_namespace *to, const struct cred *cred, uid_t /* Is cred->user the creator of the target user_ns * or the creator of one of it's parents? */ - for ( tmp = to; tmp != &init_user_ns; - tmp = tmp->creator->user_ns ) { + for ( tmp = to; tmp != &init_user_ns; tmp = tmp->parent ) { if (cred->user == tmp->creator) { return (uid_t)0; } @@ -120,8 +120,7 @@ gid_t user_ns_map_gid(struct user_namespace *to, const struct cred *cred, gid_t /* Is cred->user the creator of the target user_ns * or the creator of one of it's parents? */ - for ( tmp = to; tmp != &init_user_ns; - tmp = tmp->creator->user_ns ) { + for ( tmp = to; tmp != &init_user_ns; tmp = tmp->parent ) { if (cred->user == tmp->creator) { return (gid_t)0; } diff --git a/security/commoncap.c b/security/commoncap.c index 8b3e10e2eac..435d074853f 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -92,7 +92,7 @@ int cap_capable(const struct cred *cred, struct user_namespace *targ_ns, *If you have a capability in a parent user ns, then you have * it over all children user namespaces as well. */ - targ_ns = targ_ns->creator->user_ns; + targ_ns = targ_ns->parent; } /* We never get here */ -- cgit v1.2.3-70-g09d2 From 973c5914260d75292f71a4729753086b9e863d57 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Thu, 17 Nov 2011 01:59:07 -0800 Subject: userns: Start out with a full set of capabilities. Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- kernel/user_namespace.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'kernel/user_namespace.c') diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index c15e533d6bc..e216e1e8ce8 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -11,6 +11,7 @@ #include #include #include +#include static struct kmem_cache *user_ns_cachep __read_mostly; @@ -52,6 +53,14 @@ int create_user_ns(struct cred *new) new->gid = new->egid = new->sgid = new->fsgid = 0; put_group_info(new->group_info); new->group_info = get_group_info(&init_groups); + /* Start with the same capabilities as init but useless for doing + * anything as the capabilities are bound to the new user namespace. + */ + new->securebits = SECUREBITS_DEFAULT; + new->cap_inheritable = CAP_EMPTY_SET; + new->cap_permitted = CAP_FULL_SET; + new->cap_effective = CAP_FULL_SET; + new->cap_bset = CAP_FULL_SET; #ifdef CONFIG_KEYS key_put(new->request_key_auth); new->request_key_auth = NULL; -- cgit v1.2.3-70-g09d2 From 7b44ab978b77a91b327058a0f4db7e6fcdb90b92 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 16 Nov 2011 23:20:58 -0800 Subject: userns: Disassociate user_struct from the user_namespace. Modify alloc_uid to take a kuid and make the user hash table global. Stop holding a reference to the user namespace in struct user_struct. This simplifies the code and makes the per user accounting not care about which user namespace a uid happens to appear in. Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- fs/ioprio.c | 18 ++++++++++++++---- include/linux/sched.h | 8 ++++---- include/linux/user_namespace.h | 4 ---- kernel/sys.c | 34 +++++++++++++++++++++++----------- kernel/user.c | 28 +++++++++++++--------------- kernel/user_namespace.c | 6 +----- 6 files changed, 55 insertions(+), 43 deletions(-) (limited to 'kernel/user_namespace.c') diff --git a/fs/ioprio.c b/fs/ioprio.c index 0f1b9515213..8e35e964d9e 100644 --- a/fs/ioprio.c +++ b/fs/ioprio.c @@ -65,6 +65,7 @@ SYSCALL_DEFINE3(ioprio_set, int, which, int, who, int, ioprio) struct task_struct *p, *g; struct user_struct *user; struct pid *pgrp; + kuid_t uid; int ret; switch (class) { @@ -110,16 +111,21 @@ SYSCALL_DEFINE3(ioprio_set, int, which, int, who, int, ioprio) } while_each_pid_thread(pgrp, PIDTYPE_PGID, p); break; case IOPRIO_WHO_USER: + uid = make_kuid(current_user_ns(), who); + if (!uid_valid(uid)) + break; if (!who) user = current_user(); else - user = find_user(who); + user = find_user(uid); if (!user) break; do_each_thread(g, p) { - if (__task_cred(p)->uid != who) + const struct cred *tcred = __task_cred(p); + kuid_t tcred_uid = make_kuid(tcred->user_ns, tcred->uid); + if (!uid_eq(tcred_uid, uid)) continue; ret = set_task_ioprio(p, ioprio); if (ret) @@ -174,6 +180,7 @@ SYSCALL_DEFINE2(ioprio_get, int, which, int, who) struct task_struct *g, *p; struct user_struct *user; struct pid *pgrp; + kuid_t uid; int ret = -ESRCH; int tmpio; @@ -203,16 +210,19 @@ SYSCALL_DEFINE2(ioprio_get, int, which, int, who) } while_each_pid_thread(pgrp, PIDTYPE_PGID, p); break; case IOPRIO_WHO_USER: + uid = make_kuid(current_user_ns(), who); if (!who) user = current_user(); else - user = find_user(who); + user = find_user(uid); if (!user) break; do_each_thread(g, p) { - if (__task_cred(p)->uid != user->uid) + const struct cred *tcred = __task_cred(p); + kuid_t tcred_uid = make_kuid(tcred->user_ns, tcred->uid); + if (!uid_eq(tcred_uid, user->uid)) continue; tmpio = get_task_ioprio(p); if (tmpio < 0) diff --git a/include/linux/sched.h b/include/linux/sched.h index 6867ae9bc8a..5fdc1ebbcbc 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -90,6 +90,7 @@ struct sched_param { #include #include #include +#include #include @@ -728,8 +729,7 @@ struct user_struct { /* Hash table maintenance information */ struct hlist_node uidhash_node; - uid_t uid; - struct user_namespace *_user_ns; /* Don't use will be removed soon */ + kuid_t uid; #ifdef CONFIG_PERF_EVENTS atomic_long_t locked_vm; @@ -738,7 +738,7 @@ struct user_struct { extern int uids_sysfs_init(void); -extern struct user_struct *find_user(uid_t); +extern struct user_struct *find_user(kuid_t); extern struct user_struct root_user; #define INIT_USER (&root_user) @@ -2177,7 +2177,7 @@ extern struct task_struct *find_task_by_pid_ns(pid_t nr, extern void __set_special_pids(struct pid *pid); /* per-UID process charging. */ -extern struct user_struct * alloc_uid(struct user_namespace *, uid_t); +extern struct user_struct * alloc_uid(kuid_t); static inline struct user_struct *get_uid(struct user_struct *u) { atomic_inc(&u->__count); diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index dc2d85a7637..d767508db4f 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -6,12 +6,8 @@ #include #include -#define UIDHASH_BITS (CONFIG_BASE_SMALL ? 3 : 7) -#define UIDHASH_SZ (1 << UIDHASH_BITS) - struct user_namespace { struct kref kref; - struct hlist_head uidhash_table[UIDHASH_SZ]; struct user_namespace *parent; struct user_struct *creator; struct work_struct destroyer; diff --git a/kernel/sys.c b/kernel/sys.c index 71852417cfc..f0c43b4b665 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -175,6 +175,8 @@ SYSCALL_DEFINE3(setpriority, int, which, int, who, int, niceval) const struct cred *cred = current_cred(); int error = -EINVAL; struct pid *pgrp; + kuid_t cred_uid; + kuid_t uid; if (which > PRIO_USER || which < PRIO_PROCESS) goto out; @@ -207,18 +209,22 @@ SYSCALL_DEFINE3(setpriority, int, which, int, who, int, niceval) } while_each_pid_thread(pgrp, PIDTYPE_PGID, p); break; case PRIO_USER: + cred_uid = make_kuid(cred->user_ns, cred->uid); + uid = make_kuid(cred->user_ns, who); user = cred->user; if (!who) - who = cred->uid; - else if ((who != cred->uid) && - !(user = find_user(who))) + uid = cred_uid; + else if (!uid_eq(uid, cred_uid) && + !(user = find_user(uid))) goto out_unlock; /* No processes for this user */ do_each_thread(g, p) { - if (__task_cred(p)->uid == who) + const struct cred *tcred = __task_cred(p); + kuid_t tcred_uid = make_kuid(tcred->user_ns, tcred->uid); + if (uid_eq(tcred_uid, uid)) error = set_one_prio(p, niceval, error); } while_each_thread(g, p); - if (who != cred->uid) + if (!uid_eq(uid, cred_uid)) free_uid(user); /* For find_user() */ break; } @@ -242,6 +248,8 @@ SYSCALL_DEFINE2(getpriority, int, which, int, who) const struct cred *cred = current_cred(); long niceval, retval = -ESRCH; struct pid *pgrp; + kuid_t cred_uid; + kuid_t uid; if (which > PRIO_USER || which < PRIO_PROCESS) return -EINVAL; @@ -272,21 +280,25 @@ SYSCALL_DEFINE2(getpriority, int, which, int, who) } while_each_pid_thread(pgrp, PIDTYPE_PGID, p); break; case PRIO_USER: + cred_uid = make_kuid(cred->user_ns, cred->uid); + uid = make_kuid(cred->user_ns, who); user = cred->user; if (!who) - who = cred->uid; - else if ((who != cred->uid) && - !(user = find_user(who))) + uid = cred_uid; + else if (!uid_eq(uid, cred_uid) && + !(user = find_user(uid))) goto out_unlock; /* No processes for this user */ do_each_thread(g, p) { - if (__task_cred(p)->uid == who) { + const struct cred *tcred = __task_cred(p); + kuid_t tcred_uid = make_kuid(tcred->user_ns, tcred->uid); + if (uid_eq(tcred_uid, uid)) { niceval = 20 - task_nice(p); if (niceval > retval) retval = niceval; } } while_each_thread(g, p); - if (who != cred->uid) + if (!uid_eq(uid, cred_uid)) free_uid(user); /* for find_user() */ break; } @@ -629,7 +641,7 @@ static int set_user(struct cred *new) { struct user_struct *new_user; - new_user = alloc_uid(current_user_ns(), new->uid); + new_user = alloc_uid(make_kuid(new->user_ns, new->uid)); if (!new_user) return -EAGAIN; diff --git a/kernel/user.c b/kernel/user.c index d65fec0615a..025077e54a7 100644 --- a/kernel/user.c +++ b/kernel/user.c @@ -34,11 +34,14 @@ EXPORT_SYMBOL_GPL(init_user_ns); * when changing user ID's (ie setuid() and friends). */ +#define UIDHASH_BITS (CONFIG_BASE_SMALL ? 3 : 7) +#define UIDHASH_SZ (1 << UIDHASH_BITS) #define UIDHASH_MASK (UIDHASH_SZ - 1) #define __uidhashfn(uid) (((uid >> UIDHASH_BITS) + uid) & UIDHASH_MASK) -#define uidhashentry(ns, uid) ((ns)->uidhash_table + __uidhashfn((uid))) +#define uidhashentry(uid) (uidhash_table + __uidhashfn((__kuid_val(uid)))) static struct kmem_cache *uid_cachep; +struct hlist_head uidhash_table[UIDHASH_SZ]; /* * The uidhash_lock is mostly taken from process context, but it is @@ -58,7 +61,7 @@ struct user_struct root_user = { .files = ATOMIC_INIT(0), .sigpending = ATOMIC_INIT(0), .locked_shm = 0, - ._user_ns = &init_user_ns, + .uid = GLOBAL_ROOT_UID, }; /* @@ -72,16 +75,15 @@ static void uid_hash_insert(struct user_struct *up, struct hlist_head *hashent) static void uid_hash_remove(struct user_struct *up) { hlist_del_init(&up->uidhash_node); - put_user_ns(up->_user_ns); /* It is safe to free the uid hash table now */ } -static struct user_struct *uid_hash_find(uid_t uid, struct hlist_head *hashent) +static struct user_struct *uid_hash_find(kuid_t uid, struct hlist_head *hashent) { struct user_struct *user; struct hlist_node *h; hlist_for_each_entry(user, h, hashent, uidhash_node) { - if (user->uid == uid) { + if (uid_eq(user->uid, uid)) { atomic_inc(&user->__count); return user; } @@ -110,14 +112,13 @@ static void free_user(struct user_struct *up, unsigned long flags) * * If the user_struct could not be found, return NULL. */ -struct user_struct *find_user(uid_t uid) +struct user_struct *find_user(kuid_t uid) { struct user_struct *ret; unsigned long flags; - struct user_namespace *ns = current_user_ns(); spin_lock_irqsave(&uidhash_lock, flags); - ret = uid_hash_find(uid, uidhashentry(ns, uid)); + ret = uid_hash_find(uid, uidhashentry(uid)); spin_unlock_irqrestore(&uidhash_lock, flags); return ret; } @@ -136,9 +137,9 @@ void free_uid(struct user_struct *up) local_irq_restore(flags); } -struct user_struct *alloc_uid(struct user_namespace *ns, uid_t uid) +struct user_struct *alloc_uid(kuid_t uid) { - struct hlist_head *hashent = uidhashentry(ns, uid); + struct hlist_head *hashent = uidhashentry(uid); struct user_struct *up, *new; spin_lock_irq(&uidhash_lock); @@ -153,8 +154,6 @@ struct user_struct *alloc_uid(struct user_namespace *ns, uid_t uid) new->uid = uid; atomic_set(&new->__count, 1); - new->_user_ns = get_user_ns(ns); - /* * Before adding this, check whether we raced * on adding the same user already.. @@ -162,7 +161,6 @@ struct user_struct *alloc_uid(struct user_namespace *ns, uid_t uid) spin_lock_irq(&uidhash_lock); up = uid_hash_find(uid, hashent); if (up) { - put_user_ns(ns); key_put(new->uid_keyring); key_put(new->session_keyring); kmem_cache_free(uid_cachep, new); @@ -187,11 +185,11 @@ static int __init uid_cache_init(void) 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); for(n = 0; n < UIDHASH_SZ; ++n) - INIT_HLIST_HEAD(init_user_ns.uidhash_table + n); + INIT_HLIST_HEAD(uidhash_table + n); /* Insert the root user immediately (init already runs as root) */ spin_lock_irq(&uidhash_lock); - uid_hash_insert(&root_user, uidhashentry(&init_user_ns, 0)); + uid_hash_insert(&root_user, uidhashentry(GLOBAL_ROOT_UID)); spin_unlock_irq(&uidhash_lock); return 0; diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index e216e1e8ce8..898e973bd1e 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -27,7 +27,6 @@ int create_user_ns(struct cred *new) { struct user_namespace *ns, *parent_ns = new->user_ns; struct user_struct *root_user; - int n; ns = kmem_cache_alloc(user_ns_cachep, GFP_KERNEL); if (!ns) @@ -35,11 +34,8 @@ int create_user_ns(struct cred *new) kref_init(&ns->kref); - for (n = 0; n < UIDHASH_SZ; ++n) - INIT_HLIST_HEAD(ns->uidhash_table + n); - /* Alloc new root user. */ - root_user = alloc_uid(ns, 0); + root_user = alloc_uid(make_kuid(ns, 0)); if (!root_user) { kmem_cache_free(user_ns_cachep, ns); return -ENOMEM; -- cgit v1.2.3-70-g09d2 From 783291e6900292521a3895583785e0c04a56c5b3 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Thu, 17 Nov 2011 01:32:59 -0800 Subject: userns: Simplify the user_namespace by making userns->creator a kuid. - Transform userns->creator from a user_struct reference to a simple kuid_t, kgid_t pair. In cap_capable this allows the check to see if we are the creator of a namespace to become the classic suser style euid permission check. This allows us to remove the need for a struct cred in the mapping functions and still be able to dispaly the user namespace creators uid and gid as 0. - Remove the now unnecessary delayed_work in free_user_ns. All that is left for free_user_ns to do is to call kmem_cache_free and put_user_ns. Those functions can be called in any context so call them directly from free_user_ns removing the need for delayed work. Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- include/linux/user_namespace.h | 4 ++-- kernel/user.c | 7 ++++--- kernel/user_namespace.c | 42 ++++++++++++++++++++---------------------- security/commoncap.c | 5 +++-- 4 files changed, 29 insertions(+), 29 deletions(-) (limited to 'kernel/user_namespace.c') diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index d767508db4f..8a391bd53de 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -9,8 +9,8 @@ struct user_namespace { struct kref kref; struct user_namespace *parent; - struct user_struct *creator; - struct work_struct destroyer; + kuid_t owner; + kgid_t group; }; extern struct user_namespace init_user_ns; diff --git a/kernel/user.c b/kernel/user.c index 025077e54a7..cff38565917 100644 --- a/kernel/user.c +++ b/kernel/user.c @@ -25,7 +25,8 @@ struct user_namespace init_user_ns = { .kref = { .refcount = ATOMIC_INIT(3), }, - .creator = &root_user, + .owner = GLOBAL_ROOT_UID, + .group = GLOBAL_ROOT_GID, }; EXPORT_SYMBOL_GPL(init_user_ns); @@ -54,9 +55,9 @@ struct hlist_head uidhash_table[UIDHASH_SZ]; */ static DEFINE_SPINLOCK(uidhash_lock); -/* root_user.__count is 2, 1 for init task cred, 1 for init_user_ns->user_ns */ +/* root_user.__count is 1, for init task cred */ struct user_struct root_user = { - .__count = ATOMIC_INIT(2), + .__count = ATOMIC_INIT(1), .processes = ATOMIC_INIT(1), .files = ATOMIC_INIT(0), .sigpending = ATOMIC_INIT(0), diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 898e973bd1e..ed08836558e 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -27,6 +27,16 @@ int create_user_ns(struct cred *new) { struct user_namespace *ns, *parent_ns = new->user_ns; struct user_struct *root_user; + kuid_t owner = make_kuid(new->user_ns, new->euid); + kgid_t group = make_kgid(new->user_ns, new->egid); + + /* The creator needs a mapping in the parent user namespace + * or else we won't be able to reasonably tell userspace who + * created a user_namespace. + */ + if (!kuid_has_mapping(parent_ns, owner) || + !kgid_has_mapping(parent_ns, group)) + return -EPERM; ns = kmem_cache_alloc(user_ns_cachep, GFP_KERNEL); if (!ns) @@ -43,7 +53,9 @@ int create_user_ns(struct cred *new) /* set the new root user in the credentials under preparation */ ns->parent = parent_ns; - ns->creator = new->user; + ns->owner = owner; + ns->group = group; + free_uid(new->user); new->user = root_user; new->uid = new->euid = new->suid = new->fsuid = 0; new->gid = new->egid = new->sgid = new->fsgid = 0; @@ -63,35 +75,22 @@ int create_user_ns(struct cred *new) #endif /* tgcred will be cleared in our caller bc CLONE_THREAD won't be set */ - /* Leave the reference to our user_ns with the new cred */ + /* Leave the new->user_ns reference with the new user namespace. */ + /* Leave the reference to our user_ns with the new cred. */ new->user_ns = ns; return 0; } -/* - * Deferred destructor for a user namespace. This is required because - * free_user_ns() may be called with uidhash_lock held, but we need to call - * back to free_uid() which will want to take the lock again. - */ -static void free_user_ns_work(struct work_struct *work) +void free_user_ns(struct kref *kref) { struct user_namespace *parent, *ns = - container_of(work, struct user_namespace, destroyer); + container_of(kref, struct user_namespace, kref); + parent = ns->parent; - free_uid(ns->creator); kmem_cache_free(user_ns_cachep, ns); put_user_ns(parent); } - -void free_user_ns(struct kref *kref) -{ - struct user_namespace *ns = - container_of(kref, struct user_namespace, kref); - - INIT_WORK(&ns->destroyer, free_user_ns_work); - schedule_work(&ns->destroyer); -} EXPORT_SYMBOL(free_user_ns); uid_t user_ns_map_uid(struct user_namespace *to, const struct cred *cred, uid_t uid) @@ -101,12 +100,11 @@ uid_t user_ns_map_uid(struct user_namespace *to, const struct cred *cred, uid_t if (likely(to == cred->user_ns)) return uid; - /* Is cred->user the creator of the target user_ns * or the creator of one of it's parents? */ for ( tmp = to; tmp != &init_user_ns; tmp = tmp->parent ) { - if (cred->user == tmp->creator) { + if (uid_eq(cred->user->uid, tmp->owner)) { return (uid_t)0; } } @@ -126,7 +124,7 @@ gid_t user_ns_map_gid(struct user_namespace *to, const struct cred *cred, gid_t * or the creator of one of it's parents? */ for ( tmp = to; tmp != &init_user_ns; tmp = tmp->parent ) { - if (cred->user == tmp->creator) { + if (uid_eq(cred->user->uid, tmp->owner)) { return (gid_t)0; } } diff --git a/security/commoncap.c b/security/commoncap.c index 435d074853f..f2399d8afbe 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -76,8 +76,9 @@ int cap_capable(const struct cred *cred, struct user_namespace *targ_ns, int cap, int audit) { for (;;) { - /* The creator of the user namespace has all caps. */ - if (targ_ns != &init_user_ns && targ_ns->creator == cred->user) + /* The owner of the user namespace has all caps. */ + if (targ_ns != &init_user_ns && uid_eq(targ_ns->owner, + make_kuid(cred->user_ns, cred->euid))) return 0; /* Do we have the necessary capabilities? */ -- cgit v1.2.3-70-g09d2 From 22d917d80e842829d0ca0a561967d728eb1d6303 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Thu, 17 Nov 2011 00:11:58 -0800 Subject: userns: Rework the user_namespace adding uid/gid mapping support - Convert the old uid mapping functions into compatibility wrappers - Add a uid/gid mapping layer from user space uid and gids to kernel internal uids and gids that is extent based for simplicty and speed. * Working with number space after mapping uids/gids into their kernel internal version adds only mapping complexity over what we have today, leaving the kernel code easy to understand and test. - Add proc files /proc/self/uid_map /proc/self/gid_map These files display the mapping and allow a mapping to be added if a mapping does not exist. - Allow entering the user namespace without a uid or gid mapping. Since we are starting with an existing user our uids and gids still have global mappings so are still valid and useful they just don't have local mappings. The requirement for things to work are global uid and gid so it is odd but perfectly fine not to have a local uid and gid mapping. Not requiring global uid and gid mappings greatly simplifies the logic of setting up the uid and gid mappings by allowing the mappings to be set after the namespace is created which makes the slight weirdness worth it. - Make the mappings in the initial user namespace to the global uid/gid space explicit. Today it is an identity mapping but in the future we may want to twist this for debugging, similar to what we do with jiffies. - Document the memory ordering requirements of setting the uid and gid mappings. We only allow the mappings to be set once and there are no pointers involved so the requirments are trivial but a little atypical. Performance: In this scheme for the permission checks the performance is expected to stay the same as the actuall machine instructions should remain the same. The worst case I could think of is ls -l on a large directory where all of the stat results need to be translated with from kuids and kgids to uids and gids. So I benchmarked that case on my laptop with a dual core hyperthread Intel i5-2520M cpu with 3M of cpu cache. My benchmark consisted of going to single user mode where nothing else was running. On an ext4 filesystem opening 1,000,000 files and looping through all of the files 1000 times and calling fstat on the individuals files. This was to ensure I was benchmarking stat times where the inodes were in the kernels cache, but the inode values were not in the processors cache. My results: v3.4-rc1: ~= 156ns (unmodified v3.4-rc1 with user namespace support disabled) v3.4-rc1-userns-: ~= 155ns (v3.4-rc1 with my user namespace patches and user namespace support disabled) v3.4-rc1-userns+: ~= 164ns (v3.4-rc1 with my user namespace patches and user namespace support enabled) All of the configurations ran in roughly 120ns when I performed tests that ran in the cpu cache. So in summary the performance impact is: 1ns improvement in the worst case with user namespace support compiled out. 8ns aka 5% slowdown in the worst case with user namespace support compiled in. Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- fs/proc/base.c | 77 ++++++ include/linux/uidgid.h | 24 ++ include/linux/user_namespace.h | 30 ++- kernel/user.c | 16 ++ kernel/user_namespace.c | 545 +++++++++++++++++++++++++++++++++++++---- 5 files changed, 644 insertions(+), 48 deletions(-) (limited to 'kernel/user_namespace.c') diff --git a/fs/proc/base.c b/fs/proc/base.c index 1c8b280146d..2ee514c7e64 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -81,6 +81,7 @@ #include #include #include +#include #include #include #include @@ -2943,6 +2944,74 @@ static int proc_tgid_io_accounting(struct task_struct *task, char *buffer) } #endif /* CONFIG_TASK_IO_ACCOUNTING */ +#ifdef CONFIG_USER_NS +static int proc_id_map_open(struct inode *inode, struct file *file, + struct seq_operations *seq_ops) +{ + struct user_namespace *ns = NULL; + struct task_struct *task; + struct seq_file *seq; + int ret = -EINVAL; + + 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; + + ret = seq_open(file, seq_ops); + if (ret) + goto err_put_ns; + + seq = file->private_data; + seq->private = ns; + + return 0; +err_put_ns: + put_user_ns(ns); +err: + return ret; +} + +static int proc_id_map_release(struct inode *inode, struct file *file) +{ + struct seq_file *seq = file->private_data; + struct user_namespace *ns = seq->private; + put_user_ns(ns); + return seq_release(inode, file); +} + +static int proc_uid_map_open(struct inode *inode, struct file *file) +{ + return proc_id_map_open(inode, file, &proc_uid_seq_operations); +} + +static int proc_gid_map_open(struct inode *inode, struct file *file) +{ + return proc_id_map_open(inode, file, &proc_gid_seq_operations); +} + +static const struct file_operations proc_uid_map_operations = { + .open = proc_uid_map_open, + .write = proc_uid_map_write, + .read = seq_read, + .llseek = seq_lseek, + .release = proc_id_map_release, +}; + +static const struct file_operations proc_gid_map_operations = { + .open = proc_gid_map_open, + .write = proc_gid_map_write, + .read = seq_read, + .llseek = seq_lseek, + .release = proc_id_map_release, +}; +#endif /* CONFIG_USER_NS */ + static int proc_pid_personality(struct seq_file *m, struct pid_namespace *ns, struct pid *pid, struct task_struct *task) { @@ -3045,6 +3114,10 @@ static const struct pid_entry tgid_base_stuff[] = { #ifdef CONFIG_HARDWALL INF("hardwall", S_IRUGO, proc_pid_hardwall), #endif +#ifdef CONFIG_USER_NS + REG("uid_map", S_IRUGO|S_IWUSR, proc_uid_map_operations), + REG("gid_map", S_IRUGO|S_IWUSR, proc_gid_map_operations), +#endif }; static int proc_tgid_base_readdir(struct file * filp, @@ -3400,6 +3473,10 @@ static const struct pid_entry tid_base_stuff[] = { #ifdef CONFIG_HARDWALL INF("hardwall", S_IRUGO, proc_pid_hardwall), #endif +#ifdef CONFIG_USER_NS + REG("uid_map", S_IRUGO|S_IWUSR, proc_uid_map_operations), + REG("gid_map", S_IRUGO|S_IWUSR, proc_gid_map_operations), +#endif }; static int proc_tid_base_readdir(struct file * filp, diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h index 539856829ca..8e522cbcef2 100644 --- a/include/linux/uidgid.h +++ b/include/linux/uidgid.h @@ -127,6 +127,28 @@ static inline bool gid_valid(kgid_t gid) return !gid_eq(gid, INVALID_GID); } +#ifdef CONFIG_USER_NS + +extern kuid_t make_kuid(struct user_namespace *from, uid_t uid); +extern kgid_t make_kgid(struct user_namespace *from, gid_t gid); + +extern uid_t from_kuid(struct user_namespace *to, kuid_t uid); +extern gid_t from_kgid(struct user_namespace *to, kgid_t gid); +extern uid_t from_kuid_munged(struct user_namespace *to, kuid_t uid); +extern gid_t from_kgid_munged(struct user_namespace *to, kgid_t gid); + +static inline bool kuid_has_mapping(struct user_namespace *ns, kuid_t uid) +{ + return from_kuid(ns, uid) != (uid_t) -1; +} + +static inline bool kgid_has_mapping(struct user_namespace *ns, kgid_t gid) +{ + return from_kgid(ns, gid) != (gid_t) -1; +} + +#else + static inline kuid_t make_kuid(struct user_namespace *from, uid_t uid) { return KUIDT_INIT(uid); @@ -173,4 +195,6 @@ static inline bool kgid_has_mapping(struct user_namespace *ns, kgid_t gid) return true; } +#endif /* CONFIG_USER_NS */ + #endif /* _LINUX_UIDGID_H */ diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 8a391bd53de..4c9846d9074 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -6,7 +6,20 @@ #include #include +#define UID_GID_MAP_MAX_EXTENTS 5 + +struct uid_gid_map { /* 64 bytes -- 1 cache line */ + u32 nr_extents; + struct uid_gid_extent { + u32 first; + u32 lower_first; + u32 count; + } extent[UID_GID_MAP_MAX_EXTENTS]; +}; + struct user_namespace { + struct uid_gid_map uid_map; + struct uid_gid_map gid_map; struct kref kref; struct user_namespace *parent; kuid_t owner; @@ -33,9 +46,11 @@ static inline void put_user_ns(struct user_namespace *ns) kref_put(&ns->kref, free_user_ns); } -uid_t user_ns_map_uid(struct user_namespace *to, const struct cred *cred, uid_t uid); -gid_t user_ns_map_gid(struct user_namespace *to, const struct cred *cred, gid_t gid); - +struct seq_operations; +extern struct seq_operations proc_uid_seq_operations; +extern struct seq_operations proc_gid_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 *); #else static inline struct user_namespace *get_user_ns(struct user_namespace *ns) @@ -52,17 +67,18 @@ static inline void put_user_ns(struct user_namespace *ns) { } +#endif + static inline uid_t user_ns_map_uid(struct user_namespace *to, const struct cred *cred, uid_t uid) { - return uid; + return from_kuid_munged(to, make_kuid(cred->user_ns, uid)); } + static inline gid_t user_ns_map_gid(struct user_namespace *to, const struct cred *cred, gid_t gid) { - return gid; + return from_kgid_munged(to, make_kgid(cred->user_ns, gid)); } -#endif - #endif /* _LINUX_USER_H */ diff --git a/kernel/user.c b/kernel/user.c index cff38565917..f9e420e3669 100644 --- a/kernel/user.c +++ b/kernel/user.c @@ -22,6 +22,22 @@ * and 1 for... ? */ struct user_namespace init_user_ns = { + .uid_map = { + .nr_extents = 1, + .extent[0] = { + .first = 0, + .lower_first = 0, + .count = 4294967295, + }, + }, + .gid_map = { + .nr_extents = 1, + .extent[0] = { + .first = 0, + .lower_first = 0, + .count = 4294967295, + }, + }, .kref = { .refcount = ATOMIC_INIT(3), }, diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index ed08836558e..7eff867bfac 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -12,9 +12,19 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include static struct kmem_cache *user_ns_cachep __read_mostly; +static bool new_idmap_permitted(struct user_namespace *ns, int cap_setid, + struct uid_gid_map *map); + /* * Create a new user namespace, deriving the creator from the user in the * passed credentials, and replacing that user with the new root user for the @@ -26,7 +36,6 @@ static struct kmem_cache *user_ns_cachep __read_mostly; int create_user_ns(struct cred *new) { struct user_namespace *ns, *parent_ns = new->user_ns; - struct user_struct *root_user; kuid_t owner = make_kuid(new->user_ns, new->euid); kgid_t group = make_kgid(new->user_ns, new->egid); @@ -38,29 +47,15 @@ int create_user_ns(struct cred *new) !kgid_has_mapping(parent_ns, group)) return -EPERM; - ns = kmem_cache_alloc(user_ns_cachep, GFP_KERNEL); + ns = kmem_cache_zalloc(user_ns_cachep, GFP_KERNEL); if (!ns) return -ENOMEM; kref_init(&ns->kref); - - /* Alloc new root user. */ - root_user = alloc_uid(make_kuid(ns, 0)); - if (!root_user) { - kmem_cache_free(user_ns_cachep, ns); - return -ENOMEM; - } - - /* set the new root user in the credentials under preparation */ ns->parent = parent_ns; ns->owner = owner; ns->group = group; - free_uid(new->user); - new->user = root_user; - new->uid = new->euid = new->suid = new->fsuid = 0; - new->gid = new->egid = new->sgid = new->fsgid = 0; - put_group_info(new->group_info); - new->group_info = get_group_info(&init_groups); + /* Start with the same capabilities as init but useless for doing * anything as the capabilities are bound to the new user namespace. */ @@ -93,44 +88,512 @@ void free_user_ns(struct kref *kref) } EXPORT_SYMBOL(free_user_ns); -uid_t user_ns_map_uid(struct user_namespace *to, const struct cred *cred, uid_t uid) +static u32 map_id_range_down(struct uid_gid_map *map, u32 id, u32 count) { - struct user_namespace *tmp; + unsigned idx, extents; + u32 first, last, id2; - if (likely(to == cred->user_ns)) - return uid; + id2 = id + count - 1; - /* Is cred->user the creator of the target user_ns - * or the creator of one of it's parents? - */ - for ( tmp = to; tmp != &init_user_ns; tmp = tmp->parent ) { - if (uid_eq(cred->user->uid, tmp->owner)) { - return (uid_t)0; - } + /* Find the matching extent */ + extents = map->nr_extents; + smp_read_barrier_depends(); + for (idx = 0; idx < extents; idx++) { + first = map->extent[idx].first; + last = first + map->extent[idx].count - 1; + if (id >= first && id <= last && + (id2 >= first && id2 <= last)) + break; + } + /* Map the id or note failure */ + if (idx < extents) + id = (id - first) + map->extent[idx].lower_first; + else + id = (u32) -1; + + return id; +} + +static u32 map_id_down(struct uid_gid_map *map, u32 id) +{ + unsigned idx, extents; + u32 first, last; + + /* Find the matching extent */ + extents = map->nr_extents; + smp_read_barrier_depends(); + for (idx = 0; idx < extents; idx++) { + first = map->extent[idx].first; + last = first + map->extent[idx].count - 1; + if (id >= first && id <= last) + break; + } + /* Map the id or note failure */ + if (idx < extents) + id = (id - first) + map->extent[idx].lower_first; + else + id = (u32) -1; + + return id; +} + +static u32 map_id_up(struct uid_gid_map *map, u32 id) +{ + unsigned idx, extents; + u32 first, last; + + /* Find the matching extent */ + extents = map->nr_extents; + smp_read_barrier_depends(); + for (idx = 0; idx < extents; idx++) { + first = map->extent[idx].lower_first; + last = first + map->extent[idx].count - 1; + if (id >= first && id <= last) + break; } + /* Map the id or note failure */ + if (idx < extents) + id = (id - first) + map->extent[idx].first; + else + id = (u32) -1; + + return id; +} + +/** + * make_kuid - Map a user-namespace uid pair into a kuid. + * @ns: User namespace that the uid is in + * @uid: User identifier + * + * Maps a user-namespace uid pair into a kernel internal kuid, + * and returns that kuid. + * + * When there is no mapping defined for the user-namespace uid + * pair INVALID_UID is returned. Callers are expected to test + * for and handle handle INVALID_UID being returned. INVALID_UID + * may be tested for using uid_valid(). + */ +kuid_t make_kuid(struct user_namespace *ns, uid_t uid) +{ + /* Map the uid to a global kernel uid */ + return KUIDT_INIT(map_id_down(&ns->uid_map, uid)); +} +EXPORT_SYMBOL(make_kuid); + +/** + * from_kuid - Create a uid from a kuid user-namespace pair. + * @targ: The user namespace we want a uid in. + * @kuid: The kernel internal uid to start with. + * + * Map @kuid into the user-namespace specified by @targ and + * return the resulting uid. + * + * There is always a mapping into the initial user_namespace. + * + * If @kuid has no mapping in @targ (uid_t)-1 is returned. + */ +uid_t from_kuid(struct user_namespace *targ, kuid_t kuid) +{ + /* Map the uid from a global kernel uid */ + return map_id_up(&targ->uid_map, __kuid_val(kuid)); +} +EXPORT_SYMBOL(from_kuid); + +/** + * from_kuid_munged - Create a uid from a kuid user-namespace pair. + * @targ: The user namespace we want a uid in. + * @kuid: The kernel internal uid to start with. + * + * Map @kuid into the user-namespace specified by @targ and + * return the resulting uid. + * + * There is always a mapping into the initial user_namespace. + * + * Unlike from_kuid from_kuid_munged never fails and always + * returns a valid uid. This makes from_kuid_munged appropriate + * for use in syscalls like stat and getuid where failing the + * system call and failing to provide a valid uid are not an + * options. + * + * If @kuid has no mapping in @targ overflowuid is returned. + */ +uid_t from_kuid_munged(struct user_namespace *targ, kuid_t kuid) +{ + uid_t uid; + uid = from_kuid(targ, kuid); + + if (uid == (uid_t) -1) + uid = overflowuid; + return uid; +} +EXPORT_SYMBOL(from_kuid_munged); + +/** + * make_kgid - Map a user-namespace gid pair into a kgid. + * @ns: User namespace that the gid is in + * @uid: group identifier + * + * Maps a user-namespace gid pair into a kernel internal kgid, + * and returns that kgid. + * + * When there is no mapping defined for the user-namespace gid + * pair INVALID_GID is returned. Callers are expected to test + * for and handle INVALID_GID being returned. INVALID_GID may be + * tested for using gid_valid(). + */ +kgid_t make_kgid(struct user_namespace *ns, gid_t gid) +{ + /* Map the gid to a global kernel gid */ + return KGIDT_INIT(map_id_down(&ns->gid_map, gid)); +} +EXPORT_SYMBOL(make_kgid); + +/** + * from_kgid - Create a gid from a kgid user-namespace pair. + * @targ: The user namespace we want a gid in. + * @kgid: The kernel internal gid to start with. + * + * Map @kgid into the user-namespace specified by @targ and + * return the resulting gid. + * + * There is always a mapping into the initial user_namespace. + * + * If @kgid has no mapping in @targ (gid_t)-1 is returned. + */ +gid_t from_kgid(struct user_namespace *targ, kgid_t kgid) +{ + /* Map the gid from a global kernel gid */ + return map_id_up(&targ->gid_map, __kgid_val(kgid)); +} +EXPORT_SYMBOL(from_kgid); + +/** + * from_kgid_munged - Create a gid from a kgid user-namespace pair. + * @targ: The user namespace we want a gid in. + * @kgid: The kernel internal gid to start with. + * + * Map @kgid into the user-namespace specified by @targ and + * return the resulting gid. + * + * There is always a mapping into the initial user_namespace. + * + * Unlike from_kgid from_kgid_munged never fails and always + * returns a valid gid. This makes from_kgid_munged appropriate + * for use in syscalls like stat and getgid where failing the + * system call and failing to provide a valid gid are not options. + * + * If @kgid has no mapping in @targ overflowgid is returned. + */ +gid_t from_kgid_munged(struct user_namespace *targ, kgid_t kgid) +{ + gid_t gid; + gid = from_kgid(targ, kgid); + + if (gid == (gid_t) -1) + gid = overflowgid; + return gid; +} +EXPORT_SYMBOL(from_kgid_munged); + +static int uid_m_show(struct seq_file *seq, void *v) +{ + struct user_namespace *ns = seq->private; + struct uid_gid_extent *extent = v; + struct user_namespace *lower_ns; + uid_t lower; - /* No useful relationship so no mapping */ - return overflowuid; + lower_ns = current_user_ns(); + if ((lower_ns == ns) && lower_ns->parent) + lower_ns = lower_ns->parent; + + lower = from_kuid(lower_ns, KUIDT_INIT(extent->lower_first)); + + seq_printf(seq, "%10u %10u %10u\n", + extent->first, + lower, + extent->count); + + return 0; } -gid_t user_ns_map_gid(struct user_namespace *to, const struct cred *cred, gid_t gid) +static int gid_m_show(struct seq_file *seq, void *v) { - struct user_namespace *tmp; + struct user_namespace *ns = seq->private; + struct uid_gid_extent *extent = v; + struct user_namespace *lower_ns; + gid_t lower; - if (likely(to == cred->user_ns)) - return gid; + lower_ns = current_user_ns(); + if ((lower_ns == ns) && lower_ns->parent) + lower_ns = lower_ns->parent; - /* Is cred->user the creator of the target user_ns - * or the creator of one of it's parents? + lower = from_kgid(lower_ns, KGIDT_INIT(extent->lower_first)); + + seq_printf(seq, "%10u %10u %10u\n", + extent->first, + lower, + extent->count); + + return 0; +} + +static void *m_start(struct seq_file *seq, loff_t *ppos, struct uid_gid_map *map) +{ + struct uid_gid_extent *extent = NULL; + loff_t pos = *ppos; + + if (pos < map->nr_extents) + extent = &map->extent[pos]; + + return extent; +} + +static void *uid_m_start(struct seq_file *seq, loff_t *ppos) +{ + struct user_namespace *ns = seq->private; + + return m_start(seq, ppos, &ns->uid_map); +} + +static void *gid_m_start(struct seq_file *seq, loff_t *ppos) +{ + struct user_namespace *ns = seq->private; + + return m_start(seq, ppos, &ns->gid_map); +} + +static void *m_next(struct seq_file *seq, void *v, loff_t *pos) +{ + (*pos)++; + return seq->op->start(seq, pos); +} + +static void m_stop(struct seq_file *seq, void *v) +{ + return; +} + +struct seq_operations proc_uid_seq_operations = { + .start = uid_m_start, + .stop = m_stop, + .next = m_next, + .show = uid_m_show, +}; + +struct seq_operations proc_gid_seq_operations = { + .start = gid_m_start, + .stop = m_stop, + .next = m_next, + .show = gid_m_show, +}; + +static DEFINE_MUTEX(id_map_mutex); + +static ssize_t map_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos, + int cap_setid, + struct uid_gid_map *map, + struct uid_gid_map *parent_map) +{ + struct seq_file *seq = file->private_data; + struct user_namespace *ns = seq->private; + struct uid_gid_map new_map; + unsigned idx; + struct uid_gid_extent *extent, *last = NULL; + unsigned long page = 0; + char *kbuf, *pos, *next_line; + ssize_t ret = -EINVAL; + + /* + * The id_map_mutex serializes all writes to any given map. + * + * Any map is only ever written once. + * + * An id map fits within 1 cache line on most architectures. + * + * On read nothing needs to be done unless you are on an + * architecture with a crazy cache coherency model like alpha. + * + * There is a one time data dependency between reading the + * count of the extents and the values of the extents. The + * desired behavior is to see the values of the extents that + * were written before the count of the extents. + * + * To achieve this smp_wmb() is used on guarantee the write + * order and smp_read_barrier_depends() is guaranteed that we + * don't have crazy architectures returning stale data. + * + */ + mutex_lock(&id_map_mutex); + + ret = -EPERM; + /* Only allow one successful write to the map */ + if (map->nr_extents != 0) + goto out; + + /* Require the appropriate privilege CAP_SETUID or CAP_SETGID + * over the user namespace in order to set the id mapping. */ - for ( tmp = to; tmp != &init_user_ns; tmp = tmp->parent ) { - if (uid_eq(cred->user->uid, tmp->owner)) { - return (gid_t)0; + if (!ns_capable(ns, cap_setid)) + goto out; + + /* Get a buffer */ + ret = -ENOMEM; + page = __get_free_page(GFP_TEMPORARY); + kbuf = (char *) page; + if (!page) + goto out; + + /* Only allow <= page size writes at the beginning of the file */ + ret = -EINVAL; + if ((*ppos != 0) || (count >= PAGE_SIZE)) + goto out; + + /* Slurp in the user data */ + ret = -EFAULT; + if (copy_from_user(kbuf, buf, count)) + goto out; + kbuf[count] = '\0'; + + /* Parse the user data */ + ret = -EINVAL; + pos = kbuf; + new_map.nr_extents = 0; + for (;pos; pos = next_line) { + extent = &new_map.extent[new_map.nr_extents]; + + /* Find the end of line and ensure I don't look past it */ + next_line = strchr(pos, '\n'); + if (next_line) { + *next_line = '\0'; + next_line++; + if (*next_line == '\0') + next_line = NULL; } + + pos = skip_spaces(pos); + extent->first = simple_strtoul(pos, &pos, 10); + if (!isspace(*pos)) + goto out; + + pos = skip_spaces(pos); + extent->lower_first = simple_strtoul(pos, &pos, 10); + if (!isspace(*pos)) + goto out; + + pos = skip_spaces(pos); + extent->count = simple_strtoul(pos, &pos, 10); + if (*pos && !isspace(*pos)) + goto out; + + /* Verify there is not trailing junk on the line */ + pos = skip_spaces(pos); + if (*pos != '\0') + goto out; + + /* Verify we have been given valid starting values */ + if ((extent->first == (u32) -1) || + (extent->lower_first == (u32) -1 )) + goto out; + + /* Verify count is not zero and does not cause the extent to wrap */ + if ((extent->first + extent->count) <= extent->first) + goto out; + if ((extent->lower_first + extent->count) <= extent->lower_first) + goto out; + + /* For now only accept extents that are strictly in order */ + if (last && + (((last->first + last->count) > extent->first) || + ((last->lower_first + last->count) > extent->lower_first))) + goto out; + + new_map.nr_extents++; + last = extent; + + /* Fail if the file contains too many extents */ + if ((new_map.nr_extents == UID_GID_MAP_MAX_EXTENTS) && + (next_line != NULL)) + goto out; } + /* Be very certaint the new map actually exists */ + if (new_map.nr_extents == 0) + goto out; + + ret = -EPERM; + /* Validate the user is allowed to use user id's mapped to. */ + if (!new_idmap_permitted(ns, cap_setid, &new_map)) + goto out; + + /* Map the lower ids from the parent user namespace to the + * kernel global id space. + */ + for (idx = 0; idx < new_map.nr_extents; idx++) { + u32 lower_first; + extent = &new_map.extent[idx]; + + lower_first = map_id_range_down(parent_map, + extent->lower_first, + extent->count); + + /* Fail if we can not map the specified extent to + * the kernel global id space. + */ + if (lower_first == (u32) -1) + goto out; + + extent->lower_first = lower_first; + } + + /* Install the map */ + memcpy(map->extent, new_map.extent, + new_map.nr_extents*sizeof(new_map.extent[0])); + smp_wmb(); + map->nr_extents = new_map.nr_extents; + + *ppos = count; + ret = count; +out: + mutex_unlock(&id_map_mutex); + if (page) + free_page(page); + return ret; +} + +ssize_t proc_uid_map_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) +{ + struct seq_file *seq = file->private_data; + struct user_namespace *ns = seq->private; + + if (!ns->parent) + return -EPERM; + + return map_write(file, buf, size, ppos, CAP_SETUID, + &ns->uid_map, &ns->parent->uid_map); +} + +ssize_t proc_gid_map_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) +{ + struct seq_file *seq = file->private_data; + struct user_namespace *ns = seq->private; + + if (!ns->parent) + return -EPERM; + + return map_write(file, buf, size, ppos, CAP_SETGID, + &ns->gid_map, &ns->parent->gid_map); +} + +static bool new_idmap_permitted(struct user_namespace *ns, int cap_setid, + struct uid_gid_map *new_map) +{ + /* Allow the specified ids if we have the appropriate capability + * (CAP_SETUID or CAP_SETGID) over the parent user namespace. + */ + if (ns_capable(ns->parent, cap_setid)) + return true; - /* No useful relationship so no mapping */ - return overflowgid; + return false; } static __init int user_namespaces_init(void) -- cgit v1.2.3-70-g09d2 From 078de5f706ece36afd73bb4b8283314132d2dfdf Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 8 Feb 2012 07:00:08 -0800 Subject: userns: Store uid and gid values in struct cred with kuid_t and kgid_t types cred.h and a few trivial users of struct cred are changed. The rest of the users of struct cred are left for other patches as there are too many changes to make in one go and leave the change reviewable. If the user namespace is disabled and CONFIG_UIDGID_STRICT_TYPE_CHECKS are disabled the code will contiue to compile and behave correctly. Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- arch/x86/mm/fault.c | 2 +- fs/ioprio.c | 8 ++------ include/linux/cred.h | 16 ++++++++-------- include/linux/user_namespace.h | 8 ++++---- kernel/cred.c | 36 ++++++++++++++++++++++-------------- kernel/signal.c | 14 ++++++++------ kernel/sys.c | 26 +++++++++----------------- kernel/user_namespace.c | 4 ++-- mm/oom_kill.c | 4 ++-- security/commoncap.c | 3 +-- 10 files changed, 59 insertions(+), 62 deletions(-) (limited to 'kernel/user_namespace.c') diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c index 3ecfd1aaf21..76dcd9d8e0b 100644 --- a/arch/x86/mm/fault.c +++ b/arch/x86/mm/fault.c @@ -582,7 +582,7 @@ show_fault_oops(struct pt_regs *regs, unsigned long error_code, pte_t *pte = lookup_address(address, &level); if (pte && pte_present(*pte) && !pte_exec(*pte)) - printk(nx_warning, current_uid()); + printk(nx_warning, from_kuid(&init_user_ns, current_uid())); } printk(KERN_ALERT "BUG: unable to handle kernel "); diff --git a/fs/ioprio.c b/fs/ioprio.c index 8e35e964d9e..2072e41785d 100644 --- a/fs/ioprio.c +++ b/fs/ioprio.c @@ -123,9 +123,7 @@ SYSCALL_DEFINE3(ioprio_set, int, which, int, who, int, ioprio) break; do_each_thread(g, p) { - const struct cred *tcred = __task_cred(p); - kuid_t tcred_uid = make_kuid(tcred->user_ns, tcred->uid); - if (!uid_eq(tcred_uid, uid)) + if (!uid_eq(task_uid(p), uid)) continue; ret = set_task_ioprio(p, ioprio); if (ret) @@ -220,9 +218,7 @@ SYSCALL_DEFINE2(ioprio_get, int, which, int, who) break; do_each_thread(g, p) { - const struct cred *tcred = __task_cred(p); - kuid_t tcred_uid = make_kuid(tcred->user_ns, tcred->uid); - if (!uid_eq(tcred_uid, user->uid)) + if (!uid_eq(task_uid(p), user->uid)) continue; tmpio = get_task_ioprio(p); if (tmpio < 0) diff --git a/include/linux/cred.h b/include/linux/cred.h index 0ab3cda4a77..fac0579258f 100644 --- a/include/linux/cred.h +++ b/include/linux/cred.h @@ -123,14 +123,14 @@ struct cred { #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif - uid_t uid; /* real UID of the task */ - gid_t gid; /* real GID of the task */ - uid_t suid; /* saved UID of the task */ - gid_t sgid; /* saved GID of the task */ - uid_t euid; /* effective UID of the task */ - gid_t egid; /* effective GID of the task */ - uid_t fsuid; /* UID for VFS ops */ - gid_t fsgid; /* GID for VFS ops */ + kuid_t uid; /* real UID of the task */ + kgid_t gid; /* real GID of the task */ + kuid_t suid; /* saved UID of the task */ + kgid_t sgid; /* saved GID of the task */ + kuid_t euid; /* effective UID of the task */ + kgid_t egid; /* effective GID of the task */ + kuid_t fsuid; /* UID for VFS ops */ + kgid_t fsgid; /* GID for VFS ops */ unsigned securebits; /* SUID-less security management */ kernel_cap_t cap_inheritable; /* caps our children can inherit */ kernel_cap_t cap_permitted; /* caps we're permitted */ diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 4c9846d9074..a2c61457cba 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -70,15 +70,15 @@ static inline void put_user_ns(struct user_namespace *ns) #endif static inline uid_t user_ns_map_uid(struct user_namespace *to, - const struct cred *cred, uid_t uid) + const struct cred *cred, kuid_t uid) { - return from_kuid_munged(to, make_kuid(cred->user_ns, uid)); + return from_kuid_munged(to, uid); } static inline gid_t user_ns_map_gid(struct user_namespace *to, - const struct cred *cred, gid_t gid) + const struct cred *cred, kgid_t gid) { - return from_kgid_munged(to, make_kgid(cred->user_ns, gid)); + return from_kgid_munged(to, gid); } #endif /* _LINUX_USER_H */ diff --git a/kernel/cred.c b/kernel/cred.c index 7a0d8066988..eddc5e2e958 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -49,6 +49,14 @@ struct cred init_cred = { .subscribers = ATOMIC_INIT(2), .magic = CRED_MAGIC, #endif + .uid = GLOBAL_ROOT_UID, + .gid = GLOBAL_ROOT_GID, + .suid = GLOBAL_ROOT_UID, + .sgid = GLOBAL_ROOT_GID, + .euid = GLOBAL_ROOT_UID, + .egid = GLOBAL_ROOT_GID, + .fsuid = GLOBAL_ROOT_UID, + .fsgid = GLOBAL_ROOT_GID, .securebits = SECUREBITS_DEFAULT, .cap_inheritable = CAP_EMPTY_SET, .cap_permitted = CAP_FULL_SET, @@ -488,10 +496,10 @@ int commit_creds(struct cred *new) get_cred(new); /* we will require a ref for the subj creds too */ /* dumpability changes */ - if (old->euid != new->euid || - old->egid != new->egid || - old->fsuid != new->fsuid || - old->fsgid != new->fsgid || + if (!uid_eq(old->euid, new->euid) || + !gid_eq(old->egid, new->egid) || + !uid_eq(old->fsuid, new->fsuid) || + !gid_eq(old->fsgid, new->fsgid) || !cap_issubset(new->cap_permitted, old->cap_permitted)) { if (task->mm) set_dumpable(task->mm, suid_dumpable); @@ -500,9 +508,9 @@ int commit_creds(struct cred *new) } /* alter the thread keyring */ - if (new->fsuid != old->fsuid) + if (!uid_eq(new->fsuid, old->fsuid)) key_fsuid_changed(task); - if (new->fsgid != old->fsgid) + if (!gid_eq(new->fsgid, old->fsgid)) key_fsgid_changed(task); /* do it @@ -519,16 +527,16 @@ int commit_creds(struct cred *new) alter_cred_subscribers(old, -2); /* send notifications */ - if (new->uid != old->uid || - new->euid != old->euid || - new->suid != old->suid || - new->fsuid != old->fsuid) + if (!uid_eq(new->uid, old->uid) || + !uid_eq(new->euid, old->euid) || + !uid_eq(new->suid, old->suid) || + !uid_eq(new->fsuid, old->fsuid)) proc_id_connector(task, PROC_EVENT_UID); - if (new->gid != old->gid || - new->egid != old->egid || - new->sgid != old->sgid || - new->fsgid != old->fsgid) + if (!gid_eq(new->gid, old->gid) || + !gid_eq(new->egid, old->egid) || + !gid_eq(new->sgid, old->sgid) || + !gid_eq(new->fsgid, old->fsgid)) proc_id_connector(task, PROC_EVENT_GID); /* release the old obj and subj refs both */ diff --git a/kernel/signal.c b/kernel/signal.c index e2c5d84f2da..2734dc965f6 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1038,8 +1038,10 @@ static inline void userns_fixup_signal_uid(struct siginfo *info, struct task_str if (SI_FROMKERNEL(info)) return; - info->si_uid = user_ns_map_uid(task_cred_xxx(t, user_ns), - current_cred(), info->si_uid); + rcu_read_lock(); + info->si_uid = from_kuid_munged(task_cred_xxx(t, user_ns), + make_kuid(current_user_ns(), info->si_uid)); + rcu_read_unlock(); } #else static inline void userns_fixup_signal_uid(struct siginfo *info, struct task_struct *t) @@ -1106,7 +1108,7 @@ static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, q->info.si_code = SI_USER; q->info.si_pid = task_tgid_nr_ns(current, task_active_pid_ns(t)); - q->info.si_uid = current_uid(); + q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid()); break; case (unsigned long) SEND_SIG_PRIV: q->info.si_signo = sig; @@ -1973,7 +1975,7 @@ static void ptrace_do_notify(int signr, int exit_code, int why) info.si_signo = signr; info.si_code = exit_code; info.si_pid = task_pid_vnr(current); - info.si_uid = current_uid(); + info.si_uid = from_kuid_munged(current_user_ns(), current_uid()); /* Let the debugger run. */ ptrace_stop(exit_code, why, 1, &info); @@ -2828,7 +2830,7 @@ SYSCALL_DEFINE2(kill, pid_t, pid, int, sig) info.si_errno = 0; info.si_code = SI_USER; info.si_pid = task_tgid_vnr(current); - info.si_uid = current_uid(); + info.si_uid = from_kuid_munged(current_user_ns(), current_uid()); return kill_something_info(sig, &info, pid); } @@ -2871,7 +2873,7 @@ static int do_tkill(pid_t tgid, pid_t pid, int sig) info.si_errno = 0; info.si_code = SI_TKILL; info.si_pid = task_tgid_vnr(current); - info.si_uid = current_uid(); + info.si_uid = from_kuid_munged(current_user_ns(), current_uid()); return do_send_specific(tgid, pid, sig, &info); } diff --git a/kernel/sys.c b/kernel/sys.c index f0c43b4b665..39962818c00 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -175,7 +175,6 @@ SYSCALL_DEFINE3(setpriority, int, which, int, who, int, niceval) const struct cred *cred = current_cred(); int error = -EINVAL; struct pid *pgrp; - kuid_t cred_uid; kuid_t uid; if (which > PRIO_USER || which < PRIO_PROCESS) @@ -209,22 +208,19 @@ SYSCALL_DEFINE3(setpriority, int, which, int, who, int, niceval) } while_each_pid_thread(pgrp, PIDTYPE_PGID, p); break; case PRIO_USER: - cred_uid = make_kuid(cred->user_ns, cred->uid); uid = make_kuid(cred->user_ns, who); user = cred->user; if (!who) - uid = cred_uid; - else if (!uid_eq(uid, cred_uid) && + uid = cred->uid; + else if (!uid_eq(uid, cred->uid) && !(user = find_user(uid))) goto out_unlock; /* No processes for this user */ do_each_thread(g, p) { - const struct cred *tcred = __task_cred(p); - kuid_t tcred_uid = make_kuid(tcred->user_ns, tcred->uid); - if (uid_eq(tcred_uid, uid)) + if (uid_eq(task_uid(p), uid)) error = set_one_prio(p, niceval, error); } while_each_thread(g, p); - if (!uid_eq(uid, cred_uid)) + if (!uid_eq(uid, cred->uid)) free_uid(user); /* For find_user() */ break; } @@ -248,7 +244,6 @@ SYSCALL_DEFINE2(getpriority, int, which, int, who) const struct cred *cred = current_cred(); long niceval, retval = -ESRCH; struct pid *pgrp; - kuid_t cred_uid; kuid_t uid; if (which > PRIO_USER || which < PRIO_PROCESS) @@ -280,25 +275,22 @@ SYSCALL_DEFINE2(getpriority, int, which, int, who) } while_each_pid_thread(pgrp, PIDTYPE_PGID, p); break; case PRIO_USER: - cred_uid = make_kuid(cred->user_ns, cred->uid); uid = make_kuid(cred->user_ns, who); user = cred->user; if (!who) - uid = cred_uid; - else if (!uid_eq(uid, cred_uid) && + uid = cred->uid; + else if (!uid_eq(uid, cred->uid) && !(user = find_user(uid))) goto out_unlock; /* No processes for this user */ do_each_thread(g, p) { - const struct cred *tcred = __task_cred(p); - kuid_t tcred_uid = make_kuid(tcred->user_ns, tcred->uid); - if (uid_eq(tcred_uid, uid)) { + if (uid_eq(task_uid(p), uid)) { niceval = 20 - task_nice(p); if (niceval > retval) retval = niceval; } } while_each_thread(g, p); - if (!uid_eq(uid, cred_uid)) + if (!uid_eq(uid, cred->uid)) free_uid(user); /* for find_user() */ break; } @@ -641,7 +633,7 @@ static int set_user(struct cred *new) { struct user_struct *new_user; - new_user = alloc_uid(make_kuid(new->user_ns, new->uid)); + new_user = alloc_uid(new->uid); if (!new_user) return -EAGAIN; diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 7eff867bfac..86602316422 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -36,8 +36,8 @@ static bool new_idmap_permitted(struct user_namespace *ns, int cap_setid, int create_user_ns(struct cred *new) { struct user_namespace *ns, *parent_ns = new->user_ns; - kuid_t owner = make_kuid(new->user_ns, new->euid); - kgid_t group = make_kgid(new->user_ns, new->egid); + kuid_t owner = new->euid; + kgid_t group = new->egid; /* The creator needs a mapping in the parent user namespace * or else we won't be able to reasonably tell userspace who diff --git a/mm/oom_kill.c b/mm/oom_kill.c index 46bf2ed5594..9f09a1fde9f 100644 --- a/mm/oom_kill.c +++ b/mm/oom_kill.c @@ -410,8 +410,8 @@ static void dump_tasks(const struct mem_cgroup *memcg, const nodemask_t *nodemas } pr_info("[%5d] %5d %5d %8lu %8lu %3u %3d %5d %s\n", - task->pid, task_uid(task), task->tgid, - task->mm->total_vm, get_mm_rss(task->mm), + task->pid, from_kuid(&init_user_ns, task_uid(task)), + task->tgid, task->mm->total_vm, get_mm_rss(task->mm), task_cpu(task), task->signal->oom_adj, task->signal->oom_score_adj, task->comm); task_unlock(task); diff --git a/security/commoncap.c b/security/commoncap.c index f2399d8afbe..dbd465a5928 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -77,8 +77,7 @@ int cap_capable(const struct cred *cred, struct user_namespace *targ_ns, { for (;;) { /* The owner of the user namespace has all caps. */ - if (targ_ns != &init_user_ns && uid_eq(targ_ns->owner, - make_kuid(cred->user_ns, cred->euid))) + if (targ_ns != &init_user_ns && uid_eq(targ_ns->owner, cred->euid)) return 0; /* Do we have the necessary capabilities? */ -- cgit v1.2.3-70-g09d2