* [PATCH v3 0/4] pidfd: add task path ioctls
@ 2026-10-10 10:20 ` Chen Linxuan
0 siblings, 0 replies; 14+ messages in thread
From: Chen Linxuan @ 2026-10-10 10:20 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Kees Cook, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn,
Jann Horn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, linux-api, Chen Linxuan
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.
Since pidfds can cross pid namespace boundaries, the first patch
restricts the existing namespace ioctls to targets visible in the
caller's active pid namespace, failing with -EREMOTE like
PIDFD_GET_INFO. The second patch factors out helpers for acquiring
referenced task paths and reuses them in procfs and AppArmor. The
third patch introduces scoped cleanup for privileged pidfd task
access, carries the visibility check into the shared task acquisition
helper, and separates namespace lookup from namespace fd creation.
The final patch uses these pieces to implement the three new ioctls.
---
Changes in v3:
- Restrict the pidfd task access ioctls to targets visible in the
caller's pid namespace, failing with -EREMOTE consistent with
PIDFD_GET_INFO, following review feedback from Jann Horn and
Christian Brauner.
- Document that get_task_exe_path() leaves the output path unchanged
on failure, as suggested by Jann Horn.
- Link to v2: https://patch.msgid.link/20260831-pidfd-get-paths-v2-0-c59ea6a21b72@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>
To: Jann Horn <jannh@google.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
Cc: linux-api@vger.kernel.org
---
Chen Linxuan (4):
pidfd: restrict task access ioctls to the caller's pid namespace
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 | 158 +++++++++++++++++++++++++++++++++------------
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 | 22 +++++++
security/apparmor/task.c | 12 +---
8 files changed, 198 insertions(+), 83 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260819-pidfd-get-paths-59640d8cd1ee
Best regards,
--
Chen Linxuan <me@black-desk.cn>
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v3 1/4] pidfd: restrict task access ioctls to the caller's pid namespace
2026-10-10 10:20 ` Chen Linxuan
@ 2026-10-10 10:20 ` Chen Linxuan
-1 siblings, 0 replies; 14+ messages in thread
From: Chen Linxuan via B4 Relay @ 2026-10-10 10:20 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Kees Cook, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn,
Jann Horn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, linux-api, Chen Linxuan
From: Chen Linxuan <me@black-desk.cn>
pidfds can cross pid namespace boundaries: a task may obtain one for a
process outside of its own pid namespace hierarchy through SO_PASSPIDFD
or SO_PEERPIDFD, via SCM_RIGHTS, or through file descriptor inheritance.
The namespace ioctls gate access with a ptrace check against the
target's credentials only. That check does not account for the pid
namespace relationship between the caller and the target, so a task
inside a pid namespace can use a pidfd obtained across that boundary to
acquire namespace file descriptors of a task outside of its hierarchy.
procfs exposes the equivalent information only for tasks visible in the
reader's pid namespace, and PIDFD_GET_INFO already answers with -EREMOTE
for targets outside the caller's hierarchy. Extend that policy to the
namespace ioctls: fail with -EREMOTE unless the target is visible in the
caller's active pid namespace. Callers in ancestor pid namespaces are
unaffected.
Unlike PIDFD_GET_INFO, the visibility check is performed after the task
lookup here, so reaped targets outside of the caller's pid namespace
hierarchy answer -ESRCH instead of -EREMOTE.
Link: https://lore.kernel.org/all/CAG48ez1T5-tUHVK_iHsgUtWa-inVdrZxA3E-ZCW0iENJvasKfg@mail.gmail.com
Link: https://lore.kernel.org/all/20260925-gewischt-auftrag-tierzucht-c153cb3f9f74@brauner
Suggested-by: Jann Horn <jannh@google.com>
Suggested-by: Christian Brauner <brauner@kernel.org>
Assisted-by: LLM
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
fs/pidfs.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index a6a643f15d08..791f7efceabe 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -556,6 +556,14 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (arg)
return -EINVAL;
+ /*
+ * Require the target to be visible in the caller's pid namespace
+ * for operations that grant access to its resources, mirroring
+ * procfs and PIDFD_GET_INFO.
+ */
+ if (!pid_in_current_pidns(pidfd_pid(file)))
+ return -EREMOTE;
+
/*
* We're trying to open a file descriptor to the namespace so perform a
* filesystem cred ptrace check. Hold @task's exec_update_lock for the
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 1/4] pidfd: restrict task access ioctls to the caller's pid namespace
@ 2026-10-10 10:20 ` Chen Linxuan
0 siblings, 0 replies; 14+ messages in thread
From: Chen Linxuan @ 2026-10-10 10:20 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Kees Cook, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn,
Jann Horn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, linux-api, Chen Linxuan
pidfds can cross pid namespace boundaries: a task may obtain one for a
process outside of its own pid namespace hierarchy through SO_PASSPIDFD
or SO_PEERPIDFD, via SCM_RIGHTS, or through file descriptor inheritance.
The namespace ioctls gate access with a ptrace check against the
target's credentials only. That check does not account for the pid
namespace relationship between the caller and the target, so a task
inside a pid namespace can use a pidfd obtained across that boundary to
acquire namespace file descriptors of a task outside of its hierarchy.
procfs exposes the equivalent information only for tasks visible in the
reader's pid namespace, and PIDFD_GET_INFO already answers with -EREMOTE
for targets outside the caller's hierarchy. Extend that policy to the
namespace ioctls: fail with -EREMOTE unless the target is visible in the
caller's active pid namespace. Callers in ancestor pid namespaces are
unaffected.
Unlike PIDFD_GET_INFO, the visibility check is performed after the task
lookup here, so reaped targets outside of the caller's pid namespace
hierarchy answer -ESRCH instead of -EREMOTE.
Link: https://lore.kernel.org/all/CAG48ez1T5-tUHVK_iHsgUtWa-inVdrZxA3E-ZCW0iENJvasKfg@mail.gmail.com
Link: https://lore.kernel.org/all/20260925-gewischt-auftrag-tierzucht-c153cb3f9f74@brauner
Suggested-by: Jann Horn <jannh@google.com>
Suggested-by: Christian Brauner <brauner@kernel.org>
Assisted-by: LLM
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
fs/pidfs.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index a6a643f15d08..791f7efceabe 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -556,6 +556,14 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (arg)
return -EINVAL;
+ /*
+ * Require the target to be visible in the caller's pid namespace
+ * for operations that grant access to its resources, mirroring
+ * procfs and PIDFD_GET_INFO.
+ */
+ if (!pid_in_current_pidns(pidfd_pid(file)))
+ return -EREMOTE;
+
/*
* We're trying to open a file descriptor to the namespace so perform a
* filesystem cred ptrace check. Hold @task's exec_update_lock for the
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 1/4] pidfd: restrict task access ioctls to the caller's pid namespace
2026-10-10 10:20 ` Chen Linxuan
(?)
@ 2026-10-10 10:27 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-10 10:27 UTC (permalink / raw)
Cc: linux-security-module
> From: Chen Linxuan <me@black-desk.cn>
>
> pidfds can cross pid namespace boundaries: a task may obtain one for a
> process outside of its own pid namespace hierarchy through SO_PASSPIDFD
> or SO_PEERPIDFD, via SCM_RIGHTS, or through file descriptor inheritance.
>
> The namespace ioctls gate access with a ptrace check against the
> target's credentials only. That check does not account for the pid
> namespace relationship between the caller and the target, so a task
> inside a pid namespace can use a pidfd obtained across that boundary to
> acquire namespace file descriptors of a task outside of its hierarchy.
>
> procfs exposes the equivalent information only for tasks visible in the
> reader's pid namespace, and PIDFD_GET_INFO already answers with -EREMOTE
> for targets outside the caller's hierarchy. Extend that policy to the
> namespace ioctls: fail with -EREMOTE unless the target is visible in the
> caller's active pid namespace. Callers in ancestor pid namespaces are
> unaffected.
>
> Unlike PIDFD_GET_INFO, the visibility check is performed after the task
> lookup here, so reaped targets outside of the caller's pid namespace
> hierarchy answer -ESRCH instead of -EREMOTE.
>
> Link: https://lore.kernel.org/all/CAG48ez1T5-tUHVK_iHsgUtWa-inVdrZxA3E-ZCW0iENJvasKfg@mail.gmail.com
> Link: https://lore.kernel.org/all/20260925-gewischt-auftrag-tierzucht-c153cb3f9f74@brauner
> Suggested-by: Jann Horn <jannh@google.com>
> Suggested-by: Christian Brauner <brauner@kernel.org>
> Assisted-by: LLM
> Signed-off-by: Chen Linxuan <me@black-desk.cn>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010-pidfd-get-paths-v3-0-f245440b2b30@black-desk.cn?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 2/4] fs: Introduce task path helpers
2026-10-10 10:20 ` Chen Linxuan
@ 2026-10-10 10:20 ` Chen Linxuan
-1 siblings, 0 replies; 14+ messages in thread
From: Chen Linxuan via B4 Relay @ 2026-10-10 10:20 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Kees Cook, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn,
Jann Horn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, linux-api, Chen Linxuan
From: Chen Linxuan <me@black-desk.cn>
Introduce helpers that acquire a referenced struct path for a task's
executable, root, and working directory. Reuse them for procfs task
links and AppArmor executable-path handling instead of duplicating file
and path reference handling at each call site.
Assisted-by: LLM
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
fs/fs_struct.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
fs/proc/base.c | 34 ++--------------------------------
include/linux/fs_struct.h | 3 +++
include/linux/mm.h | 1 +
kernel/fork.c | 22 ++++++++++++++++++++++
security/apparmor/task.c | 12 +++---------
6 files changed, 75 insertions(+), 41 deletions(-)
diff --git a/fs/fs_struct.c b/fs/fs_struct.c
index 34699f3b6f88..5c772896260a 100644
--- a/fs/fs_struct.c
+++ b/fs/fs_struct.c
@@ -10,6 +10,50 @@
#include "internal.h"
#include "mount.h"
+/**
+ * get_task_root - acquire a reference to the task's root path
+ * @task: The task.
+ * @root: The task's root path.
+ *
+ * Returns 0 if the task has a root path, or -ENOENT if it does not. The
+ * caller must release the path through path_put() on success.
+ */
+int get_task_root(struct task_struct *task, struct path *root)
+{
+ int ret = -ENOENT;
+
+ task_lock(task);
+ if (task->real_fs) {
+ get_fs_root(task->real_fs, root);
+ ret = 0;
+ }
+ task_unlock(task);
+
+ return ret;
+}
+
+/**
+ * get_task_pwd - acquire a reference to the task's working directory
+ * @task: The task.
+ * @pwd: The task's working directory.
+ *
+ * Returns 0 if the task has a working directory, or -ENOENT if it does not.
+ * The caller must release the path through path_put() on success.
+ */
+int get_task_pwd(struct task_struct *task, struct path *pwd)
+{
+ int ret = -ENOENT;
+
+ task_lock(task);
+ if (task->real_fs) {
+ get_fs_pwd(task->real_fs, pwd);
+ ret = 0;
+ }
+ task_unlock(task);
+
+ return ret;
+}
+
/*
* Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values.
* It can block.
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 6a39de424f62..7e2c0538323c 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -206,31 +206,10 @@ static unsigned int __init pid_entry_nlink(const struct pid_entry *entries,
return count;
}
-static int get_task_root(struct task_struct *task, struct path *root)
-{
- int result = -ENOENT;
-
- task_lock(task);
- if (task->real_fs) {
- get_fs_root(task->real_fs, root);
- result = 0;
- }
- task_unlock(task);
- return result;
-}
-
static int proc_cwd_link(struct dentry *dentry, struct path *path,
struct task_struct *task)
{
- int result = -ENOENT;
-
- task_lock(task);
- if (task->real_fs) {
- get_fs_pwd(task->real_fs, path);
- result = 0;
- }
- task_unlock(task);
- return result;
+ return get_task_pwd(task, path);
}
static int proc_root_link(struct dentry *dentry, struct path *path,
@@ -1761,16 +1740,7 @@ static const struct file_operations proc_pid_set_comm_operations = {
static int proc_exe_link(struct dentry *dentry, struct path *exe_path,
struct task_struct *task)
{
- struct file *exe_file;
-
- exe_file = get_task_exe_file(task);
- if (exe_file) {
- *exe_path = exe_file->f_path;
- path_get(&exe_file->f_path);
- fput(exe_file);
- return 0;
- } else
- return -ENOENT;
+ return get_task_exe_path(task, exe_path);
}
static int call_proc_get_link(struct dentry *dentry, struct inode *inode, struct path *path_out)
diff --git a/include/linux/fs_struct.h b/include/linux/fs_struct.h
index 97eef8d3863d..fb725707754d 100644
--- a/include/linux/fs_struct.h
+++ b/include/linux/fs_struct.h
@@ -42,6 +42,9 @@ static inline void get_fs_pwd(struct fs_struct *fs, struct path *pwd)
read_sequnlock_excl(&fs->seq);
}
+int get_task_root(struct task_struct *task, struct path *root);
+int get_task_pwd(struct task_struct *task, struct path *pwd);
+
struct fs_struct *switch_fs_struct(struct fs_struct *new_fs);
extern bool current_chrooted(void);
diff --git a/include/linux/mm.h b/include/linux/mm.h
index dd09c438fa23..a65f24b2e65b 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -4239,6 +4239,7 @@ extern int set_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file);
extern int replace_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file);
extern struct file *get_mm_exe_file(struct mm_struct *mm);
extern struct file *get_task_exe_file(struct task_struct *task);
+int get_task_exe_path(struct task_struct *task, struct path *exe_path);
extern void vm_stat_account(struct mm_struct *, vm_flags_t, long npages);
diff --git a/kernel/fork.c b/kernel/fork.c
index 416758c8a3d4..06b3d1b24773 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1371,6 +1371,28 @@ struct file *get_task_exe_file(struct task_struct *task)
return exe_file;
}
+/**
+ * get_task_exe_path - acquire a reference to the task's executable path
+ * @task: The task.
+ * @exe_path: The task's executable path.
+ *
+ * Returns 0 if the task has an executable path, or -ENOENT if it does not.
+ * On failure, @exe_path is left unchanged. The caller must release the path
+ * through path_put() on success.
+ */
+int get_task_exe_path(struct task_struct *task, struct path *exe_path)
+{
+ struct file *exe_file = get_task_exe_file(task);
+
+ if (!exe_file)
+ return -ENOENT;
+
+ *exe_path = exe_file->f_path;
+ path_get(exe_path);
+ fput(exe_file);
+ return 0;
+}
+
/**
* get_task_mm - acquire a reference to the task's mm
* @task: The task.
diff --git a/security/apparmor/task.c b/security/apparmor/task.c
index e16ff4130bc2..05cba261dd34 100644
--- a/security/apparmor/task.c
+++ b/security/apparmor/task.c
@@ -13,6 +13,7 @@
*/
#include <linux/gfp.h>
+#include <linux/mm.h>
#include <linux/ptrace.h>
#include <linux/task_work.h>
@@ -330,22 +331,15 @@ int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer,
static const char *get_current_exe_path(char *buffer, int buffer_size)
{
- struct file *exe_file;
- struct path p;
+ struct path p __free(path_put) = {};
const char *path_str;
- exe_file = get_task_exe_file(current);
- if (!exe_file)
+ if (get_task_exe_path(current, &p))
return ERR_PTR(-ENOENT);
- p = exe_file->f_path;
- path_get(&p);
if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL))
path_str = ERR_PTR(-ENOMEM);
- fput(exe_file);
- path_put(&p);
-
return path_str;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v3 2/4] fs: Introduce task path helpers
@ 2026-10-10 10:20 ` Chen Linxuan
0 siblings, 0 replies; 14+ messages in thread
From: Chen Linxuan @ 2026-10-10 10:20 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Kees Cook, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn,
Jann Horn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, linux-api, Chen Linxuan
Introduce helpers that acquire a referenced struct path for a task's
executable, root, and working directory. Reuse them for procfs task
links and AppArmor executable-path handling instead of duplicating file
and path reference handling at each call site.
Assisted-by: LLM
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
fs/fs_struct.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
fs/proc/base.c | 34 ++--------------------------------
include/linux/fs_struct.h | 3 +++
include/linux/mm.h | 1 +
kernel/fork.c | 22 ++++++++++++++++++++++
security/apparmor/task.c | 12 +++---------
6 files changed, 75 insertions(+), 41 deletions(-)
diff --git a/fs/fs_struct.c b/fs/fs_struct.c
index 34699f3b6f88..5c772896260a 100644
--- a/fs/fs_struct.c
+++ b/fs/fs_struct.c
@@ -10,6 +10,50 @@
#include "internal.h"
#include "mount.h"
+/**
+ * get_task_root - acquire a reference to the task's root path
+ * @task: The task.
+ * @root: The task's root path.
+ *
+ * Returns 0 if the task has a root path, or -ENOENT if it does not. The
+ * caller must release the path through path_put() on success.
+ */
+int get_task_root(struct task_struct *task, struct path *root)
+{
+ int ret = -ENOENT;
+
+ task_lock(task);
+ if (task->real_fs) {
+ get_fs_root(task->real_fs, root);
+ ret = 0;
+ }
+ task_unlock(task);
+
+ return ret;
+}
+
+/**
+ * get_task_pwd - acquire a reference to the task's working directory
+ * @task: The task.
+ * @pwd: The task's working directory.
+ *
+ * Returns 0 if the task has a working directory, or -ENOENT if it does not.
+ * The caller must release the path through path_put() on success.
+ */
+int get_task_pwd(struct task_struct *task, struct path *pwd)
+{
+ int ret = -ENOENT;
+
+ task_lock(task);
+ if (task->real_fs) {
+ get_fs_pwd(task->real_fs, pwd);
+ ret = 0;
+ }
+ task_unlock(task);
+
+ return ret;
+}
+
/*
* Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values.
* It can block.
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 6a39de424f62..7e2c0538323c 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -206,31 +206,10 @@ static unsigned int __init pid_entry_nlink(const struct pid_entry *entries,
return count;
}
-static int get_task_root(struct task_struct *task, struct path *root)
-{
- int result = -ENOENT;
-
- task_lock(task);
- if (task->real_fs) {
- get_fs_root(task->real_fs, root);
- result = 0;
- }
- task_unlock(task);
- return result;
-}
-
static int proc_cwd_link(struct dentry *dentry, struct path *path,
struct task_struct *task)
{
- int result = -ENOENT;
-
- task_lock(task);
- if (task->real_fs) {
- get_fs_pwd(task->real_fs, path);
- result = 0;
- }
- task_unlock(task);
- return result;
+ return get_task_pwd(task, path);
}
static int proc_root_link(struct dentry *dentry, struct path *path,
@@ -1761,16 +1740,7 @@ static const struct file_operations proc_pid_set_comm_operations = {
static int proc_exe_link(struct dentry *dentry, struct path *exe_path,
struct task_struct *task)
{
- struct file *exe_file;
-
- exe_file = get_task_exe_file(task);
- if (exe_file) {
- *exe_path = exe_file->f_path;
- path_get(&exe_file->f_path);
- fput(exe_file);
- return 0;
- } else
- return -ENOENT;
+ return get_task_exe_path(task, exe_path);
}
static int call_proc_get_link(struct dentry *dentry, struct inode *inode, struct path *path_out)
diff --git a/include/linux/fs_struct.h b/include/linux/fs_struct.h
index 97eef8d3863d..fb725707754d 100644
--- a/include/linux/fs_struct.h
+++ b/include/linux/fs_struct.h
@@ -42,6 +42,9 @@ static inline void get_fs_pwd(struct fs_struct *fs, struct path *pwd)
read_sequnlock_excl(&fs->seq);
}
+int get_task_root(struct task_struct *task, struct path *root);
+int get_task_pwd(struct task_struct *task, struct path *pwd);
+
struct fs_struct *switch_fs_struct(struct fs_struct *new_fs);
extern bool current_chrooted(void);
diff --git a/include/linux/mm.h b/include/linux/mm.h
index dd09c438fa23..a65f24b2e65b 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -4239,6 +4239,7 @@ extern int set_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file);
extern int replace_mm_exe_file(struct mm_struct *mm, struct file *new_exe_file);
extern struct file *get_mm_exe_file(struct mm_struct *mm);
extern struct file *get_task_exe_file(struct task_struct *task);
+int get_task_exe_path(struct task_struct *task, struct path *exe_path);
extern void vm_stat_account(struct mm_struct *, vm_flags_t, long npages);
diff --git a/kernel/fork.c b/kernel/fork.c
index 416758c8a3d4..06b3d1b24773 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1371,6 +1371,28 @@ struct file *get_task_exe_file(struct task_struct *task)
return exe_file;
}
+/**
+ * get_task_exe_path - acquire a reference to the task's executable path
+ * @task: The task.
+ * @exe_path: The task's executable path.
+ *
+ * Returns 0 if the task has an executable path, or -ENOENT if it does not.
+ * On failure, @exe_path is left unchanged. The caller must release the path
+ * through path_put() on success.
+ */
+int get_task_exe_path(struct task_struct *task, struct path *exe_path)
+{
+ struct file *exe_file = get_task_exe_file(task);
+
+ if (!exe_file)
+ return -ENOENT;
+
+ *exe_path = exe_file->f_path;
+ path_get(exe_path);
+ fput(exe_file);
+ return 0;
+}
+
/**
* get_task_mm - acquire a reference to the task's mm
* @task: The task.
diff --git a/security/apparmor/task.c b/security/apparmor/task.c
index e16ff4130bc2..05cba261dd34 100644
--- a/security/apparmor/task.c
+++ b/security/apparmor/task.c
@@ -13,6 +13,7 @@
*/
#include <linux/gfp.h>
+#include <linux/mm.h>
#include <linux/ptrace.h>
#include <linux/task_work.h>
@@ -330,22 +331,15 @@ int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer,
static const char *get_current_exe_path(char *buffer, int buffer_size)
{
- struct file *exe_file;
- struct path p;
+ struct path p __free(path_put) = {};
const char *path_str;
- exe_file = get_task_exe_file(current);
- if (!exe_file)
+ if (get_task_exe_path(current, &p))
return ERR_PTR(-ENOENT);
- p = exe_file->f_path;
- path_get(&p);
if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL))
path_str = ERR_PTR(-ENOMEM);
- fput(exe_file);
- path_put(&p);
-
return path_str;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v3 2/4] fs: Introduce task path helpers
2026-10-10 10:20 ` Chen Linxuan
(?)
@ 2026-10-10 10:30 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-10 10:30 UTC (permalink / raw)
Cc: linux-security-module
> From: Chen Linxuan <me@black-desk.cn>
>
> Introduce helpers that acquire a referenced struct path for a task's
> executable, root, and working directory. Reuse them for procfs task
> links and AppArmor executable-path handling instead of duplicating file
> and path reference handling at each call site.
>
> Assisted-by: LLM
> Signed-off-by: Chen Linxuan <me@black-desk.cn>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010-pidfd-get-paths-v3-0-f245440b2b30@black-desk.cn?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 3/4] pidfd: Use scoped cleanup for task access
2026-10-10 10:20 ` Chen Linxuan
@ 2026-10-10 10:20 ` Chen Linxuan
-1 siblings, 0 replies; 14+ messages in thread
From: Chen Linxuan via B4 Relay @ 2026-10-10 10:20 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Kees Cook, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn,
Jann Horn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, linux-api, Chen Linxuan
From: Chen Linxuan <me@black-desk.cn>
Split namespace acquisition from namespace fd creation and represent a
privileged target task as a scoped class that owns both the task
reference and exec_update_lock. Use the class for namespace lookups so
ptrace access checks and task state reads remain tied to the same exec
critical section, while open_namespace() stays outside the lock.
Carry the pid namespace visibility check into the helper so that all
users of the class enforce it.
Assisted-by: LLM
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
fs/pidfs.c | 120 +++++++++++++++++++++++++++++++++++++------------------------
1 file changed, 74 insertions(+), 46 deletions(-)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 791f7efceabe..82ab4dee55e5 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -527,70 +527,76 @@ static bool pidfs_ioctl_valid(unsigned int cmd)
return false;
}
-static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+static inline void pidfd_put_task_locked(struct task_struct *task)
{
- struct task_struct *task __free(put_task) = NULL;
- struct nsproxy *nsp __free(put_nsproxy) = NULL;
- struct ns_common *ns_common = NULL;
- int error;
-
- if (!pidfs_ioctl_valid(cmd))
- return -ENOIOCTLCMD;
-
- if (cmd == FS_IOC_GETVERSION) {
- if (!arg)
- return -EINVAL;
-
- __u32 __user *argp = (__u32 __user *)arg;
- return put_user(file_inode(file)->i_generation, argp);
+ if (!IS_ERR_OR_NULL(task)) {
+ up_read(&task->signal->exec_update_lock);
+ put_task_struct(task);
}
+}
- /* Extensible IOCTL that does not open namespace FDs, take a shortcut */
- if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
- return pidfd_info(file, cmd, arg);
+/*
+ * Return @pid's task with @task's exec_update_lock held, or -EREMOTE if the
+ * task is not visible in the caller's pid namespace. The ptrace check and
+ * the callers' task state lookup must be performed while the lock is held
+ * so that they cannot race with a concurrent execve().
+ */
+static struct task_struct *pidfd_get_task_locked(struct pid *pid,
+ unsigned long arg)
+{
+ struct task_struct *task __free(put_task) =
+ get_pid_task(pid, PIDTYPE_PID);
+ int error;
- task = get_pid_task(pidfd_pid(file), PIDTYPE_PID);
if (!task)
- return -ESRCH;
+ return ERR_PTR(-ESRCH);
if (arg)
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
/*
* Require the target to be visible in the caller's pid namespace
* for operations that grant access to its resources, mirroring
* procfs and PIDFD_GET_INFO.
*/
- if (!pid_in_current_pidns(pidfd_pid(file)))
- return -EREMOTE;
+ if (!pid_in_current_pidns(pid))
+ return ERR_PTR(-EREMOTE);
- /*
- * We're trying to open a file descriptor to the namespace so perform a
- * filesystem cred ptrace check. Hold @task's exec_update_lock for the
- * duration of the ptrace check and the namespace lookup so that the
- * credentials used for the access decision match those of @task at the
- * time its namespace is read, preventing a concurrent execve() from
- * swapping the task's credentials in between the check and the use. We
- * mirror nsfs behavior.
- */
error = down_read_killable(&task->signal->exec_update_lock);
if (error)
- return error;
+ return ERR_PTR(error);
if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
- error = -EACCES;
- goto out_unlock;
+ up_read(&task->signal->exec_update_lock);
+ return ERR_PTR(-EACCES);
}
+ return_ptr(task);
+}
+
+DEFINE_CLASS(pidfd_task_locked, struct task_struct *,
+ pidfd_put_task_locked(_T),
+ pidfd_get_task_locked(pid, arg),
+ struct pid *pid, unsigned long arg)
+
+static struct ns_common *pidfd_get_namespace(struct pid *pid,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ struct nsproxy *nsp __free(put_nsproxy) = NULL;
+ struct ns_common *ns_common = NULL;
+
+ CLASS(pidfd_task_locked, task)(pid, arg);
+ if (IS_ERR(task))
+ return ERR_CAST(task);
+
scoped_guard(task_lock, task) {
nsp = task->nsproxy;
if (nsp)
get_nsproxy(nsp);
}
- if (!nsp) {
- error = -ESRCH; /* just pretend it didn't exist */
- goto out_unlock;
- }
+ if (!nsp)
+ return ERR_PTR(-ESRCH); /* just pretend it didn't exist */
switch (cmd) {
/* Namespaces that hang of nsproxy. */
@@ -672,16 +678,38 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
#endif
break;
default:
- error = -ENOIOCTLCMD;
+ return ERR_PTR(-ENOIOCTLCMD);
}
- if (!error && !ns_common)
- error = -EOPNOTSUPP;
+ if (!ns_common)
+ return ERR_PTR(-EOPNOTSUPP);
-out_unlock:
- up_read(&task->signal->exec_update_lock);
- if (error)
- return error;
+ return ns_common;
+}
+
+static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct ns_common *ns_common = NULL;
+
+ if (!pidfs_ioctl_valid(cmd))
+ return -ENOIOCTLCMD;
+
+ if (cmd == FS_IOC_GETVERSION) {
+ if (!arg)
+ return -EINVAL;
+
+ __u32 __user *argp = (__u32 __user *)arg;
+
+ return put_user(file_inode(file)->i_generation, argp);
+ }
+
+ /* Extensible IOCTL that does not open namespace FDs, take a shortcut */
+ if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
+ return pidfd_info(file, cmd, arg);
+
+ ns_common = pidfd_get_namespace(pidfd_pid(file), cmd, arg);
+ if (IS_ERR(ns_common))
+ return PTR_ERR(ns_common);
/* open_namespace() unconditionally consumes the reference */
return open_namespace(ns_common);
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v3 3/4] pidfd: Use scoped cleanup for task access
@ 2026-10-10 10:20 ` Chen Linxuan
0 siblings, 0 replies; 14+ messages in thread
From: Chen Linxuan @ 2026-10-10 10:20 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Kees Cook, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn,
Jann Horn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, linux-api, Chen Linxuan
Split namespace acquisition from namespace fd creation and represent a
privileged target task as a scoped class that owns both the task
reference and exec_update_lock. Use the class for namespace lookups so
ptrace access checks and task state reads remain tied to the same exec
critical section, while open_namespace() stays outside the lock.
Carry the pid namespace visibility check into the helper so that all
users of the class enforce it.
Assisted-by: LLM
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
fs/pidfs.c | 120 +++++++++++++++++++++++++++++++++++++------------------------
1 file changed, 74 insertions(+), 46 deletions(-)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 791f7efceabe..82ab4dee55e5 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -527,70 +527,76 @@ static bool pidfs_ioctl_valid(unsigned int cmd)
return false;
}
-static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+static inline void pidfd_put_task_locked(struct task_struct *task)
{
- struct task_struct *task __free(put_task) = NULL;
- struct nsproxy *nsp __free(put_nsproxy) = NULL;
- struct ns_common *ns_common = NULL;
- int error;
-
- if (!pidfs_ioctl_valid(cmd))
- return -ENOIOCTLCMD;
-
- if (cmd == FS_IOC_GETVERSION) {
- if (!arg)
- return -EINVAL;
-
- __u32 __user *argp = (__u32 __user *)arg;
- return put_user(file_inode(file)->i_generation, argp);
+ if (!IS_ERR_OR_NULL(task)) {
+ up_read(&task->signal->exec_update_lock);
+ put_task_struct(task);
}
+}
- /* Extensible IOCTL that does not open namespace FDs, take a shortcut */
- if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
- return pidfd_info(file, cmd, arg);
+/*
+ * Return @pid's task with @task's exec_update_lock held, or -EREMOTE if the
+ * task is not visible in the caller's pid namespace. The ptrace check and
+ * the callers' task state lookup must be performed while the lock is held
+ * so that they cannot race with a concurrent execve().
+ */
+static struct task_struct *pidfd_get_task_locked(struct pid *pid,
+ unsigned long arg)
+{
+ struct task_struct *task __free(put_task) =
+ get_pid_task(pid, PIDTYPE_PID);
+ int error;
- task = get_pid_task(pidfd_pid(file), PIDTYPE_PID);
if (!task)
- return -ESRCH;
+ return ERR_PTR(-ESRCH);
if (arg)
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
/*
* Require the target to be visible in the caller's pid namespace
* for operations that grant access to its resources, mirroring
* procfs and PIDFD_GET_INFO.
*/
- if (!pid_in_current_pidns(pidfd_pid(file)))
- return -EREMOTE;
+ if (!pid_in_current_pidns(pid))
+ return ERR_PTR(-EREMOTE);
- /*
- * We're trying to open a file descriptor to the namespace so perform a
- * filesystem cred ptrace check. Hold @task's exec_update_lock for the
- * duration of the ptrace check and the namespace lookup so that the
- * credentials used for the access decision match those of @task at the
- * time its namespace is read, preventing a concurrent execve() from
- * swapping the task's credentials in between the check and the use. We
- * mirror nsfs behavior.
- */
error = down_read_killable(&task->signal->exec_update_lock);
if (error)
- return error;
+ return ERR_PTR(error);
if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
- error = -EACCES;
- goto out_unlock;
+ up_read(&task->signal->exec_update_lock);
+ return ERR_PTR(-EACCES);
}
+ return_ptr(task);
+}
+
+DEFINE_CLASS(pidfd_task_locked, struct task_struct *,
+ pidfd_put_task_locked(_T),
+ pidfd_get_task_locked(pid, arg),
+ struct pid *pid, unsigned long arg)
+
+static struct ns_common *pidfd_get_namespace(struct pid *pid,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ struct nsproxy *nsp __free(put_nsproxy) = NULL;
+ struct ns_common *ns_common = NULL;
+
+ CLASS(pidfd_task_locked, task)(pid, arg);
+ if (IS_ERR(task))
+ return ERR_CAST(task);
+
scoped_guard(task_lock, task) {
nsp = task->nsproxy;
if (nsp)
get_nsproxy(nsp);
}
- if (!nsp) {
- error = -ESRCH; /* just pretend it didn't exist */
- goto out_unlock;
- }
+ if (!nsp)
+ return ERR_PTR(-ESRCH); /* just pretend it didn't exist */
switch (cmd) {
/* Namespaces that hang of nsproxy. */
@@ -672,16 +678,38 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
#endif
break;
default:
- error = -ENOIOCTLCMD;
+ return ERR_PTR(-ENOIOCTLCMD);
}
- if (!error && !ns_common)
- error = -EOPNOTSUPP;
+ if (!ns_common)
+ return ERR_PTR(-EOPNOTSUPP);
-out_unlock:
- up_read(&task->signal->exec_update_lock);
- if (error)
- return error;
+ return ns_common;
+}
+
+static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct ns_common *ns_common = NULL;
+
+ if (!pidfs_ioctl_valid(cmd))
+ return -ENOIOCTLCMD;
+
+ if (cmd == FS_IOC_GETVERSION) {
+ if (!arg)
+ return -EINVAL;
+
+ __u32 __user *argp = (__u32 __user *)arg;
+
+ return put_user(file_inode(file)->i_generation, argp);
+ }
+
+ /* Extensible IOCTL that does not open namespace FDs, take a shortcut */
+ if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
+ return pidfd_info(file, cmd, arg);
+
+ ns_common = pidfd_get_namespace(pidfd_pid(file), cmd, arg);
+ if (IS_ERR(ns_common))
+ return PTR_ERR(ns_common);
/* open_namespace() unconditionally consumes the reference */
return open_namespace(ns_common);
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v3 3/4] pidfd: Use scoped cleanup for task access
2026-10-10 10:20 ` Chen Linxuan
(?)
@ 2026-10-10 10:30 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-10 10:30 UTC (permalink / raw)
Cc: linux-security-module
> From: Chen Linxuan <me@black-desk.cn>
>
> Split namespace acquisition from namespace fd creation and represent a
> privileged target task as a scoped class that owns both the task
> reference and exec_update_lock. Use the class for namespace lookups so
> ptrace access checks and task state reads remain tied to the same exec
> critical section, while open_namespace() stays outside the lock.
>
> Carry the pid namespace visibility check into the helper so that all
> users of the class enforce it.
>
> Assisted-by: LLM
> Signed-off-by: Chen Linxuan <me@black-desk.cn>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010-pidfd-get-paths-v3-0-f245440b2b30@black-desk.cn?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v3 4/4] pidfd: Add task path ioctls
2026-10-10 10:20 ` Chen Linxuan
@ 2026-10-10 10:20 ` Chen Linxuan
-1 siblings, 0 replies; 14+ messages in thread
From: Chen Linxuan via B4 Relay @ 2026-10-10 10:20 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Kees Cook, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn,
Jann Horn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, linux-api, Chen Linxuan
From: Chen Linxuan <me@black-desk.cn>
Add PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT to return
close-on-exec O_PATH file descriptors referencing the target task's
executable, working directory, and root directory.
The ioctls acquire the target through the shared task acquisition
helper and are thus subject to the same checks as the namespace
ioctls. The target is sampled while holding exec_update_lock so that
the access check and the path read cannot race with execve().
This allows userspace to obtain stable path references from a pidfd
without requiring procfs.
Assisted-by: LLM
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
fs/pidfs.c | 38 ++++++++++++++++++++++++++++++++++++++
include/uapi/linux/pidfd.h | 7 +++++++
2 files changed, 45 insertions(+)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 82ab4dee55e5..e945c77bdc0e 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -4,6 +4,7 @@
#include <linux/exportfs.h>
#include <linux/file.h>
#include <linux/fs.h>
+#include <linux/fs_struct.h>
#include <linux/cgroup.h>
#include <linux/magic.h>
#include <linux/mount.h>
@@ -510,6 +511,9 @@ static bool pidfs_ioctl_valid(unsigned int cmd)
case PIDFD_GET_UTS_NAMESPACE:
case PIDFD_GET_USER_NAMESPACE:
case PIDFD_GET_PID_NAMESPACE:
+ case PIDFD_GET_EXE:
+ case PIDFD_GET_CWD:
+ case PIDFD_GET_ROOT:
return true;
}
@@ -687,9 +691,31 @@ static struct ns_common *pidfd_get_namespace(struct pid *pid,
return ns_common;
}
+static int pidfd_get_task_path(struct pid *pid, unsigned int cmd,
+ unsigned long arg, struct path *path)
+{
+ CLASS(pidfd_task_locked, task)(pid, arg);
+
+ if (IS_ERR(task))
+ return PTR_ERR(task);
+
+ switch (cmd) {
+ case PIDFD_GET_EXE:
+ return get_task_exe_path(task, path);
+ case PIDFD_GET_CWD:
+ return get_task_pwd(task, path);
+ case PIDFD_GET_ROOT:
+ return get_task_root(task, path);
+ }
+
+ return -EINVAL;
+}
+
static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct ns_common *ns_common = NULL;
+ struct path path __free(path_put) = {};
+ int error;
if (!pidfs_ioctl_valid(cmd))
return -ENOIOCTLCMD;
@@ -707,6 +733,18 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
return pidfd_info(file, cmd, arg);
+ switch (cmd) {
+ case PIDFD_GET_EXE:
+ case PIDFD_GET_CWD:
+ case PIDFD_GET_ROOT:
+ error = pidfd_get_task_path(pidfd_pid(file), cmd, arg, &path);
+ if (error)
+ return error;
+
+ return FD_ADD(O_CLOEXEC,
+ dentry_open(&path, O_PATH, current_cred()));
+ }
+
ns_common = pidfd_get_namespace(pidfd_pid(file), cmd, arg);
if (IS_ERR(ns_common))
return PTR_ERR(ns_common);
diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h
index 0919246a1611..95ce1819f423 100644
--- a/include/uapi/linux/pidfd.h
+++ b/include/uapi/linux/pidfd.h
@@ -121,4 +121,11 @@ struct pidfd_info {
#define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10)
#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info)
+/* Return an O_PATH file descriptor for the target task's executable. */
+#define PIDFD_GET_EXE _IO(PIDFS_IOCTL_MAGIC, 12)
+/* Return an O_PATH file descriptor for the target task's working directory. */
+#define PIDFD_GET_CWD _IO(PIDFS_IOCTL_MAGIC, 13)
+/* Return an O_PATH file descriptor for the target task's root directory. */
+#define PIDFD_GET_ROOT _IO(PIDFS_IOCTL_MAGIC, 14)
+
#endif /* _UAPI_LINUX_PIDFD_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v3 4/4] pidfd: Add task path ioctls
@ 2026-10-10 10:20 ` Chen Linxuan
0 siblings, 0 replies; 14+ messages in thread
From: Chen Linxuan @ 2026-10-10 10:20 UTC (permalink / raw)
To: Alexander Viro, Christian Brauner, Jan Kara, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Ingo Molnar, Peter Zijlstra, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Steven Rostedt, Ben Segall, Mel Gorman,
Valentin Schneider, K Prateek Nayak, Kees Cook, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn,
Jann Horn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, linux-api, Chen Linxuan
Add PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT to return
close-on-exec O_PATH file descriptors referencing the target task's
executable, working directory, and root directory.
The ioctls acquire the target through the shared task acquisition
helper and are thus subject to the same checks as the namespace
ioctls. The target is sampled while holding exec_update_lock so that
the access check and the path read cannot race with execve().
This allows userspace to obtain stable path references from a pidfd
without requiring procfs.
Assisted-by: LLM
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
fs/pidfs.c | 38 ++++++++++++++++++++++++++++++++++++++
include/uapi/linux/pidfd.h | 7 +++++++
2 files changed, 45 insertions(+)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 82ab4dee55e5..e945c77bdc0e 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -4,6 +4,7 @@
#include <linux/exportfs.h>
#include <linux/file.h>
#include <linux/fs.h>
+#include <linux/fs_struct.h>
#include <linux/cgroup.h>
#include <linux/magic.h>
#include <linux/mount.h>
@@ -510,6 +511,9 @@ static bool pidfs_ioctl_valid(unsigned int cmd)
case PIDFD_GET_UTS_NAMESPACE:
case PIDFD_GET_USER_NAMESPACE:
case PIDFD_GET_PID_NAMESPACE:
+ case PIDFD_GET_EXE:
+ case PIDFD_GET_CWD:
+ case PIDFD_GET_ROOT:
return true;
}
@@ -687,9 +691,31 @@ static struct ns_common *pidfd_get_namespace(struct pid *pid,
return ns_common;
}
+static int pidfd_get_task_path(struct pid *pid, unsigned int cmd,
+ unsigned long arg, struct path *path)
+{
+ CLASS(pidfd_task_locked, task)(pid, arg);
+
+ if (IS_ERR(task))
+ return PTR_ERR(task);
+
+ switch (cmd) {
+ case PIDFD_GET_EXE:
+ return get_task_exe_path(task, path);
+ case PIDFD_GET_CWD:
+ return get_task_pwd(task, path);
+ case PIDFD_GET_ROOT:
+ return get_task_root(task, path);
+ }
+
+ return -EINVAL;
+}
+
static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct ns_common *ns_common = NULL;
+ struct path path __free(path_put) = {};
+ int error;
if (!pidfs_ioctl_valid(cmd))
return -ENOIOCTLCMD;
@@ -707,6 +733,18 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
return pidfd_info(file, cmd, arg);
+ switch (cmd) {
+ case PIDFD_GET_EXE:
+ case PIDFD_GET_CWD:
+ case PIDFD_GET_ROOT:
+ error = pidfd_get_task_path(pidfd_pid(file), cmd, arg, &path);
+ if (error)
+ return error;
+
+ return FD_ADD(O_CLOEXEC,
+ dentry_open(&path, O_PATH, current_cred()));
+ }
+
ns_common = pidfd_get_namespace(pidfd_pid(file), cmd, arg);
if (IS_ERR(ns_common))
return PTR_ERR(ns_common);
diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h
index 0919246a1611..95ce1819f423 100644
--- a/include/uapi/linux/pidfd.h
+++ b/include/uapi/linux/pidfd.h
@@ -121,4 +121,11 @@ struct pidfd_info {
#define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10)
#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info)
+/* Return an O_PATH file descriptor for the target task's executable. */
+#define PIDFD_GET_EXE _IO(PIDFS_IOCTL_MAGIC, 12)
+/* Return an O_PATH file descriptor for the target task's working directory. */
+#define PIDFD_GET_CWD _IO(PIDFS_IOCTL_MAGIC, 13)
+/* Return an O_PATH file descriptor for the target task's root directory. */
+#define PIDFD_GET_ROOT _IO(PIDFS_IOCTL_MAGIC, 14)
+
#endif /* _UAPI_LINUX_PIDFD_H */
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v3 4/4] pidfd: Add task path ioctls
2026-10-10 10:20 ` Chen Linxuan
(?)
@ 2026-10-10 10:32 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-10 10:32 UTC (permalink / raw)
Cc: linux-security-module
> From: Chen Linxuan <me@black-desk.cn>
>
> Add PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT to return
> close-on-exec O_PATH file descriptors referencing the target task's
> executable, working directory, and root directory.
>
> The ioctls acquire the target through the shared task acquisition
> helper and are thus subject to the same checks as the namespace
> ioctls. The target is sampled while holding exec_update_lock so that
> the access check and the path read cannot race with execve().
>
> This allows userspace to obtain stable path references from a pidfd
> without requiring procfs.
>
> Assisted-by: LLM
> Signed-off-by: Chen Linxuan <me@black-desk.cn>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010-pidfd-get-paths-v3-0-f245440b2b30@black-desk.cn?part=4
^ permalink raw reply [flat|nested] 14+ messages in thread