The Pedigree Project 0.1
process.c
1#define _GNU_SOURCE
2
3/*
4 * Copyright (c) 2026, Pedigree Developers
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted.
8 */
9
10#include <elf.h>
11#include <errno.h>
12#include <fcntl.h>
13#include <limits.h>
14#include <pthread.h>
15#include <sched.h>
16#include <signal.h>
17#include <spawn.h>
18#include <stdint.h>
19#include <stdio.h>
20#include <string.h>
21#include <unistd.h>
22
23#include <sys/resource.h>
24#include <sys/stat.h>
25#include <sys/syscall.h>
26#include <sys/wait.h>
27
28extern void fail(void) __attribute__((noreturn));
29
30static volatile sig_atomic_t signalHandled = 0;
31static int signalReportFd = -1;
32static const char* execSignalProgram = 0;
33static volatile sig_atomic_t sigchldExpectedChild = -1;
34static volatile sig_atomic_t sigchldHandlerCalls = 0;
35static volatile sig_atomic_t sigchldHandlerSignal = 0;
36static volatile sig_atomic_t sigchldWaitResult = -1;
37static volatile sig_atomic_t sigchldWaitStatus = 0;
38static volatile sig_atomic_t signalFrameValid = 0;
39static volatile sig_atomic_t signalFrameCalls = 0;
40static volatile sig_atomic_t signalFrameExpectedPid = 0;
41static volatile sig_atomic_t signalFrameExpectedTid = 0;
42static volatile sig_atomic_t signalFrameExpectedUid = 0;
43
44enum { signalFrameResumeValue = 0x13579BDF };
45
46enum {
47 processStopGateAttempts = 20000,
48 processStopGateQuietYields = 512,
49 futexRequeueAttempts = 100000,
50 futexWait = 0,
51 futexWake = 1,
52 futexRequeue = 3,
53 futexPrivate = 128,
54 futexRequeueWaiters = 3,
55 condBroadcastWaiters = 4,
56};
57
59 int heartbeatFd;
60 volatile int ready[2];
61 volatile int stop;
62 volatile int failure;
63};
64
66 struct processStopGateProbe* probe;
67 int index;
68};
69
71 int source;
72 int destination;
73 volatile int ready;
74 volatile int woke;
75 volatile int failure;
76};
77
79 pthread_mutex_t mutex;
80 pthread_cond_t condition;
81 volatile int ready;
82 int release;
83 volatile int woke;
84};
85
86static void handleSignal(int signalNumber) {
87 if (signalNumber == SIGUSR1) {
88 signalHandled = 1;
89 if (signalReportFd >= 0) {
90 const char token = 's';
91 (void)write(signalReportFd, &token, sizeof(token));
92 }
93 }
94}
95
96static void handleLinuxSignalFrame(int signalNumber, siginfo_t* info, void* contextPointer) {
97 ucontext_t* context = contextPointer;
98 ++signalFrameCalls;
99 if (signalNumber != SIGUSR1 || !info || !context || info->si_signo != SIGUSR1 || info->si_errno ||
100 info->si_code != SI_TKILL || info->si_pid != signalFrameExpectedPid ||
101 info->si_uid != (unsigned)signalFrameExpectedUid) {
102 signalFrameValid = 0;
103 return;
104 }
105
106 if (signalFrameCalls == 1 && syscall(SYS_tkill, signalFrameExpectedTid, SIGUSR1)) {
107 signalFrameValid = 0;
108 }
109 context->uc_mcontext.gregs[REG_RAX] = signalFrameResumeValue;
110}
111
112static void handleExecSignal(int signalNumber) {
113 if (signalNumber != SIGUSR1 || !execSignalProgram || kill(getpid(), SIGTERM))
114 _exit(123);
115
116 char* const arguments[] = {(char*)execSignalProgram, (char*)"--exec-signal-child", 0};
117 execv(execSignalProgram, arguments);
118 _exit(124);
119}
120
121static void handleSigchld(int signalNumber) {
122 int savedErrno = errno;
123 if (!sigchldHandlerCalls) {
124 int statusCode = 0;
125 sigchldHandlerSignal = signalNumber;
126 pid_t expectedChild = (pid_t)sigchldExpectedChild;
127 sigchldWaitResult = expectedChild > 0 ? waitpid(expectedChild, &statusCode, WNOHANG) : -1;
128 sigchldWaitStatus = statusCode;
129 }
130 ++sigchldHandlerCalls;
131 errno = savedErrno;
132}
133
134static void status(const char* message) {
135 puts(message);
136 fflush(stdout);
137}
138
139static pid_t waitpid_bounded(pid_t child, int* statusCode, int options) {
140 for (size_t attempt = 0; attempt < processStopGateAttempts; ++attempt) {
141 pid_t result = waitpid(child, statusCode, options | WNOHANG);
142 if (result == child)
143 return result;
144 if (result < 0) {
145 if (errno == EINTR)
146 continue;
147 return result;
148 }
149 sched_yield();
150 }
151 return 0;
152}
153
154static int wait_for_atomic_value(volatile int* value, int expected) {
155 for (size_t attempt = 0; attempt < futexRequeueAttempts; ++attempt) {
156 if (__atomic_load_n(value, __ATOMIC_ACQUIRE) == expected)
157 return 0;
158 sched_yield();
159 }
160 return -1;
161}
162
163static void* run_futex_requeue_waiter(void* parameter) {
164 struct futexRequeueProbe* probe = parameter;
165 __atomic_add_fetch(&probe->ready, 1, __ATOMIC_RELEASE);
166
167 for (;;) {
168 errno = 0;
169 long result = syscall(SYS_futex, &probe->source, futexWait | futexPrivate, 0, 0, 0, 0);
170 if (!result)
171 break;
172 if (errno == EINTR)
173 continue;
174 __atomic_store_n(&probe->failure, 1, __ATOMIC_RELEASE);
175 return 0;
176 }
177
178 __atomic_add_fetch(&probe->woke, 1, __ATOMIC_RELEASE);
179 return 0;
180}
181
182static int run_raw_futex_requeue_child(void) {
183 struct futexRequeueProbe probe = {0};
184 pthread_t waiters[futexRequeueWaiters];
185
186 for (size_t i = 0; i < futexRequeueWaiters; ++i) {
187 if (pthread_create(&waiters[i], 0, run_futex_requeue_waiter, &probe))
188 return 10;
189 }
190 if (wait_for_atomic_value(&probe.ready, futexRequeueWaiters))
191 return 11;
192
193 // Accumulate every waiter on the destination. This closes the publication
194 // race without relying on delays or scheduler timing.
195 int staged = 0;
196 for (size_t attempt = 0; attempt < futexRequeueAttempts && staged < futexRequeueWaiters;
197 ++attempt) {
198 long result = syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0,
199 futexRequeueWaiters - staged, &probe.destination, 0);
200 if (result < 0)
201 return 12;
202 staged += (int)result;
203 if (staged < futexRequeueWaiters)
204 sched_yield();
205 }
206 if (staged != futexRequeueWaiters)
207 return 13;
208
209 if (syscall(SYS_futex, &probe.destination, futexRequeue | futexPrivate, 0, futexRequeueWaiters,
210 &probe.source, 0) != futexRequeueWaiters)
211 return 14;
212
213 // Plain FUTEX_REQUEUE permits equal keys and counts each selected waiter,
214 // even though retagging it to the same key is a no-op.
215 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0, futexRequeueWaiters,
216 &probe.source, 0) != futexRequeueWaiters)
217 return 15;
218
219#if UINTPTR_MAX > UINT32_MAX
220 const uintptr_t extendedRequeueCount = ((uintptr_t)1 << 32) | 1;
221 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0, extendedRequeueCount,
222 &probe.destination, 0) != 1 ||
223 syscall(SYS_futex, &probe.destination, futexRequeue | futexPrivate, 0, 1, &probe.source, 0) !=
224 1)
225 return 16;
226#endif
227
228 errno = 0;
229 if (syscall(SYS_futex, (char*)&probe.source + 1, futexRequeue | futexPrivate, 0, 0,
230 &probe.destination, 0) != -1 ||
231 errno != EINVAL)
232 return 17;
233 errno = 0;
234 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0, 0,
235 (char*)&probe.destination + 1, 0) != -1 ||
236 errno != EINVAL)
237 return 18;
238 errno = 0;
239 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, -1, 0, &probe.destination,
240 0) != -1 ||
241 errno != EINVAL)
242 return 19;
243 errno = 0;
244 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0, -1, &probe.destination,
245 0) != -1 ||
246 errno != EINVAL)
247 return 20;
248 errno = 0;
249 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0, 0, 0, 0) != -1 ||
250 errno != EFAULT)
251 return 21;
252
253 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 1, 2, &probe.destination, 0) !=
254 futexRequeueWaiters)
255 return 22;
256 if (syscall(SYS_futex, &probe.source, futexWake | futexPrivate, INT_MAX, 0, 0, 0) != 0)
257 return 23;
258 if (syscall(SYS_futex, &probe.destination, futexWake | futexPrivate, INT_MAX, 0, 0, 0) != 2)
259 return 24;
260
261 for (size_t i = 0; i < futexRequeueWaiters; ++i) {
262 if (pthread_join(waiters[i], 0))
263 return 25;
264 }
265 if (__atomic_load_n(&probe.failure, __ATOMIC_ACQUIRE) ||
266 __atomic_load_n(&probe.woke, __ATOMIC_ACQUIRE) != futexRequeueWaiters)
267 return 26;
268 return 0;
269}
270
271static void* run_cond_broadcast_waiter(void* parameter) {
272 struct condBroadcastProbe* probe = parameter;
273 if (pthread_mutex_lock(&probe->mutex))
274 return (void*)1;
275 __atomic_add_fetch(&probe->ready, 1, __ATOMIC_RELEASE);
276 while (!probe->release) {
277 if (pthread_cond_wait(&probe->condition, &probe->mutex)) {
278 pthread_mutex_unlock(&probe->mutex);
279 return (void*)1;
280 }
281 }
282 __atomic_add_fetch(&probe->woke, 1, __ATOMIC_RELEASE);
283 if (pthread_mutex_unlock(&probe->mutex))
284 return (void*)1;
285 return 0;
286}
287
288static int run_cond_broadcast_child(void) {
289 struct condBroadcastProbe probe = {
290 .mutex = PTHREAD_MUTEX_INITIALIZER,
291 .condition = PTHREAD_COND_INITIALIZER,
292 .ready = 0,
293 .release = 0,
294 .woke = 0,
295 };
296 pthread_t waiters[condBroadcastWaiters];
297
298 for (size_t i = 0; i < condBroadcastWaiters; ++i) {
299 if (pthread_create(&waiters[i], 0, run_cond_broadcast_waiter, &probe))
300 return 30;
301 }
302 if (wait_for_atomic_value(&probe.ready, condBroadcastWaiters) || pthread_mutex_lock(&probe.mutex))
303 return 31;
304
305 // Every waiter registered itself with the condition variable before it
306 // released this mutex. Give that stable cohort time to enter the barrier
307 // futexes which musl will hand off with FUTEX_REQUEUE.
308 for (size_t i = 0; i < 128; ++i)
309 sched_yield();
310 probe.release = 1;
311 if (pthread_cond_broadcast(&probe.condition) || pthread_mutex_unlock(&probe.mutex))
312 return 32;
313
314 for (size_t i = 0; i < condBroadcastWaiters; ++i) {
315 void* result = 0;
316 if (pthread_join(waiters[i], &result) || result)
317 return 33;
318 }
319 if (__atomic_load_n(&probe.woke, __ATOMIC_ACQUIRE) != condBroadcastWaiters ||
320 pthread_cond_destroy(&probe.condition) || pthread_mutex_destroy(&probe.mutex))
321 return 34;
322 return 0;
323}
324
325static void run_bounded_thread_child(int (*childTest)(void)) {
326 pid_t child = fork();
327 if (child < 0)
328 fail();
329 if (!child) {
330 alarm(10);
331 _exit(childTest());
332 }
333
334 int statusCode = 0;
335 pid_t waited;
336 do {
337 waited = waitpid(child, &statusCode, 0);
338 } while (waited < 0 && errno == EINTR);
339 if (waited != child)
340 fail();
341 if (!WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
342 fail();
343}
344
345static void* run_process_stop_gate_worker(void* parameter) {
346 struct processStopGateWorker* worker = parameter;
347 struct processStopGateProbe* probe = worker->probe;
348 const char token = (char)('a' + worker->index);
349 volatile unsigned long spin = (unsigned long)(worker->index + 1);
350 __atomic_store_n(&probe->ready[worker->index], 1, __ATOMIC_RELEASE);
351
352 while (!__atomic_load_n(&probe->stop, __ATOMIC_ACQUIRE)) {
353 for (size_t i = 0; i < 4096; ++i)
354 spin = (spin * 33) ^ i;
355
356 ssize_t written = write(probe->heartbeatFd, &token, sizeof(token));
357 if (written == sizeof(token) ||
358 (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)))
359 continue;
360 __atomic_store_n(&probe->failure, 1, __ATOMIC_RELEASE);
361 break;
362 }
363
364 return 0;
365}
366
367static void test_proc_self_fd(void) {
368 status("Testing /proc/self/fd readlink...");
369
370 int fd = open("/dev/null", O_RDONLY);
371 if (fd < 0)
372 fail();
373
374 char procname[64];
375 snprintf(procname, sizeof(procname), "/proc/self/fd/%d", fd);
376
377 char target[PATH_MAX];
378 ssize_t length = readlink(procname, target, sizeof(target));
379 if (length <= 0 || (size_t)length >= sizeof(target))
380 fail();
381 target[length] = '\0';
382
383 struct stat pathStat;
384 struct stat descriptorStat;
385 if (stat(target, &pathStat) || fstat(fd, &descriptorStat) ||
386 pathStat.st_dev != descriptorStat.st_dev || pathStat.st_ino != descriptorStat.st_ino)
387 fail();
388
389 char truncated[2];
390 if (readlink(procname, truncated, sizeof(truncated)) != (ssize_t)sizeof(truncated) ||
391 memcmp(target, truncated, sizeof(truncated)))
392 fail();
393
394 close(fd);
395 errno = 0;
396 if (readlink(procname, target, sizeof(target)) != -1 || errno != ENOENT)
397 fail();
398
399 if (isatty(STDIN_FILENO)) {
400 char tty[PATH_MAX];
401 if (ttyname_r(STDIN_FILENO, tty, sizeof(tty)))
402 fail();
403 }
404
405 status("OK");
406}
407
408void test_proc_kernel_threads(void) {
409 status("Testing userspace kernel-thread inspection...");
410
411 FILE* file = fopen("/proc/kernel/threads", "r");
412 char line[512];
413 if (!file || !fgets(line, sizeof(line), file) ||
414 strcmp(line, "process_id task_id cpu state priority user_ns system_ns idle name\n")) {
415 fail();
416 }
417 struct stat info;
418 if (fstat(fileno(file), &info) || !(info.st_mode & S_IROTH)) {
419 fail();
420 }
421
422 size_t workers = 0, idleThreads = 0;
423 while (fgets(line, sizeof(line), file)) {
424 unsigned long processId, taskId, cpu, priority, user, system;
425 unsigned idle;
426 char state, name[128];
427 if (sscanf(line, "%lu %lu %lu %c %lu %lu %lu %u %127[^\n]", &processId, &taskId, &cpu, &state,
428 &priority, &user, &system, &idle, name) != 9 ||
429 !strchr("RSZ", state) || idle > 1 || !name[0]) {
430 fail();
431 }
432 if (idle) {
433 ++idleThreads;
434 } else {
435 ++workers;
436 }
437 }
438 if (ferror(file) || fclose(file) || !workers || !idleThreads) {
439 fail();
440 }
441
442 file = fopen("/proc/loadavg", "r");
443 unsigned long running, total;
444 if (!file || !fgets(line, sizeof(line), file) ||
445 sscanf(line, "%*s %*s %*s %lu/%lu", &running, &total) != 2 || !running || running > total ||
446 total <= workers || fclose(file)) {
447 fail();
448 }
449 file = fopen("/proc/stat", "r");
450 int foundRunning = 0;
451 if (!file) {
452 fail();
453 }
454 while (fgets(line, sizeof(line), file)) {
455 if (sscanf(line, "procs_running %lu", &running) == 1) {
456 foundRunning = running > 0;
457 }
458 }
459 if (ferror(file) || fclose(file) || !foundRunning) {
460 fail();
461 }
462 status("OK");
463}
464
465static void test_vfork(void) {
466 status("Testing vfork syscall compatibility...");
467
468 pid_t child = vfork();
469 if (child < 0)
470 fail();
471 if (!child)
472 _exit(0);
473
474 int statusCode = 0;
475 if (waitpid(child, &statusCode, 0) != child || !WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
476 fail();
477
478 status("OK");
479}
480
481void test_posix_spawn(const char* program) {
482 status("Testing posix_spawn process isolation...");
483
484 const pid_t parent = getpid();
485 pid_t child = -1;
486 char* const arguments[] = {(char*)program, (char*)"--exec-shebang-unexpected-child", 0};
487 char* const environment[] = {0};
488 if (posix_spawn(&child, program, 0, 0, arguments, environment) || child <= 0 || child == parent ||
489 getpid() != parent)
490 fail();
491
492 int statusCode = 0;
493 if (waitpid_bounded(child, &statusCode, 0) != child || !WIFEXITED(statusCode) ||
494 WEXITSTATUS(statusCode) != 120 || getpid() != parent)
495 fail();
496
497 errno = 0;
498 if (waitpid(child, &statusCode, WNOHANG) != -1 || errno != ECHILD)
499 fail();
500
501 pid_t missingChild = -1;
502 char* const missingArguments[] = {(char*)"/posix-spawn-missing", 0};
503 if (posix_spawn(&missingChild, missingArguments[0], 0, 0, missingArguments, environment) !=
504 ENOENT ||
505 getpid() != parent)
506 fail();
507
508 errno = 0;
509 if (waitpid(-1, &statusCode, WNOHANG) != -1 || errno != ECHILD)
510 fail();
511
512 status("OK");
513}
514
515static void test_resource_compatibility(void) {
516 status("Testing Linux resource compatibility syscalls...");
517
518 struct rlimit limit = {0};
519 if (getrlimit(RLIMIT_CPU, &limit) || limit.rlim_cur != RLIM_INFINITY ||
520 limit.rlim_max != RLIM_INFINITY)
521 fail();
522
523 if (getrlimit(RLIMIT_RTPRIO, &limit) || limit.rlim_cur != 0 || limit.rlim_max != 0)
524 fail();
525
526 if (getrlimit(RLIMIT_RTTIME, &limit) || limit.rlim_cur != RLIM_INFINITY ||
527 limit.rlim_max != RLIM_INFINITY)
528 fail();
529
530 memset(&limit, 0, sizeof(limit));
531 if (syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, 0, &limit) || limit.rlim_cur != 16384 ||
532 limit.rlim_max != 16384)
533 fail();
534
535 memset(&limit, 0, sizeof(limit));
536 if (syscall(SYS_prlimit64, getpid(), RLIMIT_NOFILE, 0, &limit) || limit.rlim_cur != 16384 ||
537 limit.rlim_max != 16384)
538 fail();
539
540 if (syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, 0, 0))
541 fail();
542
543 errno = 0;
544 if (syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, 0, (void*)UINTPTR_MAX) != -1 || errno != EFAULT)
545 fail();
546
547 errno = 0;
548 if (syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, (void*)UINTPTR_MAX, 0) != -1 || errno != ENOSYS)
549 fail();
550
551 errno = 0;
552 if (syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, &limit, 0) != -1 || errno != ENOSYS)
553 fail();
554
555 errno = 0;
556 if (syscall(SYS_prlimit64, -1, RLIMIT_NOFILE, 0, &limit) != -1 || errno != ESRCH)
557 fail();
558
559 errno = 0;
560 if (syscall(SYS_prlimit64, 0, -1, 0, &limit) != -1 || errno != EINVAL)
561 fail();
562
563 errno = 0;
564 if (syscall(SYS_setrlimit, RLIMIT_NOFILE, (void*)UINTPTR_MAX) != -1 || errno != ENOSYS)
565 fail();
566
567 errno = 0;
568 if (syscall(SYS_setrlimit, RLIMIT_NOFILE, &limit) != -1 || errno != ENOSYS)
569 fail();
570
571 memset(&limit, 0, sizeof(limit));
572 if (syscall(SYS_getrlimit, RLIMIT_NOFILE, &limit) || limit.rlim_cur != 16384 ||
573 limit.rlim_max != 16384)
574 fail();
575
576 struct rlimit saved_memlock;
577 if (getrlimit(RLIMIT_MEMLOCK, &saved_memlock))
578 fail();
579 limit = saved_memlock;
580 limit.rlim_cur = 0;
581 if (setrlimit(RLIMIT_MEMLOCK, &limit) || getrlimit(RLIMIT_MEMLOCK, &limit) ||
582 limit.rlim_cur != 0 || limit.rlim_max != saved_memlock.rlim_max)
583 fail();
584 if (syscall(SYS_setrlimit, RLIMIT_MEMLOCK, &saved_memlock) ||
585 syscall(SYS_getrlimit, RLIMIT_MEMLOCK, &limit) || limit.rlim_cur != saved_memlock.rlim_cur ||
586 limit.rlim_max != saved_memlock.rlim_max)
587 fail();
588
589 if (syscall(SYS_membarrier, 0, 0, 0) != 0)
590 fail();
591
592 errno = 0;
593 if (syscall(SYS_membarrier, 1, 0, 0) != -1 || errno != EINVAL)
594 fail();
595
596 errno = 0;
597 if (syscall(SYS_membarrier, 0, 1, 0) != -1 || errno != EINVAL)
598 fail();
599
600 status("OK");
601}
602
603static void test_futex_requeue(void) {
604 status("Testing raw futex requeue semantics...");
605 run_bounded_thread_child(run_raw_futex_requeue_child);
606 status("OK");
607}
608
609static void test_cond_broadcast(void) {
610 status("Testing pthread condition broadcast requeue...");
611 run_bounded_thread_child(run_cond_broadcast_child);
612 status("OK");
613}
614
615static void test_signal_return(void) {
616 status("Testing signal handler return...");
617
618 struct sigaction action = {0};
619 action.sa_handler = handleSignal;
620 if (sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, 0) || kill(getpid(), SIGUSR1) ||
621 !signalHandled || signal(SIGUSR1, SIG_DFL) == SIG_ERR)
622 fail();
623
624 status("OK");
625}
626
627void test_linux_signal_frame(void) {
628 status("Testing Linux amd64 asynchronous signal frame...");
629
630 struct sigaction action = {0};
631 struct sigaction previousAction = {0};
632 action.sa_sigaction = handleLinuxSignalFrame;
633 action.sa_flags = SA_SIGINFO;
634 if (sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, &previousAction))
635 fail();
636
637 const long tid = syscall(SYS_gettid);
638 signalFrameExpectedPid = getpid();
639 signalFrameExpectedTid = tid;
640 signalFrameExpectedUid = getuid();
641 signalFrameValid = 1;
642 signalFrameCalls = 0;
643 const long resumed = tid > 0 ? syscall(SYS_tkill, tid, SIGUSR1) : -1;
644
645 // The first handler queues a masked second SIGUSR1. rt_sigreturn must
646 // restore the mask and deliver it before the original syscall resumes.
647 if (!signalFrameValid || signalFrameCalls != 2 || resumed != signalFrameResumeValue)
648 fail();
649 if (sigaction(SIGUSR1, &previousAction, 0))
650 fail();
651
652 status("OK");
653}
654
655static void test_default_signal_termination(void) {
656 status("Testing default signal termination status...");
657
658 pid_t child = fork();
659 if (child < 0)
660 fail();
661 if (!child) {
662 if (signal(SIGUSR1, SIG_DFL) == SIG_ERR || kill(getpid(), SIGUSR1))
663 _exit(126);
664 _exit(127);
665 }
666
667 int statusCode = 0;
668 if (waitpid(child, &statusCode, 0) != child || !WIFSIGNALED(statusCode) ||
669 WTERMSIG(statusCode) != SIGUSR1)
670 fail();
671
672 status("OK");
673}
674
675static void test_sigchld_wait_status(void) {
676 status("Testing SIGCHLD wait status publication...");
677
678 sigset_t sigchldSet;
679 sigset_t originalMask;
680 if (sigemptyset(&sigchldSet) || sigaddset(&sigchldSet, SIGCHLD) ||
681 sigprocmask(SIG_BLOCK, &sigchldSet, &originalMask))
682 fail();
683
684 struct sigaction action = {0};
685 struct sigaction previousAction = {0};
686 action.sa_handler = handleSigchld;
687 if (sigemptyset(&action.sa_mask) || sigaction(SIGCHLD, &action, &previousAction)) {
688 sigprocmask(SIG_SETMASK, &originalMask, 0);
689 fail();
690 }
691
692 int gate[2];
693 if (pipe(gate)) {
694 sigaction(SIGCHLD, &previousAction, 0);
695 sigprocmask(SIG_SETMASK, &originalMask, 0);
696 fail();
697 }
698
699 sigchldExpectedChild = -1;
700 sigchldHandlerCalls = 0;
701 sigchldHandlerSignal = 0;
702 sigchldWaitResult = -1;
703 sigchldWaitStatus = 0;
704
705 pid_t child = fork();
706 if (!child) {
707 close(gate[1]);
708 char token = 0;
709 ssize_t received;
710 do {
711 received = read(gate[0], &token, sizeof(token));
712 } while (received < 0 && errno == EINTR);
713 close(gate[0]);
714 _exit(received == sizeof(token) && token == 'x' ? 37 : 125);
715 }
716
717 int failed = child < 0;
718 close(gate[0]);
719 sigchldExpectedChild = child;
720
721 sigset_t deliveryMask = originalMask;
722 if (sigdelset(&deliveryMask, SIGCHLD) || sigprocmask(SIG_SETMASK, &deliveryMask, 0))
723 failed = 1;
724
725 if (child > 0) {
726 const char token = 'x';
727 ssize_t written;
728 do {
729 written = write(gate[1], &token, sizeof(token));
730 } while (written < 0 && errno == EINTR);
731 if (written != sizeof(token))
732 failed = 1;
733 }
734 close(gate[1]);
735
736 if (child > 0) {
737 for (size_t attempt = 0; attempt < 1000 && !sigchldHandlerCalls; ++attempt)
738 sched_yield();
739 }
740
741 if (sigprocmask(SIG_BLOCK, &sigchldSet, 0))
742 failed = 1;
743
744 int handlerStatus = (int)sigchldWaitStatus;
745 if (child <= 0 || sigchldHandlerCalls != 1 || sigchldHandlerSignal != SIGCHLD ||
746 (pid_t)sigchldWaitResult != child || !WIFEXITED(handlerStatus) ||
747 WEXITSTATUS(handlerStatus) != 37) {
748 failed = 1;
749 if (child > 0 && (pid_t)sigchldWaitResult != child) {
750 int rescueStatus = 0;
751 pid_t rescued = 0;
752 for (size_t attempt = 0; attempt < 1000 && !rescued; ++attempt) {
753 errno = 0;
754 rescued = waitpid(child, &rescueStatus, WNOHANG);
755 if (rescued < 0 && errno == EINTR)
756 rescued = 0;
757 if (!rescued)
758 sched_yield();
759 }
760 if (!rescued) {
761 do {
762 rescued = waitpid(child, &rescueStatus, 0);
763 } while (rescued < 0 && errno == EINTR);
764 }
765 if (rescued != child && (rescued >= 0 || errno != ECHILD))
766 failed = 1;
767 }
768 }
769
770 struct sigaction ignoredAction = {0};
771 ignoredAction.sa_handler = SIG_IGN;
772 if (sigemptyset(&ignoredAction.sa_mask) || sigaction(SIGCHLD, &ignoredAction, 0))
773 failed = 1;
774 sigchldExpectedChild = -1;
775 if (sigaction(SIGCHLD, &previousAction, 0))
776 failed = 1;
777 if (sigprocmask(SIG_SETMASK, &originalMask, 0))
778 failed = 1;
779
780 if (failed)
781 fail();
782 status("OK");
783}
784
785int process_exec_signal_child(void) {
786 struct sigaction action = {0};
787 if (sigaction(SIGUSR1, 0, &action) || action.sa_handler != SIG_DFL ||
788 sigaction(SIGTERM, 0, &action) || action.sa_handler != SIG_DFL ||
789 sigaction(SIGUSR2, 0, &action) || action.sa_handler != SIG_IGN ||
790 (action.sa_flags & SA_RESTART) || sigismember(&action.sa_mask, SIGCHLD) != 0)
791 return 125;
792
793 stack_t alternate = {0};
794 if (sigaltstack(0, &alternate) || !(alternate.ss_flags & SS_DISABLE))
795 return 126;
796
797 sigset_t currentMask;
798 if (sigprocmask(SIG_SETMASK, 0, &currentMask) || sigismember(&currentMask, SIGUSR1) != 1 ||
799 sigismember(&currentMask, SIGTERM) != 1)
800 return 127;
801
802 sigset_t terminate;
803 if (sigemptyset(&terminate) || sigaddset(&terminate, SIGTERM) ||
804 sigprocmask(SIG_UNBLOCK, &terminate, 0))
805 return 128;
806
807 for (size_t attempt = 0; attempt < 1000; ++attempt)
808 sched_yield();
809 return 129;
810}
811
812static void test_exec_signal_state(const char* program) {
813 status("Testing exec signal state replacement...");
814
815 pid_t child = fork();
816 if (child < 0)
817 fail();
818 if (!child) {
819 char alternateMemory[SIGSTKSZ];
820 stack_t alternate = {0};
821 alternate.ss_sp = alternateMemory;
822 alternate.ss_size = sizeof(alternateMemory);
823 if (sigaltstack(&alternate, 0))
824 _exit(120);
825
826 struct sigaction ignored = {0};
827 ignored.sa_handler = SIG_IGN;
828 ignored.sa_flags = SA_RESTART;
829 if (sigemptyset(&ignored.sa_mask) || sigaddset(&ignored.sa_mask, SIGCHLD) ||
830 sigaction(SIGUSR2, &ignored, 0))
831 _exit(121);
832
833 struct sigaction pending = {0};
834 pending.sa_handler = handleSignal;
835 if (sigemptyset(&pending.sa_mask) || sigaction(SIGTERM, &pending, 0))
836 _exit(122);
837
838 struct sigaction invoke = {0};
839 invoke.sa_handler = handleExecSignal;
840 invoke.sa_flags = SA_ONSTACK;
841 if (sigemptyset(&invoke.sa_mask) || sigaddset(&invoke.sa_mask, SIGTERM) ||
842 sigaction(SIGUSR1, &invoke, 0))
843 _exit(123);
844
845 execSignalProgram = program;
846 if (raise(SIGUSR1))
847 _exit(124);
848 _exit(125);
849 }
850
851 int statusCode = 0;
852 if (waitpid(child, &statusCode, 0) != child || !WIFSIGNALED(statusCode) ||
853 WTERMSIG(statusCode) != SIGTERM)
854 fail();
855
856 status("OK");
857}
858
859static int write_exec_image(const char* path, const unsigned char* image, size_t imageSize) {
860 int fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0700);
861 if (fd < 0)
862 return -1;
863
864 size_t written = 0;
865 while (written < imageSize) {
866 ssize_t result = write(fd, image + written, imageSize - written);
867 if (result < 0 && errno == EINTR)
868 continue;
869 if (result <= 0) {
870 close(fd);
871 unlink(path);
872 return -1;
873 }
874 written += result;
875 }
876
877 if (close(fd) || chmod(path, 0700)) {
878 unlink(path);
879 return -1;
880 }
881 return 0;
882}
883
884static int write_exec_fixture(const char* path, uintptr_t loadAddress, const char* interpreter,
885 int duplicateInterpreter) {
886 unsigned char image[512] = {0};
887 Elf64_Ehdr header = {0};
888 Elf64_Phdr programHeaders[3] = {0};
889 const size_t programHeaderCount = 1 + (interpreter ? 1 : 0) + duplicateInterpreter;
890 const size_t interpreterOffset = sizeof(header) + sizeof(programHeaders);
891 const size_t interpreterSize = interpreter ? strlen(interpreter) + 1 : 0;
892
893 if (interpreterSize > sizeof(image) - interpreterOffset)
894 return -1;
895
896 memcpy(header.e_ident, ELFMAG, SELFMAG);
897 header.e_ident[EI_CLASS] = ELFCLASS64;
898 header.e_ident[EI_DATA] = ELFDATA2LSB;
899 header.e_ident[EI_VERSION] = EV_CURRENT;
900 header.e_type = ET_EXEC;
901 header.e_machine = EM_X86_64;
902 header.e_version = EV_CURRENT;
903 header.e_entry = loadAddress;
904 header.e_phoff = sizeof(header);
905 header.e_ehsize = sizeof(header);
906 header.e_phentsize = sizeof(programHeaders[0]);
907 header.e_phnum = programHeaderCount;
908
909 programHeaders[0].p_type = PT_LOAD;
910 programHeaders[0].p_flags = PF_R | PF_X;
911 programHeaders[0].p_offset = 0;
912 programHeaders[0].p_vaddr = loadAddress;
913 programHeaders[0].p_paddr = programHeaders[0].p_vaddr;
914 programHeaders[0].p_filesz = sizeof(image);
915 programHeaders[0].p_memsz = sizeof(image);
916 programHeaders[0].p_align = 4096;
917
918 if (interpreter) {
919 programHeaders[1].p_type = PT_INTERP;
920 programHeaders[1].p_flags = PF_R;
921 programHeaders[1].p_offset = interpreterOffset;
922 programHeaders[1].p_filesz = interpreterSize;
923 programHeaders[1].p_memsz = interpreterSize;
924 programHeaders[1].p_align = 1;
925
926 if (duplicateInterpreter)
927 programHeaders[2] = programHeaders[1];
928 }
929
930 memcpy(image, &header, sizeof(header));
931 memcpy(image + sizeof(header), programHeaders, programHeaderCount * sizeof(programHeaders[0]));
932 if (interpreter)
933 memcpy(image + interpreterOffset, interpreter, interpreterSize);
934
935 return write_exec_image(path, image, sizeof(image));
936}
937
938static int write_partial_bss_fixture(const char* path) {
939 static const size_t imageSize = 4096;
940 static const size_t fileSize = 0x200;
941 static const size_t memorySize = 0x300;
942 static const size_t entryOffset = 0x180;
943 static const size_t probeOffset = 0x380;
944 static const uintptr_t loadAddress = 0x400000;
945 static const unsigned char code[] = {
946 0x0f, 0xb6, 0x3d, 0xf9, 0x01, 0x00, 0x00, // movzbl probe(%rip), %edi
947 0xb8, 0xe7, 0x00, 0x00, 0x00, // mov $231, %eax
948 0x0f, 0x05, // syscall
949 };
950
951 unsigned char image[imageSize];
952 memset(image, 0, sizeof(image));
953 Elf64_Ehdr* header = (Elf64_Ehdr*)image;
954 Elf64_Phdr* programHeader = (Elf64_Phdr*)(image + sizeof(*header));
955
956 memcpy(header->e_ident, ELFMAG, SELFMAG);
957 header->e_ident[EI_CLASS] = ELFCLASS64;
958 header->e_ident[EI_DATA] = ELFDATA2LSB;
959 header->e_ident[EI_VERSION] = EV_CURRENT;
960 header->e_type = ET_EXEC;
961 header->e_machine = EM_X86_64;
962 header->e_version = EV_CURRENT;
963 header->e_entry = loadAddress + entryOffset;
964 header->e_phoff = sizeof(*header);
965 header->e_ehsize = sizeof(*header);
966 header->e_phentsize = sizeof(*programHeader);
967 header->e_phnum = 1;
968
969 programHeader->p_type = PT_LOAD;
970 programHeader->p_flags = PF_R | PF_X;
971 programHeader->p_offset = 0;
972 programHeader->p_vaddr = loadAddress;
973 programHeader->p_paddr = loadAddress;
974 programHeader->p_filesz = fileSize;
975 programHeader->p_memsz = memorySize;
976 programHeader->p_align = 4096;
977
978 memcpy(image + entryOffset, code, sizeof(code));
979 image[probeOffset] = 73;
980 return write_exec_image(path, image, sizeof(image));
981}
982
983static void expect_exec_failure(const char* path, int expectedErrno, int failureBase) {
984 pid_t child = fork();
985 if (child < 0)
986 fail();
987 if (!child) {
988 char* const arguments[] = {(char*)path, 0};
989 errno = 0;
990 if (execv(path, arguments) != -1 || errno != expectedErrno)
991 _exit(failureBase);
992
993 struct sigaction action = {0};
994 action.sa_handler = handleSignal;
995 signalHandled = 0;
996 if (sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, 0) || raise(SIGUSR1) ||
997 !signalHandled)
998 _exit(failureBase + 1);
999 _exit(0);
1000 }
1001
1002 int statusCode = 0;
1003 if (waitpid(child, &statusCode, 0) != child || !WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
1004 fail();
1005}
1006
1007static void test_exec_partial_page_bss(void) {
1008 status("Testing exec partial-page BSS zeroing...");
1009
1010 char path[PATH_MAX];
1011 snprintf(path, sizeof(path), "/tmp/exec-partial-bss-%d", getpid());
1012 if (write_partial_bss_fixture(path))
1013 fail();
1014
1015 pid_t child = fork();
1016 if (child < 0)
1017 fail();
1018 if (!child) {
1019 char* const arguments[] = {path, 0};
1020 execv(path, arguments);
1021 _exit(120);
1022 }
1023
1024 int statusCode = 0;
1025 if (waitpid(child, &statusCode, 0) != child || unlink(path) || !WIFEXITED(statusCode) ||
1026 WEXITSTATUS(statusCode))
1027 fail();
1028
1029 status("OK");
1030}
1031
1032static void test_exec_failure_boundary(void) {
1033 status("Testing exec failure boundary...");
1034
1035 char missingInterpreter[PATH_MAX];
1036 char invalidLoad[PATH_MAX];
1037 char malformedInterpreter[PATH_MAX];
1038 char malformedInterpreterTarget[PATH_MAX];
1039 char duplicateInterpreter[PATH_MAX];
1040 char missingInterpreterTarget[PATH_MAX];
1041 snprintf(missingInterpreter, sizeof(missingInterpreter), "/tmp/exec-missing-interpreter-%d",
1042 getpid());
1043 snprintf(invalidLoad, sizeof(invalidLoad), "/tmp/exec-invalid-load-%d", getpid());
1044 snprintf(malformedInterpreter, sizeof(malformedInterpreter), "/tmp/exec-malformed-interp-%d",
1045 getpid());
1046 snprintf(malformedInterpreterTarget, sizeof(malformedInterpreterTarget),
1047 "/tmp/exec-malformed-interp-target-%d", getpid());
1048 snprintf(duplicateInterpreter, sizeof(duplicateInterpreter), "/tmp/exec-duplicate-interp-%d",
1049 getpid());
1050 snprintf(missingInterpreterTarget, sizeof(missingInterpreterTarget),
1051 "/tmp/exec-missing-interp-target-%d", getpid());
1052
1053 unlink(missingInterpreterTarget);
1054 if (write_exec_fixture(missingInterpreter, 0x400000, missingInterpreterTarget, 0))
1055 fail();
1056 expect_exec_failure(missingInterpreter, ENOENT, 120);
1057 if (unlink(missingInterpreter))
1058 fail();
1059
1060 if (write_exec_fixture(invalidLoad, 0, 0, 0))
1061 fail();
1062 expect_exec_failure(invalidLoad, ENOEXEC, 122);
1063 if (unlink(invalidLoad))
1064 fail();
1065
1066 static const char malformedContents[] = "not an ELF interpreter";
1067 int malformedFd = open(malformedInterpreterTarget, O_CREAT | O_TRUNC | O_WRONLY, 0700);
1068 if (malformedFd < 0)
1069 fail();
1070 if (write(malformedFd, malformedContents, sizeof(malformedContents)) !=
1071 sizeof(malformedContents) ||
1072 close(malformedFd))
1073 fail();
1074 if (chmod(malformedInterpreterTarget, 0700) ||
1075 write_exec_fixture(malformedInterpreter, 0x400000, malformedInterpreterTarget, 0))
1076 fail();
1077 expect_exec_failure(malformedInterpreter, ELIBBAD, 124);
1078 if (unlink(malformedInterpreter) || unlink(malformedInterpreterTarget))
1079 fail();
1080
1081 if (write_exec_fixture(duplicateInterpreter, 0x400000, missingInterpreterTarget, 1))
1082 fail();
1083 expect_exec_failure(duplicateInterpreter, EINVAL, 126);
1084 if (unlink(duplicateInterpreter))
1085 fail();
1086
1087 status("OK");
1088}
1089
1090static void test_wait_stop_continue(void) {
1091 status("Testing stopped and continued wait status...");
1092
1093 int gate[2];
1094 int signalReport[2];
1095 if (pipe(gate) || pipe(signalReport))
1096 fail();
1097
1098 pid_t child = fork();
1099 if (child < 0)
1100 fail();
1101 if (!child) {
1102 close(gate[1]);
1103 close(signalReport[0]);
1104 signalReportFd = signalReport[1];
1105 signalHandled = 0;
1106 struct sigaction action = {0};
1107 action.sa_handler = handleSignal;
1108 if (sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, 0) || raise(SIGSTOP))
1109 _exit(125);
1110
1111 signalReportFd = -1;
1112 close(signalReport[1]);
1113 if (!signalHandled)
1114 _exit(124);
1115
1116 char token;
1117 if (read(gate[0], &token, sizeof(token)) != sizeof(token))
1118 _exit(126);
1119 close(gate[0]);
1120 _exit(0);
1121 }
1122
1123 close(gate[0]);
1124 close(signalReport[1]);
1125 int reportFlags = fcntl(signalReport[0], F_GETFL);
1126 if (reportFlags < 0 || fcntl(signalReport[0], F_SETFL, reportFlags | O_NONBLOCK) < 0)
1127 fail();
1128 int statusCode = 0;
1129 if (waitpid(child, &statusCode, WUNTRACED) != child || !WIFSTOPPED(statusCode) ||
1130 WSTOPSIG(statusCode) != SIGSTOP)
1131 fail();
1132
1133 if (kill(child, SIGUSR1))
1134 fail();
1135 char signalToken = 0;
1136 for (size_t attempt = 0; attempt < 1000; ++attempt) {
1137 errno = 0;
1138 ssize_t received = read(signalReport[0], &signalToken, sizeof(signalToken));
1139 if (received >= 0 || (errno != EAGAIN && errno != EWOULDBLOCK))
1140 fail();
1141 sched_yield();
1142 }
1143
1144 if (kill(child, SIGCONT) || waitpid(child, &statusCode, WCONTINUED) != child ||
1145 !WIFCONTINUED(statusCode))
1146 fail();
1147
1148 ssize_t received = -1;
1149 for (size_t attempt = 0; attempt < 1000 && received < 0; ++attempt) {
1150 errno = 0;
1151 received = read(signalReport[0], &signalToken, sizeof(signalToken));
1152 if (received < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
1153 fail();
1154 sched_yield();
1155 }
1156 if (received != sizeof(signalToken) || signalToken != 's' || close(signalReport[0]))
1157 fail();
1158
1159 const char token = 'x';
1160 if (write(gate[1], &token, sizeof(token)) != sizeof(token) || close(gate[1]))
1161 fail();
1162 if (waitpid(child, &statusCode, 0) != child || !WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
1163 fail();
1164
1165 status("OK");
1166}
1167
1168static int wait_for_process_stop_gate_quiet(int heartbeatFd) {
1169 size_t quietYields = 0;
1170 for (size_t attempt = 0; attempt < processStopGateAttempts; ++attempt) {
1171 char heartbeats[128];
1172 errno = 0;
1173 ssize_t received = read(heartbeatFd, heartbeats, sizeof(heartbeats));
1174 if (received > 0) {
1175 quietYields = 0;
1176 } else if (received < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
1177 if (++quietYields == processStopGateQuietYields)
1178 return 0;
1179 } else if (received < 0 && errno == EINTR) {
1180 continue;
1181 } else {
1182 return -1;
1183 }
1184 sched_yield();
1185 }
1186 return -1;
1187}
1188
1189static int wait_for_process_stop_gate_heartbeat(int heartbeatFd) {
1190 for (size_t attempt = 0; attempt < processStopGateAttempts; ++attempt) {
1191 char heartbeat;
1192 errno = 0;
1193 ssize_t received = read(heartbeatFd, &heartbeat, sizeof(heartbeat));
1194 if (received == sizeof(heartbeat))
1195 return 0;
1196 if (received < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
1197 return -1;
1198 if (!received)
1199 return -1;
1200 sched_yield();
1201 }
1202 return -1;
1203}
1204
1205static void test_multithreaded_process_stop_gate(void) {
1206 status("Testing multithreaded process stop gate...");
1207
1208 int heartbeat[2];
1209 int release[2];
1210 if (pipe(heartbeat) || pipe(release))
1211 fail();
1212
1213 pid_t child = fork();
1214 if (child < 0)
1215 fail();
1216 if (!child) {
1217 close(heartbeat[0]);
1218 close(release[1]);
1219
1220 int flags = fcntl(heartbeat[1], F_GETFL);
1221 if (flags < 0 || fcntl(heartbeat[1], F_SETFL, flags | O_NONBLOCK) < 0)
1222 _exit(120);
1223
1224 struct processStopGateProbe probe = {
1225 .heartbeatFd = heartbeat[1],
1226 .ready = {0, 0},
1227 .stop = 0,
1228 .failure = 0,
1229 };
1230 struct processStopGateWorker workers[2] = {
1231 {&probe, 0},
1232 {&probe, 1},
1233 };
1234 pthread_t threads[2];
1235 if (pthread_create(&threads[0], 0, run_process_stop_gate_worker, &workers[0]))
1236 _exit(121);
1237 if (pthread_create(&threads[1], 0, run_process_stop_gate_worker, &workers[1])) {
1238 __atomic_store_n(&probe.stop, 1, __ATOMIC_RELEASE);
1239 pthread_join(threads[0], 0);
1240 _exit(122);
1241 }
1242
1243 size_t attempt = 0;
1244 while ((!__atomic_load_n(&probe.ready[0], __ATOMIC_ACQUIRE) ||
1245 !__atomic_load_n(&probe.ready[1], __ATOMIC_ACQUIRE)) &&
1246 attempt++ < processStopGateAttempts) {
1247 sched_yield();
1248 }
1249 if (!__atomic_load_n(&probe.ready[0], __ATOMIC_ACQUIRE) ||
1250 !__atomic_load_n(&probe.ready[1], __ATOMIC_ACQUIRE) || raise(SIGSTOP)) {
1251 __atomic_store_n(&probe.stop, 1, __ATOMIC_RELEASE);
1252 pthread_join(threads[0], 0);
1253 pthread_join(threads[1], 0);
1254 _exit(123);
1255 }
1256
1257 char token = 0;
1258 ssize_t received;
1259 do {
1260 received = read(release[0], &token, sizeof(token));
1261 } while (received < 0 && errno == EINTR);
1262 __atomic_store_n(&probe.stop, 1, __ATOMIC_RELEASE);
1263 const int firstJoin = pthread_join(threads[0], 0);
1264 const int secondJoin = pthread_join(threads[1], 0);
1265 close(release[0]);
1266 close(heartbeat[1]);
1267 _exit(received == sizeof(token) && token == 'x' && !firstJoin && !secondJoin &&
1268 !__atomic_load_n(&probe.failure, __ATOMIC_ACQUIRE)
1269 ? 0
1270 : 124);
1271 }
1272
1273 close(heartbeat[1]);
1274 close(release[0]);
1275 int failed = 0;
1276 int flags = fcntl(heartbeat[0], F_GETFL);
1277 if (flags < 0 || fcntl(heartbeat[0], F_SETFL, flags | O_NONBLOCK) < 0)
1278 failed = 1;
1279
1280 int statusCode = 0;
1281 pid_t waited = waitpid_bounded(child, &statusCode, WUNTRACED);
1282 if (waited != child || !WIFSTOPPED(statusCode) || WSTOPSIG(statusCode) != SIGSTOP)
1283 failed = 1;
1284
1285 if (!failed && wait_for_process_stop_gate_quiet(heartbeat[0]))
1286 failed = 1;
1287
1288 if (!failed &&
1289 (kill(child, SIGCONT) || waitpid_bounded(child, &statusCode, WCONTINUED) != child ||
1290 !WIFCONTINUED(statusCode) || wait_for_process_stop_gate_heartbeat(heartbeat[0]))) {
1291 failed = 1;
1292 }
1293
1294 const char token = 'x';
1295 if (!failed && write(release[1], &token, sizeof(token)) != sizeof(token))
1296 failed = 1;
1297 close(release[1]);
1298
1299 waited = waitpid_bounded(child, &statusCode, 0);
1300 if (waited != child || !WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
1301 failed = 1;
1302
1303 if (waited != child) {
1304 (void)kill(child, SIGCONT);
1305 (void)kill(child, SIGKILL);
1306 (void)waitpid_bounded(child, &statusCode, 0);
1307 }
1308 close(heartbeat[0]);
1309
1310 if (failed)
1311 fail();
1312 status("OK");
1313}
1314
1315static void test_stopped_process_sigkill(void) {
1316 status("Testing SIGKILL of stopped process...");
1317
1318 pid_t child = fork();
1319 if (child < 0)
1320 fail();
1321 if (!child) {
1322 if (raise(SIGSTOP))
1323 _exit(125);
1324 _exit(126);
1325 }
1326
1327 int failed = 0;
1328 int statusCode = 0;
1329 pid_t waited = waitpid_bounded(child, &statusCode, WUNTRACED);
1330 if (waited != child || !WIFSTOPPED(statusCode) || WSTOPSIG(statusCode) != SIGSTOP)
1331 failed = 1;
1332
1333 if (!failed && kill(child, SIGKILL))
1334 failed = 1;
1335
1336 waited = waitpid_bounded(child, &statusCode, 0);
1337 if (waited != child || !WIFSIGNALED(statusCode) || WTERMSIG(statusCode) != SIGKILL)
1338 failed = 1;
1339
1340 if (waited != child) {
1341 (void)kill(child, SIGKILL);
1342 (void)kill(child, SIGCONT);
1343 (void)waitpid_bounded(child, &statusCode, 0);
1344 }
1345
1346 if (failed)
1347 fail();
1348 status("OK");
1349}
1350
1351static void test_thread_signal_syscalls(void) {
1352 status("Testing thread-directed signal syscalls...");
1353
1354 struct sigaction action = {0};
1355 struct sigaction previousAction = {0};
1356 action.sa_handler = handleSignal;
1357 if (sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, &previousAction))
1358 fail();
1359
1360 long tid = syscall(SYS_gettid);
1361 if (tid <= 0 || syscall(SYS_tkill, tid, 0) || syscall(SYS_tgkill, getpid(), tid, 0))
1362 fail();
1363
1364 signalHandled = 0;
1365 if (syscall(SYS_tkill, tid, SIGUSR1))
1366 fail();
1367 for (size_t attempt = 0; attempt < 1000 && !signalHandled; ++attempt)
1368 sched_yield();
1369 if (!signalHandled)
1370 fail();
1371
1372 signalHandled = 0;
1373 if (syscall(SYS_tgkill, getpid(), tid, SIGUSR1))
1374 fail();
1375 for (size_t attempt = 0; attempt < 1000 && !signalHandled; ++attempt)
1376 sched_yield();
1377 if (!signalHandled)
1378 fail();
1379
1380 signalHandled = 0;
1381 if (raise(SIGUSR1) || !signalHandled)
1382 fail();
1383
1384 errno = 0;
1385 if (syscall(SYS_tkill, 0, 0) != -1 || errno != EINVAL)
1386 fail();
1387 errno = 0;
1388 if (syscall(SYS_tgkill, 0, tid, 0) != -1 || errno != EINVAL)
1389 fail();
1390 errno = 0;
1391 if (syscall(SYS_tgkill, getpid(), 0, 0) != -1 || errno != EINVAL)
1392 fail();
1393 errno = 0;
1394 if (syscall(SYS_tkill, INT_MAX, 35) != -1 || errno != ESRCH)
1395 fail();
1396 errno = 0;
1397 if (syscall(SYS_tkill, tid, 35) != -1 || errno != EINVAL)
1398 fail();
1399 errno = 0;
1400 if (syscall(SYS_tgkill, getpid(), INT_MAX, 35) != -1 || errno != ESRCH)
1401 fail();
1402 errno = 0;
1403 if (syscall(SYS_tgkill, INT_MAX, tid, 35) != -1 || errno != ESRCH)
1404 fail();
1405
1406 if (sigaction(SIGUSR1, &previousAction, 0))
1407 fail();
1408 status("OK");
1409}
1410
1411static void test_sigsuspend(void) {
1412 status("Testing sigsuspend temporary mask...");
1413
1414 struct sigaction action = {0};
1415 struct sigaction previousAction = {0};
1416 action.sa_handler = handleSignal;
1417 if (sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, &previousAction))
1418 fail();
1419
1420 sigset_t blocked;
1421 sigset_t original;
1422 sigset_t temporary;
1423 if (sigemptyset(&blocked) || sigaddset(&blocked, SIGUSR1) ||
1424 sigprocmask(SIG_BLOCK, &blocked, &original))
1425 fail();
1426 temporary = original;
1427 if (sigdelset(&temporary, SIGUSR1))
1428 fail();
1429
1430 signalHandled = 0;
1431 if (kill(getpid(), SIGUSR1))
1432 fail();
1433 errno = 0;
1434 const int result = sigsuspend(&temporary);
1435 const int suspendError = errno;
1436
1437 sigset_t restored;
1438 if (sigprocmask(SIG_SETMASK, 0, &restored) || sigprocmask(SIG_SETMASK, &original, 0) ||
1439 sigaction(SIGUSR1, &previousAction, 0))
1440 fail();
1441 if (result != -1 || suspendError != EINTR || !signalHandled ||
1442 sigismember(&restored, SIGUSR1) != 1)
1443 fail();
1444
1445 status("OK");
1446}
1447
1448void test_process_stop_contracts(void) {
1449 test_wait_stop_continue();
1450 test_multithreaded_process_stop_gate();
1451 test_stopped_process_sigkill();
1452}
1453
1454void test_process(const char* program) {
1455 printf("Testing process compatibility...\n");
1456 test_proc_self_fd();
1457 test_proc_kernel_threads();
1458 test_vfork();
1459 test_posix_spawn(program);
1460 test_resource_compatibility();
1461 test_futex_requeue();
1462 test_cond_broadcast();
1463 test_signal_return();
1464 test_linux_signal_frame();
1465 test_default_signal_termination();
1466 test_sigchld_wait_status();
1467 test_exec_signal_state(program);
1468 test_exec_partial_page_bss();
1469 test_exec_failure_boundary();
1470 test_process_stop_contracts();
1471 test_thread_signal_syscalls();
1472 test_sigsuspend();
1473}