diff options
author | Eric W. Biederman <ebiederm@xmission.com> | 2016-08-08 14:41:24 -0400 |
---|---|---|
committer | Eric W. Biederman <ebiederm@xmission.com> | 2016-08-08 14:41:24 -0400 |
commit | b376c3e1b6770ddcb4f0782be16358095fcea0b6 (patch) | |
tree | 30789f9f1e84abe4d25a52e2fcad209504c181bf /kernel/ucount.c | |
parent | dbec28460a89aa7c02c3301e9e108d98272549d2 (diff) |
userns: Add a limit on the number of user namespaces
Export the export the maximum number of user namespaces as
/proc/sys/userns/max_user_namespaces.
Acked-by: Kees Cook <keescook@chromium.org>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
Diffstat (limited to 'kernel/ucount.c')
-rw-r--r-- | kernel/ucount.c | 53 |
1 files changed, 53 insertions, 0 deletions
diff --git a/kernel/ucount.c b/kernel/ucount.c index cbde1dc87851..6c2205c0befd 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c | |||
@@ -43,7 +43,18 @@ static struct ctl_table_root set_root = { | |||
43 | .permissions = set_permissions, | 43 | .permissions = set_permissions, |
44 | }; | 44 | }; |
45 | 45 | ||
46 | static int zero = 0; | ||
47 | static int int_max = INT_MAX; | ||
46 | static struct ctl_table userns_table[] = { | 48 | static struct ctl_table userns_table[] = { |
49 | { | ||
50 | .procname = "max_user_namespaces", | ||
51 | .data = &init_user_ns.max_user_namespaces, | ||
52 | .maxlen = sizeof(init_user_ns.max_user_namespaces), | ||
53 | .mode = 0644, | ||
54 | .proc_handler = proc_dointvec_minmax, | ||
55 | .extra1 = &zero, | ||
56 | .extra2 = &int_max, | ||
57 | }, | ||
47 | { } | 58 | { } |
48 | }; | 59 | }; |
49 | #endif /* CONFIG_SYSCTL */ | 60 | #endif /* CONFIG_SYSCTL */ |
@@ -55,6 +66,8 @@ bool setup_userns_sysctls(struct user_namespace *ns) | |||
55 | setup_sysctl_set(&ns->set, &set_root, set_is_seen); | 66 | setup_sysctl_set(&ns->set, &set_root, set_is_seen); |
56 | tbl = kmemdup(userns_table, sizeof(userns_table), GFP_KERNEL); | 67 | tbl = kmemdup(userns_table, sizeof(userns_table), GFP_KERNEL); |
57 | if (tbl) { | 68 | if (tbl) { |
69 | tbl[0].data = &ns->max_user_namespaces; | ||
70 | |||
58 | ns->sysctls = __register_sysctl_table(&ns->set, "userns", tbl); | 71 | ns->sysctls = __register_sysctl_table(&ns->set, "userns", tbl); |
59 | } | 72 | } |
60 | if (!ns->sysctls) { | 73 | if (!ns->sysctls) { |
@@ -78,6 +91,46 @@ void retire_userns_sysctls(struct user_namespace *ns) | |||
78 | #endif | 91 | #endif |
79 | } | 92 | } |
80 | 93 | ||
94 | static inline bool atomic_inc_below(atomic_t *v, int u) | ||
95 | { | ||
96 | int c, old; | ||
97 | c = atomic_read(v); | ||
98 | for (;;) { | ||
99 | if (unlikely(c >= u)) | ||
100 | return false; | ||
101 | old = atomic_cmpxchg(v, c, c+1); | ||
102 | if (likely(old == c)) | ||
103 | return true; | ||
104 | c = old; | ||
105 | } | ||
106 | } | ||
107 | |||
108 | bool inc_user_namespaces(struct user_namespace *ns) | ||
109 | { | ||
110 | struct user_namespace *pos, *bad; | ||
111 | for (pos = ns; pos; pos = pos->parent) { | ||
112 | int max = READ_ONCE(pos->max_user_namespaces); | ||
113 | if (!atomic_inc_below(&pos->user_namespaces, max)) | ||
114 | goto fail; | ||
115 | } | ||
116 | return true; | ||
117 | fail: | ||
118 | bad = pos; | ||
119 | for (pos = ns; pos != bad; pos = pos->parent) | ||
120 | atomic_dec(&pos->user_namespaces); | ||
121 | |||
122 | return false; | ||
123 | } | ||
124 | |||
125 | void dec_user_namespaces(struct user_namespace *ns) | ||
126 | { | ||
127 | struct user_namespace *pos; | ||
128 | for (pos = ns; pos; pos = pos->parent) { | ||
129 | int dec = atomic_dec_if_positive(&pos->user_namespaces); | ||
130 | WARN_ON_ONCE(dec < 0); | ||
131 | } | ||
132 | } | ||
133 | |||
81 | static __init int user_namespace_sysctl_init(void) | 134 | static __init int user_namespace_sysctl_init(void) |
82 | { | 135 | { |
83 | #ifdef CONFIG_SYSCTL | 136 | #ifdef CONFIG_SYSCTL |