diff options
-rw-r--r-- | kernel/seccomp.c | 97 |
1 files changed, 67 insertions, 30 deletions
diff --git a/kernel/seccomp.c b/kernel/seccomp.c index d2596136b0d1..58125160417c 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c | |||
@@ -18,6 +18,7 @@ | |||
18 | #include <linux/compat.h> | 18 | #include <linux/compat.h> |
19 | #include <linux/sched.h> | 19 | #include <linux/sched.h> |
20 | #include <linux/seccomp.h> | 20 | #include <linux/seccomp.h> |
21 | #include <linux/slab.h> | ||
21 | #include <linux/syscalls.h> | 22 | #include <linux/syscalls.h> |
22 | 23 | ||
23 | /* #define SECCOMP_DEBUG 1 */ | 24 | /* #define SECCOMP_DEBUG 1 */ |
@@ -27,7 +28,6 @@ | |||
27 | #include <linux/filter.h> | 28 | #include <linux/filter.h> |
28 | #include <linux/ptrace.h> | 29 | #include <linux/ptrace.h> |
29 | #include <linux/security.h> | 30 | #include <linux/security.h> |
30 | #include <linux/slab.h> | ||
31 | #include <linux/tracehook.h> | 31 | #include <linux/tracehook.h> |
32 | #include <linux/uaccess.h> | 32 | #include <linux/uaccess.h> |
33 | 33 | ||
@@ -213,27 +213,23 @@ static inline void seccomp_assign_mode(unsigned long seccomp_mode) | |||
213 | 213 | ||
214 | #ifdef CONFIG_SECCOMP_FILTER | 214 | #ifdef CONFIG_SECCOMP_FILTER |
215 | /** | 215 | /** |
216 | * seccomp_attach_filter: Attaches a seccomp filter to current. | 216 | * seccomp_prepare_filter: Prepares a seccomp filter for use. |
217 | * @fprog: BPF program to install | 217 | * @fprog: BPF program to install |
218 | * | 218 | * |
219 | * Returns 0 on success or an errno on failure. | 219 | * Returns filter on success or an ERR_PTR on failure. |
220 | */ | 220 | */ |
221 | static long seccomp_attach_filter(struct sock_fprog *fprog) | 221 | static struct seccomp_filter *seccomp_prepare_filter(struct sock_fprog *fprog) |
222 | { | 222 | { |
223 | struct seccomp_filter *filter; | 223 | struct seccomp_filter *filter; |
224 | unsigned long fp_size = fprog->len * sizeof(struct sock_filter); | 224 | unsigned long fp_size; |
225 | unsigned long total_insns = fprog->len; | ||
226 | struct sock_filter *fp; | 225 | struct sock_filter *fp; |
227 | int new_len; | 226 | int new_len; |
228 | long ret; | 227 | long ret; |
229 | 228 | ||
230 | if (fprog->len == 0 || fprog->len > BPF_MAXINSNS) | 229 | if (fprog->len == 0 || fprog->len > BPF_MAXINSNS) |
231 | return -EINVAL; | 230 | return ERR_PTR(-EINVAL); |
232 | 231 | BUG_ON(INT_MAX / fprog->len < sizeof(struct sock_filter)); | |
233 | for (filter = current->seccomp.filter; filter; filter = filter->prev) | 232 | fp_size = fprog->len * sizeof(struct sock_filter); |
234 | total_insns += filter->prog->len + 4; /* include a 4 instr penalty */ | ||
235 | if (total_insns > MAX_INSNS_PER_PATH) | ||
236 | return -ENOMEM; | ||
237 | 233 | ||
238 | /* | 234 | /* |
239 | * Installing a seccomp filter requires that the task has | 235 | * Installing a seccomp filter requires that the task has |
@@ -244,11 +240,11 @@ static long seccomp_attach_filter(struct sock_fprog *fprog) | |||
244 | if (!task_no_new_privs(current) && | 240 | if (!task_no_new_privs(current) && |
245 | security_capable_noaudit(current_cred(), current_user_ns(), | 241 | security_capable_noaudit(current_cred(), current_user_ns(), |
246 | CAP_SYS_ADMIN) != 0) | 242 | CAP_SYS_ADMIN) != 0) |
247 | return -EACCES; | 243 | return ERR_PTR(-EACCES); |
248 | 244 | ||
249 | fp = kzalloc(fp_size, GFP_KERNEL|__GFP_NOWARN); | 245 | fp = kzalloc(fp_size, GFP_KERNEL|__GFP_NOWARN); |
250 | if (!fp) | 246 | if (!fp) |
251 | return -ENOMEM; | 247 | return ERR_PTR(-ENOMEM); |
252 | 248 | ||
253 | /* Copy the instructions from fprog. */ | 249 | /* Copy the instructions from fprog. */ |
254 | ret = -EFAULT; | 250 | ret = -EFAULT; |
@@ -292,13 +288,7 @@ static long seccomp_attach_filter(struct sock_fprog *fprog) | |||
292 | 288 | ||
293 | sk_filter_select_runtime(filter->prog); | 289 | sk_filter_select_runtime(filter->prog); |
294 | 290 | ||
295 | /* | 291 | return filter; |
296 | * If there is an existing filter, make it the prev and don't drop its | ||
297 | * task reference. | ||
298 | */ | ||
299 | filter->prev = current->seccomp.filter; | ||
300 | current->seccomp.filter = filter; | ||
301 | return 0; | ||
302 | 292 | ||
303 | free_filter_prog: | 293 | free_filter_prog: |
304 | kfree(filter->prog); | 294 | kfree(filter->prog); |
@@ -306,19 +296,20 @@ free_filter: | |||
306 | kfree(filter); | 296 | kfree(filter); |
307 | free_prog: | 297 | free_prog: |
308 | kfree(fp); | 298 | kfree(fp); |
309 | return ret; | 299 | return ERR_PTR(ret); |
310 | } | 300 | } |
311 | 301 | ||
312 | /** | 302 | /** |
313 | * seccomp_attach_user_filter - attaches a user-supplied sock_fprog | 303 | * seccomp_prepare_user_filter - prepares a user-supplied sock_fprog |
314 | * @user_filter: pointer to the user data containing a sock_fprog. | 304 | * @user_filter: pointer to the user data containing a sock_fprog. |
315 | * | 305 | * |
316 | * Returns 0 on success and non-zero otherwise. | 306 | * Returns 0 on success and non-zero otherwise. |
317 | */ | 307 | */ |
318 | static long seccomp_attach_user_filter(const char __user *user_filter) | 308 | static struct seccomp_filter * |
309 | seccomp_prepare_user_filter(const char __user *user_filter) | ||
319 | { | 310 | { |
320 | struct sock_fprog fprog; | 311 | struct sock_fprog fprog; |
321 | long ret = -EFAULT; | 312 | struct seccomp_filter *filter = ERR_PTR(-EFAULT); |
322 | 313 | ||
323 | #ifdef CONFIG_COMPAT | 314 | #ifdef CONFIG_COMPAT |
324 | if (is_compat_task()) { | 315 | if (is_compat_task()) { |
@@ -331,9 +322,39 @@ static long seccomp_attach_user_filter(const char __user *user_filter) | |||
331 | #endif | 322 | #endif |
332 | if (copy_from_user(&fprog, user_filter, sizeof(fprog))) | 323 | if (copy_from_user(&fprog, user_filter, sizeof(fprog))) |
333 | goto out; | 324 | goto out; |
334 | ret = seccomp_attach_filter(&fprog); | 325 | filter = seccomp_prepare_filter(&fprog); |
335 | out: | 326 | out: |
336 | return ret; | 327 | return filter; |
328 | } | ||
329 | |||
330 | /** | ||
331 | * seccomp_attach_filter: validate and attach filter | ||
332 | * @flags: flags to change filter behavior | ||
333 | * @filter: seccomp filter to add to the current process | ||
334 | * | ||
335 | * Returns 0 on success, -ve on error. | ||
336 | */ | ||
337 | static long seccomp_attach_filter(unsigned int flags, | ||
338 | struct seccomp_filter *filter) | ||
339 | { | ||
340 | unsigned long total_insns; | ||
341 | struct seccomp_filter *walker; | ||
342 | |||
343 | /* Validate resulting filter length. */ | ||
344 | total_insns = filter->prog->len; | ||
345 | for (walker = current->seccomp.filter; walker; walker = walker->prev) | ||
346 | total_insns += walker->prog->len + 4; /* 4 instr penalty */ | ||
347 | if (total_insns > MAX_INSNS_PER_PATH) | ||
348 | return -ENOMEM; | ||
349 | |||
350 | /* | ||
351 | * If there is an existing filter, make it the prev and don't drop its | ||
352 | * task reference. | ||
353 | */ | ||
354 | filter->prev = current->seccomp.filter; | ||
355 | current->seccomp.filter = filter; | ||
356 | |||
357 | return 0; | ||
337 | } | 358 | } |
338 | 359 | ||
339 | /* get_seccomp_filter - increments the reference count of the filter on @tsk */ | 360 | /* get_seccomp_filter - increments the reference count of the filter on @tsk */ |
@@ -346,6 +367,14 @@ void get_seccomp_filter(struct task_struct *tsk) | |||
346 | atomic_inc(&orig->usage); | 367 | atomic_inc(&orig->usage); |
347 | } | 368 | } |
348 | 369 | ||
370 | static inline void seccomp_filter_free(struct seccomp_filter *filter) | ||
371 | { | ||
372 | if (filter) { | ||
373 | sk_filter_free(filter->prog); | ||
374 | kfree(filter); | ||
375 | } | ||
376 | } | ||
377 | |||
349 | /* put_seccomp_filter - decrements the ref count of tsk->seccomp.filter */ | 378 | /* put_seccomp_filter - decrements the ref count of tsk->seccomp.filter */ |
350 | void put_seccomp_filter(struct task_struct *tsk) | 379 | void put_seccomp_filter(struct task_struct *tsk) |
351 | { | 380 | { |
@@ -354,8 +383,7 @@ void put_seccomp_filter(struct task_struct *tsk) | |||
354 | while (orig && atomic_dec_and_test(&orig->usage)) { | 383 | while (orig && atomic_dec_and_test(&orig->usage)) { |
355 | struct seccomp_filter *freeme = orig; | 384 | struct seccomp_filter *freeme = orig; |
356 | orig = orig->prev; | 385 | orig = orig->prev; |
357 | sk_filter_free(freeme->prog); | 386 | seccomp_filter_free(freeme); |
358 | kfree(freeme); | ||
359 | } | 387 | } |
360 | } | 388 | } |
361 | 389 | ||
@@ -533,21 +561,30 @@ static long seccomp_set_mode_filter(unsigned int flags, | |||
533 | const char __user *filter) | 561 | const char __user *filter) |
534 | { | 562 | { |
535 | const unsigned long seccomp_mode = SECCOMP_MODE_FILTER; | 563 | const unsigned long seccomp_mode = SECCOMP_MODE_FILTER; |
564 | struct seccomp_filter *prepared = NULL; | ||
536 | long ret = -EINVAL; | 565 | long ret = -EINVAL; |
537 | 566 | ||
538 | /* Validate flags. */ | 567 | /* Validate flags. */ |
539 | if (flags != 0) | 568 | if (flags != 0) |
540 | goto out; | 569 | goto out; |
541 | 570 | ||
571 | /* Prepare the new filter before holding any locks. */ | ||
572 | prepared = seccomp_prepare_user_filter(filter); | ||
573 | if (IS_ERR(prepared)) | ||
574 | return PTR_ERR(prepared); | ||
575 | |||
542 | if (!seccomp_may_assign_mode(seccomp_mode)) | 576 | if (!seccomp_may_assign_mode(seccomp_mode)) |
543 | goto out; | 577 | goto out; |
544 | 578 | ||
545 | ret = seccomp_attach_user_filter(filter); | 579 | ret = seccomp_attach_filter(flags, prepared); |
546 | if (ret) | 580 | if (ret) |
547 | goto out; | 581 | goto out; |
582 | /* Do not free the successfully attached filter. */ | ||
583 | prepared = NULL; | ||
548 | 584 | ||
549 | seccomp_assign_mode(seccomp_mode); | 585 | seccomp_assign_mode(seccomp_mode); |
550 | out: | 586 | out: |
587 | seccomp_filter_free(prepared); | ||
551 | return ret; | 588 | return ret; |
552 | } | 589 | } |
553 | #else | 590 | #else |