Skip to content

Commit 909d3b5

Browse files
committed
Merge tag 'vfs-6.13.pidfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull pidfs update from Christian Brauner: "This adds a new ioctl to retrieve information about a pidfd. A common pattern when using pidfds is having to get information about the process, which currently requires /proc being mounted, resolving the fd to a pid, and then do manual string parsing of /proc/N/status and friends. This needs to be reimplemented over and over in all userspace projects (e.g.: it has been reimplemented in systemd, dbus, dbus-daemon, polkit so far), and requires additional care in checking that the fd is still valid after having parsed the data, to avoid races. Having a programmatic API that can be used directly removes all these requirements, including having /proc mounted. As discussed at LPC24, add an ioctl with an extensible struct so that more parameters can be added later if needed. Start with returning pid/tgid/ppid and some creds unconditionally, and cgroupid optionally" * tag 'vfs-6.13.pidfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: pidfd: add ioctl to retrieve pid info
2 parents a29835c + cdda1f2 commit 909d3b5

File tree

3 files changed

+214
-4
lines changed

3 files changed

+214
-4
lines changed

fs/pidfs.c

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <linux/anon_inodes.h>
33
#include <linux/file.h>
44
#include <linux/fs.h>
5+
#include <linux/cgroup.h>
56
#include <linux/magic.h>
67
#include <linux/mount.h>
78
#include <linux/pid.h>
@@ -114,6 +115,81 @@ static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts)
114115
return poll_flags;
115116
}
116117

118+
static long pidfd_info(struct task_struct *task, unsigned int cmd, unsigned long arg)
119+
{
120+
struct pidfd_info __user *uinfo = (struct pidfd_info __user *)arg;
121+
size_t usize = _IOC_SIZE(cmd);
122+
struct pidfd_info kinfo = {};
123+
struct user_namespace *user_ns;
124+
const struct cred *c;
125+
__u64 mask;
126+
#ifdef CONFIG_CGROUPS
127+
struct cgroup *cgrp;
128+
#endif
129+
130+
if (!uinfo)
131+
return -EINVAL;
132+
if (usize < PIDFD_INFO_SIZE_VER0)
133+
return -EINVAL; /* First version, no smaller struct possible */
134+
135+
if (copy_from_user(&mask, &uinfo->mask, sizeof(mask)))
136+
return -EFAULT;
137+
138+
c = get_task_cred(task);
139+
if (!c)
140+
return -ESRCH;
141+
142+
/* Unconditionally return identifiers and credentials, the rest only on request */
143+
144+
user_ns = current_user_ns();
145+
kinfo.ruid = from_kuid_munged(user_ns, c->uid);
146+
kinfo.rgid = from_kgid_munged(user_ns, c->gid);
147+
kinfo.euid = from_kuid_munged(user_ns, c->euid);
148+
kinfo.egid = from_kgid_munged(user_ns, c->egid);
149+
kinfo.suid = from_kuid_munged(user_ns, c->suid);
150+
kinfo.sgid = from_kgid_munged(user_ns, c->sgid);
151+
kinfo.fsuid = from_kuid_munged(user_ns, c->fsuid);
152+
kinfo.fsgid = from_kgid_munged(user_ns, c->fsgid);
153+
kinfo.mask |= PIDFD_INFO_CREDS;
154+
put_cred(c);
155+
156+
#ifdef CONFIG_CGROUPS
157+
rcu_read_lock();
158+
cgrp = task_dfl_cgroup(task);
159+
kinfo.cgroupid = cgroup_id(cgrp);
160+
kinfo.mask |= PIDFD_INFO_CGROUPID;
161+
rcu_read_unlock();
162+
#endif
163+
164+
/*
165+
* Copy pid/tgid last, to reduce the chances the information might be
166+
* stale. Note that it is not possible to ensure it will be valid as the
167+
* task might return as soon as the copy_to_user finishes, but that's ok
168+
* and userspace expects that might happen and can act accordingly, so
169+
* this is just best-effort. What we can do however is checking that all
170+
* the fields are set correctly, or return ESRCH to avoid providing
171+
* incomplete information. */
172+
173+
kinfo.ppid = task_ppid_nr_ns(task, NULL);
174+
kinfo.tgid = task_tgid_vnr(task);
175+
kinfo.pid = task_pid_vnr(task);
176+
kinfo.mask |= PIDFD_INFO_PID;
177+
178+
if (kinfo.pid == 0 || kinfo.tgid == 0 || (kinfo.ppid == 0 && kinfo.pid != 1))
179+
return -ESRCH;
180+
181+
/*
182+
* If userspace and the kernel have the same struct size it can just
183+
* be copied. If userspace provides an older struct, only the bits that
184+
* userspace knows about will be copied. If userspace provides a new
185+
* struct, only the bits that the kernel knows about will be copied.
186+
*/
187+
if (copy_to_user(uinfo, &kinfo, min(usize, sizeof(kinfo))))
188+
return -EFAULT;
189+
190+
return 0;
191+
}
192+
117193
static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
118194
{
119195
struct task_struct *task __free(put_task) = NULL;
@@ -122,13 +198,17 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
122198
struct ns_common *ns_common = NULL;
123199
struct pid_namespace *pid_ns;
124200

125-
if (arg)
126-
return -EINVAL;
127-
128201
task = get_pid_task(pid, PIDTYPE_PID);
129202
if (!task)
130203
return -ESRCH;
131204

205+
/* Extensible IOCTL that does not open namespace FDs, take a shortcut */
206+
if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
207+
return pidfd_info(task, cmd, arg);
208+
209+
if (arg)
210+
return -EINVAL;
211+
132212
scoped_guard(task_lock, task) {
133213
nsp = task->nsproxy;
134214
if (nsp)

include/uapi/linux/pidfd.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,55 @@
1616
#define PIDFD_SIGNAL_THREAD_GROUP (1UL << 1)
1717
#define PIDFD_SIGNAL_PROCESS_GROUP (1UL << 2)
1818

19+
/* Flags for pidfd_info. */
20+
#define PIDFD_INFO_PID (1UL << 0) /* Always returned, even if not requested */
21+
#define PIDFD_INFO_CREDS (1UL << 1) /* Always returned, even if not requested */
22+
#define PIDFD_INFO_CGROUPID (1UL << 2) /* Always returned if available, even if not requested */
23+
24+
#define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */
25+
26+
struct pidfd_info {
27+
/*
28+
* This mask is similar to the request_mask in statx(2).
29+
*
30+
* Userspace indicates what extensions or expensive-to-calculate fields
31+
* they want by setting the corresponding bits in mask. The kernel
32+
* will ignore bits that it does not know about.
33+
*
34+
* When filling the structure, the kernel will only set bits
35+
* corresponding to the fields that were actually filled by the kernel.
36+
* This also includes any future extensions that might be automatically
37+
* filled. If the structure size is too small to contain a field
38+
* (requested or not), to avoid confusion the mask will not
39+
* contain a bit for that field.
40+
*
41+
* As such, userspace MUST verify that mask contains the
42+
* corresponding flags after the ioctl(2) returns to ensure that it is
43+
* using valid data.
44+
*/
45+
__u64 mask;
46+
/*
47+
* The information contained in the following fields might be stale at the
48+
* time it is received, as the target process might have exited as soon as
49+
* the IOCTL was processed, and there is no way to avoid that. However, it
50+
* is guaranteed that if the call was successful, then the information was
51+
* correct and referred to the intended process at the time the work was
52+
* performed. */
53+
__u64 cgroupid;
54+
__u32 pid;
55+
__u32 tgid;
56+
__u32 ppid;
57+
__u32 ruid;
58+
__u32 rgid;
59+
__u32 euid;
60+
__u32 egid;
61+
__u32 suid;
62+
__u32 sgid;
63+
__u32 fsuid;
64+
__u32 fsgid;
65+
__u32 spare0[1];
66+
};
67+
1968
#define PIDFS_IOCTL_MAGIC 0xFF
2069

2170
#define PIDFD_GET_CGROUP_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 1)
@@ -28,5 +77,6 @@
2877
#define PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 8)
2978
#define PIDFD_GET_USER_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 9)
3079
#define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10)
80+
#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info)
3181

3282
#endif /* _UAPI_LINUX_PIDFD_H */

tools/testing/selftests/pidfd/pidfd_open_test.c

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <stdlib.h>
1414
#include <string.h>
1515
#include <syscall.h>
16+
#include <sys/ioctl.h>
1617
#include <sys/mount.h>
1718
#include <sys/prctl.h>
1819
#include <sys/wait.h>
@@ -21,6 +22,32 @@
2122
#include "pidfd.h"
2223
#include "../kselftest.h"
2324

25+
#ifndef PIDFS_IOCTL_MAGIC
26+
#define PIDFS_IOCTL_MAGIC 0xFF
27+
#endif
28+
29+
#ifndef PIDFD_GET_INFO
30+
#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info)
31+
#define PIDFD_INFO_CGROUPID (1UL << 0)
32+
33+
struct pidfd_info {
34+
__u64 request_mask;
35+
__u64 cgroupid;
36+
__u32 pid;
37+
__u32 tgid;
38+
__u32 ppid;
39+
__u32 ruid;
40+
__u32 rgid;
41+
__u32 euid;
42+
__u32 egid;
43+
__u32 suid;
44+
__u32 sgid;
45+
__u32 fsuid;
46+
__u32 fsgid;
47+
__u32 spare0[1];
48+
};
49+
#endif
50+
2451
static int safe_int(const char *numstr, int *converted)
2552
{
2653
char *err = NULL;
@@ -120,10 +147,13 @@ static pid_t get_pid_from_fdinfo_file(int pidfd, const char *key, size_t keylen)
120147

121148
int main(int argc, char **argv)
122149
{
150+
struct pidfd_info info = {
151+
.request_mask = PIDFD_INFO_CGROUPID,
152+
};
123153
int pidfd = -1, ret = 1;
124154
pid_t pid;
125155

126-
ksft_set_plan(3);
156+
ksft_set_plan(4);
127157

128158
pidfd = sys_pidfd_open(-1, 0);
129159
if (pidfd >= 0) {
@@ -153,6 +183,56 @@ int main(int argc, char **argv)
153183
pid = get_pid_from_fdinfo_file(pidfd, "Pid:", sizeof("Pid:") - 1);
154184
ksft_print_msg("pidfd %d refers to process with pid %d\n", pidfd, pid);
155185

186+
if (ioctl(pidfd, PIDFD_GET_INFO, &info) < 0) {
187+
ksft_print_msg("%s - failed to get info from pidfd\n", strerror(errno));
188+
goto on_error;
189+
}
190+
if (info.pid != pid) {
191+
ksft_print_msg("pid from fdinfo file %d does not match pid from ioctl %d\n",
192+
pid, info.pid);
193+
goto on_error;
194+
}
195+
if (info.ppid != getppid()) {
196+
ksft_print_msg("ppid %d does not match ppid from ioctl %d\n",
197+
pid, info.pid);
198+
goto on_error;
199+
}
200+
if (info.ruid != getuid()) {
201+
ksft_print_msg("uid %d does not match uid from ioctl %d\n",
202+
getuid(), info.ruid);
203+
goto on_error;
204+
}
205+
if (info.rgid != getgid()) {
206+
ksft_print_msg("gid %d does not match gid from ioctl %d\n",
207+
getgid(), info.rgid);
208+
goto on_error;
209+
}
210+
if (info.euid != geteuid()) {
211+
ksft_print_msg("euid %d does not match euid from ioctl %d\n",
212+
geteuid(), info.euid);
213+
goto on_error;
214+
}
215+
if (info.egid != getegid()) {
216+
ksft_print_msg("egid %d does not match egid from ioctl %d\n",
217+
getegid(), info.egid);
218+
goto on_error;
219+
}
220+
if (info.suid != geteuid()) {
221+
ksft_print_msg("suid %d does not match suid from ioctl %d\n",
222+
geteuid(), info.suid);
223+
goto on_error;
224+
}
225+
if (info.sgid != getegid()) {
226+
ksft_print_msg("sgid %d does not match sgid from ioctl %d\n",
227+
getegid(), info.sgid);
228+
goto on_error;
229+
}
230+
if ((info.request_mask & PIDFD_INFO_CGROUPID) && info.cgroupid == 0) {
231+
ksft_print_msg("cgroupid should not be 0 when PIDFD_INFO_CGROUPID is set\n");
232+
goto on_error;
233+
}
234+
ksft_test_result_pass("get info from pidfd test: passed\n");
235+
156236
ret = 0;
157237

158238
on_error:

0 commit comments

Comments
 (0)