fs/fs_struct.c | 44 +++++++++++++ fs/pidfs.c | 153 ++++++++++++++++++++++++++++++++------------- fs/proc/base.c | 34 +--------- include/linux/fs_struct.h | 3 + include/linux/mm.h | 1 + include/uapi/linux/pidfd.h | 7 +++ kernel/fork.c | 21 +++++++ security/apparmor/task.c | 12 +--- 8 files changed, 190 insertions(+), 85 deletions(-)
Obtaining a target task's executable, working directory, or root
currently requires walking procfs symlinks such as /proc/<pid>/exe,
/proc/<pid>/cwd, and /proc/<pid>/root. That makes the operation depend
on procfs being mounted and visible to the caller, even when it already
holds a pidfd for the target.
This series adds PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT. Each
ioctl takes no argument and returns a close-on-exec O_PATH file
descriptor referencing the corresponding task path. The new ioctls use
the same ptrace permission check and nonzero-argument rejection as the
existing pidfd namespace ioctls.
The target task is sampled while holding its exec_update_lock. This
keeps the access decision and the task-state read in the same exec
critical section, preventing a concurrent execve() from changing the
credentials or target state between the check and the use.
The first patch factors out helpers for acquiring referenced task paths
and reuses them in procfs and AppArmor. The second patch introduces
scoped cleanup for privileged pidfd task access and separates namespace
lookup from namespace fd creation. The final patch uses these pieces to
implement the three new ioctls.
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
Changes in v2:
- Make pidfd_get_task_locked() own the task reference until it is
transferred to the pidfd_task_locked cleanup class.
- Link to v1: https://patch.msgid.link/20260820-pidfd-get-paths-v1-0-ac3eee4003d5@black-desk.cn
To: Alexander Viro <viro@zeniv.linux.org.uk>
To: Christian Brauner <brauner@kernel.org>
To: Jan Kara <jack@suse.cz>
To: Andrew Morton <akpm@linux-foundation.org>
To: David Hildenbrand <david@kernel.org>
To: Lorenzo Stoakes <ljs@kernel.org>
To: "Liam R. Howlett" <liam@infradead.org>
To: Vlastimil Babka <vbabka@kernel.org>
To: Mike Rapoport <rppt@kernel.org>
To: Suren Baghdasaryan <surenb@google.com>
To: Michal Hocko <mhocko@suse.com>
To: Ingo Molnar <mingo@redhat.com>
To: Peter Zijlstra <peterz@infradead.org>
To: Juri Lelli <juri.lelli@redhat.com>
To: Vincent Guittot <vincent.guittot@linaro.org>
To: Dietmar Eggemann <dietmar.eggemann@arm.com>
To: Steven Rostedt <rostedt@goodmis.org>
To: Ben Segall <bsegall@google.com>
To: Mel Gorman <mgorman@suse.de>
To: Valentin Schneider <vschneid@redhat.com>
To: K Prateek Nayak <kprateek.nayak@amd.com>
To: Kees Cook <kees@kernel.org>
To: John Johansen <john.johansen@canonical.com>
To: Georgia Garcia <georgia.garcia@canonical.com>
To: Paul Moore <paul@paul-moore.com>
To: James Morris <jmorris@namei.org>
To: "Serge E. Hallyn" <serge@hallyn.com>
Cc: linux-fsdevel@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-mm@kvack.org
Cc: apparmor@lists.ubuntu.com
Cc: linux-security-module@vger.kernel.org
---
Chen Linxuan (3):
fs: Introduce task path helpers
pidfd: Use scoped cleanup for task access
pidfd: Add task path ioctls
fs/fs_struct.c | 44 +++++++++++++
fs/pidfs.c | 153 ++++++++++++++++++++++++++++++++-------------
fs/proc/base.c | 34 +---------
include/linux/fs_struct.h | 3 +
include/linux/mm.h | 1 +
include/uapi/linux/pidfd.h | 7 +++
kernel/fork.c | 21 +++++++
security/apparmor/task.c | 12 +---
8 files changed, 190 insertions(+), 85 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260819-pidfd-get-paths-59640d8cd1ee
Best regards,
--
Chen Linxuan <me@black-desk.cn>
* Chen Linxuan via: > This series adds PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT. Each > ioctl takes no argument and returns a close-on-exec O_PATH file > descriptor referencing the corresponding task path. The new ioctls use > the same ptrace permission check and nonzero-argument rejection as the > existing pidfd namespace ioctls. How would one upgrade the O_PATH descriptor to a full descriptor without a mounted /proc? Thanks, Florian
On Fri, Sep 4, 2026 at 10:53 PM Florian Weimer <fweimer@redhat.com> wrote: > > * Chen Linxuan via: > > > This series adds PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT. Each > > ioctl takes no argument and returns a close-on-exec O_PATH file > > descriptor referencing the corresponding task path. The new ioctls use > > the same ptrace permission check and nonzero-argument rejection as the > > existing pidfd namespace ioctls. > > How would one upgrade the O_PATH descriptor to a full descriptor without > a mounted /proc? For PIDFD_GET_CWD and PIDFD_GET_ROOT, the returned O_PATH descriptor is typically used directly as a base directory for openat(2) or passed to fchdir(2), so upgrading it to a full descriptor is usually unnecessary. For cases where upgrading an O_PATH descriptor to a full descriptor is required without /proc mounted (such as inspecting the executable binary from PIDFD_GET_EXE), users can use the newly added O_EMPTYPATH flag with openat2(2) (introduced in Linux 7.2) to reopen the O_PATH descriptor. > > Thanks, > Florian > >
Hi Jann, Christian suggested adding you to the discussion on this series. I sent a follow-up reply with you, but I’m not sure whether it reached you. When you have a chance, could you please take a look at the thread and let me know if you have any concerns? Thanks, Chen Linxuan
On 2026-08-31 10:59 +0800, Chen Linxuan wrote: > Obtaining a target task's executable, working directory, or root > currently requires walking procfs symlinks such as /proc/<pid>/exe, > /proc/<pid>/cwd, and /proc/<pid>/root. That makes the operation depend > on procfs being mounted and visible to the caller, even when it already > holds a pidfd for the target. > > This series adds PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT. Each > ioctl takes no argument and returns a close-on-exec O_PATH file > descriptor referencing the corresponding task path. The new ioctls use > the same ptrace permission check and nonzero-argument rejection as the > existing pidfd namespace ioctls. > > The target task is sampled while holding its exec_update_lock. This > keeps the access decision and the task-state read in the same exec > critical section, preventing a concurrent execve() from changing the > credentials or target state between the check and the use. > > The first patch factors out helpers for acquiring referenced task paths > and reuses them in procfs and AppArmor. The second patch introduces > scoped cleanup for privileged pidfd task access and separates namespace > lookup from namespace fd creation. The final patch uses these pieces to > implement the three new ioctls. > > Signed-off-by: Chen Linxuan <me@black-desk.cn> > --- I really have difficulties forming an opinion on this. So this sounds very useful but it has implications. Right now, pidfd ioctls are available even in situations where the task in question would not be accessible via procfs, e.g., when procfs is mounted with "hidepid" options or similar. So this would expand the surface of operations you could potentially do. But again, I do think it is actually useful. Adding Jann.
On Mon, Aug 31, 2026 at 4:49 PM Christian Brauner <brauner@kernel.org> wrote:
>
> On 2026-08-31 10:59 +0800, Chen Linxuan wrote:
> > Obtaining a target task's executable, working directory, or root
> > currently requires walking procfs symlinks such as /proc/<pid>/exe,
> > /proc/<pid>/cwd, and /proc/<pid>/root. That makes the operation depend
> > on procfs being mounted and visible to the caller, even when it already
> > holds a pidfd for the target.
> >
> > This series adds PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT. Each
> > ioctl takes no argument and returns a close-on-exec O_PATH file
> > descriptor referencing the corresponding task path. The new ioctls use
> > the same ptrace permission check and nonzero-argument rejection as the
> > existing pidfd namespace ioctls.
> >
> > The target task is sampled while holding its exec_update_lock. This
> > keeps the access decision and the task-state read in the same exec
> > critical section, preventing a concurrent execve() from changing the
> > credentials or target state between the check and the use.
> >
> > The first patch factors out helpers for acquiring referenced task paths
> > and reuses them in procfs and AppArmor. The second patch introduces
> > scoped cleanup for privileged pidfd task access and separates namespace
> > lookup from namespace fd creation. The final patch uses these pieces to
> > implement the three new ioctls.
> >
> > Signed-off-by: Chen Linxuan <me@black-desk.cn>
> > ---
>
> I really have difficulties forming an opinion on this. So this sounds
> very useful but it has implications.
>
> Right now, pidfd ioctls are available even in situations where the task
> in question would not be accessible via procfs, e.g., when procfs is
> mounted with "hidepid" options or similar. So this would expand the
One point regarding hidepid: for callers that can pass
PTRACE_MODE_READ_FSCREDS, hidepid does not provide an additional
restriction. With hidepid=1 or hidepid=2, has_pid_permissions() falls
back to ptrace_may_access(..., PTRACE_MODE_READ_FSCREDS), and
hidepid=ptraceable uses that check directly. In addition, the
/proc/<pid>/{exe,cwd,root} links independently perform the same
PTRACE_MODE_READ_FSCREDS check in call_proc_get_link(), regardless of
the hidepid mode.
So for these specific path lookups, hidepid does not block a caller who
would already pass the check used by the proposed pidfd ioctls.
> surface of operations you could potentially do.
>
> But again, I do think it is actually useful. Adding Jann.
>
>
On Wed, Sep 2, 2026 at 8:47 AM Chen Linxuan <me@black-desk.cn> wrote:
> On Mon, Aug 31, 2026 at 4:49 PM Christian Brauner <brauner@kernel.org> wrote:
> > On 2026-08-31 10:59 +0800, Chen Linxuan wrote:
> > > Obtaining a target task's executable, working directory, or root
> > > currently requires walking procfs symlinks such as /proc/<pid>/exe,
> > > /proc/<pid>/cwd, and /proc/<pid>/root. That makes the operation depend
> > > on procfs being mounted and visible to the caller, even when it already
> > > holds a pidfd for the target.
> > >
> > > This series adds PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT. Each
> > > ioctl takes no argument and returns a close-on-exec O_PATH file
> > > descriptor referencing the corresponding task path. The new ioctls use
> > > the same ptrace permission check and nonzero-argument rejection as the
> > > existing pidfd namespace ioctls.
> > >
> > > The target task is sampled while holding its exec_update_lock. This
> > > keeps the access decision and the task-state read in the same exec
> > > critical section, preventing a concurrent execve() from changing the
> > > credentials or target state between the check and the use.
> > >
> > > The first patch factors out helpers for acquiring referenced task paths
> > > and reuses them in procfs and AppArmor. The second patch introduces
> > > scoped cleanup for privileged pidfd task access and separates namespace
> > > lookup from namespace fd creation. The final patch uses these pieces to
> > > implement the three new ioctls.
> > >
> > > Signed-off-by: Chen Linxuan <me@black-desk.cn>
> > > ---
> >
> > I really have difficulties forming an opinion on this. So this sounds
> > very useful but it has implications.
> >
> > Right now, pidfd ioctls are available even in situations where the task
> > in question would not be accessible via procfs, e.g., when procfs is
> > mounted with "hidepid" options or similar. So this would expand the
>
> One point regarding hidepid: for callers that can pass
> PTRACE_MODE_READ_FSCREDS, hidepid does not provide an additional
> restriction. With hidepid=1 or hidepid=2, has_pid_permissions() falls
> back to ptrace_may_access(..., PTRACE_MODE_READ_FSCREDS), and
> hidepid=ptraceable uses that check directly. In addition, the
> /proc/<pid>/{exe,cwd,root} links independently perform the same
> PTRACE_MODE_READ_FSCREDS check in call_proc_get_link(), regardless of
> the hidepid mode.
>
> So for these specific path lookups, hidepid does not block a caller who
> would already pass the check used by the proposed pidfd ioctls.
Agreed, I think with regards to hidepid there should be no issue here.
If I try to come up with scenarios in which this could introduce
additional danger, the main one I can think of would be: A task T1 is
running inside a pid namespace and has a unix domain socket connection
to a task T2 outside the namespace, with both running as the same
EUID; T1 sets SO_PASSPIDFD on its socket to obtain a pidfd pointing to
T2 on the next message sent by T2, then T1 uses that to get access to
the mount namespace of T2.
Christian, is there some mechanism that already protects against using
something like SO_PASSPIDFD to get a pidfd to a process in a parent
namespace?
Otherwise, should we add something like a "pid_vnr(pid) != 0" check
either in these new operations or in SO_PASSPIDFD?
On Mon, Sep 21, 2026 at 10:22:11PM +0200, Jann Horn wrote:
> On Wed, Sep 2, 2026 at 8:47 AM Chen Linxuan <me@black-desk.cn> wrote:
> > On Mon, Aug 31, 2026 at 4:49 PM Christian Brauner <brauner@kernel.org> wrote:
> > > On 2026-08-31 10:59 +0800, Chen Linxuan wrote:
> > > > Obtaining a target task's executable, working directory, or root
> > > > currently requires walking procfs symlinks such as /proc/<pid>/exe,
> > > > /proc/<pid>/cwd, and /proc/<pid>/root. That makes the operation depend
> > > > on procfs being mounted and visible to the caller, even when it already
> > > > holds a pidfd for the target.
> > > >
> > > > This series adds PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT. Each
> > > > ioctl takes no argument and returns a close-on-exec O_PATH file
> > > > descriptor referencing the corresponding task path. The new ioctls use
> > > > the same ptrace permission check and nonzero-argument rejection as the
> > > > existing pidfd namespace ioctls.
> > > >
> > > > The target task is sampled while holding its exec_update_lock. This
> > > > keeps the access decision and the task-state read in the same exec
> > > > critical section, preventing a concurrent execve() from changing the
> > > > credentials or target state between the check and the use.
> > > >
> > > > The first patch factors out helpers for acquiring referenced task paths
> > > > and reuses them in procfs and AppArmor. The second patch introduces
> > > > scoped cleanup for privileged pidfd task access and separates namespace
> > > > lookup from namespace fd creation. The final patch uses these pieces to
> > > > implement the three new ioctls.
> > > >
> > > > Signed-off-by: Chen Linxuan <me@black-desk.cn>
> > > > ---
> > >
> > > I really have difficulties forming an opinion on this. So this sounds
> > > very useful but it has implications.
> > >
> > > Right now, pidfd ioctls are available even in situations where the task
> > > in question would not be accessible via procfs, e.g., when procfs is
> > > mounted with "hidepid" options or similar. So this would expand the
> >
> > One point regarding hidepid: for callers that can pass
> > PTRACE_MODE_READ_FSCREDS, hidepid does not provide an additional
> > restriction. With hidepid=1 or hidepid=2, has_pid_permissions() falls
> > back to ptrace_may_access(..., PTRACE_MODE_READ_FSCREDS), and
> > hidepid=ptraceable uses that check directly. In addition, the
> > /proc/<pid>/{exe,cwd,root} links independently perform the same
> > PTRACE_MODE_READ_FSCREDS check in call_proc_get_link(), regardless of
> > the hidepid mode.
> >
> > So for these specific path lookups, hidepid does not block a caller who
> > would already pass the check used by the proposed pidfd ioctls.
>
> Agreed, I think with regards to hidepid there should be no issue here.
>
> If I try to come up with scenarios in which this could introduce
> additional danger, the main one I can think of would be: A task T1 is
> running inside a pid namespace and has a unix domain socket connection
> to a task T2 outside the namespace, with both running as the same
> EUID; T1 sets SO_PASSPIDFD on its socket to obtain a pidfd pointing to
> T2 on the next message sent by T2, then T1 uses that to get access to
> the mount namespace of T2.
>
> Christian, is there some mechanism that already protects against using
> something like SO_PASSPIDFD to get a pidfd to a process in a parent
> namespace?
> Otherwise, should we add something like a "pid_vnr(pid) != 0" check
> either in these new operations or in SO_PASSPIDFD?
Right now we don't place any hierarchical restrictions on SO_PASSPIDFD
or SO_PEERPIDFD at all and this is in use by varlink iirc.
What about requiring pid_vnr(pid != 0 for any operations that grant
access to additional resources. IOW, make the icotl for any ns
descriptor or cwd/root fail if the caller is outside the pidns
hierarchy?
On Tue, Sep 22, 2026 at 4:22 AM Jann Horn <jannh@google.com> wrote:
>
> On Wed, Sep 2, 2026 at 8:47 AM Chen Linxuan <me@black-desk.cn> wrote:
> > On Mon, Aug 31, 2026 at 4:49 PM Christian Brauner <brauner@kernel.org> wrote:
> > > On 2026-08-31 10:59 +0800, Chen Linxuan wrote:
> > > > Obtaining a target task's executable, working directory, or root
> > > > currently requires walking procfs symlinks such as /proc/<pid>/exe,
> > > > /proc/<pid>/cwd, and /proc/<pid>/root. That makes the operation depend
> > > > on procfs being mounted and visible to the caller, even when it already
> > > > holds a pidfd for the target.
> > > >
> > > > This series adds PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT. Each
> > > > ioctl takes no argument and returns a close-on-exec O_PATH file
> > > > descriptor referencing the corresponding task path. The new ioctls use
> > > > the same ptrace permission check and nonzero-argument rejection as the
> > > > existing pidfd namespace ioctls.
> > > >
> > > > The target task is sampled while holding its exec_update_lock. This
> > > > keeps the access decision and the task-state read in the same exec
> > > > critical section, preventing a concurrent execve() from changing the
> > > > credentials or target state between the check and the use.
> > > >
> > > > The first patch factors out helpers for acquiring referenced task paths
> > > > and reuses them in procfs and AppArmor. The second patch introduces
> > > > scoped cleanup for privileged pidfd task access and separates namespace
> > > > lookup from namespace fd creation. The final patch uses these pieces to
> > > > implement the three new ioctls.
> > > >
> > > > Signed-off-by: Chen Linxuan <me@black-desk.cn>
> > > > ---
> > >
> > > I really have difficulties forming an opinion on this. So this sounds
> > > very useful but it has implications.
> > >
> > > Right now, pidfd ioctls are available even in situations where the task
> > > in question would not be accessible via procfs, e.g., when procfs is
> > > mounted with "hidepid" options or similar. So this would expand the
> >
> > One point regarding hidepid: for callers that can pass
> > PTRACE_MODE_READ_FSCREDS, hidepid does not provide an additional
> > restriction. With hidepid=1 or hidepid=2, has_pid_permissions() falls
> > back to ptrace_may_access(..., PTRACE_MODE_READ_FSCREDS), and
> > hidepid=ptraceable uses that check directly. In addition, the
> > /proc/<pid>/{exe,cwd,root} links independently perform the same
> > PTRACE_MODE_READ_FSCREDS check in call_proc_get_link(), regardless of
> > the hidepid mode.
> >
> > So for these specific path lookups, hidepid does not block a caller who
> > would already pass the check used by the proposed pidfd ioctls.
>
> Agreed, I think with regards to hidepid there should be no issue here.
>
> If I try to come up with scenarios in which this could introduce
> additional danger, the main one I can think of would be: A task T1 is
> running inside a pid namespace and has a unix domain socket connection
> to a task T2 outside the namespace, with both running as the same
> EUID; T1 sets SO_PASSPIDFD on its socket to obtain a pidfd pointing to
Does SO_PEERPIDFD matter too?
> T2 on the next message sent by T2, then T1 uses that to get access to
> the mount namespace of T2.
>
> Christian, is there some mechanism that already protects against using
> something like SO_PASSPIDFD to get a pidfd to a process in a parent
> namespace?
> Otherwise, should we add something like a "pid_vnr(pid) != 0" check
> either in these new operations or in SO_PASSPIDFD?
>
On Tue, Sep 22, 2026 at 8:08 AM Chen Linxuan <me@black-desk.cn> wrote:
> On Tue, Sep 22, 2026 at 4:22 AM Jann Horn <jannh@google.com> wrote:
> > On Wed, Sep 2, 2026 at 8:47 AM Chen Linxuan <me@black-desk.cn> wrote:
> > > On Mon, Aug 31, 2026 at 4:49 PM Christian Brauner <brauner@kernel.org> wrote:
> > > > On 2026-08-31 10:59 +0800, Chen Linxuan wrote:
> > > > > Obtaining a target task's executable, working directory, or root
> > > > > currently requires walking procfs symlinks such as /proc/<pid>/exe,
> > > > > /proc/<pid>/cwd, and /proc/<pid>/root. That makes the operation depend
> > > > > on procfs being mounted and visible to the caller, even when it already
> > > > > holds a pidfd for the target.
> > > > >
> > > > > This series adds PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT. Each
> > > > > ioctl takes no argument and returns a close-on-exec O_PATH file
> > > > > descriptor referencing the corresponding task path. The new ioctls use
> > > > > the same ptrace permission check and nonzero-argument rejection as the
> > > > > existing pidfd namespace ioctls.
> > > > >
> > > > > The target task is sampled while holding its exec_update_lock. This
> > > > > keeps the access decision and the task-state read in the same exec
> > > > > critical section, preventing a concurrent execve() from changing the
> > > > > credentials or target state between the check and the use.
> > > > >
> > > > > The first patch factors out helpers for acquiring referenced task paths
> > > > > and reuses them in procfs and AppArmor. The second patch introduces
> > > > > scoped cleanup for privileged pidfd task access and separates namespace
> > > > > lookup from namespace fd creation. The final patch uses these pieces to
> > > > > implement the three new ioctls.
> > > > >
> > > > > Signed-off-by: Chen Linxuan <me@black-desk.cn>
> > > > > ---
> > > >
> > > > I really have difficulties forming an opinion on this. So this sounds
> > > > very useful but it has implications.
> > > >
> > > > Right now, pidfd ioctls are available even in situations where the task
> > > > in question would not be accessible via procfs, e.g., when procfs is
> > > > mounted with "hidepid" options or similar. So this would expand the
> > >
> > > One point regarding hidepid: for callers that can pass
> > > PTRACE_MODE_READ_FSCREDS, hidepid does not provide an additional
> > > restriction. With hidepid=1 or hidepid=2, has_pid_permissions() falls
> > > back to ptrace_may_access(..., PTRACE_MODE_READ_FSCREDS), and
> > > hidepid=ptraceable uses that check directly. In addition, the
> > > /proc/<pid>/{exe,cwd,root} links independently perform the same
> > > PTRACE_MODE_READ_FSCREDS check in call_proc_get_link(), regardless of
> > > the hidepid mode.
> > >
> > > So for these specific path lookups, hidepid does not block a caller who
> > > would already pass the check used by the proposed pidfd ioctls.
> >
> > Agreed, I think with regards to hidepid there should be no issue here.
> >
> > If I try to come up with scenarios in which this could introduce
> > additional danger, the main one I can think of would be: A task T1 is
> > running inside a pid namespace and has a unix domain socket connection
> > to a task T2 outside the namespace, with both running as the same
> > EUID; T1 sets SO_PASSPIDFD on its socket to obtain a pidfd pointing to
>
> Does SO_PEERPIDFD matter too?
Yeah, I think that would have equivalent impact.
© 2016 - 2026 Red Hat, Inc.