diff options
author | Kees Cook <keescook@chromium.org> | 2014-06-27 18:16:33 -0400 |
---|---|---|
committer | Kees Cook <keescook@chromium.org> | 2014-07-18 15:13:39 -0400 |
commit | c8bee430dc52cfca6c1aab27752a89275d78d50f (patch) | |
tree | 10e229d375150f64808709f5fe9c88b84236e327 | |
parent | 1d4457f99928a968767f6405b4a1f50845aa15fd (diff) |
seccomp: split filter prep from check and apply
In preparation for adding seccomp locking, move filter creation away
from where it is checked and applied. This will allow for locking where
no memory allocation is happening. The validation, filter attachment,
and seccomp mode setting can all happen under the future locks.
For extreme defensiveness, I've added a BUG_ON check for the calculated
size of the buffer allocation in case BPF_MAXINSN ever changes, which
shouldn't ever happen. The compiler should actually optimize out this
check since the test above it makes it impossible.
Signed-off-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Reviewed-by: Andy Lutomirski <luto@amacapital.net>
-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 |