Update capsicum-test to git commit f4d97414d48b8f8356b971ab9f45dc5c70d53c40

This includes various fixes that I submitted recently such as updating the
pdkill() tests for the actual implemented behaviour
(https://github.com/google/capsicum-test/pull/53) and lots of changes to
avoid calling sleep() and replacing it with reliable synchronization
(pull requests 49,51,52,53,54). This should make the testsuite more reliable
when running on Jenkins. Additionally, process status is now retrieved using
libprocstat instead of running `ps` and parsing the output
(https://github.com/google/capsicum-test/pull/50). This fixes one previously
failing test and speeds up execution.

Overall, this update reduces the total runtime from ~60s to about 4-5 seconds.
This commit is contained in:
Alex Richardson 2021-03-02 16:28:26 +00:00
commit 955a3f9ad5
10 changed files with 412 additions and 129 deletions

View File

@ -4,6 +4,10 @@ OS:=$(shell uname)
ARCH?=64 ARCH?=64
ARCHFLAG=-m$(ARCH) ARCHFLAG=-m$(ARCH)
ifeq ($(OS),FreeBSD)
EXTRA_LIBS=-lprocstat
endif
ifeq ($(OS),Linux) ifeq ($(OS),Linux)
PROCESSOR:=$(shell uname -p) PROCESSOR:=$(shell uname -p)

View File

@ -486,6 +486,7 @@ FORK_TEST_ON(Capability, Mmap, TmpFile("cap_mmap_operations")) {
// Given a file descriptor, create a capability with specific rights and // Given a file descriptor, create a capability with specific rights and
// make sure only those rights work. // make sure only those rights work.
#define TRY_FILE_OPS(fd, ...) do { \ #define TRY_FILE_OPS(fd, ...) do { \
SCOPED_TRACE(#__VA_ARGS__); \
cap_rights_t rights; \ cap_rights_t rights; \
cap_rights_init(&rights, __VA_ARGS__); \ cap_rights_init(&rights, __VA_ARGS__); \
TryFileOps((fd), rights); \ TryFileOps((fd), rights); \
@ -1019,6 +1020,8 @@ FORK_TEST_ON(Capability, SocketTransfer, TmpFile("cap_fd_transfer")) {
if (child == 0) { if (child == 0) {
// Child: enter cap mode // Child: enter cap mode
EXPECT_OK(cap_enter()); EXPECT_OK(cap_enter());
// Child: send startup notification
SEND_INT_MESSAGE(sock_fds[0], MSG_CHILD_STARTED);
// Child: wait to receive FD over socket // Child: wait to receive FD over socket
int rc = recvmsg(sock_fds[0], &mh, 0); int rc = recvmsg(sock_fds[0], &mh, 0);
@ -1036,10 +1039,12 @@ FORK_TEST_ON(Capability, SocketTransfer, TmpFile("cap_fd_transfer")) {
EXPECT_RIGHTS_EQ(&r_rs, &rights); EXPECT_RIGHTS_EQ(&r_rs, &rights);
TryReadWrite(cap_fd); TryReadWrite(cap_fd);
// Child: acknowledge that we have received and tested the file descriptor
SEND_INT_MESSAGE(sock_fds[0], MSG_CHILD_FD_RECEIVED);
// Child: wait for a normal read // Child: wait for a normal read
int val; AWAIT_INT_MESSAGE(sock_fds[0], MSG_PARENT_REQUEST_CHILD_EXIT);
read(sock_fds[0], &val, sizeof(val)); exit(testing::Test::HasFailure());
exit(0);
} }
int fd = open(TmpFile("cap_fd_transfer"), O_RDWR | O_CREAT, 0644); int fd = open(TmpFile("cap_fd_transfer"), O_RDWR | O_CREAT, 0644);
@ -1054,6 +1059,9 @@ FORK_TEST_ON(Capability, SocketTransfer, TmpFile("cap_fd_transfer")) {
// Confirm we can do the right operations on the capability // Confirm we can do the right operations on the capability
TryReadWrite(cap_fd); TryReadWrite(cap_fd);
// Wait for child to start up:
AWAIT_INT_MESSAGE(sock_fds[1], MSG_CHILD_STARTED);
// Send the file descriptor over the pipe to the sub-process // Send the file descriptor over the pipe to the sub-process
mh.msg_controllen = CMSG_LEN(sizeof(int)); mh.msg_controllen = CMSG_LEN(sizeof(int));
cmptr = CMSG_FIRSTHDR(&mh); cmptr = CMSG_FIRSTHDR(&mh);
@ -1063,13 +1071,14 @@ FORK_TEST_ON(Capability, SocketTransfer, TmpFile("cap_fd_transfer")) {
*(int *)CMSG_DATA(cmptr) = cap_fd; *(int *)CMSG_DATA(cmptr) = cap_fd;
buffer1[0] = 0; buffer1[0] = 0;
iov[0].iov_len = 1; iov[0].iov_len = 1;
sleep(3);
int rc = sendmsg(sock_fds[1], &mh, 0); int rc = sendmsg(sock_fds[1], &mh, 0);
EXPECT_OK(rc); EXPECT_OK(rc);
sleep(1); // Ensure subprocess runs // Check that the child received the message
int zero = 0; AWAIT_INT_MESSAGE(sock_fds[1], MSG_CHILD_FD_RECEIVED);
write(sock_fds[1], &zero, sizeof(zero));
// Tell the child to exit
SEND_INT_MESSAGE(sock_fds[1], MSG_PARENT_REQUEST_CHILD_EXIT);
} }
TEST(Capability, SyscallAt) { TEST(Capability, SyscallAt) {

View File

@ -528,6 +528,8 @@ TEST(Capmode, Abort) {
FORK_TEST_F(WithFiles, AllowedMiscSyscalls) { FORK_TEST_F(WithFiles, AllowedMiscSyscalls) {
umask(022); umask(022);
mode_t um_before = umask(022); mode_t um_before = umask(022);
int pipefds[2];
EXPECT_OK(pipe(pipefds));
EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_OK(cap_enter()); // Enter capability mode.
mode_t um = umask(022); mode_t um = umask(022);
@ -540,13 +542,19 @@ FORK_TEST_F(WithFiles, AllowedMiscSyscalls) {
pid_t pid = fork(); pid_t pid = fork();
EXPECT_OK(pid); EXPECT_OK(pid);
if (pid == 0) { if (pid == 0) {
// Child: almost immediately exit. // Child: wait for an exit message from parent (so we can test waitpid).
sleep(1); EXPECT_OK(close(pipefds[0]));
SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED);
AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT);
exit(0); exit(0);
} else if (pid > 0) { } else if (pid > 0) {
EXPECT_OK(close(pipefds[1]));
AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED);
errno = 0; errno = 0;
EXPECT_CAPMODE(ptrace_(PTRACE_PEEKDATA_, pid, &pid, NULL)); EXPECT_CAPMODE(ptrace_(PTRACE_PEEKDATA_, pid, &pid, NULL));
EXPECT_CAPMODE(waitpid(pid, NULL, 0)); EXPECT_CAPMODE(waitpid(pid, NULL, WNOHANG));
SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_REQUEST_CHILD_EXIT);
if (verbose) fprintf(stderr, " child finished\n");
} }
// No error return from sync(2) to test, but check errno remains unset. // No error return from sync(2) to test, but check errno remains unset.
@ -568,68 +576,136 @@ FORK_TEST_F(WithFiles, AllowedMiscSyscalls) {
} }
void *thread_fn(void *p) { void *thread_fn(void *p) {
int delay = *(int *)p; int fd = (int)(intptr_t)p;
sleep(delay); if (verbose) fprintf(stderr, " thread waiting to run\n");
AWAIT_INT_MESSAGE(fd, MSG_PARENT_CHILD_SHOULD_RUN);
EXPECT_OK(getpid_()); EXPECT_OK(getpid_());
EXPECT_CAPMODE(open("/dev/null", O_RDWR)); EXPECT_CAPMODE(open("/dev/null", O_RDWR));
return NULL; // Return whether there have been any failures to the main thread.
void *rval = (void *)(intptr_t)testing::Test::HasFailure();
if (verbose) fprintf(stderr, " thread finished: %p\n", rval);
return rval;
} }
// Check that restrictions are the same in subprocesses and threads // Check that restrictions are the same in subprocesses and threads
FORK_TEST(Capmode, NewThread) { FORK_TEST(Capmode, NewThread) {
// Fire off a new thread before entering capability mode // Fire off a new thread before entering capability mode
pthread_t early_thread; pthread_t early_thread;
int one = 1; // second void *thread_rval;
EXPECT_OK(pthread_create(&early_thread, NULL, thread_fn, &one)); // Create two pipes, one for synchronization with the threads, the other to
// synchronize with the children (since we can't use waitpid after cap_enter).
// Note: Could use pdfork+pdwait instead, but that is tested in procdesc.cc.
int thread_pipe[2];
EXPECT_OK(pipe(thread_pipe));
int proc_pipe[2];
EXPECT_OK(pipe(proc_pipe));
EXPECT_OK(pthread_create(&early_thread, NULL, thread_fn,
(void *)(intptr_t)thread_pipe[1]));
// Fire off a new process before entering capability mode. // Fire off a new process before entering capability mode.
if (verbose) fprintf(stderr, " starting second child (non-capability mode)\n");
int early_child = fork(); int early_child = fork();
EXPECT_OK(early_child); EXPECT_OK(early_child);
if (early_child == 0) { if (early_child == 0) {
// Child: wait and then confirm this process is unaffect by capability mode in the parent. if (verbose) fprintf(stderr, " first child started\n");
sleep(1); EXPECT_OK(close(proc_pipe[0]));
// Child: wait and then confirm this process is unaffected by capability mode in the parent.
AWAIT_INT_MESSAGE(proc_pipe[1], MSG_PARENT_CHILD_SHOULD_RUN);
int fd = open("/dev/null", O_RDWR); int fd = open("/dev/null", O_RDWR);
EXPECT_OK(fd); EXPECT_OK(fd);
close(fd); close(fd);
exit(0); // Notify the parent of success/failure.
int rval = (int)testing::Test::HasFailure();
SEND_INT_MESSAGE(proc_pipe[1], rval);
if (verbose) fprintf(stderr, " first child finished: %d\n", rval);
exit(rval);
} }
EXPECT_OK(cap_enter()); // Enter capability mode. EXPECT_OK(cap_enter()); // Enter capability mode.
// At this point the current process has both a child process and a
// child thread that were created before entering capability mode.
// - The child process is unaffected by capability mode.
// - The child thread is affected by capability mode.
SEND_INT_MESSAGE(proc_pipe[0], MSG_PARENT_CHILD_SHOULD_RUN);
// Do an allowed syscall. // Do an allowed syscall.
EXPECT_OK(getpid_()); EXPECT_OK(getpid_());
// Wait for the first child to exit (should get a zero exit code message).
AWAIT_INT_MESSAGE(proc_pipe[0], 0);
// The child processes/threads return HasFailure(), so we depend on no prior errors.
ASSERT_FALSE(testing::Test::HasFailure())
<< "Cannot continue test with pre-existing failures.";
// Now that we're in capability mode, if we create a second child process
// it will be affected by capability mode.
if (verbose) fprintf(stderr, " starting second child (in capability mode)\n");
int child = fork(); int child = fork();
EXPECT_OK(child); EXPECT_OK(child);
if (child == 0) { if (child == 0) {
if (verbose) fprintf(stderr, " second child started\n");
EXPECT_OK(close(proc_pipe[0]));
// Child: do an allowed and a disallowed syscall. // Child: do an allowed and a disallowed syscall.
EXPECT_OK(getpid_()); EXPECT_OK(getpid_());
EXPECT_CAPMODE(open("/dev/null", O_RDWR)); EXPECT_CAPMODE(open("/dev/null", O_RDWR));
exit(0); // Notify the parent of success/failure.
int rval = (int)testing::Test::HasFailure();
SEND_INT_MESSAGE(proc_pipe[1], rval);
if (verbose) fprintf(stderr, " second child finished: %d\n", rval);
exit(rval);
} }
// Don't (can't) wait for either child. // Now tell the early_started thread that it can run. We expect it to also
// be affected by capability mode since it's per-process not per-thread.
// Note: it is important that we don't allow the thread to run before fork(),
// since that could result in fork() being called while the thread holds one
// of the gtest-internal mutexes, so the child process deadlocks.
SEND_INT_MESSAGE(thread_pipe[0], MSG_PARENT_CHILD_SHOULD_RUN);
// Wait for the early-started thread. // Wait for the early-started thread.
EXPECT_OK(pthread_join(early_thread, NULL)); EXPECT_OK(pthread_join(early_thread, &thread_rval));
EXPECT_FALSE((bool)(intptr_t)thread_rval) << "thread returned failure";
// Fire off a new thread. // Wait for the second child to exit (should get a zero exit code message).
AWAIT_INT_MESSAGE(proc_pipe[0], 0);
// Fire off a new (second) child thread, which is also affected by capability mode.
ASSERT_FALSE(testing::Test::HasFailure())
<< "Cannot continue test with pre-existing failures.";
pthread_t child_thread; pthread_t child_thread;
int zero = 0; // seconds EXPECT_OK(pthread_create(&child_thread, NULL, thread_fn,
EXPECT_OK(pthread_create(&child_thread, NULL, thread_fn, &zero)); (void *)(intptr_t)thread_pipe[1]));
EXPECT_OK(pthread_join(child_thread, NULL)); SEND_INT_MESSAGE(thread_pipe[0], MSG_PARENT_CHILD_SHOULD_RUN);
EXPECT_OK(pthread_join(child_thread, &thread_rval));
EXPECT_FALSE((bool)(intptr_t)thread_rval) << "thread returned failure";
// Fork a subprocess which fires off a new thread. // Fork a subprocess which fires off a new thread.
ASSERT_FALSE(testing::Test::HasFailure())
<< "Cannot continue test with pre-existing failures.";
if (verbose) fprintf(stderr, " starting third child (in capability mode)\n");
child = fork(); child = fork();
EXPECT_OK(child); EXPECT_OK(child);
if (child == 0) { if (child == 0) {
if (verbose) fprintf(stderr, " third child started\n");
EXPECT_OK(close(proc_pipe[0]));
pthread_t child_thread2; pthread_t child_thread2;
EXPECT_OK(pthread_create(&child_thread2, NULL, thread_fn, &zero)); EXPECT_OK(pthread_create(&child_thread2, NULL, thread_fn,
EXPECT_OK(pthread_join(child_thread2, NULL)); (void *)(intptr_t)thread_pipe[1]));
exit(0); SEND_INT_MESSAGE(thread_pipe[0], MSG_PARENT_CHILD_SHOULD_RUN);
EXPECT_OK(pthread_join(child_thread2, &thread_rval));
EXPECT_FALSE((bool)(intptr_t)thread_rval) << "thread returned failure";
// Notify the parent of success/failure.
int rval = (int)testing::Test::HasFailure();
SEND_INT_MESSAGE(proc_pipe[1], rval);
if (verbose) fprintf(stderr, " third child finished: %d\n", rval);
exit(rval);
} }
// Sleep for a bit to allow the subprocess to finish. // Wait for the third child to exit (should get a zero exit code message).
sleep(2); AWAIT_INT_MESSAGE(proc_pipe[0], 0);
close(proc_pipe[0]);
close(proc_pipe[1]);
close(thread_pipe[0]);
close(thread_pipe[1]);
} }
static int had_signal = 0; static volatile sig_atomic_t had_signal = 0;
static void handle_signal(int) { had_signal = 1; } static void handle_signal(int) { had_signal = 1; }
FORK_TEST(Capmode, SelfKill) { FORK_TEST(Capmode, SelfKill) {

View File

@ -1,5 +1,15 @@
#include "capsicum-test.h" #include "capsicum-test.h"
#ifdef __FreeBSD__
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <libprocstat.h>
#endif
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
@ -48,31 +58,59 @@ char ProcessState(int pid) {
return '?'; return '?';
#endif #endif
#ifdef __FreeBSD__ #ifdef __FreeBSD__
char buffer[1024]; // First check if the process exists/we have permission to see it. This
snprintf(buffer, sizeof(buffer), "ps -p %d -o state | grep -v STAT", pid); // Avoids warning messages being printed to stderr by libprocstat.
sig_t original = signal(SIGCHLD, SIG_IGN); size_t len = 0;
FILE* cmd = popen(buffer, "r"); int name[4];
usleep(50000); // allow any pending SIGCHLD signals to arrive name[0] = CTL_KERN;
signal(SIGCHLD, original); name[1] = KERN_PROC;
int result = fgetc(cmd); name[2] = KERN_PROC_PID;
fclose(cmd); name[3] = pid;
// Map FreeBSD codes to Linux codes. if (sysctl(name, nitems(name), NULL, &len, NULL, 0) < 0 && errno == ESRCH) {
switch (result) { if (verbose) fprintf(stderr, "Process %d does not exist\n", pid);
case EOF: return '\0'; // No such process.
return '\0';
case 'D': // disk wait
case 'R': // runnable
case 'S': // sleeping
case 'T': // stopped
case 'Z': // zombie
return result;
case 'W': // idle interrupt thread
return 'S';
case 'I': // idle
return 'S';
case 'L': // waiting to acquire lock
default:
return '?';
} }
unsigned int count = 0;
struct procstat *prstat = procstat_open_sysctl();
EXPECT_NE(NULL, prstat) << "procstat_open_sysctl failed.";
errno = 0;
struct kinfo_proc *p = procstat_getprocs(prstat, KERN_PROC_PID, pid, &count);
if (p == NULL || count == 0) {
if (verbose) fprintf(stderr, "procstat_getprocs failed with %p/%d: %s\n", p, count, strerror(errno));
procstat_close(prstat);
return '\0';
}
char result = '\0';
// See state() in bin/ps/print.c
switch (p->ki_stat) {
case SSTOP:
result = 'T';
break;
case SSLEEP:
if (p->ki_tdflags & TDF_SINTR) /* interruptable (long) */
result = 'S';
else
result = 'D';
break;
case SRUN:
case SIDL:
result = 'R';
break;
case SWAIT:
case SLOCK:
// We treat SWAIT/SLOCK as 'S' here (instead of 'W'/'L').
result = 'S';
break;
case SZOMB:
result = 'Z';
break;
default:
result = '?';
break;
}
procstat_freeprocs(prstat, p);
procstat_close(prstat);
if (verbose) fprintf(stderr, "Process %d in state '%c'\n", pid, result);
return result;
#endif #endif
} }

View File

@ -140,15 +140,17 @@ const char *TmpFile(const char *pathname);
// Expect a syscall to fail with the given error. // Expect a syscall to fail with the given error.
#define EXPECT_SYSCALL_FAIL(E, C) \ #define EXPECT_SYSCALL_FAIL(E, C) \
do { \ do { \
SCOPED_TRACE(#C); \
EXPECT_GT(0, C); \ EXPECT_GT(0, C); \
EXPECT_EQ(E, errno); \ EXPECT_EQ(E, errno) << "expected '" << strerror(E) \
<< "' but got '" << strerror(errno) << "'"; \
} while (0) } while (0)
// Expect a syscall to fail with anything other than the given error. // Expect a syscall to fail with anything other than the given error.
#define EXPECT_SYSCALL_FAIL_NOT(E, C) \ #define EXPECT_SYSCALL_FAIL_NOT(E, C) \
do { \ do { \
EXPECT_GT(0, C); \ EXPECT_GT(0, C); \
EXPECT_NE(E, errno); \ EXPECT_NE(E, errno) << strerror(E); \
} while (0) } while (0)
// Expect a void syscall to fail with anything other than the given error. // Expect a void syscall to fail with anything other than the given error.
@ -163,7 +165,8 @@ const char *TmpFile(const char *pathname);
// code is OS-specific. // code is OS-specific.
#ifdef O_BENEATH #ifdef O_BENEATH
#define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \ #define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \
do { \ do { \
SCOPED_TRACE(GTEST_STRINGIFY_(openat((fd), (path), (flags)))); \
const int result = openat((fd), (path), (flags)); \ const int result = openat((fd), (path), (flags)); \
if (((flags) & O_BENEATH) == O_BENEATH) { \ if (((flags) & O_BENEATH) == O_BENEATH) { \
EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_O_BENEATH, result); \ EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_O_BENEATH, result); \
@ -175,6 +178,7 @@ const char *TmpFile(const char *pathname);
#else #else
#define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \ #define EXPECT_OPENAT_FAIL_TRAVERSAL(fd, path, flags) \
do { \ do { \
SCOPED_TRACE(GTEST_STRINGIFY_(openat((fd), (path), (flags)))); \
const int result = openat((fd), (path), (flags)); \ const int result = openat((fd), (path), (flags)); \
EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, result); \ EXPECT_SYSCALL_FAIL(E_NO_TRAVERSE_CAPABILITY, result); \
if (result >= 0) { close(result); } \ if (result >= 0) { close(result); } \
@ -200,7 +204,8 @@ const char *TmpFile(const char *pathname);
int rc = C; \ int rc = C; \
EXPECT_GT(0, rc); \ EXPECT_GT(0, rc); \
EXPECT_TRUE(errno == ECAPMODE || errno == ENOTCAPABLE) \ EXPECT_TRUE(errno == ECAPMODE || errno == ENOTCAPABLE) \
<< #C << " did not fail with ECAPMODE/ENOTCAPABLE but " << errno; \ << #C << " did not fail with ECAPMODE/ENOTCAPABLE but " << errno \
<< "(" << strerror(errno) << ")"; \
} while (0) } while (0)
// Ensure that 'rights' are a subset of 'max'. // Ensure that 'rights' are a subset of 'max'.
@ -244,6 +249,29 @@ char ProcessState(int pid);
#define EXPECT_PID_ZOMBIE(pid) EXPECT_PID_REACHES_STATES(pid, 'Z', 'Z'); #define EXPECT_PID_ZOMBIE(pid) EXPECT_PID_REACHES_STATES(pid, 'Z', 'Z');
#define EXPECT_PID_GONE(pid) EXPECT_PID_REACHES_STATES(pid, '\0', '\0'); #define EXPECT_PID_GONE(pid) EXPECT_PID_REACHES_STATES(pid, '\0', '\0');
enum {
// Magic numbers for messages sent by child processes.
MSG_CHILD_STARTED = 1234,
MSG_CHILD_FD_RECEIVED = 4321,
// Magic numbers for messages sent by parent processes.
MSG_PARENT_REQUEST_CHILD_EXIT = 9999,
MSG_PARENT_CLOSED_FD = 10000,
MSG_PARENT_CHILD_SHOULD_RUN = 10001,
};
#define SEND_INT_MESSAGE(fd, message) \
do { \
int _msg = message; \
EXPECT_EQ(sizeof(_msg), (size_t)write(fd, &_msg, sizeof(_msg))); \
} while (0)
#define AWAIT_INT_MESSAGE(fd, expected) \
do { \
int _msg = 0; \
EXPECT_EQ(sizeof(_msg), (size_t)read(fd, &_msg, sizeof(_msg))); \
EXPECT_EQ(expected, _msg); \
} while (0)
// Mark a test that can only be run as root. // Mark a test that can only be run as root.
#define GTEST_SKIP_IF_NOT_ROOT() \ #define GTEST_SKIP_IF_NOT_ROOT() \
if (getuid() != 0) { GTEST_SKIP() << "requires root"; } if (getuid() != 0) { GTEST_SKIP() << "requires root"; }

View File

@ -8,7 +8,7 @@ CXXFLAGS+=$(ARCHFLAG) -Wall -g $(GTEST_INCS) $(GTEST_FLAGS) --std=c++11
CFLAGS+=$(ARCHFLAG) -Wall -g CFLAGS+=$(ARCHFLAG) -Wall -g
capsicum-test: $(OBJECTS) libgtest.a $(LOCAL_LIBS) capsicum-test: $(OBJECTS) libgtest.a $(LOCAL_LIBS)
$(CXX) $(CXXFLAGS) -g -o $@ $(OBJECTS) libgtest.a -lpthread -lrt $(LIBSCTP) $(LIBCAPRIGHTS) $(CXX) $(CXXFLAGS) -g -o $@ $(OBJECTS) libgtest.a -lpthread -lrt $(LIBSCTP) $(LIBCAPRIGHTS) $(EXTRA_LIBS)
# Small statically-linked program for fexecve tests # Small statically-linked program for fexecve tests
# (needs to be statically linked so that execve()ing it # (needs to be statically linked so that execve()ing it

View File

@ -11,6 +11,7 @@
// Check an open call works and close the resulting fd. // Check an open call works and close the resulting fd.
#define EXPECT_OPEN_OK(f) do { \ #define EXPECT_OPEN_OK(f) do { \
SCOPED_TRACE(#f); \
int _fd = f; \ int _fd = f; \
EXPECT_OK(_fd); \ EXPECT_OK(_fd); \
close(_fd); \ close(_fd); \

View File

@ -27,10 +27,6 @@
#define __WALL 0 #define __WALL 0
#endif #endif
// TODO(drysdale): it would be nice to use proper synchronization between
// processes, rather than synchronization-via-sleep; faster too.
//------------------------------------------------ //------------------------------------------------
// Utilities for the tests. // Utilities for the tests.
@ -73,7 +69,10 @@ static void print_stat(FILE *f, const struct stat *stat) {
(long)stat->st_atime, (long)stat->st_mtime, (long)stat->st_ctime); (long)stat->st_atime, (long)stat->st_mtime, (long)stat->st_ctime);
} }
static std::map<int,bool> had_signal; static volatile sig_atomic_t had_signal[NSIG];
void clear_had_signals() {
memset(const_cast<sig_atomic_t *>(had_signal), 0, sizeof(had_signal));
}
static void handle_signal(int x) { static void handle_signal(int x) {
had_signal[x] = true; had_signal[x] = true;
} }
@ -109,7 +108,9 @@ void CheckChildFinished(pid_t pid, bool signaled=false) {
TEST(Pdfork, Simple) { TEST(Pdfork, Simple) {
int pd = -1; int pd = -1;
int pipefds[2];
pid_t parent = getpid_(); pid_t parent = getpid_();
EXPECT_OK(pipe(pipefds));
int pid = pdfork(&pd, 0); int pid = pdfork(&pd, 0);
EXPECT_OK(pid); EXPECT_OK(pid);
if (pid == 0) { if (pid == 0) {
@ -117,19 +118,29 @@ TEST(Pdfork, Simple) {
EXPECT_EQ(-1, pd); EXPECT_EQ(-1, pd);
EXPECT_NE(parent, getpid_()); EXPECT_NE(parent, getpid_());
EXPECT_EQ(parent, getppid()); EXPECT_EQ(parent, getppid());
sleep(1); close(pipefds[0]);
exit(0); SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED);
if (verbose) fprintf(stderr, "Child waiting for exit message\n");
// Terminate once the parent has completed the checks
AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT);
exit(testing::Test::HasFailure());
} }
usleep(100); // ensure the child has a chance to run close(pipefds[1]);
// Ensure the child has started.
AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED);
EXPECT_NE(-1, pd); EXPECT_NE(-1, pd);
EXPECT_PID_ALIVE(pid); EXPECT_PID_ALIVE(pid);
int pid_got; int pid_got;
EXPECT_OK(pdgetpid(pd, &pid_got)); EXPECT_OK(pdgetpid(pd, &pid_got));
EXPECT_EQ(pid, pid_got); EXPECT_EQ(pid, pid_got);
// Wait long enough for the child to exit(). // Tell the child to exit and wait until it is a zombie.
sleep(2); SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_REQUEST_CHILD_EXIT);
// EXPECT_PID_ZOMBIE waits for up to ~500ms, that should be enough time for
// the child to exit successfully.
EXPECT_PID_ZOMBIE(pid); EXPECT_PID_ZOMBIE(pid);
close(pipefds[0]);
// Wait for the the child. // Wait for the the child.
int status; int status;
@ -223,7 +234,10 @@ TEST(Pdfork, NonProcessDescriptor) {
close(fd); close(fd);
} }
static void *SubThreadMain(void *) { static void *SubThreadMain(void *arg) {
// Notify the main thread that we have started
if (verbose) fprintf(stderr, " subthread started: pipe=%p\n", arg);
SEND_INT_MESSAGE((int)(intptr_t)arg, MSG_CHILD_STARTED);
while (true) { while (true) {
if (verbose) fprintf(stderr, " subthread: \"I aten't dead\"\n"); if (verbose) fprintf(stderr, " subthread: \"I aten't dead\"\n");
usleep(100000); usleep(100000);
@ -233,11 +247,28 @@ static void *SubThreadMain(void *) {
static void *ThreadMain(void *) { static void *ThreadMain(void *) {
int pd; int pd;
int pipefds[2];
EXPECT_EQ(0, pipe(pipefds));
pid_t child = pdfork(&pd, 0); pid_t child = pdfork(&pd, 0);
if (child == 0) { if (child == 0) {
// Child: start a subthread then loop close(pipefds[0]);
// Child: start a subthread then loop.
pthread_t child_subthread; pthread_t child_subthread;
EXPECT_OK(pthread_create(&child_subthread, NULL, SubThreadMain, NULL)); // Wait for the subthread startup using another pipe.
int thread_pipefds[2];
EXPECT_EQ(0, pipe(thread_pipefds));
EXPECT_OK(pthread_create(&child_subthread, NULL, SubThreadMain,
(void *)(intptr_t)thread_pipefds[0]));
if (verbose) {
fprintf(stderr, " pdforked process %d: waiting for subthread.\n",
getpid());
}
AWAIT_INT_MESSAGE(thread_pipefds[1], MSG_CHILD_STARTED);
close(thread_pipefds[0]);
close(thread_pipefds[1]);
// Child: Notify parent that all threads have started
if (verbose) fprintf(stderr, " pdforked process %d: subthread started\n", getpid());
SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED);
while (true) { while (true) {
if (verbose) fprintf(stderr, " pdforked process %d: \"I aten't dead\"\n", getpid()); if (verbose) fprintf(stderr, " pdforked process %d: \"I aten't dead\"\n", getpid());
usleep(100000); usleep(100000);
@ -245,7 +276,9 @@ static void *ThreadMain(void *) {
exit(0); exit(0);
} }
if (verbose) fprintf(stderr, " thread generated pd %d\n", pd); if (verbose) fprintf(stderr, " thread generated pd %d\n", pd);
sleep(2); close(pipefds[1]);
AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED);
if (verbose) fprintf(stderr, "[%d] got child startup message\n", getpid_());
// Pass the process descriptor back to the main thread. // Pass the process descriptor back to the main thread.
return reinterpret_cast<void *>(pd); return reinterpret_cast<void *>(pd);
@ -278,7 +311,7 @@ TEST(Pdfork, FromThread) {
class PipePdforkBase : public ::testing::Test { class PipePdforkBase : public ::testing::Test {
public: public:
PipePdforkBase(int pdfork_flags) : pd_(-1), pid_(-1) { PipePdforkBase(int pdfork_flags) : pd_(-1), pid_(-1) {
had_signal.clear(); clear_had_signals();
int pipes[2]; int pipes[2];
EXPECT_OK(pipe(pipes)); EXPECT_OK(pipe(pipes));
pipe_ = pipes[1]; pipe_ = pipes[1];
@ -356,24 +389,34 @@ TEST_F(PipePdfork, Poll) {
// Can multiple processes poll on the same descriptor? // Can multiple processes poll on the same descriptor?
TEST_F(PipePdfork, PollMultiple) { TEST_F(PipePdfork, PollMultiple) {
int pipefds[2];
EXPECT_EQ(0, pipe(pipefds));
int child = fork(); int child = fork();
EXPECT_OK(child); EXPECT_OK(child);
if (child == 0) { if (child == 0) {
// Child: wait to give time for setup, then write to the pipe (which will close(pipefds[0]);
// induce exit of the pdfork()ed process) and exit. // Child: wait for parent to acknowledge startup
sleep(1); SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED);
// Child: wait for two messages from the parent and the forked process
// before telling the other process to terminate.
if (verbose) fprintf(stderr, "[%d] waiting for read 1\n", getpid_());
AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT);
if (verbose) fprintf(stderr, "[%d] waiting for read 2\n", getpid_());
AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT);
TerminateChild(); TerminateChild();
exit(0); if (verbose) fprintf(stderr, "[%d] about to exit\n", getpid_());
exit(testing::Test::HasFailure());
} }
usleep(100); // ensure the child has a chance to run close(pipefds[1]);
AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED);
if (verbose) fprintf(stderr, "[%d] got child startup message\n", getpid_());
// Fork again // Fork again
int doppel = fork(); int doppel = fork();
EXPECT_OK(doppel); EXPECT_OK(doppel);
// We now have: // We now have:
// pid A: main process, here // pid A: main process, here
// |--pid B: pdfork()ed process, blocked on read() // |--pid B: pdfork()ed process, blocked on read()
// |--pid C: fork()ed process, in sleep(1) above // |--pid C: fork()ed process, in read() above
// +--pid D: doppel process, here // +--pid D: doppel process, here
// Both A and D execute the following code. // Both A and D execute the following code.
@ -384,12 +427,18 @@ TEST_F(PipePdfork, PollMultiple) {
fdp.revents = 0; fdp.revents = 0;
EXPECT_EQ(0, poll(&fdp, 1, 0)); EXPECT_EQ(0, poll(&fdp, 1, 0));
// Both A and D ask C to exit, allowing it to do so.
if (verbose) fprintf(stderr, "[%d] telling child to exit\n", getpid_());
SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_REQUEST_CHILD_EXIT);
close(pipefds[0]);
// Now, wait (indefinitely) for activity on the process descriptor. // Now, wait (indefinitely) for activity on the process descriptor.
// We expect: // We expect:
// - pid C will finish its sleep, write to the pipe and exit // - pid C will finish its two read() calls, write to the pipe and exit.
// - pid B will unblock from read(), and exit // - pid B will unblock from read(), and exit
// - this will generate an event on the process descriptor... // - this will generate an event on the process descriptor...
// - ...in both process A and process D. // - ...in both process A and process D.
if (verbose) fprintf(stderr, "[%d] waiting for child to exit\n", getpid_());
EXPECT_EQ(1, poll(&fdp, 1, 2000)); EXPECT_EQ(1, poll(&fdp, 1, 2000));
EXPECT_TRUE(fdp.revents & POLLHUP); EXPECT_TRUE(fdp.revents & POLLHUP);
@ -522,6 +571,7 @@ TEST_F(PipePdfork, CloseLast) {
FORK_TEST(Pdfork, OtherUserIfRoot) { FORK_TEST(Pdfork, OtherUserIfRoot) {
GTEST_SKIP_IF_NOT_ROOT(); GTEST_SKIP_IF_NOT_ROOT();
int pd; int pd;
int status;
pid_t pid = pdfork(&pd, 0); pid_t pid = pdfork(&pd, 0);
EXPECT_OK(pid); EXPECT_OK(pid);
if (pid == 0) { if (pid == 0) {
@ -542,15 +592,29 @@ FORK_TEST(Pdfork, OtherUserIfRoot) {
EXPECT_EQ(EPERM, errno); EXPECT_EQ(EPERM, errno);
EXPECT_PID_ALIVE(pid); EXPECT_PID_ALIVE(pid);
// Succeed with pdkill though. // Ideally, we should be able to send signals via a process descriptor even
// if it's owned by another user, but this is not implementated on FreeBSD.
#ifdef __FreeBSD__
// On FreeBSD, pdkill() still performs all the same checks that kill() does
// and therefore cannot be used to send a signal to a process with another
// UID unless we are root.
EXPECT_SYSCALL_FAIL(EBADF, pdkill(pid, SIGKILL));
EXPECT_PID_ALIVE(pid);
// However, the process will be killed when we close the process descriptor.
EXPECT_OK(close(pd));
EXPECT_PID_GONE(pid);
// Can't pdwait4() after close() since close() reparents the child to a reaper (init)
EXPECT_SYSCALL_FAIL(EBADF, pdwait4_(pd, &status, WNOHANG, NULL));
#else
// Sending a signal with pdkill() should be permitted though.
EXPECT_OK(pdkill(pd, SIGKILL)); EXPECT_OK(pdkill(pd, SIGKILL));
EXPECT_PID_ZOMBIE(pid); EXPECT_PID_ZOMBIE(pid);
int status;
int rc = pdwait4_(pd, &status, WNOHANG, NULL); int rc = pdwait4_(pd, &status, WNOHANG, NULL);
EXPECT_OK(rc); EXPECT_OK(rc);
EXPECT_EQ(pid, rc); EXPECT_EQ(pid, rc);
EXPECT_TRUE(WIFSIGNALED(status)); EXPECT_TRUE(WIFSIGNALED(status));
#endif
} }
TEST_F(PipePdfork, WaitPidThenPd) { TEST_F(PipePdfork, WaitPidThenPd) {
@ -624,18 +688,27 @@ TEST_F(PipePdforkDaemon, Pdkill) {
TEST(Pdfork, PdkillOtherSignal) { TEST(Pdfork, PdkillOtherSignal) {
int pd = -1; int pd = -1;
int pipefds[2];
EXPECT_EQ(0, pipe(pipefds));
int pid = pdfork(&pd, 0); int pid = pdfork(&pd, 0);
EXPECT_OK(pid); EXPECT_OK(pid);
if (pid == 0) { if (pid == 0) {
// Child: watch for SIGUSR1 forever. // Child: tell the parent that we have started before entering the loop,
had_signal.clear(); // and importantly only do so once we have registered the SIGUSR1 handler.
close(pipefds[0]);
clear_had_signals();
signal(SIGUSR1, handle_signal); signal(SIGUSR1, handle_signal);
SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED);
// Child: watch for SIGUSR1 forever.
while (!had_signal[SIGUSR1]) { while (!had_signal[SIGUSR1]) {
usleep(100000); usleep(100000);
} }
exit(123); exit(123);
} }
sleep(1); // Wait for child to start
close(pipefds[1]);
AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED);
close(pipefds[0]);
// Send an invalid signal. // Send an invalid signal.
EXPECT_EQ(-1, pdkill(pd, 0xFFFF)); EXPECT_EQ(-1, pdkill(pd, 0xFFFF));
@ -651,14 +724,15 @@ TEST(Pdfork, PdkillOtherSignal) {
int rc = waitpid(pid, &status, __WALL); int rc = waitpid(pid, &status, __WALL);
EXPECT_OK(rc); EXPECT_OK(rc);
EXPECT_EQ(pid, rc); EXPECT_EQ(pid, rc);
EXPECT_TRUE(WIFEXITED(status)) << "0x" << std::hex << rc; EXPECT_TRUE(WIFEXITED(status)) << "status: 0x" << std::hex << status;
EXPECT_EQ(123, WEXITSTATUS(status)); EXPECT_EQ(123, WEXITSTATUS(status));
} }
pid_t PdforkParentDeath(int pdfork_flags) { pid_t PdforkParentDeath(int pdfork_flags) {
// Set up: // Set up:
// pid A: main process, here // pid A: main process, here
// +--pid B: fork()ed process, sleep(4)s then exits // +--pid B: fork()ed process, starts a child process with pdfork() then
// waits for parent to send a shutdown message.
// +--pid C: pdfork()ed process, looping forever // +--pid C: pdfork()ed process, looping forever
int sock_fds[2]; int sock_fds[2];
EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds)); EXPECT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds));
@ -668,27 +742,45 @@ pid_t PdforkParentDeath(int pdfork_flags) {
if (child == 0) { if (child == 0) {
int pd; int pd;
if (verbose) fprintf(stderr, " [%d] child about to pdfork()...\n", getpid_()); if (verbose) fprintf(stderr, " [%d] child about to pdfork()...\n", getpid_());
int pipefds[2]; // for startup notification
EXPECT_OK(pipe(pipefds));
pid_t grandchild = pdfork(&pd, pdfork_flags); pid_t grandchild = pdfork(&pd, pdfork_flags);
if (grandchild == 0) { if (grandchild == 0) {
close(pipefds[0]);
pid_t grandchildPid = getpid_();
EXPECT_EQ(sizeof(grandchildPid), (size_t)write(pipefds[1], &grandchildPid, sizeof(grandchildPid)));
while (true) { while (true) {
if (verbose) fprintf(stderr, " [%d] grandchild: \"I aten't dead\"\n", getpid_()); if (verbose) fprintf(stderr, " [%d] grandchild: \"I aten't dead\"\n", grandchildPid);
sleep(1); sleep(1);
} }
} }
close(pipefds[1]);
if (verbose) fprintf(stderr, " [%d] pdfork()ed grandchild %d, sending ID to parent\n", getpid_(), grandchild); if (verbose) fprintf(stderr, " [%d] pdfork()ed grandchild %d, sending ID to parent\n", getpid_(), grandchild);
// send grandchild pid to parent // Wait for grandchild to start.
write(sock_fds[1], &grandchild, sizeof(grandchild)); pid_t grandchild2;
sleep(4); EXPECT_EQ(sizeof(grandchild2), (size_t)read(pipefds[0], &grandchild2, sizeof(grandchild2)));
EXPECT_EQ(grandchild, grandchild2) << "received invalid grandchild pid";
if (verbose) fprintf(stderr, " [%d] grandchild %d has started successfully\n", getpid_(), grandchild);
close(pipefds[0]);
// Send grandchild pid to parent.
EXPECT_EQ(sizeof(grandchild), (size_t)write(sock_fds[1], &grandchild, sizeof(grandchild)));
if (verbose) fprintf(stderr, " [%d] sent grandchild pid %d to parent\n", getpid_(), grandchild);
// Wait for parent to acknowledge the message.
AWAIT_INT_MESSAGE(sock_fds[1], MSG_PARENT_REQUEST_CHILD_EXIT);
if (verbose) fprintf(stderr, " [%d] parent acknowledged grandchild pid %d\n", getpid_(), grandchild);
if (verbose) fprintf(stderr, " [%d] child terminating\n", getpid_()); if (verbose) fprintf(stderr, " [%d] child terminating\n", getpid_());
exit(0); exit(testing::Test::HasFailure());
} }
if (verbose) fprintf(stderr, "[%d] fork()ed child is %d\n", getpid_(), child); if (verbose) fprintf(stderr, "[%d] fork()ed child is %d\n", getpid_(), child);
pid_t grandchild; pid_t grandchild;
read(sock_fds[0], &grandchild, sizeof(grandchild)); read(sock_fds[0], &grandchild, sizeof(grandchild));
if (verbose) fprintf(stderr, "[%d] receive grandchild id %d\n", getpid_(), grandchild); if (verbose) fprintf(stderr, "[%d] received grandchild id %d\n", getpid_(), grandchild);
EXPECT_PID_ALIVE(child); EXPECT_PID_ALIVE(child);
EXPECT_PID_ALIVE(grandchild); EXPECT_PID_ALIVE(grandchild);
sleep(6); // Tell child to exit.
if (verbose) fprintf(stderr, "[%d] telling child %d to exit\n", getpid_(), child);
SEND_INT_MESSAGE(sock_fds[0], MSG_PARENT_REQUEST_CHILD_EXIT);
// Child dies, closing its process descriptor for the grandchild. // Child dies, closing its process descriptor for the grandchild.
EXPECT_PID_DEAD(child); EXPECT_PID_DEAD(child);
CheckChildFinished(child); CheckChildFinished(child);
@ -713,7 +805,7 @@ TEST(Pdfork, BagpussDaemon) {
// The exit of a pdfork()ed process should not generate SIGCHLD. // The exit of a pdfork()ed process should not generate SIGCHLD.
TEST_F(PipePdfork, NoSigchld) { TEST_F(PipePdfork, NoSigchld) {
had_signal.clear(); clear_had_signals();
sighandler_t original = signal(SIGCHLD, handle_signal); sighandler_t original = signal(SIGCHLD, handle_signal);
TerminateChild(); TerminateChild();
int rc = 0; int rc = 0;
@ -728,7 +820,7 @@ TEST_F(PipePdfork, NoSigchld) {
// all been closed should generate SIGCHLD. The child process needs // all been closed should generate SIGCHLD. The child process needs
// PD_DAEMON to survive the closure of the process descriptors. // PD_DAEMON to survive the closure of the process descriptors.
TEST_F(PipePdforkDaemon, NoPDSigchld) { TEST_F(PipePdforkDaemon, NoPDSigchld) {
had_signal.clear(); clear_had_signals();
sighandler_t original = signal(SIGCHLD, handle_signal); sighandler_t original = signal(SIGCHLD, handle_signal);
EXPECT_OK(close(pd_)); EXPECT_OK(close(pd_));
@ -766,10 +858,8 @@ TEST_F(PipePdfork, ModeBits) {
#endif #endif
TEST_F(PipePdfork, WildcardWait) { TEST_F(PipePdfork, WildcardWait) {
// TODO(FreeBSD): make wildcard wait ignore pdfork()ed children
// https://bugs.freebsd.org/201054
TerminateChild(); TerminateChild();
sleep(1); // Ensure child is truly dead. EXPECT_PID_ZOMBIE(pid_); // Ensure child is truly dead.
// Wildcard waitpid(-1) should not see the pdfork()ed child because // Wildcard waitpid(-1) should not see the pdfork()ed child because
// there is still a process descriptor for it. // there is still a process descriptor for it.
@ -782,21 +872,30 @@ TEST_F(PipePdfork, WildcardWait) {
} }
FORK_TEST(Pdfork, Pdkill) { FORK_TEST(Pdfork, Pdkill) {
had_signal.clear(); clear_had_signals();
int pd; int pd;
int pipefds[2];
EXPECT_OK(pipe(pipefds));
pid_t pid = pdfork(&pd, 0); pid_t pid = pdfork(&pd, 0);
EXPECT_OK(pid); EXPECT_OK(pid);
if (pid == 0) { if (pid == 0) {
// Child: set a SIGINT handler and sleep. // Child: set a SIGINT handler, notify the parent and sleep.
had_signal.clear(); close(pipefds[0]);
clear_had_signals();
signal(SIGINT, handle_signal); signal(SIGINT, handle_signal);
if (verbose) fprintf(stderr, "[%d] child started\n", getpid_());
SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED);
if (verbose) fprintf(stderr, "[%d] child about to sleep(10)\n", getpid_()); if (verbose) fprintf(stderr, "[%d] child about to sleep(10)\n", getpid_());
int left = sleep(10); // Note: we could receive the SIGINT just before sleep(), so we use a loop
if (verbose) fprintf(stderr, "[%d] child slept, %d sec left, had[SIGINT]=%d\n", // with a short delay instead of one long sleep().
getpid_(), left, had_signal[SIGINT]); for (int i = 0; i < 50 && !had_signal[SIGINT]; i++) {
// Expect this sleep to be interrupted by the signal (and so left > 0). usleep(100000);
exit(left == 0); }
if (verbose) fprintf(stderr, "[%d] child slept, had[SIGINT]=%d\n",
getpid_(), (int)had_signal[SIGINT]);
// Return non-zero if we didn't see SIGINT.
exit(had_signal[SIGINT] ? 0 : 99);
} }
// Parent: get child's PID. // Parent: get child's PID.
@ -804,9 +903,12 @@ FORK_TEST(Pdfork, Pdkill) {
EXPECT_OK(pdgetpid(pd, &pd_pid)); EXPECT_OK(pdgetpid(pd, &pd_pid));
EXPECT_EQ(pid, pd_pid); EXPECT_EQ(pid, pd_pid);
// Interrupt the child after a second. // Interrupt the child once it's registered the SIGINT handler.
sleep(1); close(pipefds[1]);
if (verbose) fprintf(stderr, "[%d] waiting for child\n", getpid_());
AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED);
EXPECT_OK(pdkill(pd, SIGINT)); EXPECT_OK(pdkill(pd, SIGINT));
if (verbose) fprintf(stderr, "[%d] sent SIGINT\n", getpid_());
// Make sure the child finished properly (caught signal then exited). // Make sure the child finished properly (caught signal then exited).
CheckChildFinished(pid); CheckChildFinished(pid);
@ -814,19 +916,28 @@ FORK_TEST(Pdfork, Pdkill) {
FORK_TEST(Pdfork, PdkillSignal) { FORK_TEST(Pdfork, PdkillSignal) {
int pd; int pd;
int pipefds[2];
EXPECT_OK(pipe(pipefds));
pid_t pid = pdfork(&pd, 0); pid_t pid = pdfork(&pd, 0);
EXPECT_OK(pid); EXPECT_OK(pid);
if (pid == 0) { if (pid == 0) {
// Child: sleep. No SIGINT handler. close(pipefds[0]);
if (verbose) fprintf(stderr, "[%d] child about to sleep(10)\n", getpid_()); if (verbose) fprintf(stderr, "[%d] child started\n", getpid_());
int left = sleep(10); SEND_INT_MESSAGE(pipefds[1], MSG_CHILD_STARTED);
if (verbose) fprintf(stderr, "[%d] child slept, %d sec left\n", getpid_(), left); // Child: wait for shutdown message. No SIGINT handler. The message should
// never be received, since SIGINT should terminate the process.
if (verbose) fprintf(stderr, "[%d] child about to read()\n", getpid_());
AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_REQUEST_CHILD_EXIT);
fprintf(stderr, "[%d] child read() returned unexpectedly\n", getpid_());
exit(99); exit(99);
} }
// Wait for child to start before signalling.
if (verbose) fprintf(stderr, "[%d] waiting for child\n", getpid_());
close(pipefds[1]);
AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED);
// Kill the child (as it doesn't handle SIGINT). // Kill the child (as it doesn't handle SIGINT).
sleep(1); if (verbose) fprintf(stderr, "[%d] sending SIGINT\n", getpid_());
EXPECT_OK(pdkill(pd, SIGINT)); EXPECT_OK(pdkill(pd, SIGINT));
// Make sure the child finished properly (terminated by signal). // Make sure the child finished properly (terminated by signal).
@ -922,7 +1033,7 @@ TEST_F(PipePdfork, PassProcessDescriptor) {
if (child2 == 0) { if (child2 == 0) {
// Child: close our copy of the original process descriptor. // Child: close our copy of the original process descriptor.
close(pd_); close(pd_);
SEND_INT_MESSAGE(sock_fds[0], MSG_CHILD_STARTED);
// Child: wait to receive process descriptor over socket // Child: wait to receive process descriptor over socket
if (verbose) fprintf(stderr, " [%d] child of %d waiting for process descriptor on socket\n", getpid_(), getppid()); if (verbose) fprintf(stderr, " [%d] child of %d waiting for process descriptor on socket\n", getpid_(), getppid());
int rc = recvmsg(sock_fds[0], &mh, 0); int rc = recvmsg(sock_fds[0], &mh, 0);
@ -934,13 +1045,16 @@ TEST_F(PipePdfork, PassProcessDescriptor) {
cmptr = CMSG_NXTHDR(&mh, cmptr); cmptr = CMSG_NXTHDR(&mh, cmptr);
EXPECT_TRUE(cmptr == NULL); EXPECT_TRUE(cmptr == NULL);
if (verbose) fprintf(stderr, " [%d] got process descriptor %d on socket\n", getpid_(), pd); if (verbose) fprintf(stderr, " [%d] got process descriptor %d on socket\n", getpid_(), pd);
SEND_INT_MESSAGE(sock_fds[0], MSG_CHILD_FD_RECEIVED);
// Child: confirm we can do pd*() operations on the process descriptor // Child: confirm we can do pd*() operations on the process descriptor
pid_t other; pid_t other;
EXPECT_OK(pdgetpid(pd, &other)); EXPECT_OK(pdgetpid(pd, &other));
if (verbose) fprintf(stderr, " [%d] process descriptor %d is pid %d\n", getpid_(), pd, other); if (verbose) fprintf(stderr, " [%d] process descriptor %d is pid %d\n", getpid_(), pd, other);
sleep(2); // Wait until the parent has closed the process descriptor.
AWAIT_INT_MESSAGE(sock_fds[0], MSG_PARENT_CLOSED_FD);
if (verbose) fprintf(stderr, " [%d] close process descriptor %d\n", getpid_(), pd); if (verbose) fprintf(stderr, " [%d] close process descriptor %d\n", getpid_(), pd);
close(pd); close(pd);
@ -949,7 +1063,8 @@ TEST_F(PipePdfork, PassProcessDescriptor) {
exit(HasFailure()); exit(HasFailure());
} }
usleep(1000); // Ensure subprocess runs // Wait until the child has started.
AWAIT_INT_MESSAGE(sock_fds[1], MSG_CHILD_STARTED);
// Send the process descriptor over the pipe to the sub-process // Send the process descriptor over the pipe to the sub-process
mh.msg_controllen = CMSG_LEN(sizeof(int)); mh.msg_controllen = CMSG_LEN(sizeof(int));
@ -960,13 +1075,15 @@ TEST_F(PipePdfork, PassProcessDescriptor) {
*(int *)CMSG_DATA(cmptr) = pd_; *(int *)CMSG_DATA(cmptr) = pd_;
buffer1[0] = 0; buffer1[0] = 0;
iov[0].iov_len = 1; iov[0].iov_len = 1;
sleep(1);
if (verbose) fprintf(stderr, "[%d] send process descriptor %d on socket\n", getpid_(), pd_); if (verbose) fprintf(stderr, "[%d] send process descriptor %d on socket\n", getpid_(), pd_);
int rc = sendmsg(sock_fds[1], &mh, 0); int rc = sendmsg(sock_fds[1], &mh, 0);
EXPECT_OK(rc); EXPECT_OK(rc);
// Wait until the child has received the process descriptor.
AWAIT_INT_MESSAGE(sock_fds[1], MSG_CHILD_FD_RECEIVED);
if (verbose) fprintf(stderr, "[%d] close process descriptor %d\n", getpid_(), pd_); if (verbose) fprintf(stderr, "[%d] close process descriptor %d\n", getpid_(), pd_);
close(pd_); // Not last open process descriptor close(pd_); // Not last open process descriptor
SEND_INT_MESSAGE(sock_fds[1], MSG_PARENT_CLOSED_FD);
// wait for child2 // wait for child2
int status; int status;

View File

@ -20,10 +20,13 @@ TEST(Socket, UnixDomain) {
cap_rights_t r_all; cap_rights_t r_all;
cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER); cap_rights_init(&r_all, CAP_READ, CAP_WRITE, CAP_SOCK_CLIENT, CAP_SOCK_SERVER);
int pipefds[2];
EXPECT_EQ(0, pipe(pipefds));
pid_t child = fork(); pid_t child = fork();
if (child == 0) { if (child == 0) {
// Child process: wait for server setup // Child process: wait for server setup
sleep(1); close(pipefds[0]);
AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_CHILD_SHOULD_RUN);
// Create sockets // Create sockets
int sock = socket(AF_UNIX, SOCK_STREAM, 0); int sock = socket(AF_UNIX, SOCK_STREAM, 0);
@ -94,7 +97,9 @@ TEST(Socket, UnixDomain) {
len = sizeof(value); len = sizeof(value);
EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_DEBUG, &value, &len)); EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_DEBUG, &value, &len));
// Accept the incoming connection // Tell the child process that we are ready and accept the incoming connection.
EXPECT_OK(close(pipefds[1]));
SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_CHILD_SHOULD_RUN);
len = sizeof(un); len = sizeof(un);
memset(&un, 0, sizeof(un)); memset(&un, 0, sizeof(un));
EXPECT_NOTCAPABLE(accept(cap_sock_rw, (struct sockaddr *)&un, &len)); EXPECT_NOTCAPABLE(accept(cap_sock_rw, (struct sockaddr *)&un, &len));
@ -153,11 +158,14 @@ TEST(Socket, TCP) {
getsockname(cap_sock_all, (struct sockaddr *)&addr, &len); getsockname(cap_sock_all, (struct sockaddr *)&addr, &len);
int port = ntohs(addr.sin_port); int port = ntohs(addr.sin_port);
int pipefds[2];
EXPECT_EQ(0, pipe(pipefds));
// Now we know the port involved, fork off a child. // Now we know the port involved, fork off a child.
pid_t child = fork(); pid_t child = fork();
if (child == 0) { if (child == 0) {
// Child process: wait for server setup // Child process: wait for server setup
sleep(1); close(pipefds[0]);
AWAIT_INT_MESSAGE(pipefds[1], MSG_PARENT_CHILD_SHOULD_RUN);
// Create sockets // Create sockets
int sock = socket(AF_INET, SOCK_STREAM, 0); int sock = socket(AF_INET, SOCK_STREAM, 0);
@ -206,7 +214,9 @@ TEST(Socket, TCP) {
len = sizeof(value); len = sizeof(value);
EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, &len)); EXPECT_OK(getsockopt(cap_sock_all, SOL_SOCKET, SO_REUSEPORT, &value, &len));
// Accept the incoming connection // Tell the child process that we are ready and accept the incoming connection.
EXPECT_OK(close(pipefds[1]));
SEND_INT_MESSAGE(pipefds[0], MSG_PARENT_CHILD_SHOULD_RUN);
len = sizeof(addr); len = sizeof(addr);
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
EXPECT_NOTCAPABLE(accept(cap_sock_rw, (struct sockaddr *)&addr, &len)); EXPECT_NOTCAPABLE(accept(cap_sock_rw, (struct sockaddr *)&addr, &len));

View File

@ -35,7 +35,7 @@ SRCS.capsicum-test+= \
overhead.cc \ overhead.cc \
rename.cc rename.cc
LIBADD.capsicum-test+= gtest pthread LIBADD.capsicum-test+= gtest pthread procstat
TEST_METADATA.capsicum-test= required_user="unprivileged" TEST_METADATA.capsicum-test= required_user="unprivileged"
.for p in mini-me mini-me.noexec mini-me.setuid .for p in mini-me mini-me.noexec mini-me.setuid