diff options
-rw-r--r-- | fs/exec.c | 2 | ||||
-rw-r--r-- | include/linux/seccomp.h | 2 | ||||
-rw-r--r-- | include/uapi/linux/seccomp.h | 3 | ||||
-rw-r--r-- | kernel/seccomp.c | 135 |
4 files changed, 140 insertions, 2 deletions
@@ -1216,7 +1216,7 @@ EXPORT_SYMBOL(install_exec_creds); | |||
1216 | /* | 1216 | /* |
1217 | * determine how safe it is to execute the proposed program | 1217 | * determine how safe it is to execute the proposed program |
1218 | * - the caller must hold ->cred_guard_mutex to protect against | 1218 | * - the caller must hold ->cred_guard_mutex to protect against |
1219 | * PTRACE_ATTACH | 1219 | * PTRACE_ATTACH or seccomp thread-sync |
1220 | */ | 1220 | */ |
1221 | static void check_unsafe_exec(struct linux_binprm *bprm) | 1221 | static void check_unsafe_exec(struct linux_binprm *bprm) |
1222 | { | 1222 | { |
diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h index 9ff98b4bfe2e..5d586a45a319 100644 --- a/include/linux/seccomp.h +++ b/include/linux/seccomp.h | |||
@@ -3,6 +3,8 @@ | |||
3 | 3 | ||
4 | #include <uapi/linux/seccomp.h> | 4 | #include <uapi/linux/seccomp.h> |
5 | 5 | ||
6 | #define SECCOMP_FILTER_FLAG_MASK (SECCOMP_FILTER_FLAG_TSYNC) | ||
7 | |||
6 | #ifdef CONFIG_SECCOMP | 8 | #ifdef CONFIG_SECCOMP |
7 | 9 | ||
8 | #include <linux/thread_info.h> | 10 | #include <linux/thread_info.h> |
diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h index b258878ba754..0f238a43ff1e 100644 --- a/include/uapi/linux/seccomp.h +++ b/include/uapi/linux/seccomp.h | |||
@@ -14,6 +14,9 @@ | |||
14 | #define SECCOMP_SET_MODE_STRICT 0 | 14 | #define SECCOMP_SET_MODE_STRICT 0 |
15 | #define SECCOMP_SET_MODE_FILTER 1 | 15 | #define SECCOMP_SET_MODE_FILTER 1 |
16 | 16 | ||
17 | /* Valid flags for SECCOMP_SET_MODE_FILTER */ | ||
18 | #define SECCOMP_FILTER_FLAG_TSYNC 1 | ||
19 | |||
17 | /* | 20 | /* |
18 | * All BPF programs must return a 32-bit value. | 21 | * All BPF programs must return a 32-bit value. |
19 | * The bottom 16-bits are for optional return data. | 22 | * The bottom 16-bits are for optional return data. |
diff --git a/kernel/seccomp.c b/kernel/seccomp.c index 9065d2c79c56..74f460179171 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c | |||
@@ -26,6 +26,7 @@ | |||
26 | #ifdef CONFIG_SECCOMP_FILTER | 26 | #ifdef CONFIG_SECCOMP_FILTER |
27 | #include <asm/syscall.h> | 27 | #include <asm/syscall.h> |
28 | #include <linux/filter.h> | 28 | #include <linux/filter.h> |
29 | #include <linux/pid.h> | ||
29 | #include <linux/ptrace.h> | 30 | #include <linux/ptrace.h> |
30 | #include <linux/security.h> | 31 | #include <linux/security.h> |
31 | #include <linux/tracehook.h> | 32 | #include <linux/tracehook.h> |
@@ -225,6 +226,114 @@ static inline void seccomp_assign_mode(struct task_struct *task, | |||
225 | } | 226 | } |
226 | 227 | ||
227 | #ifdef CONFIG_SECCOMP_FILTER | 228 | #ifdef CONFIG_SECCOMP_FILTER |
229 | /* Returns 1 if the parent is an ancestor of the child. */ | ||
230 | static int is_ancestor(struct seccomp_filter *parent, | ||
231 | struct seccomp_filter *child) | ||
232 | { | ||
233 | /* NULL is the root ancestor. */ | ||
234 | if (parent == NULL) | ||
235 | return 1; | ||
236 | for (; child; child = child->prev) | ||
237 | if (child == parent) | ||
238 | return 1; | ||
239 | return 0; | ||
240 | } | ||
241 | |||
242 | /** | ||
243 | * seccomp_can_sync_threads: checks if all threads can be synchronized | ||
244 | * | ||
245 | * Expects sighand and cred_guard_mutex locks to be held. | ||
246 | * | ||
247 | * Returns 0 on success, -ve on error, or the pid of a thread which was | ||
248 | * either not in the correct seccomp mode or it did not have an ancestral | ||
249 | * seccomp filter. | ||
250 | */ | ||
251 | static inline pid_t seccomp_can_sync_threads(void) | ||
252 | { | ||
253 | struct task_struct *thread, *caller; | ||
254 | |||
255 | BUG_ON(!mutex_is_locked(¤t->signal->cred_guard_mutex)); | ||
256 | BUG_ON(!spin_is_locked(¤t->sighand->siglock)); | ||
257 | |||
258 | /* Validate all threads being eligible for synchronization. */ | ||
259 | caller = current; | ||
260 | for_each_thread(caller, thread) { | ||
261 | pid_t failed; | ||
262 | |||
263 | /* Skip current, since it is initiating the sync. */ | ||
264 | if (thread == caller) | ||
265 | continue; | ||
266 | |||
267 | if (thread->seccomp.mode == SECCOMP_MODE_DISABLED || | ||
268 | (thread->seccomp.mode == SECCOMP_MODE_FILTER && | ||
269 | is_ancestor(thread->seccomp.filter, | ||
270 | caller->seccomp.filter))) | ||
271 | continue; | ||
272 | |||
273 | /* Return the first thread that cannot be synchronized. */ | ||
274 | failed = task_pid_vnr(thread); | ||
275 | /* If the pid cannot be resolved, then return -ESRCH */ | ||
276 | if (unlikely(WARN_ON(failed == 0))) | ||
277 | failed = -ESRCH; | ||
278 | return failed; | ||
279 | } | ||
280 | |||
281 | return 0; | ||
282 | } | ||
283 | |||
284 | /** | ||
285 | * seccomp_sync_threads: sets all threads to use current's filter | ||
286 | * | ||
287 | * Expects sighand and cred_guard_mutex locks to be held, and for | ||
288 | * seccomp_can_sync_threads() to have returned success already | ||
289 | * without dropping the locks. | ||
290 | * | ||
291 | */ | ||
292 | static inline void seccomp_sync_threads(void) | ||
293 | { | ||
294 | struct task_struct *thread, *caller; | ||
295 | |||
296 | BUG_ON(!mutex_is_locked(¤t->signal->cred_guard_mutex)); | ||
297 | BUG_ON(!spin_is_locked(¤t->sighand->siglock)); | ||
298 | |||
299 | /* Synchronize all threads. */ | ||
300 | caller = current; | ||
301 | for_each_thread(caller, thread) { | ||
302 | /* Skip current, since it needs no changes. */ | ||
303 | if (thread == caller) | ||
304 | continue; | ||
305 | |||
306 | /* Get a task reference for the new leaf node. */ | ||
307 | get_seccomp_filter(caller); | ||
308 | /* | ||
309 | * Drop the task reference to the shared ancestor since | ||
310 | * current's path will hold a reference. (This also | ||
311 | * allows a put before the assignment.) | ||
312 | */ | ||
313 | put_seccomp_filter(thread); | ||
314 | smp_store_release(&thread->seccomp.filter, | ||
315 | caller->seccomp.filter); | ||
316 | /* | ||
317 | * Opt the other thread into seccomp if needed. | ||
318 | * As threads are considered to be trust-realm | ||
319 | * equivalent (see ptrace_may_access), it is safe to | ||
320 | * allow one thread to transition the other. | ||
321 | */ | ||
322 | if (thread->seccomp.mode == SECCOMP_MODE_DISABLED) { | ||
323 | /* | ||
324 | * Don't let an unprivileged task work around | ||
325 | * the no_new_privs restriction by creating | ||
326 | * a thread that sets it up, enters seccomp, | ||
327 | * then dies. | ||
328 | */ | ||
329 | if (task_no_new_privs(caller)) | ||
330 | task_set_no_new_privs(thread); | ||
331 | |||
332 | seccomp_assign_mode(thread, SECCOMP_MODE_FILTER); | ||
333 | } | ||
334 | } | ||
335 | } | ||
336 | |||
228 | /** | 337 | /** |
229 | * seccomp_prepare_filter: Prepares a seccomp filter for use. | 338 | * seccomp_prepare_filter: Prepares a seccomp filter for use. |
230 | * @fprog: BPF program to install | 339 | * @fprog: BPF program to install |
@@ -364,6 +473,15 @@ static long seccomp_attach_filter(unsigned int flags, | |||
364 | if (total_insns > MAX_INSNS_PER_PATH) | 473 | if (total_insns > MAX_INSNS_PER_PATH) |
365 | return -ENOMEM; | 474 | return -ENOMEM; |
366 | 475 | ||
476 | /* If thread sync has been requested, check that it is possible. */ | ||
477 | if (flags & SECCOMP_FILTER_FLAG_TSYNC) { | ||
478 | int ret; | ||
479 | |||
480 | ret = seccomp_can_sync_threads(); | ||
481 | if (ret) | ||
482 | return ret; | ||
483 | } | ||
484 | |||
367 | /* | 485 | /* |
368 | * If there is an existing filter, make it the prev and don't drop its | 486 | * If there is an existing filter, make it the prev and don't drop its |
369 | * task reference. | 487 | * task reference. |
@@ -371,6 +489,10 @@ static long seccomp_attach_filter(unsigned int flags, | |||
371 | filter->prev = current->seccomp.filter; | 489 | filter->prev = current->seccomp.filter; |
372 | current->seccomp.filter = filter; | 490 | current->seccomp.filter = filter; |
373 | 491 | ||
492 | /* Now that the new filter is in place, synchronize to all threads. */ | ||
493 | if (flags & SECCOMP_FILTER_FLAG_TSYNC) | ||
494 | seccomp_sync_threads(); | ||
495 | |||
374 | return 0; | 496 | return 0; |
375 | } | 497 | } |
376 | 498 | ||
@@ -590,7 +712,7 @@ static long seccomp_set_mode_filter(unsigned int flags, | |||
590 | long ret = -EINVAL; | 712 | long ret = -EINVAL; |
591 | 713 | ||
592 | /* Validate flags. */ | 714 | /* Validate flags. */ |
593 | if (flags != 0) | 715 | if (flags & ~SECCOMP_FILTER_FLAG_MASK) |
594 | return -EINVAL; | 716 | return -EINVAL; |
595 | 717 | ||
596 | /* Prepare the new filter before holding any locks. */ | 718 | /* Prepare the new filter before holding any locks. */ |
@@ -598,6 +720,14 @@ static long seccomp_set_mode_filter(unsigned int flags, | |||
598 | if (IS_ERR(prepared)) | 720 | if (IS_ERR(prepared)) |
599 | return PTR_ERR(prepared); | 721 | return PTR_ERR(prepared); |
600 | 722 | ||
723 | /* | ||
724 | * Make sure we cannot change seccomp or nnp state via TSYNC | ||
725 | * while another thread is in the middle of calling exec. | ||
726 | */ | ||
727 | if (flags & SECCOMP_FILTER_FLAG_TSYNC && | ||
728 | mutex_lock_killable(¤t->signal->cred_guard_mutex)) | ||
729 | goto out_free; | ||
730 | |||
601 | spin_lock_irq(¤t->sighand->siglock); | 731 | spin_lock_irq(¤t->sighand->siglock); |
602 | 732 | ||
603 | if (!seccomp_may_assign_mode(seccomp_mode)) | 733 | if (!seccomp_may_assign_mode(seccomp_mode)) |
@@ -612,6 +742,9 @@ static long seccomp_set_mode_filter(unsigned int flags, | |||
612 | seccomp_assign_mode(current, seccomp_mode); | 742 | seccomp_assign_mode(current, seccomp_mode); |
613 | out: | 743 | out: |
614 | spin_unlock_irq(¤t->sighand->siglock); | 744 | spin_unlock_irq(¤t->sighand->siglock); |
745 | if (flags & SECCOMP_FILTER_FLAG_TSYNC) | ||
746 | mutex_unlock(¤t->signal->cred_guard_mutex); | ||
747 | out_free: | ||
615 | seccomp_filter_free(prepared); | 748 | seccomp_filter_free(prepared); |
616 | return ret; | 749 | return ret; |
617 | } | 750 | } |