[PATCH 7/8] proc: lock-free exec_update_seq fast path for stack/syscall/personality

From: Christian Brauner

Date: Mon May 25 2026 - 03:31:26 EST


/proc/<pid>/{stack,syscall,personality} took exec_update_lock for read
(via lock_trace()) to check ptrace_may_access() and then read task state.
Convert all three to exec_update_read_begin_or_lock(): snapshot the
permission decision and the task state (stack trace, syscall info,
personality) in the speculative section, then emit after validation;
fall back to exec_update_lock on a racing exec()/TSYNC. With all three
callers converted, lock_trace() and unlock_trace() are removed.

Note: the stack unwind and task_current_syscall() now run inside the
speculative section and may re-run if a concurrent exec() of the target
is detected. They are idempotent (they fill a local buffer/struct and
output is emitted only after the section validates), and exec of a given
task is rare, so the bounded re-run is acceptable. /proc/<pid>/stack
stays root-only.

Signed-off-by: Christian Brauner (Amutable) <brauner@xxxxxxxxxx>
---
fs/proc/base.c | 93 ++++++++++++++++++++++++++++++++------------------
1 file changed, 59 insertions(+), 34 deletions(-)

diff --git a/fs/proc/base.c b/fs/proc/base.c
index 65f56136ec3f..83b851b7f9d9 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -440,23 +440,6 @@ static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns,
}
#endif /* CONFIG_KALLSYMS */

-static int lock_trace(struct task_struct *task)
-{
- int err = down_read_killable(&task->signal->exec_update_lock);
- if (err)
- return err;
- if (!ptrace_may_access(task, PTRACE_MODE_ATTACH_FSCREDS)) {
- up_read(&task->signal->exec_update_lock);
- return -EPERM;
- }
- return 0;
-}
-
-static void unlock_trace(struct task_struct *task)
-{
- up_read(&task->signal->exec_update_lock);
-}
-
#ifdef CONFIG_STACKTRACE

#define MAX_STACK_TRACE_DEPTH 64
@@ -464,7 +447,10 @@ static void unlock_trace(struct task_struct *task)
static int proc_pid_stack(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
+ struct signal_struct *sig = task->signal;
unsigned long *entries;
+ unsigned int seq = 0, i, nr_entries = 0;
+ bool allowed = false;
int err;

/*
@@ -486,19 +472,27 @@ static int proc_pid_stack(struct seq_file *m, struct pid_namespace *ns,
if (!entries)
return -ENOMEM;

- err = lock_trace(task);
- if (!err) {
- unsigned int i, nr_entries;
-
+retry:
+ err = exec_update_read_begin_or_killable(sig, &seq);
+ if (err)
+ goto out;
+ allowed = ptrace_may_access(task, PTRACE_MODE_ATTACH_FSCREDS);
+ if (allowed)
nr_entries = stack_trace_save_tsk(task, entries,
MAX_STACK_TRACE_DEPTH, 0);
+ if (exec_update_read_needs_retry(sig, seq)) {
+ seq = 1;
+ goto retry;
+ }
+ exec_update_read_done(sig, seq);

- for (i = 0; i < nr_entries; i++) {
- seq_printf(m, "[<0>] %pB\n", (void *)entries[i]);
- }
-
- unlock_trace(task);
+ if (!allowed) {
+ err = -EPERM;
+ goto out;
}
+ for (i = 0; i < nr_entries; i++)
+ seq_printf(m, "[<0>] %pB\n", (void *)entries[i]);
+out:
kfree(entries);

return err;
@@ -676,15 +670,31 @@ static int proc_pid_limits(struct seq_file *m, struct pid_namespace *ns,
static int proc_pid_syscall(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
+ struct signal_struct *sig = task->signal;
struct syscall_info info;
u64 *args = &info.data.args[0];
+ unsigned int seq = 0;
+ bool allowed = false;
+ int running = 0;
int res;

- res = lock_trace(task);
+retry:
+ res = exec_update_read_begin_or_killable(sig, &seq);
if (res)
return res;
+ allowed = ptrace_may_access(task, PTRACE_MODE_ATTACH_FSCREDS);
+ if (allowed)
+ running = task_current_syscall(task, &info);
+ if (exec_update_read_needs_retry(sig, seq)) {
+ seq = 1;
+ goto retry;
+ }
+ exec_update_read_done(sig, seq);
+
+ if (!allowed)
+ return -EPERM;

- if (task_current_syscall(task, &info))
+ if (running)
seq_puts(m, "running\n");
else if (info.data.nr < 0)
seq_printf(m, "%d 0x%llx 0x%llx\n",
@@ -695,7 +705,6 @@ static int proc_pid_syscall(struct seq_file *m, struct pid_namespace *ns,
info.data.nr,
args[0], args[1], args[2], args[3], args[4], args[5],
info.sp, info.data.instruction_pointer);
- unlock_trace(task);

return 0;
}
@@ -3221,12 +3230,28 @@ static const struct file_operations proc_setgroups_operations = {
static int proc_pid_personality(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
- int err = lock_trace(task);
- if (!err) {
- seq_printf(m, "%08x\n", task->personality);
- unlock_trace(task);
+ struct signal_struct *sig = task->signal;
+ unsigned int seq = 0, personality = 0;
+ bool allowed = false;
+ int err;
+
+retry:
+ err = exec_update_read_begin_or_killable(sig, &seq);
+ if (err)
+ return err;
+ allowed = ptrace_may_access(task, PTRACE_MODE_ATTACH_FSCREDS);
+ if (allowed)
+ personality = READ_ONCE(task->personality);
+ if (exec_update_read_needs_retry(sig, seq)) {
+ seq = 1;
+ goto retry;
}
- return err;
+ exec_update_read_done(sig, seq);
+
+ if (!allowed)
+ return -EPERM;
+ seq_printf(m, "%08x\n", personality);
+ return 0;
}

#ifdef CONFIG_LIVEPATCH
--
2.47.3


--j3ezp33mpunnwnqz
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0008-proc-take-a-lock-free-exec_update_seq-fast-path-in-d.patch"