Skip to content

Commit 68db272

Browse files
committed
pidfs: ensure that PIDFS_INFO_EXIT is available
When we currently create a pidfd we check that the task hasn't been reaped right before we create the pidfd. But it is of course possible that by the time we return the pidfd to userspace the task has already been reaped since we don't check again after having created a dentry for it. This was fine until now because that race was meaningless. But now that we provide PIDFD_INFO_EXIT it is a problem because it is possible that the kernel returns a reaped pidfd and it depends on the race whether PIDFD_INFO_EXIT information is available. This depends on if the task gets reaped before or after a dentry has been attached to struct pid. Make this consistent and only returned pidfds for reaped tasks if PIDFD_INFO_EXIT information is available. This is done by performing another check whether the task has been reaped right after we attached a dentry to struct pid. Since pidfs_exit() is called before struct pid's task linkage is removed the case where the task got reaped but a dentry was already attached to struct pid and exit information was recorded and published can be handled correctly. In that case we do return a pidfd for a reaped task like we would've before. Link: https://lore.kernel.org/r/20250316-kabel-fehden-66bdb6a83436@brauner Reviewed-by: Oleg Nesterov <oleg@redhat.com> Signed-off-by: Christian Brauner <brauner@kernel.org>
1 parent 6092c50 commit 68db272

3 files changed

Lines changed: 62 additions & 5 deletions

File tree

fs/pidfs.c

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -753,8 +753,49 @@ static int pidfs_export_permission(struct handle_to_path_ctx *ctx,
753753
return 0;
754754
}
755755

756+
static inline bool pidfs_pid_valid(struct pid *pid, const struct path *path,
757+
unsigned int flags)
758+
{
759+
enum pid_type type;
760+
761+
if (flags & PIDFD_CLONE)
762+
return true;
763+
764+
/*
765+
* Make sure that if a pidfd is created PIDFD_INFO_EXIT
766+
* information will be available. So after an inode for the
767+
* pidfd has been allocated perform another check that the pid
768+
* is still alive. If it is exit information is available even
769+
* if the task gets reaped before the pidfd is returned to
770+
* userspace. The only exception is PIDFD_CLONE where no task
771+
* linkage has been established for @pid yet and the kernel is
772+
* in the middle of process creation so there's nothing for
773+
* pidfs to miss.
774+
*/
775+
if (flags & PIDFD_THREAD)
776+
type = PIDTYPE_PID;
777+
else
778+
type = PIDTYPE_TGID;
779+
780+
/*
781+
* Since pidfs_exit() is called before struct pid's task linkage
782+
* is removed the case where the task got reaped but a dentry
783+
* was already attached to struct pid and exit information was
784+
* recorded and published can be handled correctly.
785+
*/
786+
if (unlikely(!pid_has_task(pid, type))) {
787+
struct inode *inode = d_inode(path->dentry);
788+
return !!READ_ONCE(pidfs_i(inode)->exit_info);
789+
}
790+
791+
return true;
792+
}
793+
756794
static struct file *pidfs_export_open(struct path *path, unsigned int oflags)
757795
{
796+
if (!pidfs_pid_valid(d_inode(path->dentry)->i_private, path, oflags))
797+
return ERR_PTR(-ESRCH);
798+
758799
/*
759800
* Clear O_LARGEFILE as open_by_handle_at() forces it and raise
760801
* O_RDWR as pidfds always are.
@@ -818,21 +859,30 @@ static struct file_system_type pidfs_type = {
818859

819860
struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
820861
{
821-
822862
struct file *pidfd_file;
823-
struct path path;
863+
struct path path __free(path_put) = {};
824864
int ret;
825865

866+
/*
867+
* Ensure that PIDFD_CLONE can be passed as a flag without
868+
* overloading other uapi pidfd flags.
869+
*/
870+
BUILD_BUG_ON(PIDFD_CLONE == PIDFD_THREAD);
871+
BUILD_BUG_ON(PIDFD_CLONE == PIDFD_NONBLOCK);
872+
826873
ret = path_from_stashed(&pid->stashed, pidfs_mnt, get_pid(pid), &path);
827874
if (ret < 0)
828875
return ERR_PTR(ret);
829876

877+
if (!pidfs_pid_valid(pid, &path, flags))
878+
return ERR_PTR(-ESRCH);
879+
880+
flags &= ~PIDFD_CLONE;
830881
pidfd_file = dentry_open(&path, flags, current_cred());
831882
/* Raise PIDFD_THREAD explicitly as do_dentry_open() strips it. */
832883
if (!IS_ERR(pidfd_file))
833884
pidfd_file->f_flags |= (flags & PIDFD_THREAD);
834885

835-
path_put(&path);
836886
return pidfd_file;
837887
}
838888

include/uapi/linux/pidfd.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
/* Flags for pidfd_open(). */
1111
#define PIDFD_NONBLOCK O_NONBLOCK
1212
#define PIDFD_THREAD O_EXCL
13+
#ifdef __KERNEL__
14+
#include <linux/sched.h>
15+
#define PIDFD_CLONE CLONE_PIDFD
16+
#endif
1317

1418
/* Flags for pidfd_send_signal(). */
1519
#define PIDFD_SIGNAL_THREAD (1UL << 0)

kernel/fork.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2425,8 +2425,11 @@ __latent_entropy struct task_struct *copy_process(
24252425
if (clone_flags & CLONE_PIDFD) {
24262426
int flags = (clone_flags & CLONE_THREAD) ? PIDFD_THREAD : 0;
24272427

2428-
/* Note that no task has been attached to @pid yet. */
2429-
retval = __pidfd_prepare(pid, flags, &pidfile);
2428+
/*
2429+
* Note that no task has been attached to @pid yet indicate
2430+
* that via CLONE_PIDFD.
2431+
*/
2432+
retval = __pidfd_prepare(pid, flags | PIDFD_CLONE, &pidfile);
24302433
if (retval < 0)
24312434
goto bad_fork_free_pid;
24322435
pidfd = retval;

0 commit comments

Comments
 (0)