diff options
author | Eric W. Biederman <ebiederm@xmission.com> | 2012-12-28 21:58:39 -0500 |
---|---|---|
committer | Eric W. Biederman <ebiederm@xmission.com> | 2013-01-27 01:11:41 -0500 |
commit | c61a2810a2161986353705b44d9503e6bb079f4f (patch) | |
tree | 1f949f359d0344c2a47c8d0c597815aceb8091c0 /kernel/user_namespace.c | |
parent | 923c7538236564c46ee80c253a416705321f13e3 (diff) |
userns: Avoid recursion in put_user_ns
When freeing a deeply nested user namespace free_user_ns calls
put_user_ns on it's parent which may in turn call free_user_ns again.
When -fno-optimize-sibling-calls is passed to gcc one stack frame per
user namespace is left on the stack, potentially overflowing the
kernel stack. CONFIG_FRAME_POINTER forces -fno-optimize-sibling-calls
so we can't count on gcc to optimize this code.
Remove struct kref and use a plain atomic_t. Making the code more
flexible and easier to comprehend. Make the loop in free_user_ns
explict to guarantee that the stack does not overflow with
CONFIG_FRAME_POINTER enabled.
I have tested this fix with a simple program that uses unshare to
create a deeply nested user namespace structure and then calls exit.
With 1000 nesteuser namespaces before this change running my test
program causes the kernel to die a horrible death. With 10,000,000
nested user namespaces after this change my test program runs to
completion and causes no harm.
Acked-by: Serge Hallyn <serge.hallyn@canonical.com>
Pointed-out-by: Vasily Kulikov <segoon@openwall.com>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
Diffstat (limited to 'kernel/user_namespace.c')
-rw-r--r-- | kernel/user_namespace.c | 17 |
1 files changed, 9 insertions, 8 deletions
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 2b042c42fbc4..24f8ec3b64d8 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c | |||
@@ -78,7 +78,7 @@ int create_user_ns(struct cred *new) | |||
78 | return ret; | 78 | return ret; |
79 | } | 79 | } |
80 | 80 | ||
81 | kref_init(&ns->kref); | 81 | atomic_set(&ns->count, 1); |
82 | /* Leave the new->user_ns reference with the new user namespace. */ | 82 | /* Leave the new->user_ns reference with the new user namespace. */ |
83 | ns->parent = parent_ns; | 83 | ns->parent = parent_ns; |
84 | ns->owner = owner; | 84 | ns->owner = owner; |
@@ -104,15 +104,16 @@ int unshare_userns(unsigned long unshare_flags, struct cred **new_cred) | |||
104 | return create_user_ns(cred); | 104 | return create_user_ns(cred); |
105 | } | 105 | } |
106 | 106 | ||
107 | void free_user_ns(struct kref *kref) | 107 | void free_user_ns(struct user_namespace *ns) |
108 | { | 108 | { |
109 | struct user_namespace *parent, *ns = | 109 | struct user_namespace *parent; |
110 | container_of(kref, struct user_namespace, kref); | ||
111 | 110 | ||
112 | parent = ns->parent; | 111 | do { |
113 | proc_free_inum(ns->proc_inum); | 112 | parent = ns->parent; |
114 | kmem_cache_free(user_ns_cachep, ns); | 113 | proc_free_inum(ns->proc_inum); |
115 | put_user_ns(parent); | 114 | kmem_cache_free(user_ns_cachep, ns); |
115 | ns = parent; | ||
116 | } while (atomic_dec_and_test(&parent->count)); | ||
116 | } | 117 | } |
117 | EXPORT_SYMBOL(free_user_ns); | 118 | EXPORT_SYMBOL(free_user_ns); |
118 | 119 | ||