]>
Commit | Line | Data |
---|---|---|
6a930a95 BS |
1 | From: John Johansen <jjohansen@suse.de> |
2 | Subject: AppArmor: per profile controls for system rlimits | |
3 | ||
4 | Provide contol of rlimits on a per profile basis. Each profile provides | |
5 | a per limit contol and corresponding hard limit value, such that when a | |
6 | profile becomes attached to a task it sets the tasks limits to be <= to | |
7 | the profiles specified limits. Note: the profile limit value will not | |
8 | raise a tasks limit if it is already less than the profile mandates. | |
9 | ||
10 | In addition to setting a tasks limits, the ability to set limits on | |
11 | a confined task are controlled. AppArmor only controls the raising | |
12 | of a tasks limits Tasks with CAP_SYS_RESOURCE can have their hard limits | |
13 | raised up to the value specified by the profile. AppArmor does not | |
14 | prevent a task for lowering its hard limits, nor does it provide | |
15 | additional control on soft limits. | |
16 | ||
17 | AppArmor only controls the limits specified in a profile so that | |
18 | any limit not specified is free to be modified subject to standard | |
19 | linux limitations. | |
20 | ||
21 | --- | |
22 | security/apparmor/apparmor.h | 23 ++++++ | |
23 | security/apparmor/apparmorfs.c | 2 | |
24 | security/apparmor/lsm.c | 16 ++++ | |
25 | security/apparmor/main.c | 132 +++++++++++++++++++++++++++++++---- | |
26 | security/apparmor/module_interface.c | 56 ++++++++++++++ | |
27 | 5 files changed, 215 insertions(+), 14 deletions(-) | |
28 | ||
29 | --- a/security/apparmor/apparmor.h | |
30 | +++ b/security/apparmor/apparmor.h | |
31 | @@ -16,6 +16,7 @@ | |
32 | #include <linux/fs.h> | |
33 | #include <linux/binfmts.h> | |
34 | #include <linux/rcupdate.h> | |
35 | +#include <linux/resource.h> | |
36 | #include <linux/socket.h> | |
37 | #include <net/sock.h> | |
38 | ||
39 | @@ -139,6 +140,18 @@ extern unsigned int apparmor_path_max; | |
40 | ||
41 | #define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args) | |
42 | ||
43 | +/* struct aa_rlimit - rlimits settings for the profile | |
44 | + * @mask: which hard limits to set | |
45 | + * @limits: rlimit values that override task limits | |
46 | + * | |
47 | + * AppArmor rlimits are used to set confined task rlimits. Only the | |
48 | + * limits specified in @mask will be controlled by apparmor. | |
49 | + */ | |
50 | +struct aa_rlimit { | |
51 | + unsigned int mask; | |
52 | + struct rlimit limits[RLIM_NLIMITS]; | |
53 | +}; | |
54 | + | |
55 | struct aa_profile; | |
56 | ||
57 | /* struct aa_namespace - namespace for a set of profiles | |
58 | @@ -173,6 +186,8 @@ struct aa_namespace { | |
59 | * @audit_caps: caps that are to be audited | |
60 | * @quiet_caps: caps that should not be audited | |
61 | * @capabilities: capabilities granted by the process | |
62 | + * @rlimits: rlimits for the profile | |
63 | + * @task_count: how many tasks the profile is attached to | |
64 | * @count: reference count of the profile | |
65 | * @task_contexts: list of tasks confined by profile | |
66 | * @lock: lock for the task_contexts list | |
67 | @@ -210,6 +225,9 @@ struct aa_profile { | |
68 | kernel_cap_t audit_caps; | |
69 | kernel_cap_t quiet_caps; | |
70 | ||
71 | + struct aa_rlimit rlimits; | |
72 | + unsigned int task_count; | |
73 | + | |
74 | struct kref count; | |
75 | struct list_head task_contexts; | |
76 | spinlock_t lock; | |
77 | @@ -261,6 +279,7 @@ struct aa_audit { | |
78 | const char *name2; | |
79 | const char *name3; | |
80 | int request_mask, denied_mask, audit_mask; | |
81 | + int rlimit; | |
82 | struct iattr *iattr; | |
83 | pid_t task, parent; | |
84 | int family, type, protocol; | |
85 | @@ -328,6 +347,10 @@ extern int aa_may_ptrace(struct aa_task_ | |
86 | extern int aa_net_perm(struct aa_profile *profile, char *operation, | |
87 | int family, int type, int protocol); | |
88 | extern int aa_revalidate_sk(struct sock *sk, char *operation); | |
89 | +extern int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource, | |
90 | + struct rlimit *new_rlim); | |
91 | +extern void aa_set_rlimits(struct task_struct *task, struct aa_profile *profile); | |
92 | + | |
93 | ||
94 | /* lsm.c */ | |
95 | extern int apparmor_initialized; | |
96 | --- a/security/apparmor/apparmorfs.c | |
97 | +++ b/security/apparmor/apparmorfs.c | |
98 | @@ -106,7 +106,7 @@ static ssize_t aa_features_read(struct f | |
99 | { | |
100 | const char *features = "file=3.0 capability=2.0 network=1.0 " | |
101 | "change_hat=1.5 change_profile=1.0 " | |
102 | - "aanamespaces=1.0"; | |
103 | + "aanamespaces=1.0 rlimit=1.0"; | |
104 | ||
105 | return simple_read_from_buffer(buf, size, ppos, features, | |
106 | strlen(features)); | |
107 | --- a/security/apparmor/lsm.c | |
108 | +++ b/security/apparmor/lsm.c | |
109 | @@ -883,6 +883,21 @@ static int apparmor_setprocattr(struct t | |
110 | return error; | |
111 | } | |
112 | ||
113 | +static int apparmor_task_setrlimit(unsigned int resource, | |
114 | + struct rlimit *new_rlim) | |
115 | +{ | |
116 | + struct aa_profile *profile; | |
117 | + int error = 0; | |
118 | + | |
119 | + profile = aa_get_profile(current); | |
120 | + if (profile) { | |
121 | + error = aa_task_setrlimit(profile, resource, new_rlim); | |
122 | + } | |
123 | + aa_put_profile(profile); | |
124 | + | |
125 | + return error; | |
126 | +} | |
127 | + | |
128 | struct security_operations apparmor_ops = { | |
129 | .ptrace = apparmor_ptrace, | |
130 | .capget = cap_capget, | |
131 | @@ -926,6 +941,7 @@ struct security_operations apparmor_ops | |
132 | .task_free_security = apparmor_task_free_security, | |
133 | .task_post_setuid = cap_task_post_setuid, | |
134 | .task_reparent_to_init = cap_task_reparent_to_init, | |
135 | + .task_setrlimit = apparmor_task_setrlimit, | |
136 | ||
137 | .getprocattr = apparmor_getprocattr, | |
138 | .setprocattr = apparmor_setprocattr, | |
139 | --- a/security/apparmor/main.c | |
140 | +++ b/security/apparmor/main.c | |
141 | @@ -177,6 +177,9 @@ static int aa_audit_base(struct aa_profi | |
142 | if (sa->request_mask) | |
143 | audit_log_format(ab, " fsuid=%d", current->fsuid); | |
144 | ||
145 | + if (sa->rlimit) | |
146 | + audit_log_format(ab, " rlimit=%d", sa->rlimit - 1); | |
147 | + | |
148 | if (sa->iattr) { | |
149 | struct iattr *iattr = sa->iattr; | |
150 | ||
151 | @@ -872,6 +875,79 @@ int aa_revalidate_sk(struct sock *sk, ch | |
152 | ||
153 | return error; | |
154 | } | |
155 | +/** | |
156 | + * aa_task_setrlimit - test permission to set an rlimit | |
157 | + * @profile - profile confining the task | |
158 | + * @resource - the resource being set | |
159 | + * @new_rlim - the new resource limit | |
160 | + * | |
161 | + * Control raising the processes hard limit. | |
162 | + */ | |
163 | +int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource, | |
164 | + struct rlimit *new_rlim) | |
165 | +{ | |
166 | + struct aa_audit sa; | |
167 | + int error = 0; | |
168 | + | |
169 | + memset(&sa, 0, sizeof(sa)); | |
170 | + sa.operation = "setrlimit"; | |
171 | + sa.gfp_mask = GFP_KERNEL; | |
172 | + sa.rlimit = resource + 1; | |
173 | + | |
174 | + if (profile->rlimits.mask & (1 << resource) && | |
175 | + new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max) { | |
176 | + sa.error_code = -EACCES; | |
177 | + | |
178 | + error = aa_audit(profile, &sa); | |
179 | + } | |
180 | + | |
181 | + return error; | |
182 | +} | |
183 | + | |
184 | +static int aa_rlimit_nproc(struct aa_profile *profile) { | |
185 | + if (profile && (profile->rlimits.mask & (1 << RLIMIT_NPROC)) && | |
186 | + profile->task_count >= profile->rlimits.limits[RLIMIT_NPROC].rlim_max) | |
187 | + return -EAGAIN; | |
188 | + return 0; | |
189 | +} | |
190 | + | |
191 | +void aa_set_rlimits(struct task_struct *task, struct aa_profile *profile) | |
192 | +{ | |
193 | + int i, mask; | |
194 | + | |
195 | + if (!profile) | |
196 | + return; | |
197 | + | |
198 | + if (!profile->rlimits.mask) | |
199 | + return; | |
200 | + | |
201 | + task_lock(task->group_leader); | |
202 | + mask = 1; | |
203 | + for (i = 0; i < RLIM_NLIMITS; i++, mask <<= 1) { | |
204 | + struct rlimit new_rlim, *old_rlim; | |
205 | + | |
206 | + /* check to see if NPROC which is per profile and handled | |
207 | + * in clone/exec or whether this is a limit to be set | |
208 | + * can't set cpu limit either right now | |
209 | + */ | |
210 | + if (i == RLIMIT_NPROC || i == RLIMIT_CPU) | |
211 | + continue; | |
212 | + | |
213 | + old_rlim = task->signal->rlim + i; | |
214 | + new_rlim = *old_rlim; | |
215 | + | |
216 | + if (mask & profile->rlimits.mask && | |
217 | + profile->rlimits.limits[i].rlim_max < new_rlim.rlim_max) { | |
218 | + new_rlim.rlim_max = profile->rlimits.limits[i].rlim_max; | |
219 | + /* soft limit should not exceed hard limit */ | |
220 | + if (new_rlim.rlim_cur > new_rlim.rlim_max) | |
221 | + new_rlim.rlim_cur = new_rlim.rlim_max; | |
222 | + } | |
223 | + | |
224 | + *old_rlim = new_rlim; | |
225 | + } | |
226 | + task_unlock(task->group_leader); | |
227 | +} | |
228 | ||
229 | /******************************* | |
230 | * Global task related functions | |
231 | @@ -885,6 +961,7 @@ int aa_revalidate_sk(struct sock *sk, ch | |
232 | */ | |
233 | int aa_clone(struct task_struct *child) | |
234 | { | |
235 | + struct aa_audit sa; | |
236 | struct aa_task_context *cxt, *child_cxt; | |
237 | struct aa_profile *profile; | |
238 | ||
239 | @@ -894,6 +971,11 @@ int aa_clone(struct task_struct *child) | |
240 | if (!child_cxt) | |
241 | return -ENOMEM; | |
242 | ||
243 | + memset(&sa, 0, sizeof(sa)); | |
244 | + sa.operation = "clone"; | |
245 | + sa.task = child->pid; | |
246 | + sa.gfp_mask = GFP_KERNEL; | |
247 | + | |
248 | repeat: | |
249 | profile = aa_get_profile(current); | |
250 | if (profile) { | |
251 | @@ -910,18 +992,22 @@ repeat: | |
252 | goto repeat; | |
253 | } | |
254 | ||
255 | + if (aa_rlimit_nproc(profile)) { | |
256 | + sa.info = "rlimit nproc limit exceeded"; | |
257 | + unlock_profile(profile); | |
258 | + aa_audit_reject(profile, &sa); | |
259 | + aa_put_profile(profile); | |
260 | + return -EAGAIN; | |
261 | + } | |
262 | + | |
263 | /* No need to grab the child's task lock here. */ | |
264 | aa_change_task_context(child, child_cxt, profile, | |
265 | cxt->cookie, cxt->previous_profile); | |
266 | + | |
267 | unlock_profile(profile); | |
268 | ||
269 | if (APPARMOR_COMPLAIN(child_cxt) && | |
270 | profile == profile->ns->null_complain_profile) { | |
271 | - struct aa_audit sa; | |
272 | - memset(&sa, 0, sizeof(sa)); | |
273 | - sa.operation = "clone"; | |
274 | - sa.gfp_mask = GFP_KERNEL; | |
275 | - sa.task = child->pid; | |
276 | aa_audit_hint(profile, &sa); | |
277 | } | |
278 | aa_put_profile(profile); | |
279 | @@ -1156,6 +1242,10 @@ repeat: | |
280 | sa.task = current->parent->pid; | |
281 | aa_audit_reject(profile, &sa); | |
282 | } | |
283 | + if (PTR_ERR(old_profile) == -EAGAIN) { | |
284 | + sa.info = "rlimit nproc limit exceeded"; | |
285 | + aa_audit_reject(profile, &sa); | |
286 | + } | |
287 | new_profile = old_profile; | |
288 | goto cleanup; | |
289 | } | |
290 | @@ -1303,6 +1393,12 @@ static int do_change_profile(struct aa_p | |
291 | goto out; | |
292 | } | |
293 | ||
294 | + if ((error = aa_rlimit_nproc(new_profile))) { | |
295 | + sa->info = "rlimit nproc limit exceeded"; | |
296 | + aa_audit_reject(cxt->profile, sa); | |
297 | + goto out; | |
298 | + } | |
299 | + | |
300 | if (new_profile == ns->null_complain_profile) | |
301 | aa_audit_hint(cxt->profile, sa); | |
302 | ||
303 | @@ -1481,17 +1577,18 @@ struct aa_profile *__aa_replace_profile( | |
304 | ||
305 | cxt = lock_task_and_profiles(task, profile); | |
306 | if (unlikely(profile && profile->isstale)) { | |
307 | - task_unlock(task); | |
308 | - unlock_both_profiles(profile, cxt ? cxt->profile : NULL); | |
309 | - aa_free_task_context(new_cxt); | |
310 | - return ERR_PTR(-ESTALE); | |
311 | + old_profile = ERR_PTR(-ESTALE); | |
312 | + goto error; | |
313 | } | |
314 | ||
315 | if ((current->ptrace & PT_PTRACED) && aa_may_ptrace(cxt, profile)) { | |
316 | - task_unlock(task); | |
317 | - unlock_both_profiles(profile, cxt ? cxt->profile : NULL); | |
318 | - aa_free_task_context(new_cxt); | |
319 | - return ERR_PTR(-EPERM); | |
320 | + old_profile = ERR_PTR(-EPERM); | |
321 | + goto error; | |
322 | + } | |
323 | + | |
324 | + if (aa_rlimit_nproc(profile)) { | |
325 | + old_profile = ERR_PTR(-EAGAIN); | |
326 | + goto error; | |
327 | } | |
328 | ||
329 | if (cxt) | |
330 | @@ -1499,8 +1596,15 @@ struct aa_profile *__aa_replace_profile( | |
331 | aa_change_task_context(task, new_cxt, profile, 0, NULL); | |
332 | ||
333 | task_unlock(task); | |
334 | + aa_set_rlimits(task, profile); | |
335 | unlock_both_profiles(profile, old_profile); | |
336 | return old_profile; | |
337 | + | |
338 | +error: | |
339 | + task_unlock(task); | |
340 | + unlock_both_profiles(profile, cxt ? cxt->profile : NULL); | |
341 | + aa_free_task_context(new_cxt); | |
342 | + return old_profile; | |
343 | } | |
344 | ||
345 | /** | |
346 | @@ -1565,6 +1669,7 @@ void aa_change_task_context(struct task_ | |
347 | ||
348 | if (old_cxt) { | |
349 | list_del_init(&old_cxt->list); | |
350 | + old_cxt->profile->task_count--; | |
351 | call_rcu(&old_cxt->rcu, free_aa_task_context_rcu_callback); | |
352 | } | |
353 | if (new_cxt) { | |
354 | @@ -1576,6 +1681,7 @@ void aa_change_task_context(struct task_ | |
355 | new_cxt->cookie = cookie; | |
356 | new_cxt->task = task; | |
357 | new_cxt->profile = aa_dup_profile(profile); | |
358 | + profile->task_count++; | |
359 | new_cxt->previous_profile = aa_dup_profile(previous_profile); | |
360 | list_move(&new_cxt->list, &profile->task_contexts); | |
361 | } | |
362 | --- a/security/apparmor/module_interface.c | |
363 | +++ b/security/apparmor/module_interface.c | |
364 | @@ -177,6 +177,22 @@ fail: | |
365 | return 0; | |
366 | } | |
367 | ||
368 | +static int aa_is_u64(struct aa_ext *e, u64 *data, const char *name) | |
369 | +{ | |
370 | + void *pos = e->pos; | |
371 | + if (aa_is_nameX(e, AA_U64, name)) { | |
372 | + if (!aa_inbounds(e, sizeof(u64))) | |
373 | + goto fail; | |
374 | + if (data) | |
375 | + *data = le64_to_cpu(get_unaligned((u64 *)e->pos)); | |
376 | + e->pos += sizeof(u64); | |
377 | + return 1; | |
378 | + } | |
379 | +fail: | |
380 | + e->pos = pos; | |
381 | + return 0; | |
382 | +} | |
383 | + | |
384 | static size_t aa_is_array(struct aa_ext *e, const char *name) | |
385 | { | |
386 | void *pos = e->pos; | |
387 | @@ -312,6 +328,39 @@ fail: | |
388 | return 0; | |
389 | } | |
390 | ||
391 | +int aa_unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) | |
392 | +{ | |
393 | + void *pos = e->pos; | |
394 | + | |
395 | + /* rlimits are optional */ | |
396 | + if (aa_is_nameX(e, AA_STRUCT, "rlimits")) { | |
397 | + int i, size; | |
398 | + u32 tmp = 0; | |
399 | + if (!aa_is_u32(e, &tmp, NULL)) | |
400 | + goto fail; | |
401 | + profile->rlimits.mask = tmp; | |
402 | + | |
403 | + size = aa_is_array(e, NULL); | |
404 | + if (size > RLIM_NLIMITS) | |
405 | + goto fail; | |
406 | + for (i = 0; i < size; i++) { | |
407 | + u64 tmp = 0; | |
408 | + if (!aa_is_u64(e, &tmp, NULL)) | |
409 | + goto fail; | |
410 | + profile->rlimits.limits[i].rlim_max = tmp; | |
411 | + } | |
412 | + if (!aa_is_nameX(e, AA_ARRAYEND, NULL)) | |
413 | + goto fail; | |
414 | + if (!aa_is_nameX(e, AA_STRUCTEND, NULL)) | |
415 | + goto fail; | |
416 | + } | |
417 | + return 1; | |
418 | + | |
419 | +fail: | |
420 | + e->pos = pos; | |
421 | + return 0; | |
422 | +} | |
423 | + | |
424 | /** | |
425 | * aa_unpack_profile - unpack a serialized profile | |
426 | * @e: serialized data extent information | |
427 | @@ -355,6 +404,9 @@ static struct aa_profile *aa_unpack_prof | |
428 | if (!aa_is_u32(e, &(profile->set_caps), NULL)) | |
429 | goto fail; | |
430 | ||
431 | + if (!aa_unpack_rlimits(e, profile)) | |
432 | + goto fail; | |
433 | + | |
434 | size = aa_is_array(e, "net_allowed_af"); | |
435 | if (size) { | |
436 | if (size > AF_MAX) | |
437 | @@ -614,6 +666,8 @@ ssize_t aa_replace_profile(void *udata, | |
438 | sa.operation = "profile_load"; | |
439 | goto out; | |
440 | } | |
441 | + /* do not fail replacement based off of profile's NPROC rlimit */ | |
442 | + | |
443 | /* | |
444 | * Replacement needs to allocate a new aa_task_context for each | |
445 | * task confined by old_profile. To do this the profile locks | |
446 | @@ -634,6 +688,7 @@ ssize_t aa_replace_profile(void *udata, | |
447 | task_lock(task); | |
448 | task_replace(task, new_cxt, new_profile); | |
449 | task_unlock(task); | |
450 | + aa_set_rlimits(task, new_profile); | |
451 | new_cxt = NULL; | |
452 | } | |
453 | unlock_both_profiles(old_profile, new_profile); | |
454 | @@ -656,6 +711,7 @@ out: | |
455 | * | |
456 | * remove a profile from the profile list and all aa_task_context references | |
457 | * to said profile. | |
458 | + * NOTE: removing confinement does not restore rlimits to preconfinemnet values | |
459 | */ | |
460 | ssize_t aa_remove_profile(char *name, size_t size) | |
461 | { |