]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
pidfs: improve multi-threaded exec and premature thread-group leader exit polling
authorChristian Brauner <brauner@kernel.org>
Thu, 20 Mar 2025 13:24:08 +0000 (14:24 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 29 May 2025 09:12:31 +0000 (11:12 +0200)
[ Upstream commit 0fb482728ba1ee2130eaa461bf551f014447997c ]

This is another attempt trying to make pidfd polling for multi-threaded
exec and premature thread-group leader exit consistent.

A quick recap of these two cases:

(1) During a multi-threaded exec by a subthread, i.e., non-thread-group
    leader thread, all other threads in the thread-group including the
    thread-group leader are killed and the struct pid of the
    thread-group leader will be taken over by the subthread that called
    exec. IOW, two tasks change their TIDs.

(2) A premature thread-group leader exit means that the thread-group
    leader exited before all of the other subthreads in the thread-group
    have exited.

Both cases lead to inconsistencies for pidfd polling with PIDFD_THREAD.
Any caller that holds a PIDFD_THREAD pidfd to the current thread-group
leader may or may not see an exit notification on the file descriptor
depending on when poll is performed. If the poll is performed before the
exec of the subthread has concluded an exit notification is generated
for the old thread-group leader. If the poll is performed after the exec
of the subthread has concluded no exit notification is generated for the
old thread-group leader.

The correct behavior would be to simply not generate an exit
notification on the struct pid of a subhthread exec because the struct
pid is taken over by the subthread and thus remains alive.

But this is difficult to handle because a thread-group may exit
prematurely as mentioned in (2). In that case an exit notification is
reliably generated but the subthreads may continue to run for an
indeterminate amount of time and thus also may exec at some point.

So far there was no way to distinguish between (1) and (2) internally.
This tiny series tries to address this problem by discarding
PIDFD_THREAD notification on premature thread-group leader exit.

If that works correctly then no exit notifications are generated for a
PIDFD_THREAD pidfd for a thread-group leader until all subthreads have
been reaped. If a subthread should exec aftewards no exit notification
will be generated until that task exits or it creates subthreads and
repeates the cycle.

Co-Developed-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Oleg Nesterov <oleg@redhat.com>
Link: https://lore.kernel.org/r/20250320-work-pidfs-thread_group-v4-1-da678ce805bf@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/pidfs.c
kernel/exit.c
kernel/signal.c

index c0478b3c55d9fa935cf8ff3c3a1104153fe5a868..9aa4c705776dddae944c682863e6fd74f50ae1a3 100644 (file)
@@ -188,20 +188,21 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
 static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts)
 {
        struct pid *pid = pidfd_pid(file);
-       bool thread = file->f_flags & PIDFD_THREAD;
        struct task_struct *task;
        __poll_t poll_flags = 0;
 
        poll_wait(file, &pid->wait_pidfd, pts);
        /*
-        * Depending on PIDFD_THREAD, inform pollers when the thread
-        * or the whole thread-group exits.
+        * Don't wake waiters if the thread-group leader exited
+        * prematurely. They either get notified when the last subthread
+        * exits or not at all if one of the remaining subthreads execs
+        * and assumes the struct pid of the old thread-group leader.
         */
        guard(rcu)();
        task = pid_task(pid, PIDTYPE_PID);
        if (!task)
                poll_flags = EPOLLIN | EPOLLRDNORM | EPOLLHUP;
-       else if (task->exit_state && (thread || thread_group_empty(task)))
+       else if (task->exit_state && !delay_group_leader(task))
                poll_flags = EPOLLIN | EPOLLRDNORM;
 
        return poll_flags;
index 3485e5fc499e468760302d6776fc8f915362b8bd..c195f1d2b908a4f22abbdb76ef32b2407c06531b 100644 (file)
@@ -741,10 +741,10 @@ static void exit_notify(struct task_struct *tsk, int group_dead)
 
        tsk->exit_state = EXIT_ZOMBIE;
        /*
-        * sub-thread or delay_group_leader(), wake up the
-        * PIDFD_THREAD waiters.
+        * Ignore thread-group leaders that exited before all
+        * subthreads did.
         */
-       if (!thread_group_empty(tsk))
+       if (!delay_group_leader(tsk))
                do_notify_pidfd(tsk);
 
        if (unlikely(tsk->ptrace)) {
index 875e97f6205a2c9daecf5bece5d53ed09667f747..b2e5c90f29602aa1da3c882594adf4b218b6ca9b 100644 (file)
@@ -2180,8 +2180,7 @@ bool do_notify_parent(struct task_struct *tsk, int sig)
        WARN_ON_ONCE(!tsk->ptrace &&
               (tsk->group_leader != tsk || !thread_group_empty(tsk)));
        /*
-        * tsk is a group leader and has no threads, wake up the
-        * non-PIDFD_THREAD waiters.
+        * Notify for thread-group leaders without subthreads.
         */
        if (thread_group_empty(tsk))
                do_notify_pidfd(tsk);