forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
5.1 KiB
246 lines
5.1 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
|
|
#define _GNU_SOURCE |
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <limits.h> |
|
#include <linux/types.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 "pidfd.h" |
|
#include "../kselftest_harness.h" |
|
|
|
/* |
|
* UNKNOWN_FD is an fd number that should never exist in the child, as it is |
|
* used to check the negative case. |
|
*/ |
|
#define UNKNOWN_FD 111 |
|
#define UID_NOBODY 65535 |
|
|
|
static int sys_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, |
|
unsigned long idx2) |
|
{ |
|
return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); |
|
} |
|
|
|
static int __child(int sk, int memfd) |
|
{ |
|
int ret; |
|
char buf; |
|
|
|
/* |
|
* Ensure we don't leave around a bunch of orphaned children if our |
|
* tests fail. |
|
*/ |
|
ret = prctl(PR_SET_PDEATHSIG, SIGKILL); |
|
if (ret) { |
|
fprintf(stderr, "%s: Child could not set DEATHSIG\n", |
|
strerror(errno)); |
|
return -1; |
|
} |
|
|
|
ret = send(sk, &memfd, sizeof(memfd), 0); |
|
if (ret != sizeof(memfd)) { |
|
fprintf(stderr, "%s: Child failed to send fd number\n", |
|
strerror(errno)); |
|
return -1; |
|
} |
|
|
|
/* |
|
* The fixture setup is completed at this point. The tests will run. |
|
* |
|
* This blocking recv enables the parent to message the child. |
|
* Either we will read 'P' off of the sk, indicating that we need |
|
* to disable ptrace, or we will read a 0, indicating that the other |
|
* side has closed the sk. This occurs during fixture teardown time, |
|
* indicating that the child should exit. |
|
*/ |
|
while ((ret = recv(sk, &buf, sizeof(buf), 0)) > 0) { |
|
if (buf == 'P') { |
|
ret = prctl(PR_SET_DUMPABLE, 0); |
|
if (ret < 0) { |
|
fprintf(stderr, |
|
"%s: Child failed to disable ptrace\n", |
|
strerror(errno)); |
|
return -1; |
|
} |
|
} else { |
|
fprintf(stderr, "Child received unknown command %c\n", |
|
buf); |
|
return -1; |
|
} |
|
ret = send(sk, &buf, sizeof(buf), 0); |
|
if (ret != 1) { |
|
fprintf(stderr, "%s: Child failed to ack\n", |
|
strerror(errno)); |
|
return -1; |
|
} |
|
} |
|
if (ret < 0) { |
|
fprintf(stderr, "%s: Child failed to read from socket\n", |
|
strerror(errno)); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int child(int sk) |
|
{ |
|
int memfd, ret; |
|
|
|
memfd = sys_memfd_create("test", 0); |
|
if (memfd < 0) { |
|
fprintf(stderr, "%s: Child could not create memfd\n", |
|
strerror(errno)); |
|
ret = -1; |
|
} else { |
|
ret = __child(sk, memfd); |
|
close(memfd); |
|
} |
|
|
|
close(sk); |
|
return ret; |
|
} |
|
|
|
FIXTURE(child) |
|
{ |
|
/* |
|
* remote_fd is the number of the FD which we are trying to retrieve |
|
* from the child. |
|
*/ |
|
int remote_fd; |
|
/* pid points to the child which we are fetching FDs from */ |
|
pid_t pid; |
|
/* pidfd is the pidfd of the child */ |
|
int pidfd; |
|
/* |
|
* sk is our side of the socketpair used to communicate with the child. |
|
* When it is closed, the child will exit. |
|
*/ |
|
int sk; |
|
}; |
|
|
|
FIXTURE_SETUP(child) |
|
{ |
|
int ret, sk_pair[2]; |
|
|
|
ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair)) { |
|
TH_LOG("%s: failed to create socketpair", strerror(errno)); |
|
} |
|
self->sk = sk_pair[0]; |
|
|
|
self->pid = fork(); |
|
ASSERT_GE(self->pid, 0); |
|
|
|
if (self->pid == 0) { |
|
close(sk_pair[0]); |
|
if (child(sk_pair[1])) |
|
_exit(EXIT_FAILURE); |
|
_exit(EXIT_SUCCESS); |
|
} |
|
|
|
close(sk_pair[1]); |
|
|
|
self->pidfd = sys_pidfd_open(self->pid, 0); |
|
ASSERT_GE(self->pidfd, 0); |
|
|
|
/* |
|
* Wait for the child to complete setup. It'll send the remote memfd's |
|
* number when ready. |
|
*/ |
|
ret = recv(sk_pair[0], &self->remote_fd, sizeof(self->remote_fd), 0); |
|
ASSERT_EQ(sizeof(self->remote_fd), ret); |
|
} |
|
|
|
FIXTURE_TEARDOWN(child) |
|
{ |
|
EXPECT_EQ(0, close(self->pidfd)); |
|
EXPECT_EQ(0, close(self->sk)); |
|
|
|
EXPECT_EQ(0, wait_for_pid(self->pid)); |
|
} |
|
|
|
TEST_F(child, disable_ptrace) |
|
{ |
|
int uid, fd; |
|
char c; |
|
|
|
/* |
|
* Turn into nobody if we're root, to avoid CAP_SYS_PTRACE |
|
* |
|
* The tests should run in their own process, so even this test fails, |
|
* it shouldn't result in subsequent tests failing. |
|
*/ |
|
uid = getuid(); |
|
if (uid == 0) |
|
ASSERT_EQ(0, seteuid(UID_NOBODY)); |
|
|
|
ASSERT_EQ(1, send(self->sk, "P", 1, 0)); |
|
ASSERT_EQ(1, recv(self->sk, &c, 1, 0)); |
|
|
|
fd = sys_pidfd_getfd(self->pidfd, self->remote_fd, 0); |
|
EXPECT_EQ(-1, fd); |
|
EXPECT_EQ(EPERM, errno); |
|
|
|
if (uid == 0) |
|
ASSERT_EQ(0, seteuid(0)); |
|
} |
|
|
|
TEST_F(child, fetch_fd) |
|
{ |
|
int fd, ret; |
|
|
|
fd = sys_pidfd_getfd(self->pidfd, self->remote_fd, 0); |
|
ASSERT_GE(fd, 0); |
|
|
|
ret = sys_kcmp(getpid(), self->pid, KCMP_FILE, fd, self->remote_fd); |
|
if (ret < 0 && errno == ENOSYS) |
|
SKIP(return, "kcmp() syscall not supported"); |
|
EXPECT_EQ(ret, 0); |
|
|
|
ret = fcntl(fd, F_GETFD); |
|
ASSERT_GE(ret, 0); |
|
EXPECT_GE(ret & FD_CLOEXEC, 0); |
|
|
|
close(fd); |
|
} |
|
|
|
TEST_F(child, test_unknown_fd) |
|
{ |
|
int fd; |
|
|
|
fd = sys_pidfd_getfd(self->pidfd, UNKNOWN_FD, 0); |
|
EXPECT_EQ(-1, fd) { |
|
TH_LOG("getfd succeeded while fetching unknown fd"); |
|
}; |
|
EXPECT_EQ(EBADF, errno) { |
|
TH_LOG("%s: getfd did not get EBADF", strerror(errno)); |
|
} |
|
} |
|
|
|
TEST(flags_set) |
|
{ |
|
ASSERT_EQ(-1, sys_pidfd_getfd(0, 0, 1)); |
|
EXPECT_EQ(errno, EINVAL); |
|
} |
|
|
|
#if __NR_pidfd_getfd == -1 |
|
int main(void) |
|
{ |
|
fprintf(stderr, "__NR_pidfd_getfd undefined. The pidfd_getfd syscall is unavailable. Test aborting\n"); |
|
return KSFT_SKIP; |
|
} |
|
#else |
|
TEST_HARNESS_MAIN |
|
#endif
|
|
|