From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dl1-f48.google.com (mail-dl1-f48.google.com [74.125.82.48]) (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 A169C1DDC1B for ; Sat, 13 Jun 2026 00:16:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.48 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781309766; cv=none; b=TkuwZ1zU1nMJ4IahCOQA8k4pyPATkt+wKZCJZ9fpRYOt6HtPJv+/NC96BMP18H3EPU/uwVflV2D6Yq7njck3Xd+kf4CB2LWFy8tVLj/QYFz9pAT7e5ughoB8CCIDywV0Mq8fHxIw+A/tvUV+R+Dr453VINjZmVo1K27P0NfIWD4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781309766; c=relaxed/simple; bh=RnPUED+HPog3tglXbotnFhlquyETADZB3UdWdDw5JEA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=eY8EX2loLCBzEc1AkxweshK43jht/iDHaGjp1aJHrnHRXRj89d1uMqTMil0PRswMCHPE0/MhqD4dwFbXLRhbHfl89DNboRIRDnQBqeSxHV0R7w3vYbY5+fMAm93kEdK5UNt/3+j8qHoCM7+r8f16KRNMu25qC38AShtKTY4afxU= 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=Df80EFzW; arc=none smtp.client-ip=74.125.82.48 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="Df80EFzW" Received: by mail-dl1-f48.google.com with SMTP id a92af1059eb24-1384ebe7a10so306059c88.1 for ; Fri, 12 Jun 2026 17:16:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781309763; x=1781914563; 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; bh=w3kxXBLiZXfcmjiWsUXxHdjp3gU6YF1/Dy1ZeSTIMqU=; b=Df80EFzW5mL/RUg0DU/3R5hc083usK/4XXRavzvpq/bjJOYKPiTwGr/5VRgbU4dSO7 9XGN1KxX8PLfc1Q7mhWDND2S55q6R1G737neRafz5RDEUOESrgTxGWT87lA9zXqCCDEk E2TStJyegOny8Wp1V2joc2gcoPB7Cu27v78E2Iq2OmGF2P9UuPIk+8Xy4IvR1jTUgVPj 0fXd07xDvS5W5hlGIhloBM6koZnZIwl3izXjbeY0oP3yQVGcM6N5Kbie4ZGvx97RFNhp kNs3QKeFzNuNVSnmFNOwHZVl7wz4l+bdmdYRDOGj5apjrU2PlmwQp/J8a3Ehx9PU7yqs B8Yw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781309763; x=1781914563; 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; bh=w3kxXBLiZXfcmjiWsUXxHdjp3gU6YF1/Dy1ZeSTIMqU=; b=L0yfJq2WpjY6AeckLmpV1FB+LJ4KuMIuRt6JI2YPoe/S1Ll7CdqafepjRiu6wux38B O8/YvhUboHYdmnMJTyCZ8RQ7YMwdB58QK23POExg+IHTs3oORHoOUJ3LYAoOlmGszKzH k/RSo2lsBUukpPxeTGaOpL7tOqjON65Icp5VDh3eoT3REBBG9A9GEc301UrrfqVT8xFY QfX/agefbJGd4baAEQKgwARU1/GIA+Xn5W1dUktVln6ayKjjHxqufR1Ob2rVhkt1WB5L I15fBXI8/fYCM1C3aaRO8tv/PMHkKG5rEwn/DLoix6mebvTEy/7R2JDG2tI09ZWCmVht quCA== X-Forwarded-Encrypted: i=1; AFNElJ9cxFEdKcY49SjIdjHQFqHjkANNQsrOUpmos0/oN8jrm1G7arvgkQBUUZI0tY46zp6c3CUrwalxdrLXwn8=@vger.kernel.org X-Gm-Message-State: AOJu0YydxAJovlq4Yz88jgUSZ8XP9S37i0PVCdyWtBkSXro4LSU4AVjj lzXRreh4Zp/2iDTZAVFIknD4j8S/QXCuLeB/X/GOSyjMUuc8cVZGdLmt X-Gm-Gg: Acq92OHkYgcc31j06zAtza3v7MNFGLn3rHUew1KIHV97Jc6DDkbzHjHDtGIMuq+wl8B x7XDI3VQju5Gh1YfxCrY3dqr4dKxKwEjNwokcfphwLcUbPokQToNfX7LMwai/zReKJyNSgzK/Hf 0ZIpdLplYz5OWDrQ0K3PRIfm0KrFwfEpoZ2f3ZHnN87ZaaJoOY+cZt5Q27Q5OzMjF8G9HsrCJoj xfh2W0JIiXbBWrpejYNiKiYeGzwBVzsWXBEG9zr6WVaQpc/T8RENlpdjYOuCl8Fyq4lRfuwrFvz UxPkaNE80Epvyo83NW2PU44f4n99R4U06jv57SdKJn5FgAxAY45QAXrvLcjY8crlXArS0SOuA0b 2SbFNd9d1mZ+S2bkdwdAtwMpca3gQXz+Ee2oCaFC5Q5nQhJ7O+ezQYIRIdZJ5ybW4TwcLcsSqBn s3WQv2WwO8MHG7jdpUJqZ7KH79Cv0aEUaTxS15eDVtv9hP0vZfaVBzSaGGYpysZZKKqeRAK/3vA CQX X-Received: by 2002:a05:693c:37c7:b0:304:705f:e4e8 with SMTP id 5a478bee46e88-30820253c72mr2619717eec.32.1781309762394; Fri, 12 Jun 2026 17:16:02 -0700 (PDT) Received: from pop-os.scu.edu ([129.210.115.107]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3081e4898c0sm5710567eec.3.2026.06.12.17.16.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 12 Jun 2026 17:16:01 -0700 (PDT) From: Cong Wang To: Andy Lutomirski Cc: Kees Cook , linux-kernel@vger.kernel.org, Will Drewry , Christian Brauner Subject: [RFC PATCH v3 2/3] seccomp: add kernel-installed pinned-memfd redirect Date: Fri, 12 Jun 2026 17:15:32 -0700 Message-ID: <20260613001533.314739-3-xiyou.wangcong@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260613001533.314739-1-xiyou.wangcong@gmail.com> References: <20260613001533.314739-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-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce non-cooperative pinned-memfd: the kernel installs a sealed PROT_READ MAP_SHARED mapping of the supervisor's memfd directly into the trapped task's mm, without any target-side cooperation. This is what makes the feature work for fork+execve sandbox wrappers (Sandlock CLI, Firejail, Bubblewrap-style) where the target has no trusted post-exec window to install its own mappings. Two new ioctls are introduced: SECCOMP_IOCTL_NOTIF_PIN_INSTALL Supervisor names an active notification id, a memfd it owns, and a target address+size. Kernel grabs the trapped task's mm via get_task_mm(), calls vm_mmap_pgoff_to_mm() with MAP_FIXED | MAP_SHARED, PROT_READ, and VM_SEALED in extra_vm_flags. On success the VMA is installed in the target's mm, immediately sealed against munmap/mremap/ mprotect/MAP_FIXED-stomp from the target itself and any CLONE_VM peer. The range is recorded on the listener filter for SEND_REDIRECT validation. SECCOMP_IOCTL_NOTIF_SEND_REDIRECT Resumes the trapped syscall (like FLAG_CONTINUE) with arg-register substitution. The supervisor supplies an args_mask (which arg registers to replace), a ptr_mask (which of those are pointers, validated to fall inside an installed pin) and replacement values. The kernel saves the trapped task's original arg registers into a small heap record, writes substituted values via syscall_set_arguments(), and queues a task_work callback that fires at user-mode return after the syscall completes to restore the original registers. This preserves the caller-saved arg-register ABI invariant for callers that expected register contents to survive across the syscall (compilers under LTO, inline-asm syscall wrappers, anything that doesn't strictly follow psABI). The kernel-side capability is identical to what the trapped task would have done with its own (peer-uncorrupted) arguments. No per-syscall kernel-mode entrypoints are added; the substituted syscall runs in the trapped task's context against sealed pages whose contents are supervisor-controlled. Pin lifetime: the VMA lives in the target's mm and follows that mm's lifetime (the seal protects it from unmap by the target). The bookkeeping reference to the backing memfd is released when the listener filter is freed; if a target execve's, the new mm is fresh and the supervisor's bookkeeping range becomes stale (no VMA exists at that address). A future revision could explicitly invalidate stale ranges on execve; current code simply fails subsequent SEND_REDIRECTs against the stale range when validation finds no matching VMA. Assisted-by: Claude:claude-opus-4.8 Signed-off-by: Cong Wang --- include/linux/seccomp.h | 8 + include/uapi/linux/seccomp.h | 99 ++++++++++ kernel/seccomp.c | 366 +++++++++++++++++++++++++++++++++++ 3 files changed, 473 insertions(+) diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h index 9b959972bf4a..f4f733601b8c 100644 --- a/include/linux/seccomp.h +++ b/include/linux/seccomp.h @@ -16,6 +16,14 @@ #define SECCOMP_NOTIFY_ADDFD_SIZE_VER0 24 #define SECCOMP_NOTIFY_ADDFD_SIZE_LATEST SECCOMP_NOTIFY_ADDFD_SIZE_VER0 +/* sizeof() the first published struct seccomp_notif_pin_install */ +#define SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER0 32 +#define SECCOMP_NOTIFY_PIN_INSTALL_SIZE_LATEST SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER0 + +/* 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 dbfc9b37fcae..4d769b634f0b 100644 --- a/include/uapi/linux/seccomp.h +++ b/include/uapi/linux/seccomp.h @@ -137,6 +137,37 @@ struct seccomp_notif_addfd { __u32 newfd_flags; }; +/** + * struct seccomp_notif_pin_install - have the kernel install a sealed + * MAP_SHARED PROT_READ mapping of @memfd into the trapped task's mm + * at @target_addr, and record the range as a valid redirect target + * for SECCOMP_IOCTL_NOTIF_SEND_REDIRECT. + * + * The supervisor owns @memfd. The kernel installs the mapping into + * the trapped task's address space without target-side cooperation + * (the target need not mmap or mseal anything itself). The mapping + * is marked VM_SEALED at install time, so the target and any + * CLONE_VM peer cannot munmap, mremap, mprotect, or MAP_FIXED-stomp + * it. The supervisor retains write access via its own mapping of + * the same memfd in its own mm. + * + * @id: The ID of an active seccomp notification on this listener, + * identifying the trapped task whose mm receives the pin. + * @flags: Reserved, must be 0. + * @memfd: Supervisor-side fd for the backing memfd. + * @target_addr: Address in the trapped task's mm to install at. + * Must be page-aligned. MAP_FIXED semantics — no other + * mapping may exist in [@target_addr, @target_addr + @size). + * @size: Size of the pin in bytes. Must be page-aligned. + */ +struct seccomp_notif_pin_install { + __u64 id; + __u32 flags; + __u32 memfd; + __u64 target_addr; + __u64 size; +}; + #define SECCOMP_IOC_MAGIC '!' #define SECCOMP_IO(nr) _IO(SECCOMP_IOC_MAGIC, nr) #define SECCOMP_IOR(nr, type) _IOR(SECCOMP_IOC_MAGIC, nr, type) @@ -154,4 +185,72 @@ struct seccomp_notif_addfd { #define SECCOMP_IOCTL_NOTIF_SET_FLAGS SECCOMP_IOW(4, __u64) +/* Valid flags for struct seccomp_notif_resp_redirect. */ +#define SECCOMP_REDIRECT_FLAG_CONTINUE (1UL << 0) + +/* + * Number of syscall argument registers a redirect response may + * substitute (matches struct seccomp_data::args[]). + */ +#define SECCOMP_REDIRECT_ARGS 6 + +/** + * struct seccomp_notif_resp_redirect - resume the trapped syscall with + * substituted arg-register values pointing into a previously installed + * pinned-memfd region. + * + * Like SECCOMP_USER_NOTIF_FLAG_CONTINUE the syscall actually runs, but + * the kernel first rewrites the arg registers per @args_mask. Each + * pointer substitution (@ptr_mask) is validated so the whole access + * [args[i], args[i] + ptr_len[i]) lies inside one pin still mapped in + * the trapped task's current mm. Original registers are saved and + * restored at syscall exit for ABI compliance - except after a + * successful execve, whose new register file is left untouched (the + * redirect still applies, as execve copies the pathname from the + * immutable pin before the old mm is gone, closing that TOCTOU too). + * + * @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 the trapped task's + * corresponding arg register before the syscall runs. + * @ptr_mask: Subset of @args_mask. Bit i set means args[i] is a + * pointer and the access [args[i], args[i] + ptr_len[i]) + * is validated to lie entirely inside a single installed + * pin. Scalar replacements (in args_mask but not ptr_mask) + * are written verbatim. + * @_pad: Reserved, must be 0. + * @args: Replacement values for the arg registers. + * @ptr_len: For each bit set in @ptr_mask, ptr_len[i] is the byte + * length of the access starting at args[i]; it must be + * non-zero and args[i] + ptr_len[i] must not overflow. + * For every i whose bit is clear in @ptr_mask, ptr_len[i] + * must be 0. + */ +struct seccomp_notif_resp_redirect { + __u64 id; + __u32 flags; + __u32 args_mask; + __u32 ptr_mask; + __u32 _pad; + __u64 args[SECCOMP_REDIRECT_ARGS]; + __u64 ptr_len[SECCOMP_REDIRECT_ARGS]; +}; + +/* + * Install a sealed memfd-backed pin in the trapped task's mm without + * target-side cooperation. The supervisor owns the backing memfd; + * the kernel installs the mapping and marks it VM_SEALED. + */ +#define SECCOMP_IOCTL_NOTIF_PIN_INSTALL SECCOMP_IOW(5, \ + struct seccomp_notif_pin_install) + +/* + * Resume the trapped syscall with substituted arg-register values + * pointing into an installed pin. The kernel saves and restores the + * original registers at syscall exit so the caller observes ABI- + * correct register preservation. + */ +#define SECCOMP_IOCTL_NOTIF_SEND_REDIRECT SECCOMP_IOW(6, \ + struct seccomp_notif_resp_redirect) + #endif /* _UAPI_LINUX_SECCOMP_H */ diff --git a/kernel/seccomp.c b/kernel/seccomp.c index 066909393c38..51b929a6fa6a 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -43,6 +43,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include /* * When SECCOMP_IOCTL_NOTIF_ID_VALID was first introduced, it had the @@ -232,6 +238,27 @@ struct seccomp_filter { struct notification *notif; struct mutex notify_lock; wait_queue_head_t wqh; + struct list_head pins; +}; + +/* + * A sealed MAP_SHARED PROT_READ mapping of @memfd_file installed at + * @target_addr in @mm by SECCOMP_IOCTL_NOTIF_PIN_INSTALL, used as a + * redirect target for SECCOMP_IOCTL_NOTIF_SEND_REDIRECT. + * + * @mm is held via mmgrab() (mm_count, not mm_users) purely for a stable + * identity compare; it does not keep the address space alive, so execve + * tears the mapping down and switches to a fresh mm. A redirect is only + * honoured while the trapped task's current mm equals @mm: since + * VM_SEALED makes the mapping immutable for the life of that mm, an + * identity match proves the range is still the read-only memfd backing. + */ +struct seccomp_pin_range { + struct list_head list; + unsigned long target_addr; + size_t size; + struct file *memfd_file; + struct mm_struct *mm; }; /* Limit any path through the tree to 256KB worth of instructions. */ @@ -521,9 +548,44 @@ static inline pid_t seccomp_can_sync_threads(void) return 0; } +static void seccomp_pin_range_free(struct seccomp_pin_range *pin) +{ + fput(pin->memfd_file); + mmdrop(pin->mm); + kfree(pin); +} + +static void seccomp_pin_evict(struct seccomp_filter *filter, + unsigned long target_addr) +{ + struct seccomp_pin_range *old, *tmp; + + lockdep_assert_held(&filter->notify_lock); + list_for_each_entry_safe(old, tmp, &filter->pins, list) { + if (old->target_addr == target_addr) { + list_del(&old->list); + seccomp_pin_range_free(old); + } + } +} + static inline void seccomp_filter_free(struct seccomp_filter *filter) { + struct seccomp_pin_range *pin, *tmp; + if (filter) { + /* + * No other reference to @filter exists at this point. The + * pinned-memfd VMAs themselves live in the trapped task's mm + * and follow that mm's lifetime (the seal protects them from + * unmap by the target); here we only need to drop our + * bookkeeping references: the backing memfd file and the + * mmgrab() identity reference on the install-time mm. + */ + list_for_each_entry_safe(pin, tmp, &filter->pins, list) { + list_del(&pin->list); + seccomp_pin_range_free(pin); + } bpf_prog_destroy(filter->prog); kfree(filter); } @@ -708,6 +770,7 @@ static struct seccomp_filter *seccomp_prepare_filter(struct sock_fprog *fprog) refcount_set(&sfilter->refs, 1); refcount_set(&sfilter->users, 1); init_waitqueue_head(&sfilter->wqh); + INIT_LIST_HEAD(&sfilter->pins); return sfilter; } @@ -1823,6 +1886,303 @@ static long seccomp_notify_addfd(struct seccomp_filter *filter, return ret; } +/* + * Install a sealed PROT_READ MAP_SHARED mapping of @memfd_file at + * @target_addr in @target's mm, then record the range on @filter for + * later SEND_REDIRECT pointer validation. Steals one reference on + * @memfd_file on success (held until filter free); on failure, the + * caller is responsible for releasing it. + */ +static long seccomp_install_pin(struct seccomp_filter *filter, + struct task_struct *target, + struct file *memfd_file, + unsigned long target_addr, size_t size) +{ + struct seccomp_pin_range *range; + struct mm_struct *mm; + unsigned long ret; + + mm = get_task_mm(target); + if (!mm) + return -ESRCH; + + range = kmalloc_obj(*range, GFP_KERNEL_ACCOUNT); + if (!range) { + mmput(mm); + return -ENOMEM; + } + + /* + * Install a read-only, sealed, MAP_FIXED_NOREPLACE mapping: an + * existing mapping at @target_addr yields -EEXIST rather than being + * silently clobbered. + */ + ret = vm_mmap_seal_remote(mm, memfd_file, target_addr, size, 0); + if (IS_ERR_VALUE(ret)) { + mmput(mm); + kfree(range); + return (long)ret; + } + if (ret != target_addr) { + mmput(mm); + kfree(range); + return -ENOMEM; + } + + mmgrab(mm); + mmput(mm); /* For get_task_mm() */ + + range->target_addr = target_addr; + range->size = size; + range->memfd_file = memfd_file; + range->mm = mm; + + mutex_lock(&filter->notify_lock); + seccomp_pin_evict(filter, target_addr); + list_add(&range->list, &filter->pins); + mutex_unlock(&filter->notify_lock); + return 0; +} + +static long seccomp_notify_pin_install(struct seccomp_filter *filter, + struct seccomp_notif_pin_install __user *upin, + unsigned int size) +{ + struct seccomp_notif_pin_install pin; + struct seccomp_knotif *knotif; + struct task_struct *target; + struct file *memfd_file; + long ret; + + BUILD_BUG_ON(sizeof(pin) < SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER0); + BUILD_BUG_ON(sizeof(pin) != SECCOMP_NOTIFY_PIN_INSTALL_SIZE_LATEST); + + if (size < SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER0 || size >= PAGE_SIZE) + return -EINVAL; + + ret = copy_struct_from_user(&pin, sizeof(pin), upin, size); + if (ret) + return ret; + + if (pin.flags) + return -EINVAL; + if (!pin.size || !IS_ALIGNED(pin.target_addr, PAGE_SIZE) || + !IS_ALIGNED(pin.size, PAGE_SIZE)) + return -EINVAL; + if (pin.target_addr + pin.size < pin.target_addr) + return -EINVAL; + + memfd_file = fget(pin.memfd); + if (!memfd_file) + return -EBADF; + + ret = mutex_lock_interruptible(&filter->notify_lock); + if (ret < 0) + goto out_fput; + + knotif = find_notification(filter, pin.id); + if (!knotif) { + ret = -ENOENT; + goto out_unlock; + } + if (knotif->state != SECCOMP_NOTIFY_SENT) { + ret = -EINPROGRESS; + goto out_unlock; + } + + target = knotif->task; + get_task_struct(target); + mutex_unlock(&filter->notify_lock); + + ret = seccomp_install_pin(filter, target, memfd_file, + pin.target_addr, pin.size); + put_task_struct(target); + + if (ret < 0) + goto out_fput; + return 0; + +out_unlock: + mutex_unlock(&filter->notify_lock); +out_fput: + fput(memfd_file); + return ret; +} + +static bool seccomp_pin_check(struct seccomp_filter *filter, + struct mm_struct *target_mm, u64 ptr, u64 len) +{ + struct seccomp_pin_range *pin; + u64 end; + + lockdep_assert_held(&filter->notify_lock); + + if (!len) + return false; + end = ptr + len; + if (end < ptr) + return false; + + list_for_each_entry(pin, &filter->pins, list) { + if (pin->mm != target_mm) + continue; + if (ptr >= pin->target_addr && + end <= pin->target_addr + pin->size) + return true; + } + return false; +} + +struct seccomp_redirect_restore { + struct callback_head twork; + unsigned long orig_args[SECCOMP_REDIRECT_ARGS]; + u32 args_mask; + u64 self_exec_id; /* snapshot to detect an intervening execve */ +}; + +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]; + int i; + + if (READ_ONCE(current->self_exec_id) != r->self_exec_id) { + kfree(r); + return; + } + + 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); + kfree(r); +} + +static long seccomp_notify_send_redirect(struct seccomp_filter *filter, + struct seccomp_notif_resp_redirect __user *uresp, + unsigned int size) +{ + struct seccomp_notif_resp_redirect resp; + struct seccomp_knotif *knotif; + struct seccomp_redirect_restore *restore; + struct pt_regs *target_regs; + unsigned long args[SECCOMP_REDIRECT_ARGS]; + 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 (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._pad) + 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; + } + } + + restore = kzalloc_obj(*restore, GFP_KERNEL_ACCOUNT); + if (!restore) + return -ENOMEM; + init_task_work(&restore->twork, seccomp_redirect_restore_cb); + + ret = mutex_lock_interruptible(&filter->notify_lock); + if (ret < 0) { + kfree(restore); + return ret; + } + + 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; + } + + for (i = 0; i < SECCOMP_REDIRECT_ARGS; i++) { + if (!(resp.ptr_mask & (1U << i))) + continue; + if (!seccomp_pin_check(filter, knotif->task->mm, + resp.args[i], resp.ptr_len[i])) { + ret = -EFAULT; + goto out_unlock_free; + } + } + + /* + * Save original pt_regs args (target is parked in + * seccomp_do_user_notification, so its pt_regs is stable) and + * write substituted values. The trapped task's task_work fires + * at user-mode return, restoring originals for ABI compliance. + */ + 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); + + 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; + } + + /* + * Mark REPLIED with FLAG_CONTINUE so the wait-loop exit path + * runs the syscall normally. + */ + 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); + return 0; + +out_unlock_free: + mutex_unlock(&filter->notify_lock); + kfree(restore); + return ret; +} + static long seccomp_notify_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -1847,6 +2207,12 @@ static long seccomp_notify_ioctl(struct file *file, unsigned int cmd, switch (EA_IOCTL(cmd)) { case EA_IOCTL(SECCOMP_IOCTL_NOTIF_ADDFD): return seccomp_notify_addfd(filter, buf, _IOC_SIZE(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; } -- 2.43.0