From: Li Chen <me@linux.beauty>
To: Christian Brauner <brauner@kernel.org>
Cc: "Kees Cook" <kees@kernel.org>,
"Gabriel Krisman Bertazi" <krisman@kernel.org>,
"Josh Triplett" <josh@joshtriplett.org>,
"Mateusz Guzik" <mjguzik@gmail.com>,
"Andy Lutomirski" <luto@kernel.org>,
"John Ericson" <mail@johnericson.me>,
"Jonathan Corbet" <corbet@lwn.net>,
"Shuah Khan" <shuah@kernel.org>, "Arnd Bergmann" <arnd@arndb.de>,
"Oleg Nesterov" <oleg@redhat.com>,
"Andrew Morton" <akpm@linux-foundation.org>,
"Paul Moore" <paul@paul-moore.com>,
"Eric Paris" <eparis@redhat.com>,
"Mickaël Salaün" <mic@digikod.net>,
"Günther Noack" <gnoack@google.com>,
"Alexander Viro" <viro@zeniv.linux.org.uk>,
"Jan Kara" <jack@suse.cz>,
linux-api@vger.kernel.org, linux-fsdevel@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-doc@vger.kernel.org, audit@vger.kernel.org,
linux-security-module@vger.kernel.org,
linux-arch@vger.kernel.org, linux-mm@kvack.org,
"Li Chen" <me@linux.beauty>
Subject: [RFC PATCH 15/24] fork: keep embryonic tasks hidden until exec completes
Date: Thu, 16 Jul 2026 22:31:41 +0800 [thread overview]
Message-ID: <92e41fa333983c2f5c10251e64f2214c69aed0e1.1784204592.git.me@linux.beauty> (raw)
In-Reply-To: <cover.1784204592.git.me@linux.beauty>
A spawn task does not gain a valid userspace register frame when
exec_mmap() installs its private mm. A binary loader can still sleep or
fail before START_THREAD(). This can expose setup registers to ptrace or a
late failed-exec core dump. During earlier setup the task also shares the
source mm, which procfs could expose through the child's PID.
Hide procfs PID lookup, iteration, and cached dentry revalidation from
other tasks, deny coredumps, and report coredump skip state through pidfs.
Allow the embryonic task to use its own proc entries because executable and
interpreter lookup can legitimately use /proc/self. This does not expose
source state to another task. Sample the flag before reading published
credentials; acquire ordering covers the state installed by exec.
Release-publish the transition in the exec core after the final binary
handler succeeds and before audit, tracepoint, ptrace, and connector exec
events. Every successful exec observer then sees a normal task, while a
failed exec leaves the task hidden.
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Li Chen <me@linux.beauty>
---
fs/coredump.c | 4 +++-
fs/exec.c | 2 ++
fs/pidfd_spawn.c | 2 --
fs/pidfs.c | 7 ++++++-
fs/proc/base.c | 11 +++++++++--
fs/proc/internal.h | 18 +++++++++++++++++-
kernel/ptrace.c | 2 +-
7 files changed, 38 insertions(+), 8 deletions(-)
diff --git a/fs/coredump.c b/fs/coredump.c
index ac3cd74808c64..4b2f0b5c7d606 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -1166,7 +1166,9 @@ void vfs_coredump(const kernel_siginfo_t *siginfo)
.limit = rlimit(RLIMIT_CORE),
/* Snapshot MMF_DUMP_FILTER_* (unlocked) and dumpable for the dump. */
.mm_flags = __mm_flags_get_word(mm),
- .dumpable = task_exec_state_get_dumpable(current),
+ .dumpable = task_is_embryonic_exec(current) ?
+ TASK_DUMPABLE_OFF :
+ task_exec_state_get_dumpable(current),
.vma_meta = NULL,
.cpu = raw_smp_processor_id(),
};
diff --git a/fs/exec.c b/fs/exec.c
index 1a256ba26425e..292c30e80aecb 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1734,6 +1734,8 @@ static int exec_binprm(struct linux_binprm *bprm)
fput(exec);
}
+ if (task_is_embryonic_exec(current))
+ task_clear_embryonic_exec(current);
audit_bprm(bprm);
trace_sched_process_exec(current, old_pid, bprm);
ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
diff --git a/fs/pidfd_spawn.c b/fs/pidfd_spawn.c
index c0e95c2ddbe6e..41d5cbb49a0f7 100644
--- a/fs/pidfd_spawn.c
+++ b/fs/pidfd_spawn.c
@@ -400,8 +400,6 @@ static void pidfd_spawn_child(struct callback_head *work)
if (!ret)
ret = do_execveat_common(AT_FDCWD, filename,
state->argv, state->envp, 0);
- if (!ret)
- task_clear_embryonic_exec(current);
pidfd_spawn_finish_child(state, filename, ret);
if (ret) {
diff --git a/fs/pidfs.c b/fs/pidfs.c
index d62235e01b351..7a473c049dcd4 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -477,6 +477,7 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
struct user_namespace *user_ns;
struct pidfs_attr *attr;
const struct cred *c;
+ bool embryonic;
__u64 mask;
BUILD_BUG_ON(sizeof(struct pidfd_info) != PIDFD_INFO_SIZE_VER3);
@@ -533,12 +534,16 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
goto copy_out;
}
+ embryonic = task_is_embryonic_exec(task);
c = get_task_cred(task);
if (!c)
return -ESRCH;
if ((mask & PIDFD_INFO_COREDUMP) && !kinfo.coredump_mask) {
- kinfo.coredump_mask = pidfs_coredump_mask(task_exec_state_get_dumpable(task));
+ enum task_dumpable dumpable = embryonic ?
+ TASK_DUMPABLE_OFF : task_exec_state_get_dumpable(task);
+
+ kinfo.coredump_mask = pidfs_coredump_mask(dumpable);
kinfo.mask |= PIDFD_INFO_COREDUMP;
/* No coredump actually took place, so no coredump signal. */
}
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 6a39de424f62a..bd059ddcce863 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -2053,7 +2053,7 @@ static int pid_revalidate(struct inode *dir, const struct qstr *name,
goto out;
task = pid_task(proc_pid(inode), PIDTYPE_PID);
- if (task) {
+ if (task && proc_task_visible(task)) {
pid_update_inode(task, inode);
ret = 1;
}
@@ -3492,6 +3492,8 @@ struct dentry *proc_pid_lookup(struct dentry *dentry, unsigned int flags)
rcu_read_unlock();
if (!task)
goto out;
+ if (!proc_task_visible(task))
+ goto out_put_task;
/* Limit procfs to only ptraceable tasks */
if (fs_info->hide_pid == HIDEPID_NOT_PTRACEABLE) {
@@ -3539,11 +3541,12 @@ static bool next_tgid(struct tgid_iter *it)
if (pid) {
it->tgid = pid_nr_ns(pid, it->pid_ns);
it->task = pid_task(pid, PIDTYPE_TGID);
- if (it->task) {
+ if (it->task && proc_task_visible(it->task)) {
get_task_struct(it->task);
rcu_read_unlock();
return true;
}
+ it->task = NULL;
} else {
rcu_read_unlock();
return false;
@@ -3807,6 +3810,8 @@ static struct dentry *proc_task_lookup(struct inode *dir, struct dentry * dentry
rcu_read_unlock();
if (!task)
goto out;
+ if (!proc_task_visible(task))
+ goto out_drop_task;
if (!same_thread_group(leader, task))
goto out_drop_task;
@@ -3844,6 +3849,8 @@ static struct task_struct *first_tid(struct pid *pid, int tid, loff_t f_pos,
task = pid_task(pid, PIDTYPE_PID);
if (!task)
goto fail;
+ if (!proc_task_visible(task))
+ goto fail;
/* Attempt to start with the tid of a thread */
if (tid && nr) {
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index b232e1098117b..81ad44502272e 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -147,9 +147,25 @@ static inline struct pid *proc_pid(const struct inode *inode)
return PROC_I(inode)->pid;
}
+static inline bool proc_task_visible(struct task_struct *task)
+{
+ /*
+ * An embryonic task may need its own proc entries during exec setup,
+ * but it is not a valid userspace process image for other tasks yet.
+ */
+ return task == current || !task_is_embryonic_exec(task);
+}
+
static inline struct task_struct *get_proc_task(const struct inode *inode)
{
- return get_pid_task(proc_pid(inode), PIDTYPE_PID);
+ struct task_struct *task;
+
+ task = get_pid_task(proc_pid(inode), PIDTYPE_PID);
+ if (task && !proc_task_visible(task)) {
+ put_task_struct(task);
+ return NULL;
+ }
+ return task;
}
void task_dump_owner(struct task_struct *task, umode_t mode,
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index 36eb9b154a397..59e11dae803c8 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -314,7 +314,7 @@ static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
WARN(1, "denying ptrace access check without PTRACE_MODE_*CREDS\n");
return -EPERM;
}
- if (task_is_embryonic_exec(task))
+ if (task_is_embryonic_exec(task) && task != current)
return -EPERM;
/* May we inspect the given task?
--
2.52.0
next prev parent reply other threads:[~2026-07-16 14:41 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-16 14:31 [RFC PATCH 00/24] pidfd: add a minimal process spawn builder Li Chen
2026-07-16 14:31 ` [RFC PATCH 01/24] pidfd: add spawn builder uapi Li Chen
2026-07-16 14:31 ` [RFC PATCH 02/24] libfs: allow custom validation of stashed inode data Li Chen
2026-07-16 14:31 ` [RFC PATCH 03/24] pidfs: add taskless future pidfd inodes Li Chen
2026-07-16 14:31 ` [RFC PATCH 04/24] pidfd: create taskless spawn builders Li Chen
2026-07-16 14:31 ` [RFC PATCH 05/24] pidfd: add spawn builder path configuration Li Chen
2026-07-16 14:31 ` [RFC PATCH 06/24] exec: expose execveat internals to process builders Li Chen
2026-07-16 14:31 ` [RFC PATCH 07/24] fork: expose vfork completion helper Li Chen
2026-07-16 14:31 ` [RFC PATCH 08/24] pidfs: attach pids to future pidfd files Li Chen
2026-07-16 14:31 ` [RFC PATCH 09/24] fork: let process builders supply preallocated pids Li Chen
2026-07-16 14:31 ` [RFC PATCH 10/24] pidfs: publish future pidfd files Li Chen
2026-07-16 14:31 ` [RFC PATCH 11/24] pidfd: add spawn builder state tracking Li Chen
2026-07-16 14:31 ` [RFC PATCH 12/24] fork: let kernel callers create embryonic tasks Li Chen
2026-07-16 15:57 ` Andy Lutomirski
2026-07-16 14:31 ` [RFC PATCH 13/24] fork: let new tasks start with task work Li Chen
2026-07-16 14:31 ` [RFC PATCH 14/24] pidfd: create and execute spawn builder tasks Li Chen
2026-07-16 14:31 ` Li Chen [this message]
2026-07-16 14:31 ` [RFC PATCH 16/24] audit: add pidfd spawn child contexts Li Chen
2026-07-16 14:31 ` [RFC PATCH 17/24] pidfd: audit child spawn execution Li Chen
2026-07-16 14:31 ` [RFC PATCH 18/24] pidfd: make spawn builder execution signal-safe Li Chen
2026-07-16 14:31 ` [RFC PATCH 19/24] file: expose spawn file-action helpers Li Chen
2026-07-16 14:31 ` [RFC PATCH 20/24] pidfd: add initial spawn file actions Li Chen
2026-07-16 14:31 ` [RFC PATCH 21/24] pidfd: consume spawn builders on the first run attempt Li Chen
2026-07-16 14:31 ` [RFC PATCH 22/24] pidfd: expose spawn builder system calls Li Chen
2026-07-16 14:31 ` [RFC PATCH 23/24] selftests/pidfd: cover pidfd spawn builders Li Chen
2026-07-16 14:31 ` [RFC PATCH 24/24] Documentation: describe " Li Chen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=92e41fa333983c2f5c10251e64f2214c69aed0e1.1784204592.git.me@linux.beauty \
--to=me@linux.beauty \
--cc=akpm@linux-foundation.org \
--cc=arnd@arndb.de \
--cc=audit@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=corbet@lwn.net \
--cc=eparis@redhat.com \
--cc=gnoack@google.com \
--cc=jack@suse.cz \
--cc=josh@joshtriplett.org \
--cc=kees@kernel.org \
--cc=krisman@kernel.org \
--cc=linux-api@vger.kernel.org \
--cc=linux-arch@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-security-module@vger.kernel.org \
--cc=luto@kernel.org \
--cc=mail@johnericson.me \
--cc=mic@digikod.net \
--cc=mjguzik@gmail.com \
--cc=oleg@redhat.com \
--cc=paul@paul-moore.com \
--cc=shuah@kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox