summaryrefslogtreecommitdiff
path: root/capmode.cc
diff options
context:
space:
mode:
Diffstat (limited to 'capmode.cc')
-rw-r--r--capmode.cc126
1 files changed, 101 insertions, 25 deletions
diff --git a/capmode.cc b/capmode.cc
index 567773f319d9..c274f5e1c9f3 100644
--- a/capmode.cc
+++ b/capmode.cc
@@ -528,6 +528,8 @@ TEST(Capmode, Abort) {
FORK_TEST_F(WithFiles, AllowedMiscSyscalls) {
umask(022);
mode_t um_before = umask(022);
+ int pipefds[2];
+ EXPECT_OK(pipe(pipefds));
EXPECT_OK(cap_enter()); // Enter capability mode.
mode_t um = umask(022);
@@ -540,13 +542,19 @@ FORK_TEST_F(WithFiles, AllowedMiscSyscalls) {
pid_t pid = fork();
EXPECT_OK(pid);
if (pid == 0) {
- // Child: almost immediately exit.
- sleep(1);
+ // Child: wait for an exit message from parent (so we can test waitpid).
+ 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);
} else if (pid > 0) {
+ EXPECT_OK(close(pipefds[1]));
+ AWAIT_INT_MESSAGE(pipefds[0], MSG_CHILD_STARTED);
errno = 0;
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.
@@ -568,68 +576,136 @@ FORK_TEST_F(WithFiles, AllowedMiscSyscalls) {
}
void *thread_fn(void *p) {
- int delay = *(int *)p;
- sleep(delay);
+ int fd = (int)(intptr_t)p;
+ if (verbose) fprintf(stderr, " thread waiting to run\n");
+ AWAIT_INT_MESSAGE(fd, MSG_PARENT_CHILD_SHOULD_RUN);
EXPECT_OK(getpid_());
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
FORK_TEST(Capmode, NewThread) {
// Fire off a new thread before entering capability mode
pthread_t early_thread;
- int one = 1; // second
- EXPECT_OK(pthread_create(&early_thread, NULL, thread_fn, &one));
+ void *thread_rval;
+ // 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.
+ if (verbose) fprintf(stderr, " starting second child (non-capability mode)\n");
int early_child = fork();
EXPECT_OK(early_child);
if (early_child == 0) {
- // Child: wait and then confirm this process is unaffect by capability mode in the parent.
- sleep(1);
+ if (verbose) fprintf(stderr, " first child started\n");
+ 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);
EXPECT_OK(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.
+ // 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.
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();
EXPECT_OK(child);
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.
EXPECT_OK(getpid_());
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.
- 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;
- int zero = 0; // seconds
- EXPECT_OK(pthread_create(&child_thread, NULL, thread_fn, &zero));
- EXPECT_OK(pthread_join(child_thread, NULL));
+ EXPECT_OK(pthread_create(&child_thread, NULL, thread_fn,
+ (void *)(intptr_t)thread_pipe[1]));
+ 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.
+ 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();
EXPECT_OK(child);
if (child == 0) {
+ if (verbose) fprintf(stderr, " third child started\n");
+ EXPECT_OK(close(proc_pipe[0]));
pthread_t child_thread2;
- EXPECT_OK(pthread_create(&child_thread2, NULL, thread_fn, &zero));
- EXPECT_OK(pthread_join(child_thread2, NULL));
- exit(0);
+ EXPECT_OK(pthread_create(&child_thread2, NULL, thread_fn,
+ (void *)(intptr_t)thread_pipe[1]));
+ 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.
- sleep(2);
+ // Wait for the third child to exit (should get a zero exit code message).
+ 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; }
FORK_TEST(Capmode, SelfKill) {