From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pl1-f171.google.com (mail-pl1-f171.google.com [209.85.214.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3E3A73C1092 for ; Wed, 15 Jul 2026 21:20:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.171 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784150449; cv=none; b=UHw6SZ/42PRX0kp88tapMsRJ7TMlcH0kss8j7f27vcfeKl55JdupEi3V8DCwfep7MP/azPX9vseKoiZPn5BYwqhbcXRfSSVL6iJpATrb3rRIVMCztM+vPEOYNEhj+2pXY3K8RmK8hpbN42LOrKSTapJlBF4tdp7auxZeijPrlps= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784150449; c=relaxed/simple; bh=hF8ekcCPZvZZcgPvWqW4UX640HMd/t7GESPuH31Ng8M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ZxukWZXW/ld3/VPYIFpy9NEwGyJzy93489DY0PHthxTCPjJr5+pg5RJJ+F7Y3MPkRRxLAevzr1uG5WAvbuFLDwW6Tz5MxEUVtuYO8a6c/PSQ430DzJ4i3czKN9We+yfKxNFKedrZ3qvdGz/KGuCtV8YhJ39wzDmYOM4oYG0WR3c= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ej4nlKLn; arc=none smtp.client-ip=209.85.214.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ej4nlKLn" Received: by mail-pl1-f171.google.com with SMTP id d9443c01a7336-2cae1a3a744so44912465ad.3 for ; Wed, 15 Jul 2026 14:20:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784150442; x=1784755242; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=y4IQFbUz6s95kRLqrSdr+Ok20IGsrl5IS8/CWWJOA9I=; b=ej4nlKLnqEsGoCdtzUZT8e5efvty0YRqFZnTHtMwOwQGq94joL8x2V4adbHTk1PQT6 U1g/14hUA4Yjm+yI0guZRn86eiU39qPT2twrl3GWWSVBq+ab8NBvIcmIloy4s+da8D61 z0vhw2og45CGhR+laf09+HLK0KQ74qRwwb1bznEmht+g9OKwAb3ZIjqtyM1e5Tk3uJqE DmDaLI68NBngnBdmKomBc8BturdZ1UEP7JOI2zJselhUbjjnVKEvTTamJOyk4opsUfyH n7Tf/OalPNqQagGrFHJNy8UrkbCNY4248jDDFKzHetHTp7z4+9MmoK6uRWj8rOceIhkR 6xAw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784150442; x=1784755242; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=y4IQFbUz6s95kRLqrSdr+Ok20IGsrl5IS8/CWWJOA9I=; b=gwupYCfozIZ9/HKft0Mi+tmNk1KhYraNw1H7wnYKciJ0FNGz7iEjb9kgcj17FMxtCz AGdyQAYmq97IEwYhzgKx1TTCD5dQMdpeQRQ5B/Q6r/d0r0wbPHaeITSsHCAJl/E2z3aK iKfcbrFmlPPUXoYUqbo2Ax4RtJbAy4wTn7elSPL5JFTlsrQSlOr2DiJ5wbYp3yB+5l1o NMINw74oPdx2LWT7tjkLCQgBCdePzFwixxXIwAYtfypg/5iDEb6jm3V4jRLDGZVZpmdp SGPFceLfOILHfECij1Wb+90nvdM9QptzkptzuSt/tUSMaKmOCFxNw/9tw608ycFPetDg JjJA== X-Forwarded-Encrypted: i=1; AHgh+Rol6tt5IngZHVmwSY0AbFmVvHe6KF45fvM5ta2POgUxpyjBcN7g8Z6zwPQfI4eyMkjFVGIAlY3agbruL0A=@vger.kernel.org X-Gm-Message-State: AOJu0Yz2bKDsbxk6YLFo0GOE3lFR7IK6aHLjUIwn1/RWY17zWcCCckhL tHFCXwTS5REU0IYzb6ia5s1xYfEZhjGouNTnbmikPQEq+lvMk49HoU/F X-Gm-Gg: AfdE7ck5QE0CZq8KVFlKioBLA7KOQ0OkFCKFxduftNIH+vutg8218xjBHL2hKZjMG8u 7vREiQ9+3++6ToS9Q+9az8dLQErUAYzFtCCrSmUiSf6ApD/pSkaxBOJCDgsibWGzR+IpXmLaOuG rI3Ldixg0K3MIfjG8AITYsoYV/Nc5Qr0aamJTeRdPrc0AgDuMXAMpUUqAqTBq+oMo+ccTzgKxZ9 jFyU4mIjwRSlOS7AAkJLqFJtGK/x6NHOPU6L0HvYXcEZYiwDQW0p+zCK9izXDKOwj/COiep8YvM u3WVdF9w8iSSmsBntjIGeICYRT9v6pvn8fMJjAs7vzjYSrOjwF63qP94HzvR4KaEFReRaHyuO+h 0+U6yd/Yd6+AyYeoXxXMBxSyCZIhAGuX7j0rjuY2E8MJ+polesGUTWCasSdaQqUy1ny/kChvJTt xdA5mO7ZINelmiwcPFa7wSKF7qldAEf/j7rJsr9ktB+t9pxLXFcG/1Waiouw== X-Received: by 2002:a05:6a20:6a0f:b0:3bf:a8d7:4f4a with SMTP id adf61e73a8af0-3c34d71984fmr10712111637.40.1784150442469; Wed, 15 Jul 2026 14:20:42 -0700 (PDT) Received: from pop-os.tail4adac7.ts.net ([129.210.115.107]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13cd403a7f7sm4126844c88.0.2026.07.15.14.20.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 15 Jul 2026 14:20:41 -0700 (PDT) From: Cong Wang To: Andy Lutomirski Cc: Kees Cook , linux-kernel@vger.kernel.org, Will Drewry , Christian Brauner , Andrew Morton , linux-mm@kvack.org, Cong Wang Subject: [PATCH v6 5/8] seccomp: add kernel-installed pinned-memfd redirect Date: Wed, 15 Jul 2026 14:20:04 -0700 Message-ID: <20260715212007.2382846-6-xiyou.wangcong@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260715212007.2382846-1-xiyou.wangcong@gmail.com> References: <20260715212007.2382846-1-xiyou.wangcong@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Cong Wang Add SECCOMP_IOCTL_NOTIF_SEND_REDIRECT, which resumes a trapped syscall (like SECCOMP_USER_NOTIF_FLAG_CONTINUE) with selected argument registers rewritten to point into a pin installed by SECCOMP_IOCTL_NOTIF_PIN_INSTALL. This closes the user-notification TOCTOU for fork+execve sandboxes: the kernel acts on an immutable, supervisor-controlled sealed mapping instead of memory a CLONE_VM peer can rewrite after the check. The feature is gated behind SECCOMP_FILTER_FLAG_REDIRECT, declared at listener creation (it requires SECCOMP_FILTER_FLAG_NEW_LISTENER) and required by both ioctls. At most one redirect-capable filter may exist in a chain (-EBUSY otherwise), so a redirect has a single, unambiguous register fixup. The supervisor supplies an args_mask, a ptr_mask and replacement values. Each pointer substitution is validated by seccomp_pin_check(): the access [args[i], args[i] + ptr_len[i]) must lie in a single VM_SEALED, read-only, MAP_SHARED VMA still backed by the named memfd. The kernel keeps no bookkeeping; after execve or exit the VMA is gone and validation returns -EFAULT. Original arg registers are saved and restored at user-mode return (via a TWA_RESUME task_work, so a restartable syscall is not turned into a livelock), preserving the caller-saved arg-register ABI. The restore is skipped after a successful execve. rt_sigreturn is refused (-EOPNOTSUPP): it restores the whole register frame and takes no arguments to substitute. The whole redirect path is now gated by SECCOMP_ARCH_REDIRECT, the opt-in arch declares once its deny list syscall numbers are complete and verified. Only x86_64 opts in so far. Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Cong Wang --- include/linux/seccomp.h | 7 +- include/uapi/linux/seccomp.h | 55 +++++- kernel/seccomp.c | 313 +++++++++++++++++++++++++++++++++++ 3 files changed, 373 insertions(+), 2 deletions(-) diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h index a91d1fc8a2b8..5d53f8fce508 100644 --- a/include/linux/seccomp.h +++ b/include/linux/seccomp.h @@ -10,7 +10,8 @@ SECCOMP_FILTER_FLAG_SPEC_ALLOW | \ SECCOMP_FILTER_FLAG_NEW_LISTENER | \ SECCOMP_FILTER_FLAG_TSYNC_ESRCH | \ - SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV) + SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV | \ + SECCOMP_FILTER_FLAG_REDIRECT) /* sizeof() the first published struct seccomp_notif_addfd */ #define SECCOMP_NOTIFY_ADDFD_SIZE_VER0 24 @@ -21,6 +22,10 @@ #define SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER1 40 /* adds @offset */ #define SECCOMP_NOTIFY_PIN_INSTALL_SIZE_LATEST SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER1 +/* sizeof() the first published struct seccomp_notif_resp_redirect */ +#define SECCOMP_NOTIFY_RESP_REDIRECT_SIZE_VER0 120 +#define SECCOMP_NOTIFY_RESP_REDIRECT_SIZE_LATEST SECCOMP_NOTIFY_RESP_REDIRECT_SIZE_VER0 + #ifdef CONFIG_SECCOMP #include diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h index d3249294788b..bb5875f72556 100644 --- a/include/uapi/linux/seccomp.h +++ b/include/uapi/linux/seccomp.h @@ -25,6 +25,12 @@ #define SECCOMP_FILTER_FLAG_TSYNC_ESRCH (1UL << 4) /* Received notifications wait in killable state (only respond to fatal signals) */ #define SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV (1UL << 5) +/* + * Declares that this listener's notifier may issue + * SECCOMP_IOCTL_NOTIF_PIN_INSTALL / SECCOMP_IOCTL_NOTIF_SEND_REDIRECT. At most + * one such filter may exist in a task's filter chain. Requires NEW_LISTENER. + */ +#define SECCOMP_FILTER_FLAG_REDIRECT (1UL << 6) /* * All BPF programs must return a 32-bit value. @@ -139,7 +145,9 @@ struct seccomp_notif_addfd { /** * struct seccomp_notif_pin_install - have the kernel install a sealed - * MAP_SHARED mapping of @memfd into the trapped task's mm at @target_addr. + * MAP_SHARED mapping of @memfd into the trapped task's mm at @target_addr, + * which SECCOMP_IOCTL_NOTIF_SEND_REDIRECT can then use as a target for + * substituted pointer arguments. * * The supervisor owns @memfd and the kernel installs the mapping without * target-side cooperation. It is read-only and VM_SEALED, so the target and @@ -168,6 +176,45 @@ struct seccomp_notif_pin_install { __u64 offset; }; +#define SECCOMP_REDIRECT_ARGS 6 + +/** + * struct seccomp_notif_resp_redirect - resume the trapped syscall with + * substituted arg-register values, optionally pointing into an installed + * pinned-memfd region. + * + * Like SECCOMP_USER_NOTIF_FLAG_CONTINUE the syscall runs, but the kernel + * first rewrites the arg registers in @args_mask. Pointer substitutions + * (@ptr_mask) are validated against the trapped task's live mapping of + * @memfd, so a target that has exited or execve()d simply fails validation. + * Original registers are restored at syscall exit, skipped after a successful + * execve whose fresh register file must not be clobbered. + * + * @id: The ID of the seccomp notification this response consumes. + * @flags: SECCOMP_REDIRECT_FLAG_*. CONTINUE must be set. + * @args_mask: Bit i set means args[i] replaces arg register i before the + * syscall runs. + * @ptr_mask: Subset of @args_mask. Bit i set means args[i] is a pointer whose + * access [args[i], args[i] + ptr_len[i]) must lie inside a single + * VM_SEALED, read-only mapping of @memfd. Scalars (in @args_mask + * but not @ptr_mask) are written verbatim. + * @memfd: Supervisor-side fd for the backing memfd. Consulted only when + * @ptr_mask is non-zero. + * @args: Replacement values for the arg registers. + * @ptr_len: For each bit set in @ptr_mask, the byte length of the access at + * args[i]; must be non-zero and args[i] + ptr_len[i] must not + * overflow. Must be 0 where @ptr_mask bit i is clear. + */ +struct seccomp_notif_resp_redirect { + __u64 id; + __u32 flags; + __u32 args_mask; + __u32 ptr_mask; + __u32 memfd; + __u64 args[SECCOMP_REDIRECT_ARGS]; + __u64 ptr_len[SECCOMP_REDIRECT_ARGS]; +}; + #define SECCOMP_IOC_MAGIC '!' #define SECCOMP_IO(nr) _IO(SECCOMP_IOC_MAGIC, nr) #define SECCOMP_IOR(nr, type) _IOR(SECCOMP_IOC_MAGIC, nr, type) @@ -188,4 +235,10 @@ struct seccomp_notif_pin_install { #define SECCOMP_IOCTL_NOTIF_PIN_INSTALL SECCOMP_IOWR(5, \ struct seccomp_notif_pin_install) +#define SECCOMP_IOCTL_NOTIF_SEND_REDIRECT SECCOMP_IOW(6, \ + struct seccomp_notif_resp_redirect) + +/* Valid flags for struct seccomp_notif_resp_redirect. */ +#define SECCOMP_REDIRECT_FLAG_CONTINUE (1UL << 0) + #endif /* _UAPI_LINUX_SECCOMP_H */ diff --git a/kernel/seccomp.c b/kernel/seccomp.c index 12135c5df9b1..17fe4c360839 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -211,6 +211,9 @@ static inline void seccomp_cache_prepare(struct seccomp_filter *sfilter) * @log: true if all actions except for SECCOMP_RET_ALLOW should be logged * @wait_killable_recv: Put notifying process in killable state once the * notification is received by the userspace listener. + * @redirect_capable: true if installed with SECCOMP_FILTER_FLAG_REDIRECT, so + * this filter's notifier may issue SEND_REDIRECT. At most + * one filter in a stack may be redirect-capable. * @prev: points to a previously installed, or inherited, filter * @prog: the BPF program to evaluate * @notif: the struct that holds all notification related information @@ -232,6 +235,7 @@ struct seccomp_filter { refcount_t users; bool log; bool wait_killable_recv; + bool redirect_capable; struct action_cache cache; struct seccomp_filter *prev; struct bpf_prog *prog; @@ -952,6 +956,13 @@ static long seccomp_attach_filter(unsigned int flags, } } + if (flags & SECCOMP_FILTER_FLAG_REDIRECT) { + for (walker = current->seccomp.filter; walker; + walker = walker->prev) + if (walker->redirect_capable) + return -EBUSY; + } + /* Set log flag, if present. */ if (flags & SECCOMP_FILTER_FLAG_LOG) filter->log = true; @@ -960,6 +971,10 @@ static long seccomp_attach_filter(unsigned int flags, if (flags & SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV) filter->wait_killable_recv = true; + /* Set redirect-capable flag, if present. */ + if (flags & SECCOMP_FILTER_FLAG_REDIRECT) + filter->redirect_capable = true; + /* * If there is an existing filter, make it the prev and don't drop its * task reference. @@ -1937,6 +1952,292 @@ static long seccomp_notify_pin_install(struct seccomp_filter *filter, return ret; } +#ifdef SECCOMP_ARCH_REDIRECT +static bool seccomp_pin_check(struct task_struct *target, + struct file *memfd_file, u64 ptr, u64 len) +{ + struct vm_area_struct *vma; + struct mm_struct *mm; + bool ok = false; + u64 end; + + if (!len) + return false; + end = ptr + len; + if (end < ptr) + return false; + + mm = get_task_mm(target); + if (!mm) + return false; + + mmap_read_lock(mm); + vma = vma_lookup(mm, ptr); + /* + * The access must lie in a single sealed, read-only, MAP_SHARED, + * memfd-backed VMA. VM_SHARED is required so the bytes the kernel reads + * are the memfd's own pages: a MAP_PRIVATE mapping would resolve to + * anonymous COW copies the target could have written before sealing it + * read-only, defeating the guarantee. + */ + if (vma && end <= vma->vm_end && (vma->vm_flags & VM_SEALED) && + (vma->vm_flags & VM_SHARED) && !(vma->vm_flags & VM_WRITE) && + vma->vm_file && file_inode(vma->vm_file) == file_inode(memfd_file)) + ok = true; + mmap_read_unlock(mm); + + mmput(mm); + return ok; +} + +struct seccomp_redirect_restore { + struct callback_head twork; + unsigned long orig_args[SECCOMP_REDIRECT_ARGS]; + u32 args_mask; /* bit i: arg i was substituted, restore it */ + u64 self_exec_id; /* snapshot to detect an intervening execve */ +}; + +/* + * If a syscall is redirected by more than one filter, one restore is + * queued per redirect, each recording the args as they stood at its own + * redirect. Correct restoration relies on task_work running LIFO: + * the first redirect captured the true original and queued first, runs + * last, so it wins. + */ +static void seccomp_redirect_restore_cb(struct callback_head *cb) +{ + struct seccomp_redirect_restore *r = + container_of(cb, struct seccomp_redirect_restore, twork); + unsigned long args[SECCOMP_REDIRECT_ARGS]; + long ret, err; + int i; + + if (READ_ONCE(current->self_exec_id) != r->self_exec_id) { + kfree(r); + return; + } + + err = syscall_get_error(current, current_pt_regs()); + ret = syscall_get_return_value(current, current_pt_regs()); + + syscall_get_arguments(current, current_pt_regs(), args); + for (i = 0; i < SECCOMP_REDIRECT_ARGS; i++) + if (r->args_mask & (1U << i)) + args[i] = r->orig_args[i]; + syscall_set_arguments(current, current_pt_regs(), args); + + syscall_set_return_value(current, current_pt_regs(), err, ret); + + kfree(r); +} + +/* + * sigreturn/rt_sigreturn restore the entire register frame from the user + * signal stack; the SEND_REDIRECT register-restore (run from task_work at + * user-mode return) would corrupt that frame, and the syscall takes no + * arguments to substitute anyway. Refuse to redirect any of them, including + * the compat variants (legacy sigreturn and rt_sigreturn are distinct + * numbers). x32 rt_sigreturn carries __X32_SYSCALL_BIT and is not matched + * here; the deprecated x32 ABI is out of scope for redirect. + */ +static bool seccomp_redirect_is_sigreturn(const struct seccomp_data *sd) +{ +#ifdef SECCOMP_ARCH_COMPAT + if (sd->arch == SECCOMP_ARCH_COMPAT) + return sd->nr == __NR_seccomp_sigreturn_32 || + sd->nr == __NR_seccomp_rt_sigreturn_32; +#endif + return sd->nr == __NR_seccomp_sigreturn || + sd->nr == __NR_seccomp_rt_sigreturn; +} + +/* + * clone/fork-family syscalls copy the trapped task's register frame into the + * new child, which gets no restore task_work of its own. A redirected task + * creation would leave the child in user space with the substituted (pinned) + * values still in its caller-saved arg registers, breaking the ABI the restore + * preserves for the parent. There is no use case for redirecting task creation, + * so refuse it. Syscalls absent on an arch resolve to -1 and never match. + */ +static bool seccomp_redirect_is_task_create(const struct seccomp_data *sd) +{ +#ifdef SECCOMP_ARCH_COMPAT + if (sd->arch == SECCOMP_ARCH_COMPAT) + return sd->nr == __NR_seccomp_clone_32 || + sd->nr == __NR_seccomp_clone3_32 || + sd->nr == __NR_seccomp_fork_32 || + sd->nr == __NR_seccomp_vfork_32; +#endif + return sd->nr == __NR_seccomp_clone || + sd->nr == __NR_seccomp_clone3 || + sd->nr == __NR_seccomp_fork || + sd->nr == __NR_seccomp_vfork; +} + +static bool seccomp_redirect_deny(const struct seccomp_data *sd) +{ + return arch_seccomp_redirect_deny(sd) || + seccomp_redirect_is_sigreturn(sd) || + seccomp_redirect_is_task_create(sd); +} + +static long seccomp_notify_send_redirect(struct seccomp_filter *filter, + struct seccomp_notif_resp_redirect __user *uresp, + unsigned int size) +{ + unsigned long args[SECCOMP_REDIRECT_ARGS]; + struct seccomp_redirect_restore *restore; + struct seccomp_notif_resp_redirect resp; + struct file *memfd_file = NULL; + struct seccomp_knotif *knotif; + struct pt_regs *target_regs; + long ret; + int i; + + BUILD_BUG_ON(sizeof(resp) < SECCOMP_NOTIFY_RESP_REDIRECT_SIZE_VER0); + BUILD_BUG_ON(sizeof(resp) != SECCOMP_NOTIFY_RESP_REDIRECT_SIZE_LATEST); + + if (!filter->redirect_capable) + return -EPERM; + + if (size < SECCOMP_NOTIFY_RESP_REDIRECT_SIZE_VER0 || size >= PAGE_SIZE) + return -EINVAL; + + ret = copy_struct_from_user(&resp, sizeof(resp), uresp, size); + if (ret) + return ret; + + if (!(resp.flags & SECCOMP_REDIRECT_FLAG_CONTINUE)) + return -EINVAL; + if (resp.flags & ~SECCOMP_REDIRECT_FLAG_CONTINUE) + return -EINVAL; + if (resp.args_mask & ~((1U << SECCOMP_REDIRECT_ARGS) - 1)) + return -EINVAL; + if (resp.ptr_mask & ~resp.args_mask) + return -EINVAL; + if (!resp.args_mask) + return -EINVAL; + for (i = 0; i < SECCOMP_REDIRECT_ARGS; i++) { + if (resp.ptr_mask & (1U << i)) { + if (!resp.ptr_len[i]) + return -EINVAL; + } else if (resp.ptr_len[i]) { + return -EINVAL; + } + } + if (resp.ptr_mask) { + memfd_file = fget(resp.memfd); + if (!memfd_file) + return -EBADF; + } + + restore = kzalloc_obj(*restore, GFP_KERNEL_ACCOUNT); + if (!restore) { + ret = -ENOMEM; + goto out_free; + } + init_task_work(&restore->twork, seccomp_redirect_restore_cb); + + ret = mutex_lock_interruptible(&filter->notify_lock); + if (ret < 0) + goto out_free; + + knotif = find_notification(filter, resp.id); + if (!knotif) { + ret = -ENOENT; + goto out_unlock_free; + } + if (knotif->state != SECCOMP_NOTIFY_SENT) { + ret = -EINPROGRESS; + goto out_unlock_free; + } + + if (seccomp_redirect_deny(knotif->data)) { + ret = -EOPNOTSUPP; + goto out_unlock_free; + } + +#ifdef SECCOMP_ARCH_COMPAT + if (knotif->data->arch == SECCOMP_ARCH_COMPAT) { + for (i = 0; i < SECCOMP_REDIRECT_ARGS; i++) + if (resp.ptr_mask & (1U << i)) + resp.args[i] = (u32)resp.args[i]; + } +#endif + + for (i = 0; i < SECCOMP_REDIRECT_ARGS; i++) { + if (!(resp.ptr_mask & (1U << i))) + continue; + if (!seccomp_pin_check(knotif->task, memfd_file, + resp.args[i], resp.ptr_len[i])) { + ret = -EFAULT; + goto out_unlock_free; + } + } + + target_regs = task_pt_regs(knotif->task); + syscall_get_arguments(knotif->task, target_regs, args); + + for (i = 0; i < SECCOMP_REDIRECT_ARGS; i++) + restore->orig_args[i] = args[i]; + restore->args_mask = resp.args_mask; + restore->self_exec_id = READ_ONCE(knotif->task->self_exec_id); + + for (i = 0; i < SECCOMP_REDIRECT_ARGS; i++) + if (resp.args_mask & (1U << i)) + args[i] = resp.args[i]; + syscall_set_arguments(knotif->task, target_regs, args); + + /* + * Use TWA_RESUME, not TWA_SIGNAL. TWA_SIGNAL sets TIF_NOTIFY_SIGNAL, + * which makes signal_pending() true for the entire redirected syscall + * An interruptible syscall would then bail out with -ERESTARTSYS before + * doing any work, restart, re-trap and get redirected again, it is a + * livelock. TWA_RESUME does not feed signal_pending(), and the restore + * still runs before signal delivery: get_signal() runs task_work_run() + * before it dequeues a signal, so the original args are back in pt_regs + * before handle_signal() builds the sigframe or the -ERESTART* path + * rewinds for restart. + */ + ret = task_work_add(knotif->task, &restore->twork, TWA_RESUME); + if (ret) { + for (i = 0; i < SECCOMP_REDIRECT_ARGS; i++) + args[i] = restore->orig_args[i]; + syscall_set_arguments(knotif->task, target_regs, args); + goto out_unlock_free; + } + + knotif->state = SECCOMP_NOTIFY_REPLIED; + knotif->error = 0; + knotif->val = 0; + knotif->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE; + if (filter->notif->flags & SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP) + complete_on_current_cpu(&knotif->ready); + else + complete(&knotif->ready); + + mutex_unlock(&filter->notify_lock); + if (memfd_file) + fput(memfd_file); + return 0; + +out_unlock_free: + mutex_unlock(&filter->notify_lock); +out_free: + if (memfd_file) + fput(memfd_file); + kfree(restore); + return ret; +} +#else /* !SECCOMP_ARCH_REDIRECT */ +static long seccomp_notify_send_redirect(struct seccomp_filter *filter, + struct seccomp_notif_resp_redirect __user *uresp, + unsigned int size) +{ + return -EOPNOTSUPP; +} +#endif /* SECCOMP_ARCH_REDIRECT */ + static long seccomp_notify_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -1964,6 +2265,9 @@ static long seccomp_notify_ioctl(struct file *file, unsigned int cmd, case EA_IOCTL(SECCOMP_IOCTL_NOTIF_PIN_INSTALL): return seccomp_notify_pin_install(filter, buf, _IOC_SIZE(cmd)); + case EA_IOCTL(SECCOMP_IOCTL_NOTIF_SEND_REDIRECT): + return seccomp_notify_send_redirect(filter, buf, + _IOC_SIZE(cmd)); default: return -EINVAL; } @@ -2103,6 +2407,15 @@ static long seccomp_set_mode_filter(unsigned int flags, ((flags & SECCOMP_FILTER_FLAG_NEW_LISTENER) == 0)) return -EINVAL; +#ifndef SECCOMP_ARCH_REDIRECT + /* Redirect denylist inputs are not verified on this arch. */ + if (flags & SECCOMP_FILTER_FLAG_REDIRECT) + return -EINVAL; +#endif + if ((flags & SECCOMP_FILTER_FLAG_REDIRECT) && + ((flags & SECCOMP_FILTER_FLAG_NEW_LISTENER) == 0)) + return -EINVAL; + /* Prepare the new filter before holding any locks. */ prepared = seccomp_prepare_user_filter(filter); if (IS_ERR(prepared)) -- 2.43.0