Loading tools/testing/selftests/pidfd/.gitignore +1 −0 Original line number Diff line number Diff line Loading @@ -6,3 +6,4 @@ pidfd_wait pidfd_fdinfo_test pidfd_getfd_test pidfd_setns_test pidfd_file_handle_test tools/testing/selftests/pidfd/Makefile +2 −1 Original line number Diff line number Diff line Loading @@ -2,7 +2,8 @@ CFLAGS += -g $(KHDR_INCLUDES) -pthread -Wall TEST_GEN_PROGS := pidfd_test pidfd_fdinfo_test pidfd_open_test \ pidfd_poll_test pidfd_wait pidfd_getfd_test pidfd_setns_test pidfd_poll_test pidfd_wait pidfd_getfd_test pidfd_setns_test \ pidfd_file_handle_test include ../lib.mk tools/testing/selftests/pidfd/pidfd.h +39 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ #include <sys/wait.h> #include "../kselftest.h" #include "../clone3/clone3_selftests.h" #ifndef P_PIDFD #define P_PIDFD 3 Loading Loading @@ -68,6 +69,11 @@ #define PIDFD_SKIP 3 #define PIDFD_XFAIL 4 static inline int sys_waitid(int which, pid_t pid, siginfo_t *info, int options) { return syscall(__NR_waitid, which, pid, info, options, NULL); } static inline int wait_for_pid(pid_t pid) { int status, ret; Loading Loading @@ -114,4 +120,37 @@ static inline int sys_memfd_create(const char *name, unsigned int flags) return syscall(__NR_memfd_create, name, flags); } static inline pid_t create_child(int *pidfd, unsigned flags) { struct __clone_args args = { .flags = CLONE_PIDFD | flags, .exit_signal = SIGCHLD, .pidfd = ptr_to_u64(pidfd), }; return sys_clone3(&args, sizeof(struct __clone_args)); } static inline ssize_t read_nointr(int fd, void *buf, size_t count) { ssize_t ret; do { ret = read(fd, buf, count); } while (ret < 0 && errno == EINTR); return ret; } static inline ssize_t write_nointr(int fd, const void *buf, size_t count) { ssize_t ret; do { ret = write(fd, buf, count); } while (ret < 0 && errno == EINTR); return ret; } #endif /* __PIDFD_H */ tools/testing/selftests/pidfd/pidfd_file_handle_test.c 0 → 100644 +503 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0 #define _GNU_SOURCE #include <errno.h> #include <fcntl.h> #include <limits.h> #include <linux/types.h> #include <poll.h> #include <sched.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <syscall.h> #include <sys/prctl.h> #include <sys/wait.h> #include <unistd.h> #include <sys/socket.h> #include <linux/kcmp.h> #include <sys/stat.h> #include "pidfd.h" #include "../kselftest_harness.h" FIXTURE(file_handle) { pid_t pid; int pidfd; pid_t child_pid1; int child_pidfd1; pid_t child_pid2; int child_pidfd2; pid_t child_pid3; int child_pidfd3; }; FIXTURE_SETUP(file_handle) { int ret; int ipc_sockets[2]; char c; self->pid = getpid(); self->pidfd = sys_pidfd_open(self->pid, 0); ASSERT_GE(self->pidfd, 0); ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); EXPECT_EQ(ret, 0); self->child_pid1 = create_child(&self->child_pidfd1, CLONE_NEWUSER); EXPECT_GE(self->child_pid1, 0); if (self->child_pid1 == 0) { close(ipc_sockets[0]); if (write_nointr(ipc_sockets[1], "1", 1) < 0) _exit(EXIT_FAILURE); close(ipc_sockets[1]); pause(); _exit(EXIT_SUCCESS); } close(ipc_sockets[1]); ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); close(ipc_sockets[0]); ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); EXPECT_EQ(ret, 0); self->child_pid2 = create_child(&self->child_pidfd2, CLONE_NEWUSER | CLONE_NEWPID); EXPECT_GE(self->child_pid2, 0); if (self->child_pid2 == 0) { close(ipc_sockets[0]); if (write_nointr(ipc_sockets[1], "1", 1) < 0) _exit(EXIT_FAILURE); close(ipc_sockets[1]); pause(); _exit(EXIT_SUCCESS); } close(ipc_sockets[1]); ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); close(ipc_sockets[0]); ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); EXPECT_EQ(ret, 0); self->child_pid3 = create_child(&self->child_pidfd3, CLONE_NEWUSER | CLONE_NEWPID); EXPECT_GE(self->child_pid3, 0); if (self->child_pid3 == 0) { close(ipc_sockets[0]); if (write_nointr(ipc_sockets[1], "1", 1) < 0) _exit(EXIT_FAILURE); close(ipc_sockets[1]); pause(); _exit(EXIT_SUCCESS); } close(ipc_sockets[1]); ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); close(ipc_sockets[0]); } FIXTURE_TEARDOWN(file_handle) { EXPECT_EQ(close(self->pidfd), 0); EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd1, SIGKILL, NULL, 0), 0); if (self->child_pidfd1 >= 0) EXPECT_EQ(0, close(self->child_pidfd1)); EXPECT_EQ(sys_waitid(P_PID, self->child_pid1, NULL, WEXITED), 0); EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd2, SIGKILL, NULL, 0), 0); if (self->child_pidfd2 >= 0) EXPECT_EQ(0, close(self->child_pidfd2)); EXPECT_EQ(sys_waitid(P_PID, self->child_pid2, NULL, WEXITED), 0); if (self->child_pidfd3 >= 0) { EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd3, SIGKILL, NULL, 0), 0); EXPECT_EQ(0, close(self->child_pidfd3)); EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0); } } /* * Test that we can decode a pidfs file handle in the same pid * namespace. */ TEST_F(file_handle, file_handle_same_pidns) { int mnt_id; struct file_handle *fh; int pidfd = -EBADF; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd1, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd1, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_CLOEXEC); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_NONBLOCK); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); free(fh); } /* * Test that we can decode a pidfs file handle from a child pid * namespace. */ TEST_F(file_handle, file_handle_child_pidns) { int mnt_id; struct file_handle *fh; int pidfd = -EBADF; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_CLOEXEC); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_NONBLOCK); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); free(fh); } /* * Test that we fail to decode a pidfs file handle from an ancestor * child pid namespace. */ TEST_F(file_handle, file_handle_foreign_pidns) { int mnt_id; struct file_handle *fh; pid_t pid; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->pidfd, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(setns(self->child_pidfd2, CLONE_NEWUSER | CLONE_NEWPID), 0); pid = fork(); ASSERT_GE(pid, 0); if (pid == 0) { int pidfd = open_by_handle_at(self->pidfd, fh, 0); if (pidfd >= 0) { TH_LOG("Managed to open pidfd outside of the caller's pid namespace hierarchy"); _exit(1); } _exit(0); } ASSERT_EQ(wait_for_pid(pid), 0); free(fh); } /* * Test that we can decode a pidfs file handle of a process that has * exited but not been reaped. */ TEST_F(file_handle, pid_has_exited) { int mnt_id, pidfd, child_pidfd3; struct file_handle *fh; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd3, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd3, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); child_pidfd3 = self->child_pidfd3; self->child_pidfd3 = -EBADF; EXPECT_EQ(sys_pidfd_send_signal(child_pidfd3, SIGKILL, NULL, 0), 0); EXPECT_EQ(close(child_pidfd3), 0); EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED | WNOWAIT), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0); } /* * Test that we fail to decode a pidfs file handle of a process that has * already been reaped. */ TEST_F(file_handle, pid_has_been_reaped) { int mnt_id, pidfd, child_pidfd3; struct file_handle *fh; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd3, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd3, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); child_pidfd3 = self->child_pidfd3; self->child_pidfd3 = -EBADF; EXPECT_EQ(sys_pidfd_send_signal(child_pidfd3, SIGKILL, NULL, 0), 0); EXPECT_EQ(close(child_pidfd3), 0); EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_LT(pidfd, 0); } /* * Test valid flags to open a pidfd file handle. Note, that * PIDFD_NONBLOCK is defined as O_NONBLOCK and O_NONBLOCK is an alias to * O_NDELAY. Also note that PIDFD_THREAD is an alias for O_EXCL. */ TEST_F(file_handle, open_by_handle_at_valid_flags) { int mnt_id; struct file_handle *fh; int pidfd = -EBADF; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_RDONLY | O_WRONLY | O_RDWR | O_NONBLOCK | O_NDELAY | O_CLOEXEC | O_EXCL); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); } /* * Test that invalid flags passed to open a pidfd file handle are * rejected. */ TEST_F(file_handle, open_by_handle_at_invalid_flags) { int mnt_id; struct file_handle *fh; int pidfd = -EBADF; static const struct invalid_pidfs_file_handle_flags { int oflag; const char *oflag_name; } invalid_pidfs_file_handle_flags[] = { { FASYNC, "FASYNC" }, { O_CREAT, "O_CREAT" }, { O_NOCTTY, "O_NOCTTY" }, { O_CREAT, "O_CREAT" }, { O_TRUNC, "O_TRUNC" }, { O_APPEND, "O_APPEND" }, { O_SYNC, "O_SYNC" }, { O_DSYNC, "O_DSYNC" }, { O_DIRECT, "O_DIRECT" }, { O_DIRECTORY, "O_DIRECTORY" }, { O_NOFOLLOW, "O_NOFOLLOW" }, { O_NOATIME, "O_NOATIME" }, { O_PATH, "O_PATH" }, { O_TMPFILE, "O_TMPFILE" }, /* * O_LARGEFILE is added implicitly by * open_by_handle_at() so pidfs simply masks it off. */ }; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0); for (int i = 0; i < ARRAY_SIZE(invalid_pidfs_file_handle_flags); i++) { pidfd = open_by_handle_at(self->pidfd, fh, invalid_pidfs_file_handle_flags[i].oflag); ASSERT_LT(pidfd, 0) { TH_LOG("open_by_handle_at() succeeded with invalid flags: %s", invalid_pidfs_file_handle_flags[i].oflag_name); } } } /* Test that lookup fails. */ TEST_F(file_handle, lookup_must_fail) { int mnt_id; struct file_handle *fh; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_NE(name_to_handle_at(self->child_pidfd2, "lookup-is-not-possible-with-pidfs", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(errno, ENOTDIR); ASSERT_NE(name_to_handle_at(self->child_pidfd2, "lookup-is-not-possible-with-pidfs", fh, &mnt_id, 0), 0); ASSERT_EQ(errno, ENOTDIR); } #ifndef AT_HANDLE_CONNECTABLE #define AT_HANDLE_CONNECTABLE 0x002 #endif /* * Test that AT_HANDLE_CONNECTABLE is rejected. Connectable file handles * don't make sense for pidfs. Note that currently AT_HANDLE_CONNECTABLE * is rejected because it is incompatible with AT_EMPTY_PATH which is * required with pidfds as we don't support lookup. */ TEST_F(file_handle, invalid_name_to_handle_at_flags) { int mnt_id; struct file_handle *fh; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_NE(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH | AT_HANDLE_CONNECTABLE), 0); } #ifndef AT_HANDLE_FID #define AT_HANDLE_FID 0x200 #endif /* * Test that a request with AT_HANDLE_FID always leads to decodable file * handle as pidfs always provides export operations. */ TEST_F(file_handle, valid_name_to_handle_at_flags) { int mnt_id, pidfd; struct file_handle *fh; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH | AT_HANDLE_FID), 0); ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); } TEST_HARNESS_MAIN tools/testing/selftests/pidfd/pidfd_setns_test.c +4 −43 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ #include <linux/ioctl.h> #include "pidfd.h" #include "../clone3/clone3_selftests.h" #include "../kselftest_harness.h" #ifndef PIDFS_IOCTL_MAGIC Loading Loading @@ -118,22 +117,6 @@ FIXTURE(current_nsset) int child_pidfd_derived_nsfds2[PIDFD_NS_MAX]; }; static int sys_waitid(int which, pid_t pid, int options) { return syscall(__NR_waitid, which, pid, NULL, options, NULL); } pid_t create_child(int *pidfd, unsigned flags) { struct __clone_args args = { .flags = CLONE_PIDFD | flags, .exit_signal = SIGCHLD, .pidfd = ptr_to_u64(pidfd), }; return sys_clone3(&args, sizeof(struct clone_args)); } static bool switch_timens(void) { int fd, ret; Loading @@ -150,28 +133,6 @@ static bool switch_timens(void) return ret == 0; } static ssize_t read_nointr(int fd, void *buf, size_t count) { ssize_t ret; do { ret = read(fd, buf, count); } while (ret < 0 && errno == EINTR); return ret; } static ssize_t write_nointr(int fd, const void *buf, size_t count) { ssize_t ret; do { ret = write(fd, buf, count); } while (ret < 0 && errno == EINTR); return ret; } FIXTURE_SETUP(current_nsset) { int i, proc_fd, ret; Loading Loading @@ -229,7 +190,7 @@ FIXTURE_SETUP(current_nsset) _exit(EXIT_SUCCESS); } ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, WEXITED | WNOWAIT), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, NULL, WEXITED | WNOWAIT), 0); self->pidfd = sys_pidfd_open(self->pid, 0); EXPECT_GE(self->pidfd, 0) { Loading Loading @@ -432,9 +393,9 @@ FIXTURE_TEARDOWN(current_nsset) EXPECT_EQ(0, close(self->child_pidfd1)); if (self->child_pidfd2 >= 0) EXPECT_EQ(0, close(self->child_pidfd2)); ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid1, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid2, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, NULL, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid1, NULL, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid2, NULL, WEXITED), 0); } static int preserve_ns(const int pid, const char *ns) Loading Loading
tools/testing/selftests/pidfd/.gitignore +1 −0 Original line number Diff line number Diff line Loading @@ -6,3 +6,4 @@ pidfd_wait pidfd_fdinfo_test pidfd_getfd_test pidfd_setns_test pidfd_file_handle_test
tools/testing/selftests/pidfd/Makefile +2 −1 Original line number Diff line number Diff line Loading @@ -2,7 +2,8 @@ CFLAGS += -g $(KHDR_INCLUDES) -pthread -Wall TEST_GEN_PROGS := pidfd_test pidfd_fdinfo_test pidfd_open_test \ pidfd_poll_test pidfd_wait pidfd_getfd_test pidfd_setns_test pidfd_poll_test pidfd_wait pidfd_getfd_test pidfd_setns_test \ pidfd_file_handle_test include ../lib.mk
tools/testing/selftests/pidfd/pidfd.h +39 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ #include <sys/wait.h> #include "../kselftest.h" #include "../clone3/clone3_selftests.h" #ifndef P_PIDFD #define P_PIDFD 3 Loading Loading @@ -68,6 +69,11 @@ #define PIDFD_SKIP 3 #define PIDFD_XFAIL 4 static inline int sys_waitid(int which, pid_t pid, siginfo_t *info, int options) { return syscall(__NR_waitid, which, pid, info, options, NULL); } static inline int wait_for_pid(pid_t pid) { int status, ret; Loading Loading @@ -114,4 +120,37 @@ static inline int sys_memfd_create(const char *name, unsigned int flags) return syscall(__NR_memfd_create, name, flags); } static inline pid_t create_child(int *pidfd, unsigned flags) { struct __clone_args args = { .flags = CLONE_PIDFD | flags, .exit_signal = SIGCHLD, .pidfd = ptr_to_u64(pidfd), }; return sys_clone3(&args, sizeof(struct __clone_args)); } static inline ssize_t read_nointr(int fd, void *buf, size_t count) { ssize_t ret; do { ret = read(fd, buf, count); } while (ret < 0 && errno == EINTR); return ret; } static inline ssize_t write_nointr(int fd, const void *buf, size_t count) { ssize_t ret; do { ret = write(fd, buf, count); } while (ret < 0 && errno == EINTR); return ret; } #endif /* __PIDFD_H */
tools/testing/selftests/pidfd/pidfd_file_handle_test.c 0 → 100644 +503 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0 #define _GNU_SOURCE #include <errno.h> #include <fcntl.h> #include <limits.h> #include <linux/types.h> #include <poll.h> #include <sched.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <syscall.h> #include <sys/prctl.h> #include <sys/wait.h> #include <unistd.h> #include <sys/socket.h> #include <linux/kcmp.h> #include <sys/stat.h> #include "pidfd.h" #include "../kselftest_harness.h" FIXTURE(file_handle) { pid_t pid; int pidfd; pid_t child_pid1; int child_pidfd1; pid_t child_pid2; int child_pidfd2; pid_t child_pid3; int child_pidfd3; }; FIXTURE_SETUP(file_handle) { int ret; int ipc_sockets[2]; char c; self->pid = getpid(); self->pidfd = sys_pidfd_open(self->pid, 0); ASSERT_GE(self->pidfd, 0); ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); EXPECT_EQ(ret, 0); self->child_pid1 = create_child(&self->child_pidfd1, CLONE_NEWUSER); EXPECT_GE(self->child_pid1, 0); if (self->child_pid1 == 0) { close(ipc_sockets[0]); if (write_nointr(ipc_sockets[1], "1", 1) < 0) _exit(EXIT_FAILURE); close(ipc_sockets[1]); pause(); _exit(EXIT_SUCCESS); } close(ipc_sockets[1]); ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); close(ipc_sockets[0]); ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); EXPECT_EQ(ret, 0); self->child_pid2 = create_child(&self->child_pidfd2, CLONE_NEWUSER | CLONE_NEWPID); EXPECT_GE(self->child_pid2, 0); if (self->child_pid2 == 0) { close(ipc_sockets[0]); if (write_nointr(ipc_sockets[1], "1", 1) < 0) _exit(EXIT_FAILURE); close(ipc_sockets[1]); pause(); _exit(EXIT_SUCCESS); } close(ipc_sockets[1]); ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); close(ipc_sockets[0]); ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); EXPECT_EQ(ret, 0); self->child_pid3 = create_child(&self->child_pidfd3, CLONE_NEWUSER | CLONE_NEWPID); EXPECT_GE(self->child_pid3, 0); if (self->child_pid3 == 0) { close(ipc_sockets[0]); if (write_nointr(ipc_sockets[1], "1", 1) < 0) _exit(EXIT_FAILURE); close(ipc_sockets[1]); pause(); _exit(EXIT_SUCCESS); } close(ipc_sockets[1]); ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); close(ipc_sockets[0]); } FIXTURE_TEARDOWN(file_handle) { EXPECT_EQ(close(self->pidfd), 0); EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd1, SIGKILL, NULL, 0), 0); if (self->child_pidfd1 >= 0) EXPECT_EQ(0, close(self->child_pidfd1)); EXPECT_EQ(sys_waitid(P_PID, self->child_pid1, NULL, WEXITED), 0); EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd2, SIGKILL, NULL, 0), 0); if (self->child_pidfd2 >= 0) EXPECT_EQ(0, close(self->child_pidfd2)); EXPECT_EQ(sys_waitid(P_PID, self->child_pid2, NULL, WEXITED), 0); if (self->child_pidfd3 >= 0) { EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd3, SIGKILL, NULL, 0), 0); EXPECT_EQ(0, close(self->child_pidfd3)); EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0); } } /* * Test that we can decode a pidfs file handle in the same pid * namespace. */ TEST_F(file_handle, file_handle_same_pidns) { int mnt_id; struct file_handle *fh; int pidfd = -EBADF; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd1, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd1, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_CLOEXEC); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_NONBLOCK); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); free(fh); } /* * Test that we can decode a pidfs file handle from a child pid * namespace. */ TEST_F(file_handle, file_handle_child_pidns) { int mnt_id; struct file_handle *fh; int pidfd = -EBADF; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_CLOEXEC); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_NONBLOCK); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); free(fh); } /* * Test that we fail to decode a pidfs file handle from an ancestor * child pid namespace. */ TEST_F(file_handle, file_handle_foreign_pidns) { int mnt_id; struct file_handle *fh; pid_t pid; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->pidfd, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(setns(self->child_pidfd2, CLONE_NEWUSER | CLONE_NEWPID), 0); pid = fork(); ASSERT_GE(pid, 0); if (pid == 0) { int pidfd = open_by_handle_at(self->pidfd, fh, 0); if (pidfd >= 0) { TH_LOG("Managed to open pidfd outside of the caller's pid namespace hierarchy"); _exit(1); } _exit(0); } ASSERT_EQ(wait_for_pid(pid), 0); free(fh); } /* * Test that we can decode a pidfs file handle of a process that has * exited but not been reaped. */ TEST_F(file_handle, pid_has_exited) { int mnt_id, pidfd, child_pidfd3; struct file_handle *fh; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd3, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd3, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); child_pidfd3 = self->child_pidfd3; self->child_pidfd3 = -EBADF; EXPECT_EQ(sys_pidfd_send_signal(child_pidfd3, SIGKILL, NULL, 0), 0); EXPECT_EQ(close(child_pidfd3), 0); EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED | WNOWAIT), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0); } /* * Test that we fail to decode a pidfs file handle of a process that has * already been reaped. */ TEST_F(file_handle, pid_has_been_reaped) { int mnt_id, pidfd, child_pidfd3; struct file_handle *fh; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd3, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd3, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); child_pidfd3 = self->child_pidfd3; self->child_pidfd3 = -EBADF; EXPECT_EQ(sys_pidfd_send_signal(child_pidfd3, SIGKILL, NULL, 0), 0); EXPECT_EQ(close(child_pidfd3), 0); EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_LT(pidfd, 0); } /* * Test valid flags to open a pidfd file handle. Note, that * PIDFD_NONBLOCK is defined as O_NONBLOCK and O_NONBLOCK is an alias to * O_NDELAY. Also note that PIDFD_THREAD is an alias for O_EXCL. */ TEST_F(file_handle, open_by_handle_at_valid_flags) { int mnt_id; struct file_handle *fh; int pidfd = -EBADF; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, O_RDONLY | O_WRONLY | O_RDWR | O_NONBLOCK | O_NDELAY | O_CLOEXEC | O_EXCL); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); } /* * Test that invalid flags passed to open a pidfd file handle are * rejected. */ TEST_F(file_handle, open_by_handle_at_invalid_flags) { int mnt_id; struct file_handle *fh; int pidfd = -EBADF; static const struct invalid_pidfs_file_handle_flags { int oflag; const char *oflag_name; } invalid_pidfs_file_handle_flags[] = { { FASYNC, "FASYNC" }, { O_CREAT, "O_CREAT" }, { O_NOCTTY, "O_NOCTTY" }, { O_CREAT, "O_CREAT" }, { O_TRUNC, "O_TRUNC" }, { O_APPEND, "O_APPEND" }, { O_SYNC, "O_SYNC" }, { O_DSYNC, "O_DSYNC" }, { O_DIRECT, "O_DIRECT" }, { O_DIRECTORY, "O_DIRECTORY" }, { O_NOFOLLOW, "O_NOFOLLOW" }, { O_NOATIME, "O_NOATIME" }, { O_PATH, "O_PATH" }, { O_TMPFILE, "O_TMPFILE" }, /* * O_LARGEFILE is added implicitly by * open_by_handle_at() so pidfs simply masks it off. */ }; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0); for (int i = 0; i < ARRAY_SIZE(invalid_pidfs_file_handle_flags); i++) { pidfd = open_by_handle_at(self->pidfd, fh, invalid_pidfs_file_handle_flags[i].oflag); ASSERT_LT(pidfd, 0) { TH_LOG("open_by_handle_at() succeeded with invalid flags: %s", invalid_pidfs_file_handle_flags[i].oflag_name); } } } /* Test that lookup fails. */ TEST_F(file_handle, lookup_must_fail) { int mnt_id; struct file_handle *fh; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_NE(name_to_handle_at(self->child_pidfd2, "lookup-is-not-possible-with-pidfs", fh, &mnt_id, AT_EMPTY_PATH), 0); ASSERT_EQ(errno, ENOTDIR); ASSERT_NE(name_to_handle_at(self->child_pidfd2, "lookup-is-not-possible-with-pidfs", fh, &mnt_id, 0), 0); ASSERT_EQ(errno, ENOTDIR); } #ifndef AT_HANDLE_CONNECTABLE #define AT_HANDLE_CONNECTABLE 0x002 #endif /* * Test that AT_HANDLE_CONNECTABLE is rejected. Connectable file handles * don't make sense for pidfs. Note that currently AT_HANDLE_CONNECTABLE * is rejected because it is incompatible with AT_EMPTY_PATH which is * required with pidfds as we don't support lookup. */ TEST_F(file_handle, invalid_name_to_handle_at_flags) { int mnt_id; struct file_handle *fh; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_NE(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH | AT_HANDLE_CONNECTABLE), 0); } #ifndef AT_HANDLE_FID #define AT_HANDLE_FID 0x200 #endif /* * Test that a request with AT_HANDLE_FID always leads to decodable file * handle as pidfs always provides export operations. */ TEST_F(file_handle, valid_name_to_handle_at_flags) { int mnt_id, pidfd; struct file_handle *fh; struct stat st1, st2; fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ); ASSERT_NE(fh, NULL); memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ); fh->handle_bytes = MAX_HANDLE_SZ; ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH | AT_HANDLE_FID), 0); ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0); pidfd = open_by_handle_at(self->pidfd, fh, 0); ASSERT_GE(pidfd, 0); ASSERT_EQ(fstat(pidfd, &st2), 0); ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); ASSERT_EQ(close(pidfd), 0); } TEST_HARNESS_MAIN
tools/testing/selftests/pidfd/pidfd_setns_test.c +4 −43 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ #include <linux/ioctl.h> #include "pidfd.h" #include "../clone3/clone3_selftests.h" #include "../kselftest_harness.h" #ifndef PIDFS_IOCTL_MAGIC Loading Loading @@ -118,22 +117,6 @@ FIXTURE(current_nsset) int child_pidfd_derived_nsfds2[PIDFD_NS_MAX]; }; static int sys_waitid(int which, pid_t pid, int options) { return syscall(__NR_waitid, which, pid, NULL, options, NULL); } pid_t create_child(int *pidfd, unsigned flags) { struct __clone_args args = { .flags = CLONE_PIDFD | flags, .exit_signal = SIGCHLD, .pidfd = ptr_to_u64(pidfd), }; return sys_clone3(&args, sizeof(struct clone_args)); } static bool switch_timens(void) { int fd, ret; Loading @@ -150,28 +133,6 @@ static bool switch_timens(void) return ret == 0; } static ssize_t read_nointr(int fd, void *buf, size_t count) { ssize_t ret; do { ret = read(fd, buf, count); } while (ret < 0 && errno == EINTR); return ret; } static ssize_t write_nointr(int fd, const void *buf, size_t count) { ssize_t ret; do { ret = write(fd, buf, count); } while (ret < 0 && errno == EINTR); return ret; } FIXTURE_SETUP(current_nsset) { int i, proc_fd, ret; Loading Loading @@ -229,7 +190,7 @@ FIXTURE_SETUP(current_nsset) _exit(EXIT_SUCCESS); } ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, WEXITED | WNOWAIT), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, NULL, WEXITED | WNOWAIT), 0); self->pidfd = sys_pidfd_open(self->pid, 0); EXPECT_GE(self->pidfd, 0) { Loading Loading @@ -432,9 +393,9 @@ FIXTURE_TEARDOWN(current_nsset) EXPECT_EQ(0, close(self->child_pidfd1)); if (self->child_pidfd2 >= 0) EXPECT_EQ(0, close(self->child_pidfd2)); ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid1, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid2, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, NULL, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid1, NULL, WEXITED), 0); ASSERT_EQ(sys_waitid(P_PID, self->child_pid2, NULL, WEXITED), 0); } static int preserve_ns(const int pid, const char *ns) Loading