basic: port various pidfd/pidref helpers to PIDFD_GET_INFO and PIDFD_GET_*_NAMESPACE (#35242)

Supersedes #35308 (cherry-picked one commit and replaced the rest)

(I left a few comments that's folded by GitHub. Please make sure to
check them too.)
This commit is contained in:
Lennart Poettering
2025-01-06 11:23:08 +01:00
committed by GitHub
35 changed files with 748 additions and 433 deletions
+25
View File
@@ -68,6 +68,27 @@ int cg_cgroupid_open(int cgroupfs_fd, uint64_t id) {
return RET_NERRNO(open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC));
}
int cg_path_from_cgroupid(int cgroupfs_fd, uint64_t id, char **ret) {
_cleanup_close_ int cgfd = -EBADF;
int r;
cgfd = cg_cgroupid_open(cgroupfs_fd, id);
if (cgfd < 0)
return cgfd;
_cleanup_free_ char *path = NULL;
r = fd_get_path(cgfd, &path);
if (r < 0)
return r;
if (!path_startswith(path, "/sys/fs/cgroup/"))
return -EXDEV; /* recognizable error */
if (ret)
*ret = TAKE_PTR(path);
return 0;
}
int cg_get_cgroupid_at(int dfd, const char *path, uint64_t *ret) {
cg_file_handle fh = CG_FILE_HANDLE_INIT;
int mnt_id;
@@ -842,6 +863,10 @@ int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret_
if (pidref_is_remote(pidref))
return -EREMOTE;
// XXX: Ideally we'd use pidfd_get_cgroupid() + cg_path_from_cgroupid() here, to extract this
// bit of information from pidfd directly. However, the latter requires privilege and it's
// not entirely clear how to handle cgroups from outer namespace.
r = cg_pid_get_path(controller, pidref->pid, &path);
if (r < 0)
return r;
+1
View File
@@ -183,6 +183,7 @@ typedef enum CGroupUnified {
int cg_path_open(const char *controller, const char *path);
int cg_cgroupid_open(int fsfd, uint64_t id);
int cg_path_from_cgroupid(int cgroupfs_fd, uint64_t id, char **ret);
int cg_get_cgroupid_at(int dfd, const char *path, uint64_t *ret);
static inline int cg_path_get_cgroupid(const char *path, uint64_t *ret) {
return cg_get_cgroupid_at(AT_FDCWD, path, ret);
+1 -1
View File
@@ -103,7 +103,7 @@ int chattr_full(
continue;
if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) {
if (errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno))
if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno))
return -errno;
log_full_errno(FLAGS_SET(flags, CHATTR_WARN_UNSUPPORTED_FLAGS) ? LOG_WARNING : LOG_DEBUG,
+7 -1
View File
@@ -158,7 +158,7 @@ static inline bool ERRNO_IS_NEG_RESOURCE(intmax_t r) {
}
_DEFINE_ABS_WRAPPER(RESOURCE);
/* Seven different errors for "operation/system call/ioctl/socket feature not supported" */
/* Seven different errors for "operation/system call/socket feature not supported" */
static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) {
return IN_SET(r,
-EOPNOTSUPP,
@@ -172,6 +172,12 @@ static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) {
}
_DEFINE_ABS_WRAPPER(NOT_SUPPORTED);
/* ioctl() with unsupported command/arg might additionally return EINVAL */
static inline bool ERRNO_IS_NEG_IOCTL_NOT_SUPPORTED(intmax_t r) {
return ERRNO_IS_NEG_NOT_SUPPORTED(r) || r == -EINVAL;
}
_DEFINE_ABS_WRAPPER(IOCTL_NOT_SUPPORTED);
/* Two different errors for access problems */
static inline bool ERRNO_IS_NEG_PRIVILEGE(intmax_t r) {
return IN_SET(r,
+1
View File
@@ -72,6 +72,7 @@ basic_sources = files(
'parse-util.c',
'path-util.c',
'percent-util.c',
'pidfd-util.c',
'pidref.c',
'prioq.c',
'proc-cmdline.c',
+48
View File
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <linux/types.h>
#if HAVE_PIDFD_OPEN
#include <sys/pidfd.h>
#endif
#ifndef PIDFS_IOCTL_MAGIC
# define PIDFS_IOCTL_MAGIC 0xFF
#endif
#ifndef PIDFD_GET_CGROUP_NAMESPACE
# define PIDFD_GET_CGROUP_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 1)
# define PIDFD_GET_IPC_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 2)
# define PIDFD_GET_MNT_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 3)
# define PIDFD_GET_NET_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 4)
# define PIDFD_GET_PID_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 5)
# define PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 6)
# define PIDFD_GET_TIME_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 7)
# define PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 8)
# define PIDFD_GET_USER_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 9)
# define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10)
#endif
#ifndef PIDFD_GET_INFO
struct pidfd_info {
__u64 mask;
__u64 cgroupid;
__u32 pid;
__u32 tgid;
__u32 ppid;
__u32 ruid;
__u32 rgid;
__u32 euid;
__u32 egid;
__u32 suid;
__u32 sgid;
__u32 fsuid;
__u32 fsgid;
__u32 spare0[1];
};
#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info)
#define PIDFD_INFO_PID (1UL << 0)
#define PIDFD_INFO_CREDS (1UL << 1)
#define PIDFD_INFO_CGROUPID (1UL << 2)
#endif
+203 -163
View File
@@ -18,29 +18,30 @@
#include "mountpoint-util.h"
#include "namespace-util.h"
#include "parse-util.h"
#include "pidfd-util.h"
#include "process-util.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "user-util.h"
const struct namespace_info namespace_info[_NAMESPACE_TYPE_MAX + 1] = {
[NAMESPACE_CGROUP] = { "cgroup", "ns/cgroup", CLONE_NEWCGROUP, PROC_CGROUP_INIT_INO },
[NAMESPACE_IPC] = { "ipc", "ns/ipc", CLONE_NEWIPC, PROC_IPC_INIT_INO },
[NAMESPACE_NET] = { "net", "ns/net", CLONE_NEWNET, 0 },
[NAMESPACE_CGROUP] = { "cgroup", "ns/cgroup", CLONE_NEWCGROUP, PIDFD_GET_CGROUP_NAMESPACE, PROC_CGROUP_INIT_INO },
[NAMESPACE_IPC] = { "ipc", "ns/ipc", CLONE_NEWIPC, PIDFD_GET_IPC_NAMESPACE, PROC_IPC_INIT_INO },
[NAMESPACE_NET] = { "net", "ns/net", CLONE_NEWNET, PIDFD_GET_NET_NAMESPACE, 0 },
/* So, the mount namespace flag is called CLONE_NEWNS for historical
* reasons. Let's expose it here under a more explanatory name: "mnt".
* This is in-line with how the kernel exposes namespaces in /proc/$PID/ns. */
[NAMESPACE_MOUNT] = { "mnt", "ns/mnt", CLONE_NEWNS, 0 },
[NAMESPACE_PID] = { "pid", "ns/pid", CLONE_NEWPID, PROC_PID_INIT_INO },
[NAMESPACE_USER] = { "user", "ns/user", CLONE_NEWUSER, PROC_USER_INIT_INO },
[NAMESPACE_UTS] = { "uts", "ns/uts", CLONE_NEWUTS, PROC_UTS_INIT_INO },
[NAMESPACE_TIME] = { "time", "ns/time", CLONE_NEWTIME, PROC_TIME_INIT_INO },
{ /* Allow callers to iterate over the array without using _NAMESPACE_TYPE_MAX. */ },
[NAMESPACE_MOUNT] = { "mnt", "ns/mnt", CLONE_NEWNS, PIDFD_GET_MNT_NAMESPACE, 0 },
[NAMESPACE_PID] = { "pid", "ns/pid", CLONE_NEWPID, PIDFD_GET_PID_NAMESPACE, PROC_PID_INIT_INO },
[NAMESPACE_USER] = { "user", "ns/user", CLONE_NEWUSER, PIDFD_GET_USER_NAMESPACE, PROC_USER_INIT_INO },
[NAMESPACE_UTS] = { "uts", "ns/uts", CLONE_NEWUTS, PIDFD_GET_UTS_NAMESPACE, PROC_UTS_INIT_INO },
[NAMESPACE_TIME] = { "time", "ns/time", CLONE_NEWTIME, PIDFD_GET_TIME_NAMESPACE, PROC_TIME_INIT_INO },
{}, /* Allow callers to iterate over the array without using _NAMESPACE_TYPE_MAX. */
};
#define pid_namespace_path(pid, type) procfs_file_alloca(pid, namespace_info[type].proc_path)
static NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag) {
NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag) {
for (NamespaceType t = 0; t < _NAMESPACE_TYPE_MAX; t++)
if (((namespace_info[t].clone_flag ^ clone_flag) & (CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUSER|CLONE_NEWUTS|CLONE_NEWTIME)) == 0)
return t;
@@ -48,6 +49,62 @@ static NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag) {
return _NAMESPACE_TYPE_INVALID;
}
static int pidref_namespace_open_by_type_internal(const PidRef *pidref, NamespaceType type, bool *need_verify) {
int r;
assert(pidref_is_set(pidref));
assert(type >= 0 && type < _NAMESPACE_TYPE_MAX);
if (pidref_is_remote(pidref))
return -EREMOTE;
if (pidref->fd >= 0) {
r = pidfd_get_namespace(pidref->fd, namespace_info[type].pidfd_get_ns_ioctl_cmd);
if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
return r;
}
if (need_verify) /* The caller shall call pidref_verify() later */
*need_verify = true;
_cleanup_close_ int nsfd = -EBADF;
const char *p;
p = pid_namespace_path(pidref->pid, type);
nsfd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (nsfd < 0) {
if (errno == ENOENT && proc_mounted() == 0)
return -ENOSYS;
return -errno;
}
if (!need_verify) { /* Otherwise we verify on our own */
r = pidref_verify(pidref);
if (r < 0)
return r;
}
return TAKE_FD(nsfd);
}
int pidref_namespace_open_by_type(const PidRef *pidref, NamespaceType type) {
return pidref_namespace_open_by_type_internal(pidref, type, NULL);
}
int namespace_open_by_type(NamespaceType type) {
_cleanup_(pidref_done) PidRef self = PIDREF_NULL;
int r;
assert(type >= 0 && type < _NAMESPACE_TYPE_MAX);
r = pidref_set_self(&self);
if (r < 0)
return r;
return pidref_namespace_open_by_type(&self, type);
}
int pidref_namespace_open(
const PidRef *pidref,
int *ret_pidns_fd,
@@ -58,58 +115,56 @@ int pidref_namespace_open(
_cleanup_close_ int pidns_fd = -EBADF, mntns_fd = -EBADF, netns_fd = -EBADF,
userns_fd = -EBADF, root_fd = -EBADF;
bool need_verify = false;
int r;
assert(pidref_is_set(pidref));
if (ret_pidns_fd) {
const char *pidns;
if (pidref_is_remote(pidref))
return -EREMOTE;
pidns = pid_namespace_path(pidref->pid, NAMESPACE_PID);
pidns_fd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (ret_pidns_fd) {
pidns_fd = pidref_namespace_open_by_type_internal(pidref, NAMESPACE_PID, &need_verify);
if (pidns_fd < 0)
return -errno;
return pidns_fd;
}
if (ret_mntns_fd) {
const char *mntns;
mntns = pid_namespace_path(pidref->pid, NAMESPACE_MOUNT);
mntns_fd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
mntns_fd = pidref_namespace_open_by_type_internal(pidref, NAMESPACE_MOUNT, &need_verify);
if (mntns_fd < 0)
return -errno;
return mntns_fd;
}
if (ret_netns_fd) {
const char *netns;
netns = pid_namespace_path(pidref->pid, NAMESPACE_NET);
netns_fd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
netns_fd = pidref_namespace_open_by_type_internal(pidref, NAMESPACE_NET, &need_verify);
if (netns_fd < 0)
return -errno;
return netns_fd;
}
if (ret_userns_fd) {
const char *userns;
userns = pid_namespace_path(pidref->pid, NAMESPACE_USER);
userns_fd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (userns_fd < 0 && errno != ENOENT)
return -errno;
userns_fd = pidref_namespace_open_by_type_internal(pidref, NAMESPACE_USER, &need_verify);
if (userns_fd < 0 && !IN_SET(userns_fd, -ENOENT, -ENOPKG))
return userns_fd;
}
if (ret_root_fd) {
const char *root;
root = procfs_file_alloca(pidref->pid, "root");
root_fd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
root_fd = RET_NERRNO(open(root, O_CLOEXEC|O_DIRECTORY));
if (root_fd == -ENOENT && proc_mounted() == 0)
return -ENOSYS;
if (root_fd < 0)
return -errno;
return root_fd;
need_verify = true;
}
r = pidref_verify(pidref);
if (r < 0)
return r;
if (need_verify) {
r = pidref_verify(pidref);
if (r < 0)
return r;
}
if (ret_pidns_fd)
*ret_pidns_fd = TAKE_FD(pidns_fd);
@@ -154,10 +209,10 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
/* Can't setns to your own userns, since then you could escalate from non-root to root in
* your own namespace, so check if namespaces are equal before attempting to enter. */
r = inode_same_at(userns_fd, "", AT_FDCWD, "/proc/self/ns/user", AT_EMPTY_PATH);
r = is_our_namespace(userns_fd, NAMESPACE_USER);
if (r < 0)
return r;
if (r)
if (r > 0)
userns_fd = -EBADF;
}
@@ -188,50 +243,128 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
return reset_uid_gid();
}
int fd_is_ns(int fd, unsigned long nsflag) {
struct statfs s;
int fd_is_namespace(int fd, NamespaceType type) {
int r;
/* Checks whether the specified file descriptor refers to a namespace created by specifying nsflag in clone().
* On old kernels there's no nice way to detect that, hence on those we'll return a recognizable error (EUCLEAN),
* so that callers can handle this somewhat nicely.
*
* This function returns > 0 if the fd definitely refers to a network namespace, 0 if it definitely does not
* refer to a network namespace, -EUCLEAN if we can't determine, and other negative error codes on error. */
/* Checks whether the specified file descriptor refers to a namespace (of type if type != _NAMESPACE_INVALID). */
if (fstatfs(fd, &s) < 0)
assert(fd >= 0);
assert(type < _NAMESPACE_TYPE_MAX);
r = fd_is_fs_type(fd, NSFS_MAGIC);
if (r <= 0)
return r;
if (type < 0)
return true;
int clone_flag = ioctl(fd, NS_GET_NSTYPE);
if (clone_flag < 0)
return -errno;
if (!is_fs_type(&s, NSFS_MAGIC)) {
/* On really old kernels, there was no "nsfs", and network namespace sockets belonged to procfs
* instead. Handle that in a somewhat smart way. */
NamespaceType found_type = clone_flag_to_namespace_type(clone_flag);
if (found_type < 0)
return -EBADF; /* Uh? Unknown namespace type? */
if (is_fs_type(&s, PROC_SUPER_MAGIC)) {
struct statfs t;
return found_type == type;
}
/* OK, so it is procfs. Let's see if our own network namespace is procfs, too. If so, then the
* passed fd might refer to a network namespace, but we can't know for sure. In that case,
* return a recognizable error. */
int is_our_namespace(int fd, NamespaceType type) {
int r;
if (statfs("/proc/self/ns/net", &t) < 0)
return -errno;
assert(fd >= 0);
assert(type < _NAMESPACE_TYPE_MAX);
if (s.f_type == t.f_type)
return -EUCLEAN; /* It's possible, we simply don't know */
r = fd_is_namespace(fd, type);
if (r < 0)
return r;
if (r == 0) /* Not a namespace or not of the right type? */
return -EUCLEAN;
_cleanup_close_ int our_ns = namespace_open_by_type(type);
if (our_ns < 0)
return our_ns;
return fd_inode_same(fd, our_ns);
}
int namespace_is_init(NamespaceType type) {
int r;
assert(type >= 0);
assert(type < _NAMESPACE_TYPE_MAX);
if (namespace_info[type].root_inode == 0)
return -EBADR; /* Cannot answer this question */
const char *p = pid_namespace_path(0, type);
struct stat st;
r = RET_NERRNO(stat(p, &st));
if (r == -ENOENT)
/* If the /proc/ns/<type> API is not around in /proc/ then ns is off in the kernel and we are in the init ns */
return proc_mounted() == 0 ? -ENOSYS : true;
if (r < 0)
return r;
return st.st_ino == namespace_info[type].root_inode;
}
int pidref_in_same_namespace(PidRef *pid1, PidRef *pid2, NamespaceType type) {
_cleanup_close_ int ns1 = -EBADF, ns2 = -EBADF;
/* Accepts NULL to indicate our own process */
assert(!pid1 || pidref_is_set(pid1));
assert(!pid2 || pidref_is_set(pid2));
assert(type >= 0 && type < _NAMESPACE_TYPE_MAX);
if (pidref_equal(pid1, pid2))
return true;
if (!pid1)
ns1 = namespace_open_by_type(type);
else
ns1 = pidref_namespace_open_by_type(pid1, type);
if (ns1 < 0)
return ns1;
if (!pid2)
ns2 = namespace_open_by_type(type);
else
ns2 = pidref_namespace_open_by_type(pid2, type);
if (ns2 < 0)
return ns2;
return fd_inode_same(ns1, ns2);
}
int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret) {
int r;
assert(pid >= 0);
assert(type >= 0 && type < _NAMESPACE_TYPE_MAX);
assert(ret);
for (;;) {
pid_t ppid;
r = pid_get_ppid(pid, &ppid);
if (r < 0)
return r;
r = in_same_namespace(pid, ppid, type);
if (r < 0)
return r;
if (r == 0) {
/* If the parent and the child are not in the same namespace, then the child is
* the leader we are looking for. */
*ret = pid;
return 0;
}
return 0; /* No! */
pid = ppid;
}
r = ioctl(fd, NS_GET_NSTYPE);
if (r < 0) {
if (errno == ENOTTY) /* Old kernels didn't know this ioctl, let's also return a recognizable error in that case */
return -EUCLEAN;
return -errno;
}
return (unsigned long) r == nsflag;
}
int detach_mount_namespace(void) {
@@ -402,30 +535,6 @@ int netns_acquire(void) {
return TAKE_FD(netns_fd);
}
int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type) {
const char *ns_path;
struct stat ns_st1, ns_st2;
if (pid1 == 0)
pid1 = getpid_cached();
if (pid2 == 0)
pid2 = getpid_cached();
if (pid1 == pid2)
return 1;
ns_path = pid_namespace_path(pid1, type);
if (stat(ns_path, &ns_st1) < 0)
return -errno;
ns_path = pid_namespace_path(pid2, type);
if (stat(ns_path, &ns_st2) < 0)
return -errno;
return stat_inode_same(&ns_st1, &ns_st2);
}
int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range) {
_cleanup_free_ char *buffer = NULL;
const char *range, *shift;
@@ -464,75 +573,6 @@ int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_r
return 0;
}
int namespace_open_by_type(NamespaceType type) {
const char *p;
int fd;
assert(type >= 0);
assert(type < _NAMESPACE_TYPE_MAX);
p = pid_namespace_path(0, type);
fd = RET_NERRNO(open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC));
if (fd == -ENOENT && proc_mounted() == 0)
return -ENOSYS;
return fd;
}
int namespace_is_init(NamespaceType type) {
int r;
assert(type >= 0);
assert(type <= _NAMESPACE_TYPE_MAX);
if (namespace_info[type].root_inode == 0)
return -EBADR; /* Cannot answer this question */
const char *p = pid_namespace_path(0, type);
struct stat st;
r = RET_NERRNO(stat(p, &st));
if (r == -ENOENT)
/* If the /proc/ns/<type> API is not around in /proc/ then ns is off in the kernel and we are in the init ns */
return proc_mounted() == 0 ? -ENOSYS : true;
if (r < 0)
return r;
return st.st_ino == namespace_info[type].root_inode;
}
int is_our_namespace(int fd, NamespaceType request_type) {
int clone_flag;
assert(fd >= 0);
clone_flag = ioctl(fd, NS_GET_NSTYPE);
if (clone_flag < 0)
return -errno;
NamespaceType found_type = clone_flag_to_namespace_type(clone_flag);
if (found_type < 0)
return -EBADF; /* Uh? Unknown namespace type? */
if (request_type >= 0 && request_type != found_type) /* It's a namespace, but not of the right type? */
return -EUCLEAN;
struct stat st_fd, st_ours;
if (fstat(fd, &st_fd) < 0)
return -errno;
const char *p = pid_namespace_path(0, found_type);
if (stat(p, &st_ours) < 0) {
if (errno == ENOENT)
return proc_mounted() == 0 ? -ENOSYS : -ENOENT;
return -errno;
}
return stat_inode_same(&st_ours, &st_fd);
}
int is_idmapping_supported(const char *path) {
_cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF, dir_fd = -EBADF;
_cleanup_free_ char *uid_map = NULL, *gid_map = NULL;
+25 -14
View File
@@ -3,11 +3,9 @@
#include <sys/types.h>
typedef enum NamespaceType NamespaceType;
#include "pidref.h"
enum NamespaceType {
typedef enum NamespaceType {
NAMESPACE_CGROUP,
NAMESPACE_IPC,
NAMESPACE_NET,
@@ -18,15 +16,21 @@ enum NamespaceType {
NAMESPACE_TIME,
_NAMESPACE_TYPE_MAX,
_NAMESPACE_TYPE_INVALID = -EINVAL,
};
} NamespaceType;
extern const struct namespace_info {
const char *proc_name;
const char *proc_path;
unsigned int clone_flag;
unsigned long clone_flag;
unsigned long pidfd_get_ns_ioctl_cmd;
ino_t root_inode;
} namespace_info[_NAMESPACE_TYPE_MAX + 1];
NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag);
int pidref_namespace_open_by_type(const PidRef *pidref, NamespaceType type);
int namespace_open_by_type(NamespaceType type);
int pidref_namespace_open(
const PidRef *pidref,
int *ret_pidns_fd,
@@ -41,9 +45,24 @@ int namespace_open(
int *ret_netns_fd,
int *ret_userns_fd,
int *ret_root_fd);
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
int fd_is_ns(int fd, unsigned long nsflag);
int fd_is_namespace(int fd, NamespaceType type);
int is_our_namespace(int fd, NamespaceType type);
int namespace_is_init(NamespaceType type);
int pidref_in_same_namespace(PidRef *pid1, PidRef *pid2, NamespaceType type);
static inline int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type) {
assert(pid1 >= 0);
assert(pid2 >= 0);
return pidref_in_same_namespace(pid1 == 0 ? NULL : &PIDREF_MAKE_FROM_PID(pid1),
pid2 == 0 ? NULL : &PIDREF_MAKE_FROM_PID(pid2),
type);
}
int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret);
int detach_mount_namespace(void);
int detach_mount_namespace_harder(uid_t target_uid, gid_t target_gid);
@@ -69,14 +88,6 @@ int userns_acquire(const char *uid_map, const char *gid_map);
int netns_acquire(void);
int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type);
int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range);
int namespace_open_by_type(NamespaceType type);
int namespace_is_init(NamespaceType type);
int is_our_namespace(int fd, NamespaceType type);
int is_idmapping_supported(const char *path);
+251
View File
@@ -0,0 +1,251 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/ioctl.h>
#include <unistd.h>
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "macro.h"
#include "memory-util.h"
#include "missing_magic.h"
#include "parse-util.h"
#include "path-util.h"
#include "pidfd-util.h"
#include "process-util.h"
#include "stat-util.h"
#include "string-util.h"
static int have_pidfs = -1;
static int pidfd_check_pidfs(void) {
if (have_pidfs >= 0)
return have_pidfs;
_cleanup_close_ int fd = pidfd_open(getpid_cached(), 0);
if (fd < 0) {
if (ERRNO_IS_NOT_SUPPORTED(errno))
return (have_pidfs = false);
return -errno;
}
return (have_pidfs = fd_is_fs_type(fd, PID_FS_MAGIC));
}
int pidfd_get_namespace(int fd, unsigned long ns_type_cmd) {
static bool cached_supported = true;
/* Obtain the namespace fd from pidfd directly through ioctl(PIDFD_GET_*_NAMESPACE).
*
* Returns -EOPNOTSUPP if ioctl on pidfds are not supported, -ENOPKG if the requested namespace
* is disabled in kernel. (The errno used are different from what kernel returns via ioctl(),
* see below) */
assert(fd >= 0);
/* If we know ahead of time that pidfs is unavailable, shortcut things. But otherwise we don't
* call pidfd_check_pidfs() here, which is kinda extraneous and our own cache is required
* anyways (pidfs is introduced in kernel 6.9 while ioctl support there is added in 6.11). */
if (have_pidfs == 0 || !cached_supported)
return -EOPNOTSUPP;
int nsfd = ioctl(fd, ns_type_cmd);
if (nsfd < 0) {
/* Kernel returns EOPNOTSUPP if the ns type in question is disabled. Hence we need to look
* at precise errno instead of generic ERRNO_IS_(IOCTL_)NOT_SUPPORTED. */
if (IN_SET(errno, ENOTTY, EINVAL)) {
cached_supported = false;
return -EOPNOTSUPP;
}
if (errno == EOPNOTSUPP) /* Translate to something more recognizable */
return -ENOPKG;
return -errno;
}
return nsfd;
}
static int pidfd_get_info(int fd, struct pidfd_info *info) {
static bool cached_supported = true;
assert(fd >= 0);
assert(info);
if (have_pidfs == 0 || !cached_supported)
return -EOPNOTSUPP;
if (ioctl(fd, PIDFD_GET_INFO, info) < 0) {
if (ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) {
cached_supported = false;
return -EOPNOTSUPP;
}
return -errno;
}
return 0;
}
static int pidfd_get_pid_fdinfo(int fd, pid_t *ret) {
char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
_cleanup_free_ char *fdinfo = NULL;
int r;
assert(fd >= 0);
xsprintf(path, "/proc/self/fdinfo/%i", fd);
r = read_full_virtual_file(path, &fdinfo, NULL);
if (r == -ENOENT)
return proc_fd_enoent_errno();
if (r < 0)
return r;
char *p = find_line_startswith(fdinfo, "Pid:");
if (!p)
return -ENOTTY; /* not a pidfd? */
p = skip_leading_chars(p, /* bad = */ NULL);
p[strcspn(p, WHITESPACE)] = 0;
if (streq(p, "0"))
return -EREMOTE; /* PID is in foreign PID namespace? */
if (streq(p, "-1"))
return -ESRCH; /* refers to reaped process? */
return parse_pid(p, ret);
}
static int pidfd_get_pid_ioctl(int fd, pid_t *ret) {
struct pidfd_info info = { .mask = PIDFD_INFO_PID };
int r;
assert(fd >= 0);
r = pidfd_get_info(fd, &info);
if (r < 0)
return r;
assert(FLAGS_SET(info.mask, PIDFD_INFO_PID));
if (ret)
*ret = info.pid;
return 0;
}
int pidfd_get_pid(int fd, pid_t *ret) {
int r;
/* Converts a pidfd into a pid. We try ioctl(PIDFD_GET_INFO) (kernel 6.13+) first,
* /proc/self/fdinfo/ as fallback. Well known errors:
*
* -EBADF → fd invalid
* -ESRCH → fd valid, but process is already reaped
*
* pidfd_get_pid_fdinfo() might additionally fail for other reasons:
*
* -ENOSYS → /proc/ not mounted
* -ENOTTY → fd valid, but not a pidfd
* -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one
* (when using PIDFD_GET_INFO this is indistinguishable from -ESRCH)
*/
assert(fd >= 0);
r = pidfd_get_pid_ioctl(fd, ret);
if (r != -EOPNOTSUPP)
return r;
return pidfd_get_pid_fdinfo(fd, ret);
}
int pidfd_verify_pid(int pidfd, pid_t pid) {
pid_t current_pid;
int r;
assert(pidfd >= 0);
assert(pid > 0);
r = pidfd_get_pid(pidfd, &current_pid);
if (r < 0)
return r;
return current_pid != pid ? -ESRCH : 0;
}
int pidfd_get_ppid(int fd, pid_t *ret) {
struct pidfd_info info = { .mask = PIDFD_INFO_PID };
int r;
assert(fd >= 0);
r = pidfd_get_info(fd, &info);
if (r < 0)
return r;
assert(FLAGS_SET(info.mask, PIDFD_INFO_PID));
if (info.ppid == 0) /* See comments in pid_get_ppid() */
return -EADDRNOTAVAIL;
if (ret)
*ret = info.ppid;
return 0;
}
int pidfd_get_uid(int fd, uid_t *ret) {
struct pidfd_info info = { .mask = PIDFD_INFO_CREDS };
int r;
assert(fd >= 0);
r = pidfd_get_info(fd, &info);
if (r < 0)
return r;
assert(FLAGS_SET(info.mask, PIDFD_INFO_CREDS));
if (ret)
*ret = info.ruid;
return 0;
}
int pidfd_get_cgroupid(int fd, uint64_t *ret) {
struct pidfd_info info = { .mask = PIDFD_INFO_CGROUPID };
int r;
assert(fd >= 0);
r = pidfd_get_info(fd, &info);
if (r < 0)
return r;
assert(FLAGS_SET(info.mask, PIDFD_INFO_CGROUPID));
if (ret)
*ret = info.cgroupid;
return 0;
}
int pidfd_get_inode_id(int fd, uint64_t *ret) {
int r;
assert(fd >= 0);
r = pidfd_check_pidfs();
if (r < 0)
return r;
if (r == 0)
return -EOPNOTSUPP;
struct stat st;
if (fstat(fd, &st) < 0)
return -errno;
if (ret)
*ret = (uint64_t) st.st_ino;
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include "missing_pidfd.h"
#include "missing_syscall.h"
int pidfd_get_namespace(int fd, unsigned long ns_type_cmd);
int pidfd_get_pid(int fd, pid_t *ret);
int pidfd_verify_pid(int pidfd, pid_t pid);
int pidfd_get_ppid(int fd, pid_t *ret);
int pidfd_get_uid(int fd, uid_t *ret);
int pidfd_get_cgroupid(int fd, uint64_t *ret);
int pidfd_get_inode_id(int fd, uint64_t *ret);
+7 -34
View File
@@ -1,36 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#if HAVE_PIDFD_OPEN
#include <sys/pidfd.h>
#endif
#include "errno-util.h"
#include "fd-util.h"
#include "missing_magic.h"
#include "missing_syscall.h"
#include "missing_wait.h"
#include "parse-util.h"
#include "pidfd-util.h"
#include "pidref.h"
#include "process-util.h"
#include "signal-util.h"
#include "stat-util.h"
static int pidfd_inode_ids_supported(void) {
static int cached = -1;
if (cached >= 0)
return cached;
_cleanup_close_ int fd = pidfd_open(getpid_cached(), 0);
if (fd < 0) {
if (ERRNO_IS_NOT_SUPPORTED(errno))
return (cached = false);
return -errno;
}
return (cached = fd_is_fs_type(fd, PID_FS_MAGIC));
}
int pidref_acquire_pidfd_id(PidRef *pidref) {
int r;
@@ -49,19 +27,14 @@ int pidref_acquire_pidfd_id(PidRef *pidref) {
if (pidref->fd_id > 0)
return 0;
r = pidfd_inode_ids_supported();
if (r < 0)
r = pidfd_get_inode_id(pidref->fd, &pidref->fd_id);
if (r < 0) {
if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_debug_errno(r, "Failed to get inode number of pidfd for pid " PID_FMT ": %m",
pidref->pid);
return r;
if (r == 0)
return -EOPNOTSUPP;
}
struct stat st;
if (fstat(pidref->fd, &st) < 0)
return log_debug_errno(errno, "Failed to get inode number of pidfd for pid " PID_FMT ": %m",
pidref->pid);
pidref->fd_id = (uint64_t) st.st_ino;
return 0;
}
+58 -119
View File
@@ -48,6 +48,7 @@
#include "nulstr-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "pidfd-util.h"
#include "process-util.h"
#include "raw-clone.h"
#include "rlimit-util.h"
@@ -395,33 +396,6 @@ int container_get_leader(const char *machine, pid_t *pid) {
return 0;
}
int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret) {
int r;
assert(ret);
for (;;) {
pid_t ppid;
r = get_process_ppid(pid, &ppid);
if (r < 0)
return r;
r = in_same_namespace(pid, ppid, type);
if (r < 0)
return r;
if (r == 0) {
/* If the parent and the child are not in the same
* namespace, then the child is the leader we are
* looking for. */
*ret = pid;
return 0;
}
pid = ppid;
}
}
int pid_is_kernel_thread(pid_t pid) {
_cleanup_free_ char *line = NULL;
unsigned long long flags;
@@ -584,7 +558,6 @@ int pid_get_uid(pid_t pid, uid_t *ret) {
}
int pidref_get_uid(const PidRef *pid, uid_t *ret) {
uid_t uid;
int r;
if (!pidref_is_set(pid))
@@ -593,6 +566,13 @@ int pidref_get_uid(const PidRef *pid, uid_t *ret) {
if (pidref_is_remote(pid))
return -EREMOTE;
if (pid->fd >= 0) {
r = pidfd_get_uid(pid->fd, ret);
if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
return r;
}
uid_t uid;
r = pid_get_uid(pid->pid, &uid);
if (r < 0)
return r;
@@ -678,7 +658,7 @@ int get_process_environ(pid_t pid, char **ret) {
return 0;
}
int get_process_ppid(pid_t pid, pid_t *ret) {
int pid_get_ppid(pid_t pid, pid_t *ret) {
_cleanup_free_ char *line = NULL;
unsigned long ppid;
const char *p;
@@ -733,6 +713,35 @@ int get_process_ppid(pid_t pid, pid_t *ret) {
return 0;
}
int pidref_get_ppid(const PidRef *pidref, pid_t *ret) {
int r;
if (!pidref_is_set(pidref))
return -ESRCH;
if (pidref_is_remote(pidref))
return -EREMOTE;
if (pidref->fd >= 0) {
r = pidfd_get_ppid(pidref->fd, ret);
if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
return r;
}
pid_t ppid;
r = pid_get_ppid(pidref->pid, ret ? &ppid : NULL);
if (r < 0)
return r;
r = pidref_verify(pidref);
if (r < 0)
return r;
if (ret)
*ret = ppid;
return 0;
}
int pid_get_start_time(pid_t pid, usec_t *ret) {
_cleanup_free_ char *line = NULL;
const char *p;
@@ -1073,25 +1082,8 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) {
return 0;
}
int pid_is_my_child(pid_t pid) {
pid_t ppid;
int r;
if (pid < 0)
return -ESRCH;
if (pid <= 1)
return false;
r = get_process_ppid(pid, &ppid);
if (r < 0)
return r;
return ppid == getpid_cached();
}
int pidref_is_my_child(const PidRef *pid) {
int r, result;
int r;
if (!pidref_is_set(pid))
return -ESRCH;
@@ -1099,38 +1091,30 @@ int pidref_is_my_child(const PidRef *pid) {
if (pidref_is_remote(pid))
return -EREMOTE;
result = pid_is_my_child(pid->pid);
if (result < 0)
return result;
if (pid->pid == 1 || pidref_is_self(pid))
return false;
r = pidref_verify(pid);
pid_t ppid;
r = pidref_get_ppid(pid, &ppid);
if (r < 0)
return r;
return result;
return ppid == getpid_cached();
}
int pid_is_unwaited(pid_t pid) {
/* Checks whether a PID is still valid at all, including a zombie */
int pid_is_my_child(pid_t pid) {
if (pid < 0)
return -ESRCH;
if (pid == 0)
return false;
if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */
return true;
if (pid == getpid_cached())
return true;
if (kill(pid, 0) >= 0)
return true;
return errno != ESRCH;
return pidref_is_my_child(&PIDREF_MAKE_FROM_PID(pid));
}
int pidref_is_unwaited(const PidRef *pid) {
int r;
/* Checks whether a PID is still valid at all, including a zombie */
if (!pidref_is_set(pid))
return -ESRCH;
@@ -1149,6 +1133,14 @@ int pidref_is_unwaited(const PidRef *pid) {
return true;
}
int pid_is_unwaited(pid_t pid) {
if (pid == 0)
return true;
return pidref_is_unwaited(&PIDREF_MAKE_FROM_PID(pid));
}
int pid_is_alive(pid_t pid) {
int r;
@@ -1859,59 +1851,6 @@ int get_oom_score_adjust(int *ret) {
return 0;
}
int pidfd_get_pid(int fd, pid_t *ret) {
char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
_cleanup_free_ char *fdinfo = NULL;
int r;
/* Converts a pidfd into a pid. Well known errors:
*
* -EBADF → fd invalid
* -ENOSYS → /proc/ not mounted
* -ENOTTY → fd valid, but not a pidfd
* -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one
* -ESRCH → fd valid, but process is already reaped
*/
assert(fd >= 0);
xsprintf(path, "/proc/self/fdinfo/%i", fd);
r = read_full_virtual_file(path, &fdinfo, NULL);
if (r == -ENOENT)
return proc_fd_enoent_errno();
if (r < 0)
return r;
char *p = find_line_startswith(fdinfo, "Pid:");
if (!p)
return -ENOTTY; /* not a pidfd? */
p = skip_leading_chars(p, /* bad = */ NULL);
p[strcspn(p, WHITESPACE)] = 0;
if (streq(p, "0"))
return -EREMOTE; /* PID is in foreign PID namespace? */
if (streq(p, "-1"))
return -ESRCH; /* refers to reaped process? */
return parse_pid(p, ret);
}
int pidfd_verify_pid(int pidfd, pid_t pid) {
pid_t current_pid;
int r;
assert(pidfd >= 0);
assert(pid > 0);
r = pidfd_get_pid(pidfd, &current_pid);
if (r < 0)
return r;
return current_pid != pid ? -ESRCH : 0;
}
static int rlimit_to_nice(rlim_t limit) {
if (limit <= 1)
return PRIO_MAX-1; /* i.e. 19 */
+3 -8
View File
@@ -14,7 +14,6 @@
#include "alloc-util.h"
#include "format-util.h"
#include "macro.h"
#include "namespace-util.h"
#include "pidref.h"
#include "time-util.h"
@@ -53,15 +52,14 @@ int get_process_gid(pid_t pid, gid_t *ret);
int get_process_cwd(pid_t pid, char **ret);
int get_process_root(pid_t pid, char **ret);
int get_process_environ(pid_t pid, char **ret);
int get_process_ppid(pid_t pid, pid_t *ret);
int pid_get_ppid(pid_t pid, pid_t *ret);
int pidref_get_ppid(const PidRef *pidref, pid_t *ret);
int pid_get_start_time(pid_t pid, usec_t *ret);
int pidref_get_start_time(const PidRef* pid, usec_t *ret);
int pidref_get_start_time(const PidRef *pid, usec_t *ret);
int get_process_umask(pid_t pid, mode_t *ret);
int container_get_leader(const char *machine, pid_t *pid);
int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret);
int wait_for_terminate(pid_t pid, siginfo_t *status);
typedef enum WaitFlags {
@@ -250,9 +248,6 @@ assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX);
/* Like TAKE_PTR() but for pid_t, resetting them to 0 */
#define TAKE_PID(pid) TAKE_GENERIC(pid, pid_t, 0)
int pidfd_get_pid(int fd, pid_t *ret);
int pidfd_verify_pid(int pidfd, pid_t pid);
int setpriority_closest(int priority);
_noreturn_ void freeze(void);
+1 -1
View File
@@ -2334,7 +2334,7 @@ int pty_open_peer_racefree(int fd, int mode) {
if (peer_fd >= 0)
return peer_fd;
if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EINVAL) /* new ioctl() is not supported, return a clear error */
if (ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) /* new ioctl() is not supported, return a clear error */
return -EOPNOTSUPP;
if (errno != EIO)
+1
View File
@@ -16,6 +16,7 @@
#include "fileio.h"
#include "macro.h"
#include "missing_threads.h"
#include "namespace-util.h"
#include "process-util.h"
#include "stat-util.h"
#include "string-table.h"