* FAILED: patch "[PATCH] pidfs: don't let PIDFD_GET_INFO miss a reaped task" failed to apply to 6.18-stable tree
@ 2026-11-07 10:15 Sasha Levin
2026-11-11 19:50 ` [PATCH 6.18.y 1/2] pidfs: prepare to drop exit_info pointer Sasha Levin
0 siblings, 1 reply; 3+ messages in thread
From: Sasha Levin @ 2026-11-07 10:15 UTC (permalink / raw)
To: josef, brauner; +Cc: stable
The patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x 172658cf600904ff6121e240f69a0ccb36e65d6b
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '20261107051552-failed-6.18-172658cf6009@kernel.org' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From 172658cf600904ff6121e240f69a0ccb36e65d6b Mon Sep 17 00:00:00 2001
From: Josef Bacik <josef@toxicpanda.com>
Date: Sun, 4 Oct 2026 15:00:12 +0000
Subject: [PATCH] pidfs: don't let PIDFD_GET_INFO miss a reaped task
PIDFD_GET_INFO describes the task while there is one, and with
PIDFD_INFO_EXIT its exit status once it has been reaped. pidfd_info()
looks at the two in the wrong order and can miss both:
pidfd_info() release_task()
PIDFS_ATTR_BIT_EXIT is not set
pidfs_exit()
__unhash_process()
get_pid_task() == NULL
goto copy_out
The ioctl succeeds with neither PIDFD_INFO_PID nor PIDFD_INFO_EXIT in the
mask. If the task is unhashed a little later, after get_pid_task(), its
pid reads as 0 and the ioctl fails with -ESRCH, although the caller asked
for the exit information and it is available by now.
The second case does not even need an exit. When a sub-thread execs,
de_thread() hands the leader's pid over to it and releases the old leader.
A caller that got hold of the old leader reports the tid of the sub-thread
as the pid, or -ESRCH for a process that is alive.
Look up the task first. pidfs_exit() runs before the task is unhashed, so
a caller that finds no task finds the exit information; this needs a
barrier on both sides because taking tasklist_lock does not order the
store in pidfs_exit() against the one in detach_pid(). And start over if
the task no longer owns the pid when all the fields have been read: then
there is either exit information or another task.
Go by the pid for that check and for the pid we report, not by
->thread_pid of the task. exchange_tids() moves the pid to the exec'ing
thread before it updates ->thread_pid of either task. A caller that
finds the new leader in between would read the old tid of the sub-thread
from ->thread_pid, and a check of ->thread_pid would restart until the
exec'ing thread gets to its store. On PREEMPT_RT that thread can be
preempted with tasklist_lock held and can't move to another CPU. If the
RT task that preempted it calls PIDFD_GET_INFO, the ioctl spins until
the exec'ing thread gets the CPU back, and that never happens if the
exec'ing thread is an RT task of lower priority.
Christian Brauner <brauner@kernel.org> says:
Go by @pid instead of ->thread_pid of the task for the reported pid and
for the restart check.
Fixes: 7477d7dce48a ("pidfs: allow to retrieve exit information")
Fixes: cdda1f26e74b ("pidfd: add ioctl to retrieve pid info")
Cc: stable@vger.kernel.org
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/pidfs.c | 43 +++++++++++++++++++++++++++++++++++++------
1 file changed, 37 insertions(+), 6 deletions(-)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 7f8c323ce6cb8..66c1046e29290 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -362,6 +362,12 @@ static __u32 pidfs_coredump_mask(enum task_dumpable dumpable)
PIDFD_INFO_COREDUMP_SIGNAL | \
PIDFD_INFO_COREDUMP_CODE)
+static bool pidfs_task_owns_pid(struct task_struct *task, struct pid *pid)
+{
+ guard(rcu)();
+ return pid_task(pid, PIDTYPE_PID) == task;
+}
+
static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
{
struct pidfd_info __user *uinfo = (struct pidfd_info __user *)arg;
@@ -392,6 +398,16 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
return -EREMOTE;
attr = READ_ONCE(pid->attr);
+again:
+ /*
+ * Look for the task first. pidfs_exit() records the exit information
+ * before the task is unhashed, so if the task is gone the information
+ * is there.
+ */
+ task = get_pid_task(pid, PIDTYPE_PID);
+ if (!task)
+ smp_rmb(); /* pairs with smp_mb__after_atomic() in pidfs_exit() */
+
if (mask & PIDFD_INFO_EXIT) {
if (test_bit(PIDFS_ATTR_BIT_EXIT, &attr->attr_mask)) {
smp_rmb();
@@ -414,7 +430,6 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
}
}
- task = get_pid_task(pid, PIDTYPE_PID);
if (!task) {
/*
* If the task has already been reaped, only exit
@@ -468,16 +483,27 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
* task might return as soon as the copy_to_user finishes, but that's ok
* and userspace expects that might happen and can act accordingly, so
* this is just best-effort. What we can do however is checking that all
- * the fields are set correctly, or return ESRCH to avoid providing
- * incomplete information. */
+ * the fields are set correctly, to avoid providing incomplete
+ * information.
+ */
kinfo.ppid = task_ppid_vnr(task);
kinfo.tgid = task_tgid_vnr(task);
- kinfo.pid = task_pid_vnr(task);
+ kinfo.pid = pid_vnr(pid);
kinfo.mask |= PIDFD_INFO_PID;
- if (kinfo.pid == 0 || kinfo.tgid == 0)
- return -ESRCH;
+ /*
+ * The task was reaped, or lost @pid to a sub-thread that called exec,
+ * while we were looking at it. Start over: there is exit information
+ * now, or a new task. Don't go by ->thread_pid, exchange_tids()
+ * updates it only after it has moved @pid.
+ */
+ if (!pidfs_task_owns_pid(task, pid) || kinfo.tgid == 0) {
+ put_task_struct(task);
+ task = NULL;
+ memset(&kinfo, 0, sizeof(kinfo));
+ goto again;
+ }
copy_out:
if (mask & PIDFD_INFO_SUPPORTED_MASK) {
@@ -790,6 +816,11 @@ void pidfs_exit(struct task_struct *tsk)
/* Ensure that PIDFD_GET_INFO sees either all or nothing. */
smp_wmb();
set_bit(PIDFS_ATTR_BIT_EXIT, &attr->attr_mask);
+ /*
+ * Whoever sees that the task is unhashed must see the bit, see
+ * pidfd_info(). Taking tasklist_lock does not order the two stores.
+ */
+ smp_mb__after_atomic();
}
#ifdef CONFIG_COREDUMP
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 6.18.y 1/2] pidfs: prepare to drop exit_info pointer
2026-11-07 10:15 FAILED: patch "[PATCH] pidfs: don't let PIDFD_GET_INFO miss a reaped task" failed to apply to 6.18-stable tree Sasha Levin
@ 2026-11-11 19:50 ` Sasha Levin
2026-11-11 19:50 ` [PATCH 6.18.y 2/2] pidfs: don't let PIDFD_GET_INFO miss a reaped task Sasha Levin
0 siblings, 1 reply; 3+ messages in thread
From: Sasha Levin @ 2026-11-11 19:50 UTC (permalink / raw)
To: stable; +Cc: Christian Brauner, Alexander Mikhalitsyn, Oleg Nesterov,
Sasha Levin
From: Christian Brauner <brauner@kernel.org>
[ Upstream commit ad6e3ea683ef4086cdf3d2ec40cb3f6e62edb945 ]
There will likely be more info that we need to store in struct
pidfs_attr. We need to make sure that some of the information such as
exit info or coredump info that consists of multiple bits is either
available completely or not at all, but never partially. Currently we
use a pointer that we assign to. That doesn't scale. We can't waste a
pointer for each mulit-part information struct we want to expose. Use a
bitmask instead.
Link: https://patch.msgid.link/20251028-work-coredump-signal-v1-6-ca449b7b7aa0@kernel.org
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@canonical.com>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>
Stable-dep-of: 172658cf6009 ("pidfs: don't let PIDFD_GET_INFO miss a reaped task")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/pidfs.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 4341492b91e12..d2afc85b9df37 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -49,7 +49,12 @@ struct pidfs_exit_info {
__u32 coredump_mask;
};
+enum pidfs_attr_mask_bits {
+ PIDFS_ATTR_BIT_EXIT = 0,
+};
+
struct pidfs_attr {
+ unsigned long attr_mask;
struct simple_xattrs *xattrs;
struct pidfs_exit_info __pei;
struct pidfs_exit_info *exit_info;
@@ -325,8 +330,8 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
attr = READ_ONCE(pid->attr);
if (mask & PIDFD_INFO_EXIT) {
- exit_info = READ_ONCE(attr->exit_info);
- if (exit_info) {
+ if (test_bit(PIDFS_ATTR_BIT_EXIT, &attr->attr_mask)) {
+ smp_rmb();
kinfo.mask |= PIDFD_INFO_EXIT;
#ifdef CONFIG_CGROUPS
kinfo.cgroupid = exit_info->cgroupid;
@@ -681,7 +686,8 @@ void pidfs_exit(struct task_struct *tsk)
exit_info->exit_code = tsk->exit_code;
/* Ensure that PIDFD_GET_INFO sees either all or nothing. */
- smp_store_release(&attr->exit_info, &attr->__pei);
+ smp_wmb();
+ set_bit(PIDFS_ATTR_BIT_EXIT, &attr->attr_mask);
}
#ifdef CONFIG_COREDUMP
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 6.18.y 2/2] pidfs: don't let PIDFD_GET_INFO miss a reaped task
2026-11-11 19:50 ` [PATCH 6.18.y 1/2] pidfs: prepare to drop exit_info pointer Sasha Levin
@ 2026-11-11 19:50 ` Sasha Levin
0 siblings, 0 replies; 3+ messages in thread
From: Sasha Levin @ 2026-11-11 19:50 UTC (permalink / raw)
To: stable; +Cc: Josef Bacik, Christian Brauner (Amutable), Sasha Levin
From: Josef Bacik <josef@toxicpanda.com>
[ Upstream commit 172658cf600904ff6121e240f69a0ccb36e65d6b ]
PIDFD_GET_INFO describes the task while there is one, and with
PIDFD_INFO_EXIT its exit status once it has been reaped. pidfd_info()
looks at the two in the wrong order and can miss both:
pidfd_info() release_task()
PIDFS_ATTR_BIT_EXIT is not set
pidfs_exit()
__unhash_process()
get_pid_task() == NULL
goto copy_out
The ioctl succeeds with neither PIDFD_INFO_PID nor PIDFD_INFO_EXIT in the
mask. If the task is unhashed a little later, after get_pid_task(), its
pid reads as 0 and the ioctl fails with -ESRCH, although the caller asked
for the exit information and it is available by now.
The second case does not even need an exit. When a sub-thread execs,
de_thread() hands the leader's pid over to it and releases the old leader.
A caller that got hold of the old leader reports the tid of the sub-thread
as the pid, or -ESRCH for a process that is alive.
Look up the task first. pidfs_exit() runs before the task is unhashed, so
a caller that finds no task finds the exit information; this needs a
barrier on both sides because taking tasklist_lock does not order the
store in pidfs_exit() against the one in detach_pid(). And start over if
the task no longer owns the pid when all the fields have been read: then
there is either exit information or another task.
Go by the pid for that check and for the pid we report, not by
->thread_pid of the task. exchange_tids() moves the pid to the exec'ing
thread before it updates ->thread_pid of either task. A caller that
finds the new leader in between would read the old tid of the sub-thread
from ->thread_pid, and a check of ->thread_pid would restart until the
exec'ing thread gets to its store. On PREEMPT_RT that thread can be
preempted with tasklist_lock held and can't move to another CPU. If the
RT task that preempted it calls PIDFD_GET_INFO, the ioctl spins until
the exec'ing thread gets the CPU back, and that never happens if the
exec'ing thread is an RT task of lower priority.
Christian Brauner <brauner@kernel.org> says:
Go by @pid instead of ->thread_pid of the task for the reported pid and
for the restart check.
Fixes: 7477d7dce48a ("pidfs: allow to retrieve exit information")
Fixes: cdda1f26e74b ("pidfd: add ioctl to retrieve pid info")
Cc: stable@vger.kernel.org
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
[ sashal: inlined the RCU-protected ownership check in pidfd_info() instead of adding pidfs_task_owns_pid() ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/pidfs.c | 41 +++++++++++++++++++++++++++++++++++------
1 file changed, 35 insertions(+), 6 deletions(-)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index d2afc85b9df37..a014db891aaa8 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -309,6 +309,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 task_owns_pid;
__u64 mask;
BUILD_BUG_ON(sizeof(struct pidfd_info) != PIDFD_INFO_SIZE_VER1);
@@ -329,6 +330,17 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
return -EREMOTE;
attr = READ_ONCE(pid->attr);
+ exit_info = &attr->__pei;
+again:
+ /*
+ * Look for the task first. pidfs_exit() records the exit information
+ * before the task is unhashed, so if the task is gone the information
+ * is there.
+ */
+ task = get_pid_task(pid, PIDTYPE_PID);
+ if (!task)
+ smp_rmb(); /* pairs with smp_mb__after_atomic() in pidfs_exit() */
+
if (mask & PIDFD_INFO_EXIT) {
if (test_bit(PIDFS_ATTR_BIT_EXIT, &attr->attr_mask)) {
smp_rmb();
@@ -346,7 +358,6 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
kinfo.coredump_mask = READ_ONCE(attr->__pei.coredump_mask);
}
- task = get_pid_task(pid, PIDTYPE_PID);
if (!task) {
/*
* If the task has already been reaped, only exit
@@ -404,16 +415,29 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
* task might return as soon as the copy_to_user finishes, but that's ok
* and userspace expects that might happen and can act accordingly, so
* this is just best-effort. What we can do however is checking that all
- * the fields are set correctly, or return ESRCH to avoid providing
- * incomplete information. */
+ * the fields are set correctly, to avoid providing incomplete
+ * information.
+ */
kinfo.ppid = task_ppid_nr_ns(task, NULL);
kinfo.tgid = task_tgid_vnr(task);
- kinfo.pid = task_pid_vnr(task);
+ kinfo.pid = pid_vnr(pid);
kinfo.mask |= PIDFD_INFO_PID;
- if (kinfo.pid == 0 || kinfo.tgid == 0)
- return -ESRCH;
+ /*
+ * The task was reaped, or lost @pid to a sub-thread that called exec,
+ * while we were looking at it. Start over: there is exit information
+ * now, or a new task. Don't go by ->thread_pid, exchange_tids()
+ * updates it only after it has moved @pid.
+ */
+ scoped_guard(rcu)
+ task_owns_pid = pid_task(pid, PIDTYPE_PID) == task;
+ if (!task_owns_pid || kinfo.tgid == 0) {
+ put_task_struct(task);
+ task = NULL;
+ memset(&kinfo, 0, sizeof(kinfo));
+ goto again;
+ }
copy_out:
/*
@@ -688,6 +712,11 @@ void pidfs_exit(struct task_struct *tsk)
/* Ensure that PIDFD_GET_INFO sees either all or nothing. */
smp_wmb();
set_bit(PIDFS_ATTR_BIT_EXIT, &attr->attr_mask);
+ /*
+ * Whoever sees that the task is unhashed must see the bit, see
+ * pidfd_info(). Taking tasklist_lock does not order the two stores.
+ */
+ smp_mb__after_atomic();
}
#ifdef CONFIG_COREDUMP
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-11 19:50 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-07 10:15 FAILED: patch "[PATCH] pidfs: don't let PIDFD_GET_INFO miss a reaped task" failed to apply to 6.18-stable tree Sasha Levin
2026-11-11 19:50 ` [PATCH 6.18.y 1/2] pidfs: prepare to drop exit_info pointer Sasha Levin
2026-11-11 19:50 ` [PATCH 6.18.y 2/2] pidfs: don't let PIDFD_GET_INFO miss a reaped task Sasha Levin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox