linux-fsdevel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH RFC 0/2] pidfs: keep pidfs dentry stashed once created
@ 2025-06-16 14:02 Christian Brauner
  2025-06-16 14:02 ` [PATCH RFC 1/2] " Christian Brauner
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Christian Brauner @ 2025-06-16 14:02 UTC (permalink / raw)
  To: Oleg Nesterov, linux-fsdevel; +Cc: Christian Brauner

Keep pidfs dentries around after a pidfd has been created for it.
Such pidfs dentry will only be cleaned up once the struct pid gets
reaped. This also allows us to fix a race condition that currently
exists in the code.

The current scheme allocated pidfs dentries on-demand repeatedly.

If someone opens a pidfd for a struct pid a pidfs dentry is allocated
and stashed in pid->stashed. Once the last pidfd for the struct pid is
closed the pidfs dentry is released and removed from pid->stashed.

So if 10 callers create a pidfs for the same struct pid sequentially,
i.e., each closing the pidfd before the other creates a new one then a
new pidfs dentry is allocated every time.

Because multiple tasks acquiring and releasing a pidfd for the same
struct pid can race with each other a task may still find a valid pidfs
entry from the previous task in pid->stashed and reuse it. Or it might
find a dead dentry in there and fail to reuse it and so stashes a new
pidfs dentry. Multiple tasks may race to stash a new pidfs dentry but
only one will succeed, the other ones will put their dentry.

The current scheme ensures that a reference to a struct pid can only be
taken if the task is still alive or if a pidfs dentry already existed
and exit information has been stashed in the pidfs inode.

Except that it's also buggy. If a pidfs dentry is stashed in
pid->stashed after pidfs_exit() but before __unhash_process() is called
we will return a pidfd for a reaped task without exit information being
available.

The pidfds_pid_valid() check does not guard against this race as it
doens't sync at all with pidfs_exit(). The pid_has_task() check might be
successful simply because we're before __unhash_process() but after
pidfs_exit().

The current scheme also makes it impossible to pin information that
needs to be available after the task has exited or coredumped and that
should not be lost simply because the pidfd got closed. The next opened
should still see the stashed information.

This switches to a scheme were pidfs entries are retained after a pidfd
was created for the struct pid. So retain a reference is retained that
belongs to the exit path and that will be put once the task does get
reaped. In the new model pidfs dentries are still allocated on-demand
but then kept until the task gets reaped.

The synchronization mechanism used the pid->wait_pidfd.lock in struct
pid. If the path_from_stashed() fastpath fails a new pidfs dentry is
allocated and afterwards the pid->wait_pidfd.lock is taken. If no other
task managed to stash its dentry there the callers will be stashed.

Similarly when a pidfs dentry is pruned via
dentry->d_prune::pidfs_dentry_prune() the pid->wait_pidfd.lock is also
taken.

And finally the pid->wait_pidfd.lock is taken during pidfs_exit(). This
allows us to fix the bug mentioned earlier where we hand out a pidfd for
a reaped task without having exit information set.

Once pidfs_exit() holds the pid->wait_pidfd.lock and sees that no pidfs
dentry is available in pid->stashed it knows that no new dentry can be
stashed while it holds the pid->wait_pidfd.lock. It thus sets a
ERR_PTR(-ESRCH) sentinel in pid->stashed. That sentinel allows
pidfs_stash_dentry() to detect that the struct pid has already been
reaped and refuse to stash a new dentry in pid->stashed.

This also has some subtle interactions with the path_from_stashed()
fastpath that need to be considered. The path_from_stashed() fast path
will try go get a reference to the pidfs dentry in pid->stashed to avoid
having to allocate and stash a pidfs dentry. If it finds dentry in there
it will return it.

To not confuse path_from_stashed() pidfs_exit() must not replace a pidfs
dentry stashed in pid->stashed with the ERR_PTR(-ESRCH) sentinel as
path_from_stashed() could legitimately obtain another reference before
pidfs_exit() was able to call dput() to put the final pidfs dentry
reference. If it were to put the sentinel into pid->stashed it would
invalidate a struct pid even though a pidfd was just created for it.

So if a pidfs dentry is stashed in pid->stashed pidfs_exit() must leave
clearing out pid->stashed to dentry->d_prune::pidfs_dentry_prune().
Concurrent calls to path_from_stashed() will see a dead dentry in there
and will be forced into the slowpath.

Inversely, path_from_stashed() must take care to not try and take a
reference on the ERR_PTR(-ESRCH) sentinel. So stashed_dentry_get() must
be prepared to see a ERR_PTR(-ESRCH) sentinel in pid->stashed.

Note that it doesn't matter whether the path_from_stashed() fast path
sees NULL, a dead dentry, or the ERR_PTR(-ESRCH) sentinel in
pid->stashed. Any of those forces path_from_stashed() into the slowpath
at which point pid->wait_pidfd.lock must be acquired serializing against
pidfs_exit() and pidfs_dentry_prune().

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
Christian Brauner (2):
      pidfs: keep pidfs dentry stashed once created
      pidfs: remove pidfs_pid_valid()

 fs/internal.h |   2 +
 fs/libfs.c    |  22 ++++++--
 fs/pidfs.c    | 169 ++++++++++++++++++++++++++++++++++++----------------------
 3 files changed, 124 insertions(+), 69 deletions(-)
---
base-commit: 19272b37aa4f83ca52bdf9c16d5d81bdd1354494
change-id: 20250613-work-pidfs-269232caa91f


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2025-06-17 10:56 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-16 14:02 [PATCH RFC 0/2] pidfs: keep pidfs dentry stashed once created Christian Brauner
2025-06-16 14:02 ` [PATCH RFC 1/2] " Christian Brauner
2025-06-16 14:02 ` [PATCH RFC 2/2] pidfs: remove pidfs_pid_valid() Christian Brauner
2025-06-17 10:56 ` [PATCH RFC 0/2] pidfs: keep pidfs dentry stashed once created Christian Brauner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).