Received: by 2002:ac0:a5b6:0:0:0:0:0 with SMTP id m51-v6csp696118imm; Thu, 31 May 2018 07:53:02 -0700 (PDT) X-Google-Smtp-Source: ADUXVKLtdMxxLh3APZPGmCZJRvOaqRnHIaGzQcMpp59Uw8sTI72k9I2RbGOj9j6oByCWBUHg6ZhN X-Received: by 2002:a65:4146:: with SMTP id x6-v6mr5631622pgp.221.1527778382641; Thu, 31 May 2018 07:53:02 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1527778382; cv=none; d=google.com; s=arc-20160816; b=hUalb3HCy8/VMQZj2wS/3rqqemPtgIFPClXBsYWD/JdeJtDuuuygVgVcUYKw34NCRx 8O5THj4nEwPSb7iWO1yQ13oFgbWebccnj/0ZSWuiBDnxdJcj9bh2ZRrxvb8nv5HUV65+ ZFSqYlf+oBws6P0kzXlli1v1+9UCtstTvTQliHZFk1gmrjIAWW28AadIbQ5d1DMhKybR VFPY+M/Gy5j2NsCdNUWE3R7+wjrGifvkNGXZlpUmW8tnPuvpLj8E5yRlGo9xv0K6NqJ3 qhF/oI1MbdX60WFfsirewP9n3yKiiRsfxgtMUmUMYrIivM8QhMdoYq3Te4cPCoTm/P/i 2Flw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=HvnjVpuWFXdMQ3fZJN6uLhO4YKx/3FJN+p8JkhOoNeA=; b=DIvCQX7pU9T38QVxZNScrdq/YatgAwfJsY78qTDD4EuTKCNq+DOC6rTF0s5HQQLQfW R4rzf0JmyWc3GR//gGAXK2zeakNBj1UDJlv9f/kCIakVA1N4NRAmh1+btFB6kzgy2gDu DQLRaVBr6wp9JMZKSY+rB89HYmE0hUTBsotLocP8v7A24GSaJtdbyZnX1M3e0V1lZEdD QK+s6oxZQioAVQXoSuymAoZBrnTQ40yXVbxuhTo31y8O+UwtpOKYFjpQ6qdCkgNXWeAf H1ndW+j+UNaCq/vHFerDGwBJ4fJfTljchcenSitALYm3ksTxqxH75ubk76K2ByMwY814 87QQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@tycho-ws.20150623.gappssmtp.com header.s=20150623 header.b=Wxx+Ncly; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id b8-v6si37159844ple.469.2018.05.31.07.52.48; Thu, 31 May 2018 07:53:02 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@tycho-ws.20150623.gappssmtp.com header.s=20150623 header.b=Wxx+Ncly; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755496AbeEaOvC (ORCPT + 99 others); Thu, 31 May 2018 10:51:02 -0400 Received: from mail-it0-f66.google.com ([209.85.214.66]:50652 "EHLO mail-it0-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755414AbeEaOut (ORCPT ); Thu, 31 May 2018 10:50:49 -0400 Received: by mail-it0-f66.google.com with SMTP id p3-v6so27953797itc.0 for ; Thu, 31 May 2018 07:50:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tycho-ws.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=HvnjVpuWFXdMQ3fZJN6uLhO4YKx/3FJN+p8JkhOoNeA=; b=Wxx+NclyXun3Eqa39S6w/Oel2peQoDMbtrqrEW1FrdvfZYEhComT+9pC2xsRFWGF+T jzKWYgLAVThT9ThQEZ5tluvMayLtbz0YahXlpi2pt36m1yh+ORdd80rtLLnfl97sWJZp h/sA1rhNpOt4NSqPdmzLdJZxkg5QiZwddwJbeis/s3sYmmtD3SvCaUwsF7b8YTkAuhGZ nHBamtMb589wfs7giS78F2hCkxeP9g/R8q2Hs44mQDW61e1OG9g1bM5lh6q4FvkN9yF/ 2daL7pAskzww0Kpp8uXgtwRlWLRDzszf/5yHE6e0lGjZuADSZa7Do3u4JgbNJILiyiSt u1Yg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=HvnjVpuWFXdMQ3fZJN6uLhO4YKx/3FJN+p8JkhOoNeA=; b=pKDmwXkclFtEXWGiqDt2o8DVANZKV+/PM72FpOI1RnOKJbKrkUupYWCdxVOlCd2vf5 9BkiZevO0ZJ9t2JoCrGbVnihdRmlIGEl2ZQ5EQJCYDhWkl0uRqjqDepq4JXG0Oq/Situ 6V5QTfmmIe61VCMCDjUtCXYfAL6cqJPFdOy5SNgzBVnxiGUmFlAgq299wI3SeuqyLiGC lX4vCXDd29i6NGOD0ENkVw+Vort1eo+PElkUxLvbB2o/RzeE3HW60NiqQaATojxbhEET F7uJC2vTTvoLiBysOILg05ztZwKy6+g9E+HUpb13Pn6N3GEZ6vNhmw+fCvExc1dBiFye jyIA== X-Gm-Message-State: APt69E3fsM64jFZjpuFCBhePK0mMP+Pomno9dz88zoIDxlpT7cojjm6i Dk7YAaZMoEE032qvaU8cS67I46N7Wr8= X-Received: by 2002:a24:1994:: with SMTP id b142-v6mr291310itb.14.1527778248294; Thu, 31 May 2018 07:50:48 -0700 (PDT) Received: from cisco.lan ([8.24.24.129]) by smtp.gmail.com with ESMTPSA id m14-v6sm208506iti.36.2018.05.31.07.50.46 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 31 May 2018 07:50:46 -0700 (PDT) From: Tycho Andersen To: linux-kernel@vger.kernel.org, containers@lists.linux-foundation.org Cc: Kees Cook , Andy Lutomirski , Oleg Nesterov , "Eric W . Biederman" , "Serge E . Hallyn" , Christian Brauner , Tyler Hicks , Akihiro Suda , "Tobin C . Harding" , Tycho Andersen Subject: [PATCH v3 1/4] seccomp: add a return code to trap to userspace Date: Thu, 31 May 2018 08:49:46 -0600 Message-Id: <20180531144949.24995-2-tycho@tycho.ws> X-Mailer: git-send-email 2.17.0 In-Reply-To: <20180531144949.24995-1-tycho@tycho.ws> References: <20180531144949.24995-1-tycho@tycho.ws> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This patch introduces a means for syscalls matched in seccomp to notify some other task that a particular filter has been triggered. The motivation for this is primarily for use with containers. For example, if a container does an init_module(), we obviously don't want to load this untrusted code, which may be compiled for the wrong version of the kernel anyway. Instead, we could parse the module image, figure out which module the container is trying to load and load it on the host. As another example, containers cannot mknod(), since this checks capable(CAP_SYS_ADMIN). However, harmless devices like /dev/null or /dev/zero should be ok for containers to mknod, but we'd like to avoid hard coding some whitelist in the kernel. Another example is mount(), which has many security restrictions for good reason, but configuration or runtime knowledge could potentially be used to relax these restrictions. This patch adds functionality that is already possible via at least two other means that I know about, both of which involve ptrace(): first, one could ptrace attach, and then iterate through syscalls via PTRACE_SYSCALL. Unfortunately this is slow, so a faster version would be to install a filter that does SECCOMP_RET_TRACE, which triggers a PTRACE_EVENT_SECCOMP. Since ptrace allows only one tracer, if the container runtime is that tracer, users inside the container (or outside) trying to debug it will not be able to use ptrace, which is annoying. It also means that older distributions based on Upstart cannot boot inside containers using ptrace, since upstart itself uses ptrace to start services. The actual implementation of this is fairly small, although getting the synchronization right was/is slightly complex. Finally, it's worth noting that the classic seccomp TOCTOU of reading memory data from the task still applies here, but can be avoided with careful design of the userspace handler: if the userspace handler reads all of the task memory that is necessary before applying its security policy, the tracee's subsequent memory edits will not be read by the tracer. v2: * make id a u64; the idea here being that it will never overflow, because 64 is huge (one syscall every nanosecond => wrap every 584 years) (Andy) * prevent nesting of user notifications: if someone is already attached the tree in one place, nobody else can attach to the tree (Andy) * notify the listener of signals the tracee receives as well (Andy) * implement poll v3: * lockdep fix (Oleg) * drop unnecessary WARN()s (Christian) * rearrange error returns to be more rpetty (Christian) * fix build in !CONFIG_SECCOMP_USER_NOTIFICATION case Signed-off-by: Tycho Andersen CC: Kees Cook CC: Andy Lutomirski CC: Oleg Nesterov CC: Eric W. Biederman CC: "Serge E. Hallyn" CC: Christian Brauner CC: Tyler Hicks CC: Akihiro Suda --- arch/Kconfig | 7 + include/linux/seccomp.h | 3 +- include/uapi/linux/seccomp.h | 18 +- kernel/seccomp.c | 398 +++++++++++++++++- tools/testing/selftests/seccomp/seccomp_bpf.c | 195 ++++++++- 5 files changed, 615 insertions(+), 6 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index 75dd23acf133..1c1ae8d8c8b9 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -401,6 +401,13 @@ config SECCOMP_FILTER See Documentation/prctl/seccomp_filter.txt for details. +config SECCOMP_USER_NOTIFICATION + bool "Enable the SECCOMP_RET_USER_NOTIF seccomp action" + depends on SECCOMP_FILTER + help + Enable SECCOMP_RET_USER_NOTIF, a return code which can be used by seccomp + programs to notify a userspace listener that a particular event happened. + config HAVE_GCC_PLUGINS bool help diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h index c723a5c4e3ff..0fd3e0676a1c 100644 --- a/include/linux/seccomp.h +++ b/include/linux/seccomp.h @@ -5,7 +5,8 @@ #include #define SECCOMP_FILTER_FLAG_MASK (SECCOMP_FILTER_FLAG_TSYNC | \ - SECCOMP_FILTER_FLAG_LOG) + SECCOMP_FILTER_FLAG_LOG | \ + SECCOMP_FILTER_FLAG_GET_LISTENER) #ifdef CONFIG_SECCOMP diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h index 2a0bd9dd104d..8160e6cad528 100644 --- a/include/uapi/linux/seccomp.h +++ b/include/uapi/linux/seccomp.h @@ -17,8 +17,9 @@ #define SECCOMP_GET_ACTION_AVAIL 2 /* Valid flags for SECCOMP_SET_MODE_FILTER */ -#define SECCOMP_FILTER_FLAG_TSYNC 1 -#define SECCOMP_FILTER_FLAG_LOG 2 +#define SECCOMP_FILTER_FLAG_TSYNC 1 +#define SECCOMP_FILTER_FLAG_LOG 2 +#define SECCOMP_FILTER_FLAG_GET_LISTENER 4 /* * All BPF programs must return a 32-bit value. @@ -34,6 +35,7 @@ #define SECCOMP_RET_KILL SECCOMP_RET_KILL_THREAD #define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */ #define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */ +#define SECCOMP_RET_USER_NOTIF 0x7fc00000U /* notifies userspace */ #define SECCOMP_RET_TRACE 0x7ff00000U /* pass to a tracer or disallow */ #define SECCOMP_RET_LOG 0x7ffc0000U /* allow after logging */ #define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ @@ -59,4 +61,16 @@ struct seccomp_data { __u64 args[6]; }; +struct seccomp_notif { + __u64 id; + pid_t pid; + struct seccomp_data data; +}; + +struct seccomp_notif_resp { + __u64 id; + __s32 error; + __s64 val; +}; + #endif /* _UAPI_LINUX_SECCOMP_H */ diff --git a/kernel/seccomp.c b/kernel/seccomp.c index dc77548167ef..f69327d5f7c7 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -31,6 +31,7 @@ #endif #ifdef CONFIG_SECCOMP_FILTER +#include #include #include #include @@ -38,6 +39,52 @@ #include #include +#ifdef CONFIG_SECCOMP_USER_NOTIFICATION +#include + +enum notify_state { + SECCOMP_NOTIFY_INIT, + SECCOMP_NOTIFY_SENT, + SECCOMP_NOTIFY_REPLIED, +}; + +struct seccomp_knotif { + /* The pid whose filter triggered the notification */ + pid_t pid; + + /* + * The "cookie" for this request; this is unique for this filter. + */ + u32 id; + + /* + * The seccomp data. This pointer is valid the entire time this + * notification is active, since it comes from __seccomp_filter which + * eclipses the entire lifecycle here. + */ + const struct seccomp_data *data; + + /* + * Notification states. When SECCOMP_RET_USER_NOTIF is returned, a + * struct seccomp_knotif is created and starts out in INIT. Once the + * handler reads the notification off of an FD, it transitions to READ. + * If a signal is received the state transitions back to INIT and + * another message is sent. When the userspace handler replies, state + * transitions to REPLIED. + */ + enum notify_state state; + + /* The return values, only valid when in SECCOMP_NOTIFY_REPLIED */ + int error; + long val; + + /* Signals when this has entered SECCOMP_NOTIFY_REPLIED */ + struct completion ready; + + struct list_head list; +}; +#endif + /** * struct seccomp_filter - container for seccomp BPF programs * @@ -64,6 +111,27 @@ struct seccomp_filter { bool log; struct seccomp_filter *prev; struct bpf_prog *prog; + +#ifdef CONFIG_SECCOMP_USER_NOTIFICATION + /* + * A semaphore that users of this notification can wait on for + * changes. Actual reads and writes are still controlled with + * filter->notify_lock. + */ + struct semaphore request; + + /* A lock for all notification-related accesses. */ + struct mutex notify_lock; + + /* Is there currently an attached listener? */ + bool has_listener; + + /* The id of the next request. */ + u64 next_id; + + /* A list of struct seccomp_knotif elements. */ + struct list_head notifications; +#endif }; /* Limit any path through the tree to 256KB worth of instructions. */ @@ -383,6 +451,13 @@ static struct seccomp_filter *seccomp_prepare_filter(struct sock_fprog *fprog) if (!sfilter) return ERR_PTR(-ENOMEM); +#ifdef CONFIG_SECCOMP_USER_NOTIFICATION + mutex_init(&sfilter->notify_lock); + sema_init(&sfilter->request, 0); + INIT_LIST_HEAD(&sfilter->notifications); + sfilter->next_id = get_random_u64(); +#endif + ret = bpf_prog_create_from_user(&sfilter->prog, fprog, seccomp_check_filter, save_orig); if (ret < 0) { @@ -547,13 +622,15 @@ static void seccomp_send_sigsys(int syscall, int reason) #define SECCOMP_LOG_TRACE (1 << 4) #define SECCOMP_LOG_LOG (1 << 5) #define SECCOMP_LOG_ALLOW (1 << 6) +#define SECCOMP_LOG_USER_NOTIF (1 << 7) static u32 seccomp_actions_logged = SECCOMP_LOG_KILL_PROCESS | SECCOMP_LOG_KILL_THREAD | SECCOMP_LOG_TRAP | SECCOMP_LOG_ERRNO | SECCOMP_LOG_TRACE | - SECCOMP_LOG_LOG; + SECCOMP_LOG_LOG | + SECCOMP_LOG_USER_NOTIF; static inline void seccomp_log(unsigned long syscall, long signr, u32 action, bool requested) @@ -572,6 +649,9 @@ static inline void seccomp_log(unsigned long syscall, long signr, u32 action, case SECCOMP_RET_TRACE: log = requested && seccomp_actions_logged & SECCOMP_LOG_TRACE; break; + case SECCOMP_RET_USER_NOTIF: + log = requested && seccomp_actions_logged & SECCOMP_LOG_USER_NOTIF; + break; case SECCOMP_RET_LOG: log = seccomp_actions_logged & SECCOMP_LOG_LOG; break; @@ -645,6 +725,81 @@ void secure_computing_strict(int this_syscall) } #else +#ifdef CONFIG_SECCOMP_USER_NOTIFICATION +static u64 seccomp_next_notify_id(struct seccomp_filter *filter) +{ + /* Note: overflow is ok here, the id just needs to be unique */ + return filter->next_id++; +} + +static void seccomp_do_user_notification(int this_syscall, + struct seccomp_filter *match, + const struct seccomp_data *sd) +{ + int err; + long ret = 0; + struct seccomp_knotif n = {}; + + mutex_lock(&match->notify_lock); + err = -ENOSYS; + if (!match->has_listener) + goto out; + + n.pid = current->pid; + n.state = SECCOMP_NOTIFY_INIT; + n.data = sd; + n.id = seccomp_next_notify_id(match); + init_completion(&n.ready); + + list_add(&n.list, &match->notifications); + + mutex_unlock(&match->notify_lock); + up(&match->request); + + err = wait_for_completion_interruptible(&n.ready); + mutex_lock(&match->notify_lock); + + /* + * Here it's possible we got a signal and then had to wait on the mutex + * while the reply was sent, so let's be sure there wasn't a response + * in the meantime. + */ + if (err < 0 && n.state != SECCOMP_NOTIFY_REPLIED) { + /* + * We got a signal. Let's tell userspace about it (potentially + * again, if we had already notified them about the first one). + */ + if (n.state == SECCOMP_NOTIFY_SENT) { + n.state = SECCOMP_NOTIFY_INIT; + up(&match->request); + } + mutex_unlock(&match->notify_lock); + err = wait_for_completion_killable(&n.ready); + mutex_lock(&match->notify_lock); + if (err < 0) + goto remove_list; + } + + ret = n.val; + err = n.error; + +remove_list: + list_del(&n.list); +out: + mutex_unlock(&match->notify_lock); + syscall_set_return_value(current, task_pt_regs(current), + err, ret); +} +#else +static void seccomp_do_user_notification(int this_syscall, + struct seccomp_filter *match, + const struct seccomp_data *sd) +{ + seccomp_log(this_syscall, SIGSYS, SECCOMP_RET_USER_NOTIF, true); + do_exit(SIGSYS); +} +#endif + #ifdef CONFIG_SECCOMP_FILTER static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd, const bool recheck_after_trace) @@ -722,6 +877,9 @@ static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd, return 0; + case SECCOMP_RET_USER_NOTIF: + seccomp_do_user_notification(this_syscall, match, sd); + goto skip; case SECCOMP_RET_LOG: seccomp_log(this_syscall, 0, action, true); return 0; @@ -828,6 +986,9 @@ static long seccomp_set_mode_strict(void) } #ifdef CONFIG_SECCOMP_FILTER +static struct file *init_listener(struct task_struct *, + struct seccomp_filter *); + /** * seccomp_set_mode_filter: internal function for setting seccomp filter * @flags: flags to change filter behavior @@ -847,6 +1008,8 @@ static long seccomp_set_mode_filter(unsigned int flags, const unsigned long seccomp_mode = SECCOMP_MODE_FILTER; struct seccomp_filter *prepared = NULL; long ret = -EINVAL; + int listener = 0; + struct file *listener_f = NULL; /* Validate flags. */ if (flags & ~SECCOMP_FILTER_FLAG_MASK) @@ -857,13 +1020,28 @@ static long seccomp_set_mode_filter(unsigned int flags, if (IS_ERR(prepared)) return PTR_ERR(prepared); + if (flags & SECCOMP_FILTER_FLAG_GET_LISTENER) { + listener = get_unused_fd_flags(O_RDWR); + if (listener < 0) { + ret = listener; + goto out_free; + } + + listener_f = init_listener(current, prepared); + if (IS_ERR(listener_f)) { + put_unused_fd(listener); + ret = PTR_ERR(listener_f); + goto out_free; + } + } + /* * Make sure we cannot change seccomp or nnp state via TSYNC * while another thread is in the middle of calling exec. */ if (flags & SECCOMP_FILTER_FLAG_TSYNC && mutex_lock_killable(¤t->signal->cred_guard_mutex)) - goto out_free; + goto out_put_fd; spin_lock_irq(¤t->sighand->siglock); @@ -881,6 +1059,16 @@ static long seccomp_set_mode_filter(unsigned int flags, spin_unlock_irq(¤t->sighand->siglock); if (flags & SECCOMP_FILTER_FLAG_TSYNC) mutex_unlock(¤t->signal->cred_guard_mutex); +out_put_fd: + if (flags & SECCOMP_FILTER_FLAG_GET_LISTENER) { + if (ret < 0) { + fput(listener_f); + put_unused_fd(listener); + } else { + fd_install(listener, listener_f); + ret = listener; + } + } out_free: seccomp_filter_free(prepared); return ret; @@ -909,6 +1097,9 @@ static long seccomp_get_action_avail(const char __user *uaction) case SECCOMP_RET_LOG: case SECCOMP_RET_ALLOW: break; + case SECCOMP_RET_USER_NOTIF: + if (IS_ENABLED(CONFIG_SECCOMP_USER_NOTIFICATION)) + break; default: return -EOPNOTSUPP; } @@ -1105,6 +1296,7 @@ long seccomp_get_metadata(struct task_struct *task, #define SECCOMP_RET_KILL_THREAD_NAME "kill_thread" #define SECCOMP_RET_TRAP_NAME "trap" #define SECCOMP_RET_ERRNO_NAME "errno" +#define SECCOMP_RET_USER_NOTIF_NAME "user_notif" #define SECCOMP_RET_TRACE_NAME "trace" #define SECCOMP_RET_LOG_NAME "log" #define SECCOMP_RET_ALLOW_NAME "allow" @@ -1114,6 +1306,7 @@ static const char seccomp_actions_avail[] = SECCOMP_RET_KILL_THREAD_NAME " " SECCOMP_RET_TRAP_NAME " " SECCOMP_RET_ERRNO_NAME " " + SECCOMP_RET_USER_NOTIF_NAME " " SECCOMP_RET_TRACE_NAME " " SECCOMP_RET_LOG_NAME " " SECCOMP_RET_ALLOW_NAME; @@ -1131,6 +1324,7 @@ static const struct seccomp_log_name seccomp_log_names[] = { { SECCOMP_LOG_TRACE, SECCOMP_RET_TRACE_NAME }, { SECCOMP_LOG_LOG, SECCOMP_RET_LOG_NAME }, { SECCOMP_LOG_ALLOW, SECCOMP_RET_ALLOW_NAME }, + { SECCOMP_LOG_USER_NOTIF, SECCOMP_RET_USER_NOTIF_NAME }, { } }; @@ -1279,3 +1473,203 @@ static int __init seccomp_sysctl_init(void) device_initcall(seccomp_sysctl_init) #endif /* CONFIG_SYSCTL */ + +#ifdef CONFIG_SECCOMP_USER_NOTIFICATION +static int seccomp_notify_release(struct inode *inode, struct file *file) +{ + struct seccomp_filter *filter = file->private_data; + struct seccomp_knotif *knotif; + + mutex_lock(&filter->notify_lock); + + /* + * If this file is being closed because e.g. the task who owned it + * died, let's wake everyone up who was waiting on us. + */ + list_for_each_entry(knotif, &filter->notifications, list) { + if (knotif->state == SECCOMP_NOTIFY_REPLIED) + continue; + + knotif->state = SECCOMP_NOTIFY_REPLIED; + knotif->error = -ENOSYS; + knotif->val = 0; + + complete(&knotif->ready); + } + + filter->has_listener = false; + mutex_unlock(&filter->notify_lock); + __put_seccomp_filter(filter); + return 0; +} + +static ssize_t seccomp_notify_read(struct file *f, char __user *buf, + size_t size, loff_t *ppos) +{ + struct seccomp_filter *filter = f->private_data; + struct seccomp_knotif *knotif = NULL, *cur; + struct seccomp_notif unotif; + ssize_t ret; + + /* No offset reads. */ + if (*ppos != 0) + return -EINVAL; + + ret = down_interruptible(&filter->request); + if (ret < 0) + return ret; + + mutex_lock(&filter->notify_lock); + list_for_each_entry(cur, &filter->notifications, list) { + if (cur->state == SECCOMP_NOTIFY_INIT) { + knotif = cur; + break; + } + } + + /* + * If we didn't find a notification, it could be that the task was + * interrupted between the time we were woken and when we were able to + * acquire the rw lock. Should we retry here or just -ENOENT? -ENOENT + * for now. + */ + if (!knotif) { + ret = -ENOENT; + goto out; + } + + unotif.id = knotif->id; + unotif.pid = knotif->pid; + unotif.data = *(knotif->data); + + size = min_t(size_t, size, sizeof(struct seccomp_notif)); + if (copy_to_user(buf, &unotif, size)) { + ret = -EFAULT; + goto out; + } + + ret = sizeof(unotif); + knotif->state = SECCOMP_NOTIFY_SENT; + +out: + mutex_unlock(&filter->notify_lock); + return ret; +} + +static ssize_t seccomp_notify_write(struct file *file, const char __user *buf, + size_t size, loff_t *ppos) +{ + struct seccomp_filter *filter = file->private_data; + struct seccomp_notif_resp resp = {}; + struct seccomp_knotif *knotif = NULL; + ssize_t ret = -EINVAL; + + /* No partial writes. */ + if (*ppos != 0) + return -EINVAL; + + size = min_t(size_t, size, sizeof(resp)); + if (copy_from_user(&resp, buf, size)) + return -EFAULT; + + ret = mutex_lock_interruptible(&filter->notify_lock); + if (ret < 0) + return ret; + + list_for_each_entry(knotif, &filter->notifications, list) { + if (knotif->id == resp.id) + break; + } + + if (!knotif || knotif->id != resp.id) { + ret = -EINVAL; + goto out; + } + + /* Allow exactly one reply. */ + if (knotif->state != SECCOMP_NOTIFY_SENT) { + ret = -EINVAL; + goto out; + } + + ret = size; + knotif->state = SECCOMP_NOTIFY_REPLIED; + knotif->error = resp.error; + knotif->val = resp.val; + complete(&knotif->ready); +out: + mutex_unlock(&filter->notify_lock); + return ret; +} + +static __poll_t seccomp_notify_poll(struct file *file, + struct poll_table_struct *poll_tab) +{ + struct seccomp_filter *filter = file->private_data; + __poll_t ret = 0; + struct seccomp_knotif *cur; + + ret = mutex_lock_interruptible(&filter->notify_lock); + if (ret < 0) + return ret; + + list_for_each_entry(cur, &filter->notifications, list) { + if (cur->state == SECCOMP_NOTIFY_INIT) + ret |= EPOLLIN | EPOLLRDNORM; + if (cur->state == SECCOMP_NOTIFY_SENT) + ret |= EPOLLOUT | EPOLLWRNORM; + } + + mutex_unlock(&filter->notify_lock); + + return ret; +} + +static const struct file_operations seccomp_notify_ops = { + .read = seccomp_notify_read, + .write = seccomp_notify_write, + .poll = seccomp_notify_poll, + .release = seccomp_notify_release, +}; + +static struct file *init_listener(struct task_struct *task, + struct seccomp_filter *filter) +{ + struct file *ret = ERR_PTR(-EBUSY); + struct seccomp_filter *cur; + bool have_listener = false; + int filter_nesting = 0; + + for (cur = task->seccomp.filter; cur; cur = cur->prev) { + mutex_lock_nested(&cur->notify_lock, filter_nesting); + filter_nesting++; + if (cur->has_listener) + have_listener = true; + } + + if (have_listener) + goto out; + + ret = anon_inode_getfile("seccomp notify", &seccomp_notify_ops, + filter, O_RDWR); + if (IS_ERR(ret)) + goto out; + + + /* The file has a reference to it now */ + __get_seccomp_filter(filter); + filter->has_listener = true; + +out: + for (cur = task->seccomp.filter; cur; cur = cur->prev) + mutex_unlock(&cur->notify_lock); + + return ret; +} +#else +static struct file *init_listener(struct task_struct *task, + struct seccomp_filter *filter) +{ + return ERR_PTR(-EINVAL); +} +#endif diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c index 168c66d74fc5..f439bd3fb15f 100644 --- a/tools/testing/selftests/seccomp/seccomp_bpf.c +++ b/tools/testing/selftests/seccomp/seccomp_bpf.c @@ -40,10 +40,12 @@ #include #include #include +#include #define _GNU_SOURCE #include #include +#include #include "../kselftest_harness.h" @@ -150,6 +152,24 @@ struct seccomp_metadata { }; #endif +#ifndef SECCOMP_FILTER_FLAG_GET_LISTENER +#define SECCOMP_FILTER_FLAG_GET_LISTENER 4 + +#define SECCOMP_RET_USER_NOTIF 0x7fc00000U + +struct seccomp_notif { + __u64 id; + pid_t pid; + struct seccomp_data data; +}; + +struct seccomp_notif_resp { + __u64 id; + __s32 error; + __s64 val; +}; +#endif + #ifndef seccomp int seccomp(unsigned int op, unsigned int flags, void *args) { @@ -2072,7 +2092,8 @@ TEST(seccomp_syscall_mode_lock) TEST(detect_seccomp_filter_flags) { unsigned int flags[] = { SECCOMP_FILTER_FLAG_TSYNC, - SECCOMP_FILTER_FLAG_LOG }; + SECCOMP_FILTER_FLAG_LOG, + SECCOMP_FILTER_FLAG_GET_LISTENER }; unsigned int flag, all_flags; int i; long ret; @@ -2917,6 +2938,178 @@ TEST(get_metadata) ASSERT_EQ(0, kill(pid, SIGKILL)); } +static int user_trap_syscall(int nr, unsigned int flags) +{ + struct sock_filter filter[] = { + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, + offsetof(struct seccomp_data, nr)), + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, nr, 0, 1), + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_USER_NOTIF), + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), + }; + + struct sock_fprog prog = { + .len = (unsigned short)ARRAY_SIZE(filter), + .filter = filter, + }; + + return seccomp(SECCOMP_SET_MODE_FILTER, flags, &prog); +} + +static int read_notif(int listener, struct seccomp_notif *req) +{ + int ret; + + do { + errno = 0; + ret = read(listener, req, sizeof(*req)); + } while (ret == -1 && errno == ENOENT); + return ret; +} + +static void signal_handler(int signal) +{ +} + +#define USER_NOTIF_MAGIC 116983961184613L +TEST(get_user_notification_syscall) +{ + pid_t pid; + long ret; + int status, listener; + struct seccomp_notif req = {}; + struct seccomp_notif_resp resp = {}; + struct pollfd pollfd; + + struct sock_filter filter[] = { + BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), + }; + struct sock_fprog prog = { + .len = (unsigned short)ARRAY_SIZE(filter), + .filter = filter, + }; + + pid = fork(); + ASSERT_GE(pid, 0); + + /* Check that we get -ENOSYS with no listener attached */ + if (pid == 0) { + if (user_trap_syscall(__NR_getpid, 0) < 0) + exit(1); + ret = syscall(__NR_getpid); + exit(ret >= 0 || errno != ENOSYS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* Add some no-op filters so that we (don't) trigger lockdep. */ + EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); + EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); + EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); + EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0); + + /* Check that the basic notification machinery works */ + listener = user_trap_syscall(__NR_getpid, + SECCOMP_FILTER_FLAG_GET_LISTENER); + EXPECT_GE(listener, 0); + + /* Installing a second listener in the chain should EBUSY */ + EXPECT_EQ(user_trap_syscall(__NR_getpid, + SECCOMP_FILTER_FLAG_GET_LISTENER), + -1); + EXPECT_EQ(errno, EBUSY); + + pid = fork(); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = syscall(__NR_getpid); + exit(ret != USER_NOTIF_MAGIC); + } + + EXPECT_EQ(read(listener, &req, sizeof(req)), sizeof(req)); + + pollfd.fd = listener; + pollfd.events = POLLIN | POLLOUT; + + EXPECT_GT(poll(&pollfd, 1, -1), 0); + EXPECT_EQ(pollfd.revents, POLLOUT); + + EXPECT_EQ(req.data.nr, __NR_getpid); + + resp.id = req.id; + resp.error = 0; + resp.val = USER_NOTIF_MAGIC; + + EXPECT_EQ(write(listener, &resp, sizeof(resp)), sizeof(resp)); + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* + * Check that nothing bad happens when we kill the task in the middle + * of a syscall. + */ + pid = fork(); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = syscall(__NR_getpid); + exit(ret != USER_NOTIF_MAGIC); + } + + ret = read(listener, &req, sizeof(req)); + EXPECT_EQ(ret, sizeof(req)); + + EXPECT_EQ(kill(pid, SIGKILL), 0); + EXPECT_EQ(waitpid(pid, NULL, 0), pid); + + resp.id = req.id; + ret = write(listener, &resp, sizeof(resp)); + EXPECT_EQ(ret, -1); + EXPECT_EQ(errno, EINVAL); + + /* + * Check that we get another notification about a signal in the middle + * of a syscall. + */ + pid = fork(); + ASSERT_GE(pid, 0); + + if (pid == 0) { + if (signal(SIGUSR1, signal_handler) == SIG_ERR) { + perror("signal"); + exit(1); + } + ret = syscall(__NR_getpid); + exit(ret != USER_NOTIF_MAGIC); + } + + ret = read_notif(listener, &req); + EXPECT_EQ(ret, sizeof(req)); + EXPECT_EQ(errno, 0); + + EXPECT_EQ(kill(pid, SIGUSR1), 0); + + ret = read_notif(listener, &req); + EXPECT_EQ(ret, sizeof(req)); + EXPECT_EQ(errno, 0); + + resp.id = req.id; + ret = write(listener, &resp, sizeof(resp)); + EXPECT_EQ(ret, sizeof(resp)); + EXPECT_EQ(errno, 0); + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + close(listener); +} + /* * TODO: * - add microbenchmarks -- 2.17.0