Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

seccomp, ptrace: add support for dumping seccomp filters

This patch adds support for dumping a process' (classic BPF) seccomp
filters via ptrace.

PTRACE_SECCOMP_GET_FILTER allows the tracer to dump the user's classic BPF
seccomp filters. addr should be an integer which represents the ith seccomp
filter (0 is the most recently installed filter). data should be a struct
sock_filter * with enough room for the ith filter, or NULL, in which case
the filter is not saved. The return value for this command is the number of
BPF instructions the program represents, or negative in the case of errors.
Command specific errors are ENOENT: which indicates that there is no ith
filter in this seccomp tree, and EMEDIUMTYPE, which indicates that the ith
filter was not installed as a classic BPF filter.

A caveat with this approach is that there is no way to get explicitly at
the heirarchy of seccomp filters, and users need to memcmp() filters to
decide which are inherited. This means that a task which installs two of
the same filter can potentially confuse users of this interface.

v2: * make save_orig const
* check that the orig_prog exists (not necessary right now, but when
grows eBPF support it will be)
* s/n/filter_off and make it an unsigned long to match ptrace
* count "down" the tree instead of "up" when passing a filter offset

v3: * don't take the current task's lock for inspecting its seccomp mode
* use a 0x42** constant for the ptrace command value

v4: * don't copy to userspace while holding spinlocks

v5: * add another condition to WARN_ON

v6: * rebase on net-next

Signed-off-by: Tycho Andersen <tycho.andersen@canonical.com>
Acked-by: Kees Cook <keescook@chromium.org>
CC: Will Drewry <wad@chromium.org>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
CC: Andy Lutomirski <luto@amacapital.net>
CC: Pavel Emelyanov <xemul@parallels.com>
CC: Serge E. Hallyn <serge.hallyn@ubuntu.com>
CC: Alexei Starovoitov <ast@kernel.org>
CC: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Tycho Andersen and committed by
David S. Miller
f8e529ed 5b9e3bd5

+93 -1
+11
include/linux/seccomp.h
··· 95 95 return; 96 96 } 97 97 #endif /* CONFIG_SECCOMP_FILTER */ 98 + 99 + #if defined(CONFIG_SECCOMP_FILTER) && defined(CONFIG_CHECKPOINT_RESTORE) 100 + extern long seccomp_get_filter(struct task_struct *task, 101 + unsigned long filter_off, void __user *data); 102 + #else 103 + static inline long seccomp_get_filter(struct task_struct *task, 104 + unsigned long n, void __user *data) 105 + { 106 + return -EINVAL; 107 + } 108 + #endif /* CONFIG_SECCOMP_FILTER && CONFIG_CHECKPOINT_RESTORE */ 98 109 #endif /* _LINUX_SECCOMP_H */
+2
include/uapi/linux/ptrace.h
··· 64 64 #define PTRACE_GETSIGMASK 0x420a 65 65 #define PTRACE_SETSIGMASK 0x420b 66 66 67 + #define PTRACE_SECCOMP_GET_FILTER 0x420c 68 + 67 69 /* Read signals from a shared (process wide) queue */ 68 70 #define PTRACE_PEEKSIGINFO_SHARED (1 << 0) 69 71
+5
kernel/ptrace.c
··· 1016 1016 break; 1017 1017 } 1018 1018 #endif 1019 + 1020 + case PTRACE_SECCOMP_GET_FILTER: 1021 + ret = seccomp_get_filter(child, addr, datavp); 1022 + break; 1023 + 1019 1024 default: 1020 1025 break; 1021 1026 }
+75 -1
kernel/seccomp.c
··· 347 347 { 348 348 struct seccomp_filter *sfilter; 349 349 int ret; 350 + const bool save_orig = config_enabled(CONFIG_CHECKPOINT_RESTORE); 350 351 351 352 if (fprog->len == 0 || fprog->len > BPF_MAXINSNS) 352 353 return ERR_PTR(-EINVAL); ··· 371 370 return ERR_PTR(-ENOMEM); 372 371 373 372 ret = bpf_prog_create_from_user(&sfilter->prog, fprog, 374 - seccomp_check_filter, false); 373 + seccomp_check_filter, save_orig); 375 374 if (ret < 0) { 376 375 kfree(sfilter); 377 376 return ERR_PTR(ret); ··· 868 867 /* prctl interface doesn't have flags, so they are always zero. */ 869 868 return do_seccomp(op, 0, uargs); 870 869 } 870 + 871 + #if defined(CONFIG_SECCOMP_FILTER) && defined(CONFIG_CHECKPOINT_RESTORE) 872 + long seccomp_get_filter(struct task_struct *task, unsigned long filter_off, 873 + void __user *data) 874 + { 875 + struct seccomp_filter *filter; 876 + struct sock_fprog_kern *fprog; 877 + long ret; 878 + unsigned long count = 0; 879 + 880 + if (!capable(CAP_SYS_ADMIN) || 881 + current->seccomp.mode != SECCOMP_MODE_DISABLED) { 882 + return -EACCES; 883 + } 884 + 885 + spin_lock_irq(&task->sighand->siglock); 886 + if (task->seccomp.mode != SECCOMP_MODE_FILTER) { 887 + ret = -EINVAL; 888 + goto out; 889 + } 890 + 891 + filter = task->seccomp.filter; 892 + while (filter) { 893 + filter = filter->prev; 894 + count++; 895 + } 896 + 897 + if (filter_off >= count) { 898 + ret = -ENOENT; 899 + goto out; 900 + } 901 + count -= filter_off; 902 + 903 + filter = task->seccomp.filter; 904 + while (filter && count > 1) { 905 + filter = filter->prev; 906 + count--; 907 + } 908 + 909 + if (WARN_ON(count != 1 || !filter)) { 910 + /* The filter tree shouldn't shrink while we're using it. */ 911 + ret = -ENOENT; 912 + goto out; 913 + } 914 + 915 + fprog = filter->prog->orig_prog; 916 + if (!fprog) { 917 + /* This must be a new non-cBPF filter, since we save every 918 + * every cBPF filter's orig_prog above when 919 + * CONFIG_CHECKPOINT_RESTORE is enabled. 920 + */ 921 + ret = -EMEDIUMTYPE; 922 + goto out; 923 + } 924 + 925 + ret = fprog->len; 926 + if (!data) 927 + goto out; 928 + 929 + get_seccomp_filter(task); 930 + spin_unlock_irq(&task->sighand->siglock); 931 + 932 + if (copy_to_user(data, fprog->filter, bpf_classic_proglen(fprog))) 933 + ret = -EFAULT; 934 + 935 + put_seccomp_filter(task); 936 + return ret; 937 + 938 + out: 939 + spin_unlock_irq(&task->sighand->siglock); 940 + return ret; 941 + } 942 + #endif