aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/seccomp.c
diff options
context:
space:
mode:
authorKees Cook <keescook@chromium.org>2014-06-27 18:16:33 -0400
committerKees Cook <keescook@chromium.org>2014-07-18 15:13:39 -0400
commitc8bee430dc52cfca6c1aab27752a89275d78d50f (patch)
tree10e229d375150f64808709f5fe9c88b84236e327 /kernel/seccomp.c
parent1d4457f99928a968767f6405b4a1f50845aa15fd (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>
Diffstat (limited to 'kernel/seccomp.c')
-rw-r--r--kernel/seccomp.c97
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 */
221static long seccomp_attach_filter(struct sock_fprog *fprog) 221static 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
303free_filter_prog: 293free_filter_prog:
304 kfree(filter->prog); 294 kfree(filter->prog);
@@ -306,19 +296,20 @@ free_filter:
306 kfree(filter); 296 kfree(filter);
307free_prog: 297free_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 */
318static long seccomp_attach_user_filter(const char __user *user_filter) 308static struct seccomp_filter *
309seccomp_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);
335out: 326out:
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 */
337static 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
370static 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 */
350void put_seccomp_filter(struct task_struct *tsk) 379void 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);
550out: 586out:
587 seccomp_filter_free(prepared);
551 return ret; 588 return ret;
552} 589}
553#else 590#else