[PATCH v3 4/4] pidfd: Add task path ioctls
From: Chen Linxuan via B4 Relay
Date: Sat Oct 10 2026 - 06:24:05 EST
From: Chen Linxuan <me@xxxxxxxxxxxxx>
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@xxxxxxxxxxxxx>
---
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