The Pedigree Project 0.1
vfork-contract-test/main.c
1/* Copyright (c) 2026, Pedigree Developers. */
2#define _GNU_SOURCE
3#include <errno.h>
4#include <fcntl.h>
5#include <limits.h>
6#include <pthread.h>
7#include <sched.h>
8#include <signal.h>
9#include <spawn.h>
10#include <stdint.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <time.h>
15#include <unistd.h>
16
17#include <sys/mman.h>
18#include <sys/syscall.h>
19#include <sys/wait.h>
20
21#if !defined(__x86_64__)
22int main(void) {
23 puts("VFORK-CONTRACT: SKIP x86-64 syscall fixture");
24 return 77;
25}
26#else
27
28#define CHECK(expression) \
29 do { \
30 if (!(expression)) { \
31 fprintf(stderr, "VFORK-CONTRACT: FAIL line=%d errno=%d\n", __LINE__, errno); \
32 return 1; \
33 } \
34 } while (0)
35
36extern char** environ;
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;
46
47struct clone3_args {
48 uint64_t flags, pidfd, child_tid, parent_tid, exit_signal;
49 uint64_t stack, stack_size, tls, set_tid, set_tid_size, cgroup;
50};
51_Static_assert(sizeof(struct clone3_args) == 88, "Linux clone3 argument layout");
52
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
57
58/* The child cannot return through a C syscall wrapper after replacing its stack. */
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);
62__asm__(
63 ".text\n"
64 ".global raw_clone_start\n"
65 ".type raw_clone_start,@function\n"
66 "raw_clone_start:\n"
67 "push %r12\n"
68 "push %r13\n"
69 "mov %rdi,%r12\n"
70 "mov %rsi,%r13\n"
71 "mov %rdx,%rax\n"
72 "mov 0(%rax),%rdi\n"
73 "or 32(%rax),%rdi\n"
74 "mov 40(%rax),%rsi\n"
75 "add 48(%rax),%rsi\n"
76 "and $-16,%rsi\n"
77 "mov 24(%rax),%rdx\n"
78 "mov 16(%rax),%r10\n"
79 "mov 56(%rax),%r8\n"
80 "mov $56,%eax\n"
81 "syscall\n"
82 "test %rax,%rax\n"
83 "jnz 1f\n"
84 "xor %ebp,%ebp\n"
85 "mov %r13,%rdi\n"
86 "call *%r12\n"
87 "mov %eax,%edi\n"
88 "mov $60,%eax\n"
89 "syscall\n"
90 "ud2\n"
91 "1: pop %r13\n"
92 "pop %r12\n"
93 "ret\n"
94 ".size raw_clone_start,.-raw_clone_start\n"
95 ".global clone3_start\n"
96 ".type clone3_start,@function\n"
97 "clone3_start:\n"
98 "push %r12\n"
99 "push %r13\n"
100 "mov %rdi,%r12\n"
101 "mov %rsi,%r13\n"
102 "mov %rdx,%rdi\n"
103 "mov %rcx,%rsi\n"
104 "mov $435,%eax\n"
105 "syscall\n"
106 "test %rax,%rax\n"
107 "jnz 1f\n"
108 "xor %ebp,%ebp\n"
109 "mov %r13,%rdi\n"
110 "call *%r12\n"
111 "mov %eax,%edi\n"
112 "mov $60,%eax\n"
113 "syscall\n"
114 "ud2\n"
115 "1: pop %r13\n"
116 "pop %r12\n"
117 "ret\n"
118 ".size clone3_start,.-clone3_start\n"
119 /* Read FS without touching libc or assuming the supplied TLS is a pthread. */
120 ".global tls_child\n"
121 ".type tls_child,@function\n"
122 "tls_child:\n"
123 "mov %rdi,%r8\n"
124 "sub $8,%rsp\n"
125 "mov %rsp,%rsi\n"
126 "mov $0x1003,%edi\n"
127 "mov $158,%eax\n"
128 "syscall\n"
129 "test %rax,%rax\n"
130 "jnz 2f\n"
131 "cmp %r8,(%rsp)\n"
132 "jne 2f\n"
133 "mov $27,%eax\n"
134 "jmp 3f\n"
135 "2: mov $85,%eax\n"
136 "3: add $8,%rsp\n"
137 "ret\n"
138 ".size tls_child,.-tls_child\n");
139
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);
142 if (child < 0) {
143 errno = -child;
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);
146 return -1;
147 }
148 return child;
149}
150
151static void pause_briefly(void) {
152 const struct timespec delay = {0, 30000000};
153 syscall(SYS_nanosleep, &delay, NULL);
154}
155
156static int exited(pid_t child, int code) {
157 int status;
158 pid_t result;
159 do {
160 result = waitpid(child, &status, 0);
161 } while (result < 0 && errno == EINTR);
162 CHECK(result == child);
163 CHECK(WIFEXITED(status) && WEXITSTATUS(status) == code);
164 return 0;
165}
166
167static int repeated_exit(void) {
168 for (int iteration = 0; iteration < 12; ++iteration) {
169 shared_value = 0;
170 pid_t child = vfork();
171 CHECK(child >= 0);
172 if (!child) {
173 shared_value = 1;
174 pause_briefly();
175 shared_value = 2;
176 _exit(17);
177 }
178 CHECK(shared_value == 2);
179 CHECK(exited(child, 17) == 0);
180 }
181 return 0;
182}
183
184static int private_fork(void) {
185 shared_value = 11;
186 pid_t child = fork();
187 CHECK(child >= 0);
188 if (!child) {
189 shared_value = 12;
190 _exit(20);
191 }
192 CHECK(exited(child, 20) == 0 && shared_value == 11);
193 return 0;
194}
195
196static int private_clone_child(void* ignored) {
197 (void)ignored;
198 if (child_tid != syscall(SYS_getpid))
199 return 86;
200 shared_value = 12;
201 return 29;
202}
203
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;
209 shared_value = 11;
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);
222 }
223 CHECK(munmap(stack, 65536) == 0);
224 puts("VFORK-CONTRACT: ordinary clone/clone3 modifier isolation PASS");
225 return 0;
226}
227
228static int failed_exec(void) {
229 char* arguments[] = {"missing", NULL};
230 shared_value = 0;
231 pid_t child = vfork();
232 CHECK(child >= 0);
233 if (!child) {
234 if (syscall(SYS_execve, "/does-not-exist/vfork-contract", arguments, environ) != -1)
235 _exit(81);
236 shared_value = 3;
237 pause_briefly();
238 shared_value = 4;
239 _exit(18);
240 }
241 CHECK(shared_value == 4);
242 CHECK(exited(child, 18) == 0);
243 return 0;
244}
245
246static int successful_exec(void) {
247 int gate[2];
248 CHECK(pipe(gate) == 0);
249 char descriptor[24];
250 snprintf(descriptor, sizeof(descriptor), "%d", gate[0]);
251 char* arguments[] = {self, "--exec-child", descriptor, NULL};
252 shared_value = 0;
253 pid_t child = vfork();
254 CHECK(child >= 0);
255 if (!child) {
256 shared_value = 5;
257 syscall(SYS_execve, self, arguments, environ);
258 _exit(82);
259 }
260 CHECK(shared_value == 5);
261 // The replacement image cannot exit until vfork has returned to this write.
262 CHECK(write(gate[1], "x", 1) == 1);
263 CHECK(exited(child, 0) == 0);
264 CHECK(close(gate[0]) == 0 && close(gate[1]) == 0);
265 return 0;
266}
267
268static int clone_child(void* ignored) {
269 (void)ignored;
270 int result = 19;
271 seen_tid = (int)syscall(SYS_getpid);
272 if ((clone_flags & CLONE_CHILD_SETTID) && child_tid != seen_tid)
273 result = 86;
274 if (clone_flags & CLONE_NEWUTS) {
275 if (sethostname("vfork-child", 11))
276 result = 87;
277 }
278 if (clear_sighand) {
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)
282 result = 88;
283 }
284 child_mapping = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
285 if (child_mapping == MAP_FAILED)
286 result = 83;
287 else
288 child_mapping[0] = 42;
289 shared_value = 6;
290 __atomic_store_n(&child_ready, seen_tid, __ATOMIC_RELEASE);
291 char byte;
292 if (read(gate[0], &byte, 1) != 1 || byte != 'x')
293 result = 89;
294 shared_value = 7;
295 return result;
296}
297
298static void* gate_observer(void* ignored) {
299 (void)ignored;
300 int child;
301 while (!(child = __atomic_load_n(&child_ready, __ATOMIC_ACQUIRE)))
302 sched_yield();
303 if (child < 0)
304 return NULL;
305 // Give an incorrectly unblocked creator a chance to publish parent_returned.
306 pause_briefly();
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;
311 return NULL;
312}
313
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);
323 shared_value = 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;
329 pthread_t observer;
330 CHECK(pthread_create(&observer, NULL, gate_observer, NULL) == 0);
331 struct {
332 struct clone3_args args;
333 uint64_t extension;
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}};
340 long child;
341 // Musl's public wrapper rejects TLS/clear-TID modifiers before issuing clone.
342 if (clone3_size || (modifiers & (CLONE_CHILD_CLEARTID | CLONE_SETTLS))) {
343 child = start_raw_clone(clone_child, NULL, &record.args, clone3_size);
344 } else {
345 child = clone(clone_child, stack + stack_size, clone_flags | SIGCHLD, NULL, &parent_tid, NULL,
346 &child_tid);
347 }
348 __atomic_store_n(&parent_returned, 1, __ATOMIC_RELEASE);
349 if (child < 0)
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));
355 CHECK(
356 child_tid ==
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);
364 return 0;
365}
366
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";
370 if (clone3_size)
371 api = "clone3";
372 else if (modifiers & (CLONE_CHILD_CLEARTID | CLONE_SETTLS))
373 api = "raw-clone";
374 if (result)
375 fprintf(stderr,
376 "VFORK-CONTRACT: clone matrix api=%s modifiers=%#lx size=%zu clear_sighand=%d\n", api,
377 modifiers, clone3_size, reset_handlers);
378 return result;
379}
380
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,
389 .stack_size = 65536,
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);
397 return 0;
398}
399
400static void caught_signal(int signal) {
401 (void)signal;
402}
403
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");
425 return 0;
426}
427
428static int rejected_clone3(struct clone3_args* args, size_t size, int expected) {
429 errno = 0;
430 long result = syscall(CLONE3_SYSCALL, args, size);
431 if (!result)
432 _exit(90);
433 CHECK(result == -1 && errno == expected);
434 return 0;
435}
436
437static int rejected_clones(void) {
438 const unsigned long invalid[] = {CLONE_VM | SIGCHLD,
439 CLONE_VFORK | SIGCHLD,
440 VFORK_FLAGS,
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) {
445 errno = 0;
446 long result = syscall(SYS_clone, invalid[i], NULL, NULL, NULL, 0);
447 if (!result)
448 _exit(90);
449 CHECK(result == -1 && errno == EINVAL);
450 }
451 const unsigned long bad_tid_flags[] = {CLONE_PARENT_SETTID, CLONE_CHILD_SETTID};
452 for (size_t i = 0; i < 2; ++i) {
453 errno = 0;
454 long result =
455 syscall(SYS_clone, VFORK_FLAGS | SIGCHLD | bad_tid_flags[i], NULL, (void*)1, (void*)1, 0);
456 if (!result)
457 _exit(90);
458 CHECK(result == -1 && errno == EFAULT);
459 }
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);
465 struct {
466 struct clone3_args args;
467 uint64_t extra;
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),
476 CLONE_VM,
477 CLONE_VFORK};
478 for (size_t i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) {
479 args = valid;
480 args.flags = flags[i];
481 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
482 }
483 args = valid;
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) {
489 args = valid;
490 if (field == 0)
491 args.pidfd = 1;
492 if (field == 1)
493 args.set_tid = 1;
494 if (field == 2)
495 args.set_tid_size = 1;
496 if (field == 3)
497 args.cgroup = 1;
498 CHECK(rejected_clone3(&args, 88, EINVAL) == 0);
499 }
500 args = valid;
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);
511 args.stack = 1;
512 args.stack_size = 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) {
518 args = valid;
519 args.flags |= bad_tid_flags[i];
520 args.parent_tid = args.child_tid = 1;
521 CHECK(rejected_clone3(&args, 88, EFAULT) == 0);
522 }
523 int status;
524 CHECK(waitpid(-1, &status, WNOHANG) == -1 && errno == ECHILD);
525 puts("VFORK-CONTRACT: rejected clone/clone3 arguments PASS");
526 return 0;
527}
528
529struct exec_arguments {
530 char* path;
531 char** argv;
532};
533
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;
539}
540
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);
547 char descriptor[24];
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};
551 child_tid = -37;
552 seen_tid = 0;
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);
564 if (fail)
565 CHECK(seen_tid == child);
566 else
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);
570 }
571 }
572 CHECK(munmap(stack, 65536) == 0);
573 puts("VFORK-CONTRACT: clone/clone3 clear-TID exec and failed exec PASS");
574 return 0;
575}
576
577static int* futex_alias;
578static int futex_waiting, futex_result, futex_errno;
579
580static void* futex_observer(void* ignored) {
581 (void)ignored;
582 int tid;
583 while (!(tid = __atomic_load_n(&child_ready, __ATOMIC_ACQUIRE)))
584 sched_yield();
585 if (tid < 0)
586 return NULL;
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);
590 futex_errno = errno;
591 return NULL;
592}
593
594static int futex_child(void* ignored) {
595 (void)ignored;
596 __atomic_store_n(&child_ready, (int)syscall(SYS_getpid), __ATOMIC_RELEASE);
597 while (!__atomic_load_n(&futex_waiting, __ATOMIC_ACQUIRE))
598 sched_yield();
599 pause_briefly();
600 return 28;
601}
602
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;
612 *ctid = -37;
613 pthread_t observer;
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);
622 if (child < 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);
626 if (futex_result)
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);
630 }
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");
634 return 0;
635}
636
637typedef int (*spawn_function)(pid_t*, const char*, const posix_spawn_file_actions_t*,
638 const posix_spawnattr_t*, char* const[], char* const[]);
639
640static int spawn_worker(int closed_fd) {
641 char byte;
642 sigset_t mask;
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);
653 return 0;
654}
655
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);
661 char descriptor[24];
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;
675 sigemptyset(&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
689 if (use_vfork)
690 flags |= POSIX_SPAWN_USEVFORK;
691#else
692 (void)use_vfork;
693#endif
694 CHECK(posix_spawnattr_setflags(&attr, flags) == 0);
695 pid_t child = -1;
696 int error = start(&child, path, &actions, &attr, argv, env);
697 errno = error;
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);
702 // This worker cannot finish until the successful spawn has returned after exec.
703 CHECK(write(input[1], "x", 1) == 1 && close(output[1]) == 0);
704 char byte;
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);
712 return 0;
713}
714
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);
723 if (failure == 1)
724 CHECK(posix_spawn_file_actions_addopen(&actions, 100, "/does-not-exist/spawn-input",
725 O_RDONLY, 0) == 0);
726 if (failure == 2)
727 CHECK(posix_spawn_file_actions_adddup2(&actions, invalid, STDIN_FILENO) == 0);
728 pid_t child = -1;
729 int error = start(&child, failure ? path : missing, &actions, NULL, argv, environ);
730 errno = error;
731 CHECK(error == (failure == 2 ? EBADF : ENOENT));
732 CHECK(posix_spawn_file_actions_destroy(&actions) == 0);
733 int status;
734 CHECK(waitpid(-1, &status, WNOHANG) == -1 && errno == ECHILD);
735 }
736 }
737 return 0;
738}
739
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, '/');
746 CHECK(name);
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);
755 free(saved_path);
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");
760 CHECK(pipe);
761 char output[64];
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");
766 return 0;
767}
768
769static void* pthread_worker(void* ignored) {
770 (void)ignored;
771 int initial = tls_value;
772 tls_value = 20;
773 errno = EDOM;
774 shared_value = 99;
775 return (void*)(uintptr_t)(initial != 0);
776}
777
778static int pthread_control(void) {
779 pthread_t thread;
780 tls_value = 10;
781 shared_value = 0;
782 errno = EINTR;
783 CHECK(pthread_create(&thread, NULL, pthread_worker, NULL) == 0);
784 void* result;
785 CHECK(pthread_join(thread, &result) == 0);
786 CHECK(!result && tls_value == 10 && errno == EINTR && shared_value == 99);
787 return 0;
788}
789
790static void* kill_observer(void* ignored) {
791 (void)ignored;
792 int child;
793 while (!(child = __atomic_load_n(&child_ready, __ATOMIC_ACQUIRE)))
794 sched_yield();
795 pause_briefly();
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;
802 return NULL;
803}
804
805static int signal_exit(void) {
806 pthread_t observer;
807 child_ready = parent_returned = observer_failure = 0;
808 CHECK(pthread_create(&observer, NULL, kill_observer, NULL) == 0);
809 pid_t child = vfork();
810 CHECK(child >= 0);
811 if (!child) {
812 __atomic_store_n(&child_ready, (int)syscall(SYS_getpid), __ATOMIC_RELEASE);
813 for (;;)
814 syscall(SYS_pause);
815 }
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);
819 return 0;
820}
821
822int main(int argc, char** argv) {
823 if (argc == 3 && !strcmp(argv[1], "--exec-child")) {
824 char byte;
825 return read(atoi(argv[2]), &byte, 1) == 1 && byte == 'x' ? 0 : 84;
826 }
827 if (argc == 3 && !strcmp(argv[1], "--spawn-child"))
828 return spawn_worker(atoi(argv[2]));
829 alarm(90);
830 ssize_t length = readlink("/proc/self/exe", self, sizeof(self) - 1);
831 CHECK(length > 0 && length < (ssize_t)sizeof(self));
832 self[length] = 0;
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");
848 return 0;
849}
850#endif