18#include <sys/syscall.h>
21#if !defined(__x86_64__)
23 puts(
"VFORK-CONTRACT: SKIP x86-64 syscall fixture");
28#define CHECK(expression) \
30 if (!(expression)) { \
31 fprintf(stderr, "VFORK-CONTRACT: FAIL line=%d errno=%d\n", __LINE__, errno); \
37static char self[PATH_MAX];
38static volatile int shared_value;
39static int child_ready, parent_returned, observer_failure;
40static char* child_mapping;
41static int observer_status;
42static int gate[2], parent_tid, child_tid, seen_tid;
43static unsigned long clone_flags;
44static int clear_sighand;
45static __thread
int tls_value;
48 uint64_t flags, pidfd, child_tid, parent_tid, exit_signal;
49 uint64_t stack, stack_size, tls, set_tid, set_tid_size, cgroup;
51_Static_assert(
sizeof(
struct clone3_args) == 88,
"Linux clone3 argument layout");
53#define CLEAR_SIGHAND (1ULL << 32)
54#define VFORK_FLAGS (CLONE_VM | CLONE_VFORK)
55#define TID_FLAGS (CLONE_PARENT_SETTID | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)
56#define CLONE3_SYSCALL 435
59extern long clone3_start(
int (*fn)(
void*),
void* arg,
struct clone3_args* args,
size_t size);
60extern long raw_clone_start(
int (*fn)(
void*),
void* arg,
struct clone3_args* args);
61extern int tls_child(
void* expected);
64 ".global raw_clone_start\n"
65 ".type raw_clone_start,@function\n"
94 ".size raw_clone_start,.-raw_clone_start\n"
95 ".global clone3_start\n"
96 ".type clone3_start,@function\n"
118 ".size clone3_start,.-clone3_start\n"
120 ".global tls_child\n"
121 ".type tls_child,@function\n"
138 ".size tls_child,.-tls_child\n");
140static long start_raw_clone(
int (*fn)(
void*),
void* arg,
struct clone3_args* args,
size_t size) {
141 long child = size ? clone3_start(fn, arg, args, size) : raw_clone_start(fn, arg, args);
144 fprintf(stderr,
"VFORK-CONTRACT: %s flags=%#llx size=%zu errno=%d\n",
145 size ?
"clone3" :
"raw clone", (unsigned long long)args->flags, size, errno);
151static void pause_briefly(
void) {
152 const struct timespec delay = {0, 30000000};
153 syscall(SYS_nanosleep, &delay, NULL);
156static int exited(pid_t child,
int code) {
160 result = waitpid(child, &status, 0);
161 }
while (result < 0 && errno == EINTR);
162 CHECK(result == child);
163 CHECK(WIFEXITED(status) && WEXITSTATUS(status) == code);
167static int repeated_exit(
void) {
168 for (
int iteration = 0; iteration < 12; ++iteration) {
170 pid_t child = vfork();
178 CHECK(shared_value == 2);
179 CHECK(exited(child, 17) == 0);
184static int private_fork(
void) {
186 pid_t child = fork();
192 CHECK(exited(child, 20) == 0 && shared_value == 11);
196static int private_clone_child(
void* ignored) {
198 if (child_tid != syscall(SYS_getpid))
204static int private_clone(
void) {
205 char* stack = mmap(NULL, 65536, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
206 CHECK(stack != MAP_FAILED);
207 for (
int api = 0; api < 2; ++api) {
208 parent_tid = child_tid = -37;
210 struct clone3_args args = {.flags = TID_FLAGS,
211 .child_tid = (uintptr_t)&child_tid,
212 .parent_tid = (uintptr_t)&parent_tid,
213 .exit_signal = SIGCHLD,
214 .stack = (uintptr_t)stack,
215 .stack_size = 65536};
216 long child = start_raw_clone(private_clone_child, NULL, &args, api ?
sizeof(args) : 0);
217 CHECK(child > 0 && exited(child, 29) == 0);
218 if (parent_tid != child || child_tid != -37 || shared_value != 11)
219 fprintf(stderr,
"VFORK-CONTRACT: private clone api=%s ptid=%d ctid=%d shared=%d\n",
220 api ?
"clone3" :
"raw-clone", parent_tid, child_tid, shared_value);
221 CHECK(parent_tid == child && child_tid == -37 && shared_value == 11);
223 CHECK(munmap(stack, 65536) == 0);
224 puts(
"VFORK-CONTRACT: ordinary clone/clone3 modifier isolation PASS");
228static int failed_exec(
void) {
229 char* arguments[] = {
"missing", NULL};
231 pid_t child = vfork();
234 if (syscall(SYS_execve,
"/does-not-exist/vfork-contract", arguments, environ) != -1)
241 CHECK(shared_value == 4);
242 CHECK(exited(child, 18) == 0);
246static int successful_exec(
void) {
248 CHECK(pipe(gate) == 0);
250 snprintf(descriptor,
sizeof(descriptor),
"%d", gate[0]);
251 char* arguments[] = {self,
"--exec-child", descriptor, NULL};
253 pid_t child = vfork();
257 syscall(SYS_execve, self, arguments, environ);
260 CHECK(shared_value == 5);
262 CHECK(write(gate[1],
"x", 1) == 1);
263 CHECK(exited(child, 0) == 0);
264 CHECK(close(gate[0]) == 0 && close(gate[1]) == 0);
268static int clone_child(
void* ignored) {
271 seen_tid = (int)syscall(SYS_getpid);
272 if ((clone_flags & CLONE_CHILD_SETTID) && child_tid != seen_tid)
274 if (clone_flags & CLONE_NEWUTS) {
275 if (sethostname(
"vfork-child", 11))
279 struct sigaction caught, ignored_action;
280 if (sigaction(SIGUSR1, NULL, &caught) || sigaction(SIGUSR2, NULL, &ignored_action) ||
281 caught.sa_handler != SIG_DFL || ignored_action.sa_handler != SIG_IGN)
284 child_mapping = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
285 if (child_mapping == MAP_FAILED)
288 child_mapping[0] = 42;
290 __atomic_store_n(&child_ready, seen_tid, __ATOMIC_RELEASE);
292 if (read(gate[0], &
byte, 1) != 1 ||
byte !=
'x')
298static void* gate_observer(
void* ignored) {
301 while (!(child = __atomic_load_n(&child_ready, __ATOMIC_ACQUIRE)))
307 if (__atomic_load_n(&parent_returned, __ATOMIC_ACQUIRE))
308 observer_failure = 1;
309 if (write(gate[1],
"x", 1) != 1)
310 observer_failure = 2;
314static int spawn_clone_case(
unsigned long modifiers,
size_t clone3_size,
int reset_handlers) {
315 const size_t stack_size = 65536;
316 char* stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
317 CHECK(stack != MAP_FAILED);
318 if (clone3_size == 96)
319 CHECK(mprotect(stack, 4096, PROT_NONE) == 0);
320 char hostname[256], after[256];
321 CHECK(gethostname(hostname,
sizeof(hostname)) == 0);
322 CHECK(pipe(gate) == 0);
324 child_mapping = NULL;
325 parent_tid = child_tid = -37;
326 child_ready = parent_returned = observer_failure = seen_tid = 0;
327 clone_flags = VFORK_FLAGS | modifiers;
328 clear_sighand = reset_handlers;
330 CHECK(pthread_create(&observer, NULL, gate_observer, NULL) == 0);
332 struct clone3_args args;
334 } record = {.args = {.flags = clone_flags | (reset_handlers ? CLEAR_SIGHAND : 0),
335 .child_tid = (uintptr_t)&child_tid,
336 .parent_tid = (uintptr_t)&parent_tid,
337 .exit_signal = SIGCHLD,
338 .stack = (uintptr_t)stack,
339 .stack_size = stack_size}};
342 if (clone3_size || (modifiers & (CLONE_CHILD_CLEARTID | CLONE_SETTLS))) {
343 child = start_raw_clone(clone_child, NULL, &record.args, clone3_size);
345 child = clone(clone_child, stack + stack_size, clone_flags | SIGCHLD, NULL, &parent_tid, NULL,
348 __atomic_store_n(&parent_returned, 1, __ATOMIC_RELEASE);
350 __atomic_store_n(&child_ready, -1, __ATOMIC_RELEASE);
351 CHECK(pthread_join(observer, NULL) == 0);
352 CHECK(child > 0 && shared_value == 7 && !observer_failure && seen_tid == child);
353 CHECK(exited(child, 19) == 0);
354 CHECK(parent_tid == ((modifiers & CLONE_PARENT_SETTID) ? child : -37));
357 ((modifiers & CLONE_CHILD_CLEARTID) ? 0 : ((modifiers & CLONE_CHILD_SETTID) ? child : -37)));
358 CHECK(gethostname(after,
sizeof(after)) == 0 && !strcmp(hostname, after));
359 CHECK(child_mapping && child_mapping != MAP_FAILED && child_mapping[0] == 42);
360 child_mapping[4095] = 43;
361 CHECK(munmap(child_mapping, 4096) == 0);
362 CHECK(munmap(stack, stack_size) == 0);
363 CHECK(close(gate[0]) == 0 && close(gate[1]) == 0);
367static int spawn_clone(
unsigned long modifiers,
size_t clone3_size,
int reset_handlers) {
368 int result = spawn_clone_case(modifiers, clone3_size, reset_handlers);
369 const char* api =
"libc-clone";
372 else if (modifiers & (CLONE_CHILD_CLEARTID | CLONE_SETTLS))
376 "VFORK-CONTRACT: clone matrix api=%s modifiers=%#lx size=%zu clear_sighand=%d\n", api,
377 modifiers, clone3_size, reset_handlers);
381static int clone_tls(
void) {
382 char* stack = mmap(NULL, 65536, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
383 CHECK(stack != MAP_FAILED);
384 uintptr_t tls[16] = {0}, before, after;
385 CHECK(syscall(SYS_arch_prctl, 0x1003, &before) == 0);
386 struct clone3_args args = {.flags = VFORK_FLAGS | CLONE_SETTLS,
387 .exit_signal = SIGCHLD,
388 .stack = (uintptr_t)stack,
390 .tls = (uintptr_t)tls};
391 long child = start_raw_clone(tls_child, tls, &args, 0);
392 CHECK(child > 0 && exited(child, 27) == 0);
393 child = start_raw_clone(tls_child, tls, &args,
sizeof(args));
394 CHECK(child > 0 && exited(child, 27) == 0);
395 CHECK(syscall(SYS_arch_prctl, 0x1003, &after) == 0 && before == after);
396 CHECK(munmap(stack, 65536) == 0);
400static void caught_signal(
int signal) {
404static int clone_matrix(
void) {
405 const unsigned long modifiers[] = {
406 0, CLONE_PARENT_SETTID, CLONE_CHILD_SETTID, CLONE_CHILD_CLEARTID,
407 TID_FLAGS, TID_FLAGS | CLONE_NEWUTS};
408 for (
size_t i = 0; i <
sizeof(modifiers) /
sizeof(modifiers[0]); ++i)
409 CHECK(spawn_clone(modifiers[i], 0, 0) == 0);
410 CHECK(spawn_clone(0, 64, 0) == 0);
411 CHECK(spawn_clone(TID_FLAGS, 80, 0) == 0);
412 CHECK(spawn_clone(TID_FLAGS | CLONE_NEWUTS, 88, 0) == 0);
413 CHECK(spawn_clone(0, 96, 0) == 0);
414 struct sigaction caught = {.sa_handler = caught_signal}, ignored = {.sa_handler = SIG_IGN};
415 struct sigaction old1, old2, after;
416 sigemptyset(&caught.sa_mask);
417 sigemptyset(&ignored.sa_mask);
418 CHECK(sigaction(SIGUSR1, &caught, &old1) == 0 && sigaction(SIGUSR2, &ignored, &old2) == 0);
419 CHECK(spawn_clone(0, 88, 1) == 0);
420 CHECK(sigaction(SIGUSR1, NULL, &after) == 0 && after.sa_handler == caught_signal);
421 CHECK(sigaction(SIGUSR2, NULL, &after) == 0 && after.sa_handler == SIG_IGN);
422 CHECK(sigaction(SIGUSR1, &old1, NULL) == 0 && sigaction(SIGUSR2, &old2, NULL) == 0);
423 CHECK(clone_tls() == 0);
424 puts(
"VFORK-CONTRACT: clone/clone3 sharing, blocking, TID, TLS and signals PASS");
428static int rejected_clone3(
struct clone3_args* args,
size_t size,
int expected) {
430 long result = syscall(CLONE3_SYSCALL, args, size);
433 CHECK(result == -1 && errno == expected);
437static int rejected_clones(
void) {
438 const unsigned long invalid[] = {CLONE_VM | SIGCHLD,
439 CLONE_VFORK | SIGCHLD,
441 VFORK_FLAGS | CLONE_FILES | SIGCHLD,
442 VFORK_FLAGS | CLONE_FS | SIGCHLD,
443 VFORK_FLAGS | CLONE_SIGHAND | SIGCHLD};
444 for (
size_t i = 0; i <
sizeof(invalid) /
sizeof(invalid[0]); ++i) {
446 long result = syscall(SYS_clone, invalid[i], NULL, NULL, NULL, 0);
449 CHECK(result == -1 && errno == EINVAL);
451 const unsigned long bad_tid_flags[] = {CLONE_PARENT_SETTID, CLONE_CHILD_SETTID};
452 for (
size_t i = 0; i < 2; ++i) {
455 syscall(SYS_clone, VFORK_FLAGS | SIGCHLD | bad_tid_flags[i], NULL, (
void*)1, (
void*)1, 0);
458 CHECK(result == -1 && errno == EFAULT);
460 struct clone3_args valid = {.flags = VFORK_FLAGS, .exit_signal = SIGCHLD};
461 struct clone3_args args = valid;
462 CHECK(rejected_clone3(&args, 63, EINVAL) == 0);
463 CHECK(rejected_clone3(&args, 4097, E2BIG) == 0);
464 CHECK(rejected_clone3((
void*)1, 88, EFAULT) == 0);
466 struct clone3_args args;
468 } extended = {valid, 1};
469 CHECK(rejected_clone3(&extended.args,
sizeof(extended), E2BIG) == 0);
470 const uint64_t flags[] = {VFORK_FLAGS | SIGCHLD,
471 VFORK_FLAGS | CLONE_FILES,
472 VFORK_FLAGS | CLONE_FS,
473 VFORK_FLAGS | CLONE_SIGHAND,
474 VFORK_FLAGS | CLEAR_SIGHAND | CLONE_SIGHAND,
475 VFORK_FLAGS | (1ULL << 63),
478 for (
size_t i = 0; i <
sizeof(flags) /
sizeof(flags[0]); ++i) {
480 args.flags = flags[i];
481 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
484 args.exit_signal = 0;
485 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
486 args.exit_signal = SIGUSR1;
487 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
488 for (
int field = 0; field < 4; ++field) {
495 args.set_tid_size = 1;
498 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
501 args.stack_size = 4096;
502 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
503 char* pages = mmap(NULL, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
504 CHECK(pages != MAP_FAILED);
505 memcpy(pages + 4096 - 88, &valid, 88);
506 CHECK(mprotect(pages + 4096, 4096, PROT_NONE) == 0);
507 CHECK(rejected_clone3((
void*)(pages + 4096 - 88), 96, EFAULT) == 0);
508 CHECK(munmap(pages, 8192) == 0);
509 args.stack = UINT64_MAX - 2047;
510 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
513 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
514 args.stack = 0xffff800000000000ULL;
515 args.stack_size = 4096;
516 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
517 for (
size_t i = 0; i < 2; ++i) {
519 args.flags |= bad_tid_flags[i];
520 args.parent_tid = args.child_tid = 1;
521 CHECK(rejected_clone3(&args, 88, EFAULT) == 0);
524 CHECK(waitpid(-1, &status, WNOHANG) == -1 && errno == ECHILD);
525 puts(
"VFORK-CONTRACT: rejected clone/clone3 arguments PASS");
529struct exec_arguments {
534static int clone_exec_child(
void* opaque) {
535 struct exec_arguments* args = opaque;
536 syscall(SYS_execve, args->path, args->argv, environ);
537 seen_tid = child_tid;
538 return errno == ENOENT && child_tid > 0 ? 18 : 91;
541static int clone_exec(
void) {
542 char* stack = mmap(NULL, 65536, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
543 CHECK(stack != MAP_FAILED);
544 for (
int api = 0; api < 2; ++api) {
545 for (
int fail = 0; fail < 2; ++fail) {
546 CHECK(pipe(gate) == 0);
548 snprintf(descriptor,
sizeof(descriptor),
"%d", gate[0]);
549 char* argv[] = {self,
"--exec-child", descriptor, NULL};
550 struct exec_arguments exec = {fail ?
"/does-not-exist/vfork-contract" : self, argv};
553 unsigned long flags = VFORK_FLAGS | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID;
554 struct clone3_args args = {.flags = flags,
555 .child_tid = (uintptr_t)&child_tid,
556 .exit_signal = SIGCHLD,
557 .stack = (uintptr_t)stack,
558 .stack_size = 65536};
559 long child = start_raw_clone(clone_exec_child, &exec, &args, api ?
sizeof(args) : 0);
560 if (child <= 0 || child_tid != 0)
561 fprintf(stderr,
"VFORK-CONTRACT: clear-TID exec api=%s failed_exec=%d child=%ld ctid=%d\n",
562 api ?
"clone3" :
"raw-clone", fail, child, child_tid);
563 CHECK(child > 0 && child_tid == 0);
565 CHECK(seen_tid == child);
567 CHECK(write(gate[1],
"x", 1) == 1);
568 CHECK(exited(child, fail ? 18 : 0) == 0);
569 CHECK(close(gate[0]) == 0 && close(gate[1]) == 0);
572 CHECK(munmap(stack, 65536) == 0);
573 puts(
"VFORK-CONTRACT: clone/clone3 clear-TID exec and failed exec PASS");
577static int* futex_alias;
578static int futex_waiting, futex_result, futex_errno;
580static void* futex_observer(
void* ignored) {
583 while (!(tid = __atomic_load_n(&child_ready, __ATOMIC_ACQUIRE)))
587 struct timespec timeout = {2, 0};
588 __atomic_store_n(&futex_waiting, 1, __ATOMIC_RELEASE);
589 futex_result = syscall(SYS_futex, futex_alias, 0, tid, &timeout, NULL, 0);
594static int futex_child(
void* ignored) {
596 __atomic_store_n(&child_ready, (
int)syscall(SYS_getpid), __ATOMIC_RELEASE);
597 while (!__atomic_load_n(&futex_waiting, __ATOMIC_ACQUIRE))
603static int shared_clear_tid(
void) {
604 int fd = syscall(SYS_memfd_create,
"vfork-ctid", 0);
605 CHECK(fd >= 0 && ftruncate(fd, 4096) == 0);
606 int* ctid = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
607 futex_alias = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
608 char* stack = mmap(NULL, 65536, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
609 CHECK(ctid != MAP_FAILED && futex_alias != MAP_FAILED && stack != MAP_FAILED);
610 for (
int api = 0; api < 2; ++api) {
611 child_ready = futex_waiting = futex_result = futex_errno = 0;
614 CHECK(pthread_create(&observer, NULL, futex_observer, NULL) == 0);
615 unsigned long flags = VFORK_FLAGS | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID;
616 struct clone3_args args = {.flags = flags,
617 .child_tid = (uintptr_t)ctid,
618 .exit_signal = SIGCHLD,
619 .stack = (uintptr_t)stack,
620 .stack_size = 65536};
621 long child = start_raw_clone(futex_child, NULL, &args, api ?
sizeof(args) : 0);
623 __atomic_store_n(&child_ready, -1, __ATOMIC_RELEASE);
624 CHECK(pthread_join(observer, NULL) == 0 && child > 0);
625 CHECK(exited(child, 28) == 0);
627 fprintf(stderr,
"VFORK-CONTRACT: shared clear-TID futex api=%s errno=%d\n",
628 api ?
"clone3" :
"raw-clone", futex_errno);
629 CHECK(futex_result == 0 && *ctid == 0 && *futex_alias == 0);
631 CHECK(munmap(ctid, 4096) == 0 && munmap(futex_alias, 4096) == 0);
632 CHECK(munmap(stack, 65536) == 0 && close(fd) == 0);
633 puts(
"VFORK-CONTRACT: shared-file clear-TID wake PASS");
637typedef int (*spawn_function)(pid_t*,
const char*,
const posix_spawn_file_actions_t*,
638 const posix_spawnattr_t*,
char*
const[],
char*
const[]);
640static int spawn_worker(
int closed_fd) {
643 struct sigaction action;
644 CHECK(read(STDIN_FILENO, &
byte, 1) == 1 &&
byte ==
'x');
645 CHECK(fcntl(closed_fd, F_GETFD) == -1 && errno == EBADF);
646 CHECK((fcntl(100, F_GETFL) & O_ACCMODE) == O_RDONLY && read(100, &
byte, 1) == 0);
647 CHECK(sigprocmask(SIG_SETMASK, NULL, &mask) == 0 && sigismember(&mask, SIGUSR1) == 1);
648 CHECK(sigaction(SIGUSR2, NULL, &action) == 0 && action.sa_handler == SIG_DFL);
649 CHECK(getpgrp() == getpid());
650 const char* token = getenv(
"VFORK_SPAWN_TOKEN");
651 CHECK(token && !strcmp(token,
"child"));
652 CHECK(write(STDOUT_FILENO,
"p", 1) == 1);
656static int spawn_success(spawn_function start,
const char* path,
int use_vfork) {
657 int input[2], output[2];
658 CHECK(pipe(input) == 0 && pipe(output) == 0);
659 int sentinel = fcntl(STDOUT_FILENO, F_DUPFD, 64);
660 CHECK(sentinel >= 0 && sentinel != 100);
662 snprintf(descriptor,
sizeof(descriptor),
"%d", sentinel);
663 char* argv[] = {self,
"--spawn-child", descriptor, NULL};
664 char* env[] = {
"VFORK_SPAWN_TOKEN=child", NULL};
665 posix_spawn_file_actions_t actions;
666 posix_spawnattr_t attr;
667 CHECK(posix_spawn_file_actions_init(&actions) == 0 && posix_spawnattr_init(&attr) == 0);
668 CHECK(posix_spawn_file_actions_adddup2(&actions, input[0], STDIN_FILENO) == 0);
669 CHECK(posix_spawn_file_actions_adddup2(&actions, output[1], STDOUT_FILENO) == 0);
670 CHECK(posix_spawn_file_actions_addopen(&actions, 100,
"/dev/null", O_RDONLY, 0) == 0);
671 const int closing[] = {input[0], input[1], output[0], output[1], sentinel};
672 for (
size_t i = 0; i <
sizeof(closing) /
sizeof(closing[0]); ++i)
673 CHECK(posix_spawn_file_actions_addclose(&actions, closing[i]) == 0);
674 sigset_t mask, defaults, parent_mask, after_mask;
676 sigaddset(&mask, SIGUSR1);
677 sigemptyset(&defaults);
678 sigaddset(&defaults, SIGUSR2);
679 CHECK(sigprocmask(SIG_UNBLOCK, &mask, &parent_mask) == 0);
680 struct sigaction ignored = {.sa_handler = SIG_IGN}, parent_action, after_action;
681 sigemptyset(&ignored.sa_mask);
682 CHECK(sigaction(SIGUSR2, &ignored, &parent_action) == 0);
683 pid_t parent_group = getpgrp();
684 CHECK(posix_spawnattr_setsigmask(&attr, &mask) == 0);
685 CHECK(posix_spawnattr_setsigdefault(&attr, &defaults) == 0);
686 CHECK(posix_spawnattr_setpgroup(&attr, 0) == 0);
687 short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETPGROUP;
688#ifdef POSIX_SPAWN_USEVFORK
690 flags |= POSIX_SPAWN_USEVFORK;
694 CHECK(posix_spawnattr_setflags(&attr, flags) == 0);
696 int error = start(&child, path, &actions, &attr, argv, env);
698 CHECK(error == 0 && child > 0);
699 CHECK(fcntl(sentinel, F_GETFD) >= 0 && getpgrp() == parent_group);
700 CHECK(sigprocmask(SIG_SETMASK, NULL, &after_mask) == 0 && sigismember(&after_mask, SIGUSR1) == 0);
701 CHECK(sigaction(SIGUSR2, NULL, &after_action) == 0 && after_action.sa_handler == SIG_IGN);
703 CHECK(write(input[1],
"x", 1) == 1 && close(output[1]) == 0);
705 CHECK(read(output[0], &
byte, 1) == 1 &&
byte ==
'p');
706 CHECK(exited(child, 0) == 0);
707 CHECK(close(input[0]) == 0 && close(input[1]) == 0 && close(output[0]) == 0);
708 CHECK(close(sentinel) == 0);
709 CHECK(posix_spawn_file_actions_destroy(&actions) == 0 && posix_spawnattr_destroy(&attr) == 0);
710 CHECK(sigaction(SIGUSR2, &parent_action, NULL) == 0);
711 CHECK(sigprocmask(SIG_SETMASK, &parent_mask, NULL) == 0);
715static int spawn_failures(spawn_function start,
const char* path,
const char* missing) {
716 char* argv[] = {self,
"--unused", NULL};
717 for (
int iteration = 0; iteration < 2; ++iteration) {
718 for (
int failure = 0; failure < 3; ++failure) {
719 posix_spawn_file_actions_t actions;
720 CHECK(posix_spawn_file_actions_init(&actions) == 0);
721 int invalid = fcntl(STDOUT_FILENO, F_DUPFD, 64);
722 CHECK(invalid >= 0 && close(invalid) == 0);
724 CHECK(posix_spawn_file_actions_addopen(&actions, 100,
"/does-not-exist/spawn-input",
727 CHECK(posix_spawn_file_actions_adddup2(&actions, invalid, STDIN_FILENO) == 0);
729 int error = start(&child, failure ? path : missing, &actions, NULL, argv, environ);
731 CHECK(error == (failure == 2 ? EBADF : ENOENT));
732 CHECK(posix_spawn_file_actions_destroy(&actions) == 0);
734 CHECK(waitpid(-1, &status, WNOHANG) == -1 && errno == ECHILD);
740static int spawn_contracts(
void) {
741 CHECK(spawn_success(posix_spawn, self, 1) == 0);
742 CHECK(spawn_failures(posix_spawn, self,
"/does-not-exist/spawn-image") == 0);
743 puts(
"VFORK-CONTRACT: posix_spawn file actions, attributes and failures PASS");
744 char directory[PATH_MAX + 32];
745 const char* name = strrchr(self,
'/');
747 snprintf(directory,
sizeof(directory),
"/does-not-exist:%.*s", (
int)(name - self), self);
748 const char* path = getenv(
"PATH");
749 char* saved_path = path ? strdup(path) : NULL;
750 CHECK(!path || saved_path);
751 CHECK(setenv(
"PATH", directory, 1) == 0);
752 CHECK(spawn_success(posix_spawnp, name + 1, 0) == 0);
753 CHECK(spawn_failures(posix_spawnp, name + 1,
"vfork-contract-missing-image") == 0);
754 CHECK(saved_path ? setenv(
"PATH", saved_path, 1) == 0 : unsetenv(
"PATH") == 0);
756 puts(
"VFORK-CONTRACT: posix_spawnp PATH lookup, actions and failures PASS");
757 int status = system(
"exit 7");
758 CHECK(status >= 0 && WIFEXITED(status) && WEXITSTATUS(status) == 7);
759 FILE* pipe = popen(
"printf 'vfork-wrapper\\n'",
"r");
762 CHECK(fgets(output,
sizeof(output), pipe) && !strcmp(output,
"vfork-wrapper\n"));
763 status = pclose(pipe);
764 CHECK(status >= 0 && WIFEXITED(status) && WEXITSTATUS(status) == 0);
765 puts(
"VFORK-CONTRACT: system and popen PASS");
769static void* pthread_worker(
void* ignored) {
771 int initial = tls_value;
775 return (
void*)(uintptr_t)(initial != 0);
778static int pthread_control(
void) {
783 CHECK(pthread_create(&thread, NULL, pthread_worker, NULL) == 0);
785 CHECK(pthread_join(thread, &result) == 0);
786 CHECK(!result && tls_value == 10 && errno == EINTR && shared_value == 99);
790static void* kill_observer(
void* ignored) {
793 while (!(child = __atomic_load_n(&child_ready, __ATOMIC_ACQUIRE)))
796 if (__atomic_load_n(&parent_returned, __ATOMIC_ACQUIRE))
797 observer_failure = 1;
798 if (kill(child, SIGKILL))
799 observer_failure = 2;
800 if (waitpid(child, &observer_status, 0) != child)
801 observer_failure = 3;
805static int signal_exit(
void) {
807 child_ready = parent_returned = observer_failure = 0;
808 CHECK(pthread_create(&observer, NULL, kill_observer, NULL) == 0);
809 pid_t child = vfork();
812 __atomic_store_n(&child_ready, (
int)syscall(SYS_getpid), __ATOMIC_RELEASE);
816 __atomic_store_n(&parent_returned, 1, __ATOMIC_RELEASE);
817 CHECK(pthread_join(observer, NULL) == 0 && !observer_failure);
818 CHECK(WIFSIGNALED(observer_status) && WTERMSIG(observer_status) == SIGKILL);
822int main(
int argc,
char** argv) {
823 if (argc == 3 && !strcmp(argv[1],
"--exec-child")) {
825 return read(atoi(argv[2]), &
byte, 1) == 1 &&
byte ==
'x' ? 0 : 84;
827 if (argc == 3 && !strcmp(argv[1],
"--spawn-child"))
828 return spawn_worker(atoi(argv[2]));
830 ssize_t length = readlink(
"/proc/self/exe", self,
sizeof(self) - 1);
831 CHECK(length > 0 && length < (ssize_t)
sizeof(self));
833 CHECK(private_fork() == 0);
834 CHECK(private_clone() == 0);
835 CHECK(pthread_control() == 0);
836 puts(
"VFORK-CONTRACT: ordinary fork and pthread controls PASS");
837 CHECK(repeated_exit() == 0);
838 CHECK(failed_exec() == 0);
839 CHECK(successful_exec() == 0);
840 CHECK(signal_exit() == 0);
841 puts(
"VFORK-CONTRACT: vfork exit, exec, failed exec and kill/reap PASS");
842 CHECK(clone_matrix() == 0);
843 CHECK(clone_exec() == 0);
844 CHECK(shared_clear_tid() == 0);
845 CHECK(rejected_clones() == 0);
846 CHECK(spawn_contracts() == 0);
847 puts(
"VFORK-CONTRACT: PASS");