[Devel,RHEL7,COMMIT] ms/seccomp, ptrace: add support for dumping seccomp filters

Submitted by Konstantin Khorenko on Nov. 16, 2016, 12:17 p.m.

Details

Message ID 201611161217.uAGCH5hC012148@finist_cl7.x64_64.work.ct
State New
Series "seccomp, ptrace: add support for dumping seccomp filters"
Headers show

Commit Message

Konstantin Khorenko Nov. 16, 2016, 12:17 p.m.
The commit is pushed to "branch-rh7-3.10.0-327.36.1.vz7.19.x-ovz" and will appear at https://src.openvz.org/scm/ovz/vzkernel.git
after rh7-3.10.0-327.36.1.vz7.19.9
------>
commit 42b65fd18057d64410a0519962cd0650c762c99f
Author: Tycho Andersen <tycho.andersen@canonical.com>
Date:   Wed Nov 16 16:17:05 2016 +0400

    ms/seccomp, ptrace: add support for dumping seccomp filters
    
    ML: f8e529ed941ba2bbcbf310b575d968159ce7e895
    
    https://jira.sw.ru/browse/PSBM-55322
    
    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>
    
    Signed-off-by: Andrey Vagin <avagin@openvz.org>
---
 include/linux/seccomp.h     | 11 ++++++++
 include/uapi/linux/ptrace.h |  2 ++
 kernel/ptrace.c             |  5 ++++
 kernel/seccomp.c            | 62 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 80 insertions(+)

Patch hide | download patch | download mbox

diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h
index 6f19cfd..fd83f3e 100644
--- a/include/linux/seccomp.h
+++ b/include/linux/seccomp.h
@@ -87,4 +87,15 @@  static inline void get_seccomp_filter(struct task_struct *tsk)
 	return;
 }
 #endif /* CONFIG_SECCOMP_FILTER */
+
+#if defined(CONFIG_SECCOMP_FILTER) && defined(CONFIG_CHECKPOINT_RESTORE)
+extern long seccomp_get_filter(struct task_struct *task,
+			       unsigned long filter_off, void __user *data);
+#else
+static inline long seccomp_get_filter(struct task_struct *task,
+				      unsigned long n, void __user *data)
+{
+	return -EINVAL;
+}
+#endif /* CONFIG_SECCOMP_FILTER && CONFIG_CHECKPOINT_RESTORE */
 #endif /* _LINUX_SECCOMP_H */
diff --git a/include/uapi/linux/ptrace.h b/include/uapi/linux/ptrace.h
index a7a6979..fb81065 100644
--- a/include/uapi/linux/ptrace.h
+++ b/include/uapi/linux/ptrace.h
@@ -64,6 +64,8 @@  struct ptrace_peeksiginfo_args {
 #define PTRACE_GETSIGMASK	0x420a
 #define PTRACE_SETSIGMASK	0x420b
 
+#define PTRACE_SECCOMP_GET_FILTER	0x420c
+
 /* Read signals from a shared (process wide) queue */
 #define PTRACE_PEEKSIGINFO_SHARED	(1 << 0)
 
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index f26c160..fb4f3bf 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -1039,6 +1039,11 @@  int ptrace_request(struct task_struct *child, long request,
 		break;
 	}
 #endif
+
+	case PTRACE_SECCOMP_GET_FILTER:
+		ret = seccomp_get_filter(child, addr, datavp);
+		break;
+
 	default:
 		break;
 	}
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index c1bac82..574b784 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -515,3 +515,65 @@  long prctl_set_seccomp(unsigned long seccomp_mode, char __user *filter)
 out:
 	return ret;
 }
+
+#if defined(CONFIG_SECCOMP_FILTER) && defined(CONFIG_CHECKPOINT_RESTORE)
+long seccomp_get_filter(struct task_struct *task, unsigned long filter_off,
+			void __user *data)
+{
+	struct seccomp_filter *filter;
+	long ret;
+	unsigned long count = 0;
+
+	if (!capable(CAP_SYS_ADMIN) ||
+	    current->seccomp.mode != SECCOMP_MODE_DISABLED) {
+		return -EACCES;
+	}
+
+	spin_lock_irq(&task->sighand->siglock);
+	if (task->seccomp.mode != SECCOMP_MODE_FILTER) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	filter = task->seccomp.filter;
+	while (filter) {
+		filter = filter->prev;
+		count++;
+	}
+
+	if (filter_off >= count) {
+		ret = -ENOENT;
+		goto out;
+	}
+	count -= filter_off;
+
+	filter = task->seccomp.filter;
+	while (filter && count > 1) {
+		filter = filter->prev;
+		count--;
+	}
+
+	if (WARN_ON(count != 1 || !filter)) {
+		/* The filter tree shouldn't shrink while we're using it. */
+		ret = -ENOENT;
+		goto out;
+	}
+
+	ret = filter->len;
+	if (!data)
+		goto out;
+
+	get_seccomp_filter(task);
+	spin_unlock_irq(&task->sighand->siglock);
+
+	if (copy_to_user(data, filter->insns, filter->len))
+		ret = -EFAULT;
+
+	put_seccomp_filter(task);
+	return ret;
+
+out:
+	spin_unlock_irq(&task->sighand->siglock);
+	return ret;
+}
+#endif