* [PATCH v4 1/3] fs: Introduce task path helpers
2026-10-27 8:46 [PATCH v4 0/3] pidfd: add task path ioctls Chen Linxuan via B4 Relay
@ 2026-10-27 8:46 ` Chen Linxuan via B4 Relay
2026-10-28 8:46 ` sashiko-bot
2026-10-27 8:46 ` [PATCH v4 2/3] pidfd: Use scoped cleanup for task access Chen Linxuan via B4 Relay
2026-10-27 8:46 ` [PATCH v4 3/3] pidfd: Add task path ioctls Chen Linxuan via B4 Relay
2 siblings, 1 reply; 7+ messages in thread
From: Chen Linxuan via B4 Relay @ 2026-10-27 8:46 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,
Kees Cook, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
Mel Gorman, Valentin Schneider, K Prateek Nayak, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, 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.
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 3dff299bfe1b..1ddec096eb59 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,
@@ -1762,16 +1741,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 691cef038d1e..dc125564f536 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -4222,6 +4222,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 2142c6f38119..3ceedde9c3e1 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1369,6 +1369,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.56.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v4 2/3] pidfd: Use scoped cleanup for task access
2026-10-27 8:46 [PATCH v4 0/3] pidfd: add task path ioctls Chen Linxuan via B4 Relay
2026-10-27 8:46 ` [PATCH v4 1/3] fs: Introduce task path helpers Chen Linxuan via B4 Relay
@ 2026-10-27 8:46 ` Chen Linxuan via B4 Relay
2026-10-28 8:46 ` sashiko-bot
2026-10-27 8:46 ` [PATCH v4 3/3] pidfd: Add task path ioctls Chen Linxuan via B4 Relay
2 siblings, 1 reply; 7+ messages in thread
From: Chen Linxuan via B4 Relay @ 2026-10-27 8:46 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,
Kees Cook, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
Mel Gorman, Valentin Schneider, K Prateek Nayak, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, 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.
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 cfb93c4ccf23..b70ec50f64da 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.56.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v4 3/3] pidfd: Add task path ioctls
2026-10-27 8:46 [PATCH v4 0/3] pidfd: add task path ioctls Chen Linxuan via B4 Relay
2026-10-27 8:46 ` [PATCH v4 1/3] fs: Introduce task path helpers Chen Linxuan via B4 Relay
2026-10-27 8:46 ` [PATCH v4 2/3] pidfd: Use scoped cleanup for task access Chen Linxuan via B4 Relay
@ 2026-10-27 8:46 ` Chen Linxuan via B4 Relay
2026-10-28 8:46 ` sashiko-bot
2 siblings, 1 reply; 7+ messages in thread
From: Chen Linxuan via B4 Relay @ 2026-10-27 8:46 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,
Kees Cook, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
Mel Gorman, Valentin Schneider, K Prateek Nayak, John Johansen,
Georgia Garcia, Paul Moore, James Morris, Serge E. Hallyn
Cc: linux-fsdevel, linux-kernel, linux-mm, apparmor,
linux-security-module, 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.
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 b70ec50f64da..1e5b0dcb9975 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.56.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH v4 3/3] pidfd: Add task path ioctls
2026-10-27 8:46 ` [PATCH v4 3/3] pidfd: Add task path ioctls Chen Linxuan via B4 Relay
@ 2026-10-28 8:46 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-28 8:46 UTC (permalink / raw)
To: Chen Linxuan, Alexander Viro, Christian Brauner, Jan Kara,
Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Kees Cook, Ingo Molnar,
Peter Zijlstra, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, John Johansen, Georgia Garcia, Paul Moore,
James Morris, Serge E. Hallyn
Cc: linux-mm, apparmor, linux-security-module
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] pidfd: missing d_weak_revalidate() check in pidfd_ioctl() task path opens
--
commit ce70068de308fcb3a1353124233724116074eb7b
Author: Chen Linxuan <me@black-desk.cn>
pidfd: Add task path ioctls
This commit adds the PIDFD_GET_EXE, PIDFD_GET_CWD, and PIDFD_GET_ROOT ioctls
to return close-on-exec O_PATH file descriptors referencing a target task's
executable, current working directory, and root directory.
> diff --git a/fs/pidfs.c b/fs/pidfs.c
> index b70ec50f64da9..1e5b0dcb99756 100644
> --- a/fs/pidfs.c
> +++ b/fs/pidfs.c
[ ... ]
> @@ -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()));
> + }
[Severity: Medium]
Should pidfd_ioctl() check DCACHE_OP_WEAK_REVALIDATE and invoke
d_weak_revalidate() on path.dentry before calling dentry_open()?
When a task's root, cwd, or exe is opened through /proc/<pid>/{root,cwd,exe},
proc_pid_get_link() jumps to the target path via nd_jump_link(), setting
ND_JUMPED in struct nameidata. Before do_o_path() calls vfs_open(),
path_lookupat() calls complete_walk() in fs/namei.c:
if (likely(!(nd->state & ND_JUMPED)))
return 0;
if (likely(!(dentry->d_flags & DCACHE_OP_WEAK_REVALIDATE)))
return 0;
status = dentry->d_op->d_weak_revalidate(dentry, nd->flags);
Because pidfd_ioctl() passes &path directly to
dentry_open() -> vfs_open() -> do_dentry_open() with O_PATH,
d_weak_revalidate() is never called.
In particular, failfs relies on failfs_d_weak_revalidate() in fs/failfs.c to
prevent a task's root or cwd in failfs from being opened with O_PATH:
static int failfs_d_weak_revalidate(struct dentry *dentry, unsigned int flags)
{
/*
* The root is only ever reached as a path-walk terminal by jumping
* to it: as "/" when it is the caller's root, or through a
* /proc/<pid>/{root,cwd} magic link. ->permission() already fails
* every walk of a component, but a jump lands on the root without
* one. Refuse here too so the root cannot be pinned by an O_PATH
* open or encoded into a file handle.
*/
return -EOPNOTSUPP;
}
If a task sets its working directory or root to failfs via
fchdir(FD_FAILFS_ROOT) or fchroot(FD_FAILFS_ROOT, 0), calling
ioctl(pidfd, PIDFD_GET_CWD, 0) or ioctl(pidfd, PIDFD_GET_ROOT, 0) bypasses
failfs_d_weak_revalidate() and returns an O_PATH file descriptor pinning
failfs_root_path, in addition to skipping weak revalidation on filesystems
such as NFS, 9p, and overlayfs.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261027-pidfd-get-paths-v4-0-e2a6a47e3c95@black-desk.cn?part=3
^ permalink raw reply [flat|nested] 7+ messages in thread