23#include <sys/resource.h>
25#include <sys/syscall.h>
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;
44enum { signalFrameResumeValue = 0x13579BDF };
47 processStopGateAttempts = 20000,
48 processStopGateQuietYields = 512,
49 futexRequeueAttempts = 100000,
54 futexRequeueWaiters = 3,
55 condBroadcastWaiters = 4,
60 volatile int ready[2];
79 pthread_mutex_t mutex;
80 pthread_cond_t condition;
86static void handleSignal(
int signalNumber) {
87 if (signalNumber == SIGUSR1) {
89 if (signalReportFd >= 0) {
90 const char token =
's';
91 (void)write(signalReportFd, &token,
sizeof(token));
96static void handleLinuxSignalFrame(
int signalNumber, siginfo_t* info,
void* contextPointer) {
97 ucontext_t* context = contextPointer;
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;
106 if (signalFrameCalls == 1 && syscall(SYS_tkill, signalFrameExpectedTid, SIGUSR1)) {
107 signalFrameValid = 0;
109 context->uc_mcontext.gregs[REG_RAX] = signalFrameResumeValue;
112static void handleExecSignal(
int signalNumber) {
113 if (signalNumber != SIGUSR1 || !execSignalProgram || kill(getpid(), SIGTERM))
116 char*
const arguments[] = {(
char*)execSignalProgram, (
char*)
"--exec-signal-child", 0};
117 execv(execSignalProgram, arguments);
121static void handleSigchld(
int signalNumber) {
122 int savedErrno = errno;
123 if (!sigchldHandlerCalls) {
125 sigchldHandlerSignal = signalNumber;
126 pid_t expectedChild = (pid_t)sigchldExpectedChild;
127 sigchldWaitResult = expectedChild > 0 ? waitpid(expectedChild, &statusCode, WNOHANG) : -1;
128 sigchldWaitStatus = statusCode;
130 ++sigchldHandlerCalls;
134static void status(
const char*
message) {
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);
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)
163static void* run_futex_requeue_waiter(
void* parameter) {
165 __atomic_add_fetch(&probe->ready, 1, __ATOMIC_RELEASE);
169 long result = syscall(SYS_futex, &probe->source, futexWait | futexPrivate, 0, 0, 0, 0);
174 __atomic_store_n(&probe->failure, 1, __ATOMIC_RELEASE);
178 __atomic_add_fetch(&probe->woke, 1, __ATOMIC_RELEASE);
182static int run_raw_futex_requeue_child(
void) {
184 pthread_t waiters[futexRequeueWaiters];
186 for (
size_t i = 0; i < futexRequeueWaiters; ++i) {
187 if (pthread_create(&waiters[i], 0, run_futex_requeue_waiter, &probe))
190 if (wait_for_atomic_value(&probe.ready, futexRequeueWaiters))
196 for (
size_t attempt = 0; attempt < futexRequeueAttempts && staged < futexRequeueWaiters;
198 long result = syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0,
199 futexRequeueWaiters - staged, &probe.destination, 0);
202 staged += (int)result;
203 if (staged < futexRequeueWaiters)
206 if (staged != futexRequeueWaiters)
209 if (syscall(SYS_futex, &probe.destination, futexRequeue | futexPrivate, 0, futexRequeueWaiters,
210 &probe.source, 0) != futexRequeueWaiters)
215 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0, futexRequeueWaiters,
216 &probe.source, 0) != futexRequeueWaiters)
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) !=
229 if (syscall(SYS_futex, (
char*)&probe.source + 1, futexRequeue | futexPrivate, 0, 0,
230 &probe.destination, 0) != -1 ||
234 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0, 0,
235 (
char*)&probe.destination + 1, 0) != -1 ||
239 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, -1, 0, &probe.destination,
244 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0, -1, &probe.destination,
249 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 0, 0, 0, 0) != -1 ||
253 if (syscall(SYS_futex, &probe.source, futexRequeue | futexPrivate, 1, 2, &probe.destination, 0) !=
256 if (syscall(SYS_futex, &probe.source, futexWake | futexPrivate, INT_MAX, 0, 0, 0) != 0)
258 if (syscall(SYS_futex, &probe.destination, futexWake | futexPrivate, INT_MAX, 0, 0, 0) != 2)
261 for (
size_t i = 0; i < futexRequeueWaiters; ++i) {
262 if (pthread_join(waiters[i], 0))
265 if (__atomic_load_n(&probe.failure, __ATOMIC_ACQUIRE) ||
266 __atomic_load_n(&probe.woke, __ATOMIC_ACQUIRE) != futexRequeueWaiters)
271static void* run_cond_broadcast_waiter(
void* parameter) {
273 if (pthread_mutex_lock(&probe->mutex))
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);
282 __atomic_add_fetch(&probe->woke, 1, __ATOMIC_RELEASE);
283 if (pthread_mutex_unlock(&probe->mutex))
288static int run_cond_broadcast_child(
void) {
290 .mutex = PTHREAD_MUTEX_INITIALIZER,
291 .condition = PTHREAD_COND_INITIALIZER,
296 pthread_t waiters[condBroadcastWaiters];
298 for (
size_t i = 0; i < condBroadcastWaiters; ++i) {
299 if (pthread_create(&waiters[i], 0, run_cond_broadcast_waiter, &probe))
302 if (wait_for_atomic_value(&probe.ready, condBroadcastWaiters) || pthread_mutex_lock(&probe.mutex))
308 for (
size_t i = 0; i < 128; ++i)
311 if (pthread_cond_broadcast(&probe.condition) || pthread_mutex_unlock(&probe.mutex))
314 for (
size_t i = 0; i < condBroadcastWaiters; ++i) {
316 if (pthread_join(waiters[i], &result) || result)
319 if (__atomic_load_n(&probe.woke, __ATOMIC_ACQUIRE) != condBroadcastWaiters ||
320 pthread_cond_destroy(&probe.condition) || pthread_mutex_destroy(&probe.mutex))
325static void run_bounded_thread_child(
int (*childTest)(
void)) {
326 pid_t child = fork();
337 waited = waitpid(child, &statusCode, 0);
338 }
while (waited < 0 && errno == EINTR);
341 if (!WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
345static void* run_process_stop_gate_worker(
void* parameter) {
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);
352 while (!__atomic_load_n(&probe->stop, __ATOMIC_ACQUIRE)) {
353 for (
size_t i = 0; i < 4096; ++i)
354 spin = (spin * 33) ^ i;
356 ssize_t written = write(probe->heartbeatFd, &token,
sizeof(token));
357 if (written ==
sizeof(token) ||
358 (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)))
360 __atomic_store_n(&probe->failure, 1, __ATOMIC_RELEASE);
367static void test_proc_self_fd(
void) {
368 status(
"Testing /proc/self/fd readlink...");
370 int fd = open(
"/dev/null", O_RDONLY);
375 snprintf(procname,
sizeof(procname),
"/proc/self/fd/%d", fd);
377 char target[PATH_MAX];
378 ssize_t length = readlink(procname, target,
sizeof(target));
379 if (length <= 0 || (
size_t)length >=
sizeof(target))
381 target[length] =
'\0';
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)
390 if (readlink(procname, truncated,
sizeof(truncated)) != (ssize_t)
sizeof(truncated) ||
391 memcmp(target, truncated,
sizeof(truncated)))
396 if (readlink(procname, target,
sizeof(target)) != -1 || errno != ENOENT)
399 if (isatty(STDIN_FILENO)) {
401 if (ttyname_r(STDIN_FILENO, tty,
sizeof(tty)))
408void test_proc_kernel_threads(
void) {
409 status(
"Testing userspace kernel-thread inspection...");
411 FILE* file = fopen(
"/proc/kernel/threads",
"r");
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")) {
418 if (fstat(fileno(file), &info) || !(info.st_mode & S_IROTH)) {
422 size_t workers = 0, idleThreads = 0;
423 while (fgets(line,
sizeof(line), file)) {
424 unsigned long processId, taskId, cpu, priority, user, system;
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]) {
438 if (ferror(file) || fclose(file) || !workers || !idleThreads) {
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)) {
449 file = fopen(
"/proc/stat",
"r");
450 int foundRunning = 0;
454 while (fgets(line,
sizeof(line), file)) {
455 if (sscanf(line,
"procs_running %lu", &running) == 1) {
456 foundRunning = running > 0;
459 if (ferror(file) || fclose(file) || !foundRunning) {
465static void test_vfork(
void) {
466 status(
"Testing vfork syscall compatibility...");
468 pid_t child = vfork();
475 if (waitpid(child, &statusCode, 0) != child || !WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
481void test_posix_spawn(
const char* program) {
482 status(
"Testing posix_spawn process isolation...");
484 const pid_t parent = getpid();
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 ||
493 if (waitpid_bounded(child, &statusCode, 0) != child || !WIFEXITED(statusCode) ||
494 WEXITSTATUS(statusCode) != 120 || getpid() != parent)
498 if (waitpid(child, &statusCode, WNOHANG) != -1 || errno != ECHILD)
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) !=
509 if (waitpid(-1, &statusCode, WNOHANG) != -1 || errno != ECHILD)
515static void test_resource_compatibility(
void) {
516 status(
"Testing Linux resource compatibility syscalls...");
518 struct rlimit limit = {0};
519 if (getrlimit(RLIMIT_CPU, &limit) || limit.rlim_cur != RLIM_INFINITY ||
520 limit.rlim_max != RLIM_INFINITY)
523 if (getrlimit(RLIMIT_RTPRIO, &limit) || limit.rlim_cur != 0 || limit.rlim_max != 0)
526 if (getrlimit(RLIMIT_RTTIME, &limit) || limit.rlim_cur != RLIM_INFINITY ||
527 limit.rlim_max != RLIM_INFINITY)
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)
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)
540 if (syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, 0, 0))
544 if (syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, 0, (
void*)UINTPTR_MAX) != -1 || errno != EFAULT)
548 if (syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, (
void*)UINTPTR_MAX, 0) != -1 || errno != ENOSYS)
552 if (syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, &limit, 0) != -1 || errno != ENOSYS)
556 if (syscall(SYS_prlimit64, -1, RLIMIT_NOFILE, 0, &limit) != -1 || errno != ESRCH)
560 if (syscall(SYS_prlimit64, 0, -1, 0, &limit) != -1 || errno != EINVAL)
564 if (syscall(SYS_setrlimit, RLIMIT_NOFILE, (
void*)UINTPTR_MAX) != -1 || errno != ENOSYS)
568 if (syscall(SYS_setrlimit, RLIMIT_NOFILE, &limit) != -1 || errno != ENOSYS)
571 memset(&limit, 0,
sizeof(limit));
572 if (syscall(SYS_getrlimit, RLIMIT_NOFILE, &limit) || limit.rlim_cur != 16384 ||
573 limit.rlim_max != 16384)
576 struct rlimit saved_memlock;
577 if (getrlimit(RLIMIT_MEMLOCK, &saved_memlock))
579 limit = saved_memlock;
581 if (setrlimit(RLIMIT_MEMLOCK, &limit) || getrlimit(RLIMIT_MEMLOCK, &limit) ||
582 limit.rlim_cur != 0 || limit.rlim_max != saved_memlock.rlim_max)
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)
589 if (syscall(SYS_membarrier, 0, 0, 0) != 0)
593 if (syscall(SYS_membarrier, 1, 0, 0) != -1 || errno != EINVAL)
597 if (syscall(SYS_membarrier, 0, 1, 0) != -1 || errno != EINVAL)
603static void test_futex_requeue(
void) {
604 status(
"Testing raw futex requeue semantics...");
605 run_bounded_thread_child(run_raw_futex_requeue_child);
609static void test_cond_broadcast(
void) {
610 status(
"Testing pthread condition broadcast requeue...");
611 run_bounded_thread_child(run_cond_broadcast_child);
615static void test_signal_return(
void) {
616 status(
"Testing signal handler return...");
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)
627void test_linux_signal_frame(
void) {
628 status(
"Testing Linux amd64 asynchronous signal frame...");
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))
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;
647 if (!signalFrameValid || signalFrameCalls != 2 || resumed != signalFrameResumeValue)
649 if (sigaction(SIGUSR1, &previousAction, 0))
655static void test_default_signal_termination(
void) {
656 status(
"Testing default signal termination status...");
658 pid_t child = fork();
662 if (signal(SIGUSR1, SIG_DFL) == SIG_ERR || kill(getpid(), SIGUSR1))
668 if (waitpid(child, &statusCode, 0) != child || !WIFSIGNALED(statusCode) ||
669 WTERMSIG(statusCode) != SIGUSR1)
675static void test_sigchld_wait_status(
void) {
676 status(
"Testing SIGCHLD wait status publication...");
679 sigset_t originalMask;
680 if (sigemptyset(&sigchldSet) || sigaddset(&sigchldSet, SIGCHLD) ||
681 sigprocmask(SIG_BLOCK, &sigchldSet, &originalMask))
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);
694 sigaction(SIGCHLD, &previousAction, 0);
695 sigprocmask(SIG_SETMASK, &originalMask, 0);
699 sigchldExpectedChild = -1;
700 sigchldHandlerCalls = 0;
701 sigchldHandlerSignal = 0;
702 sigchldWaitResult = -1;
703 sigchldWaitStatus = 0;
705 pid_t child = fork();
711 received = read(gate[0], &token,
sizeof(token));
712 }
while (received < 0 && errno == EINTR);
714 _exit(received ==
sizeof(token) && token ==
'x' ? 37 : 125);
717 int failed = child < 0;
719 sigchldExpectedChild = child;
721 sigset_t deliveryMask = originalMask;
722 if (sigdelset(&deliveryMask, SIGCHLD) || sigprocmask(SIG_SETMASK, &deliveryMask, 0))
726 const char token =
'x';
729 written = write(gate[1], &token,
sizeof(token));
730 }
while (written < 0 && errno == EINTR);
731 if (written !=
sizeof(token))
737 for (
size_t attempt = 0; attempt < 1000 && !sigchldHandlerCalls; ++attempt)
741 if (sigprocmask(SIG_BLOCK, &sigchldSet, 0))
744 int handlerStatus = (int)sigchldWaitStatus;
745 if (child <= 0 || sigchldHandlerCalls != 1 || sigchldHandlerSignal != SIGCHLD ||
746 (pid_t)sigchldWaitResult != child || !WIFEXITED(handlerStatus) ||
747 WEXITSTATUS(handlerStatus) != 37) {
749 if (child > 0 && (pid_t)sigchldWaitResult != child) {
750 int rescueStatus = 0;
752 for (
size_t attempt = 0; attempt < 1000 && !rescued; ++attempt) {
754 rescued = waitpid(child, &rescueStatus, WNOHANG);
755 if (rescued < 0 && errno == EINTR)
762 rescued = waitpid(child, &rescueStatus, 0);
763 }
while (rescued < 0 && errno == EINTR);
765 if (rescued != child && (rescued >= 0 || errno != ECHILD))
770 struct sigaction ignoredAction = {0};
771 ignoredAction.sa_handler = SIG_IGN;
772 if (sigemptyset(&ignoredAction.sa_mask) || sigaction(SIGCHLD, &ignoredAction, 0))
774 sigchldExpectedChild = -1;
775 if (sigaction(SIGCHLD, &previousAction, 0))
777 if (sigprocmask(SIG_SETMASK, &originalMask, 0))
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)
793 stack_t alternate = {0};
794 if (sigaltstack(0, &alternate) || !(alternate.ss_flags & SS_DISABLE))
797 sigset_t currentMask;
798 if (sigprocmask(SIG_SETMASK, 0, ¤tMask) || sigismember(¤tMask, SIGUSR1) != 1 ||
799 sigismember(¤tMask, SIGTERM) != 1)
803 if (sigemptyset(&terminate) || sigaddset(&terminate, SIGTERM) ||
804 sigprocmask(SIG_UNBLOCK, &terminate, 0))
807 for (
size_t attempt = 0; attempt < 1000; ++attempt)
812static void test_exec_signal_state(
const char* program) {
813 status(
"Testing exec signal state replacement...");
815 pid_t child = fork();
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))
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))
833 struct sigaction pending = {0};
834 pending.sa_handler = handleSignal;
835 if (sigemptyset(&pending.sa_mask) || sigaction(SIGTERM, &pending, 0))
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))
845 execSignalProgram = program;
852 if (waitpid(child, &statusCode, 0) != child || !WIFSIGNALED(statusCode) ||
853 WTERMSIG(statusCode) != SIGTERM)
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);
865 while (written < imageSize) {
866 ssize_t result = write(fd, image + written, imageSize - written);
867 if (result < 0 && errno == EINTR)
877 if (close(fd) || chmod(path, 0700)) {
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;
893 if (interpreterSize >
sizeof(image) - interpreterOffset)
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;
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;
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;
926 if (duplicateInterpreter)
927 programHeaders[2] = programHeaders[1];
930 memcpy(image, &header,
sizeof(header));
931 memcpy(image +
sizeof(header), programHeaders, programHeaderCount *
sizeof(programHeaders[0]));
933 memcpy(image + interpreterOffset, interpreter, interpreterSize);
935 return write_exec_image(path, image,
sizeof(image));
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,
947 0xb8, 0xe7, 0x00, 0x00, 0x00,
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));
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);
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;
978 memcpy(image + entryOffset, code,
sizeof(code));
979 image[probeOffset] = 73;
980 return write_exec_image(path, image,
sizeof(image));
983static void expect_exec_failure(
const char* path,
int expectedErrno,
int failureBase) {
984 pid_t child = fork();
988 char*
const arguments[] = {(
char*)path, 0};
990 if (execv(path, arguments) != -1 || errno != expectedErrno)
993 struct sigaction action = {0};
994 action.sa_handler = handleSignal;
996 if (sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, 0) || raise(SIGUSR1) ||
998 _exit(failureBase + 1);
1003 if (waitpid(child, &statusCode, 0) != child || !WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
1007static void test_exec_partial_page_bss(
void) {
1008 status(
"Testing exec partial-page BSS zeroing...");
1010 char path[PATH_MAX];
1011 snprintf(path,
sizeof(path),
"/tmp/exec-partial-bss-%d", getpid());
1012 if (write_partial_bss_fixture(path))
1015 pid_t child = fork();
1019 char*
const arguments[] = {path, 0};
1020 execv(path, arguments);
1025 if (waitpid(child, &statusCode, 0) != child || unlink(path) || !WIFEXITED(statusCode) ||
1026 WEXITSTATUS(statusCode))
1032static void test_exec_failure_boundary(
void) {
1033 status(
"Testing exec failure boundary...");
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",
1043 snprintf(invalidLoad,
sizeof(invalidLoad),
"/tmp/exec-invalid-load-%d", getpid());
1044 snprintf(malformedInterpreter,
sizeof(malformedInterpreter),
"/tmp/exec-malformed-interp-%d",
1046 snprintf(malformedInterpreterTarget,
sizeof(malformedInterpreterTarget),
1047 "/tmp/exec-malformed-interp-target-%d", getpid());
1048 snprintf(duplicateInterpreter,
sizeof(duplicateInterpreter),
"/tmp/exec-duplicate-interp-%d",
1050 snprintf(missingInterpreterTarget,
sizeof(missingInterpreterTarget),
1051 "/tmp/exec-missing-interp-target-%d", getpid());
1053 unlink(missingInterpreterTarget);
1054 if (write_exec_fixture(missingInterpreter, 0x400000, missingInterpreterTarget, 0))
1056 expect_exec_failure(missingInterpreter, ENOENT, 120);
1057 if (unlink(missingInterpreter))
1060 if (write_exec_fixture(invalidLoad, 0, 0, 0))
1062 expect_exec_failure(invalidLoad, ENOEXEC, 122);
1063 if (unlink(invalidLoad))
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)
1070 if (write(malformedFd, malformedContents,
sizeof(malformedContents)) !=
1071 sizeof(malformedContents) ||
1074 if (chmod(malformedInterpreterTarget, 0700) ||
1075 write_exec_fixture(malformedInterpreter, 0x400000, malformedInterpreterTarget, 0))
1077 expect_exec_failure(malformedInterpreter, ELIBBAD, 124);
1078 if (unlink(malformedInterpreter) || unlink(malformedInterpreterTarget))
1081 if (write_exec_fixture(duplicateInterpreter, 0x400000, missingInterpreterTarget, 1))
1083 expect_exec_failure(duplicateInterpreter, EINVAL, 126);
1084 if (unlink(duplicateInterpreter))
1090static void test_wait_stop_continue(
void) {
1091 status(
"Testing stopped and continued wait status...");
1094 int signalReport[2];
1095 if (pipe(gate) || pipe(signalReport))
1098 pid_t child = fork();
1103 close(signalReport[0]);
1104 signalReportFd = signalReport[1];
1106 struct sigaction action = {0};
1107 action.sa_handler = handleSignal;
1108 if (sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, 0) || raise(SIGSTOP))
1111 signalReportFd = -1;
1112 close(signalReport[1]);
1117 if (read(gate[0], &token,
sizeof(token)) !=
sizeof(token))
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)
1129 if (waitpid(child, &statusCode, WUNTRACED) != child || !WIFSTOPPED(statusCode) ||
1130 WSTOPSIG(statusCode) != SIGSTOP)
1133 if (kill(child, SIGUSR1))
1135 char signalToken = 0;
1136 for (
size_t attempt = 0; attempt < 1000; ++attempt) {
1138 ssize_t received = read(signalReport[0], &signalToken,
sizeof(signalToken));
1139 if (received >= 0 || (errno != EAGAIN && errno != EWOULDBLOCK))
1144 if (kill(child, SIGCONT) || waitpid(child, &statusCode, WCONTINUED) != child ||
1145 !WIFCONTINUED(statusCode))
1148 ssize_t received = -1;
1149 for (
size_t attempt = 0; attempt < 1000 && received < 0; ++attempt) {
1151 received = read(signalReport[0], &signalToken,
sizeof(signalToken));
1152 if (received < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
1156 if (received !=
sizeof(signalToken) || signalToken !=
's' || close(signalReport[0]))
1159 const char token =
'x';
1160 if (write(gate[1], &token,
sizeof(token)) !=
sizeof(token) || close(gate[1]))
1162 if (waitpid(child, &statusCode, 0) != child || !WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
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];
1173 ssize_t received = read(heartbeatFd, heartbeats,
sizeof(heartbeats));
1176 }
else if (received < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
1177 if (++quietYields == processStopGateQuietYields)
1179 }
else if (received < 0 && errno == EINTR) {
1189static int wait_for_process_stop_gate_heartbeat(
int heartbeatFd) {
1190 for (
size_t attempt = 0; attempt < processStopGateAttempts; ++attempt) {
1193 ssize_t received = read(heartbeatFd, &heartbeat,
sizeof(heartbeat));
1194 if (received ==
sizeof(heartbeat))
1196 if (received < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
1205static void test_multithreaded_process_stop_gate(
void) {
1206 status(
"Testing multithreaded process stop gate...");
1210 if (pipe(heartbeat) || pipe(release))
1213 pid_t child = fork();
1217 close(heartbeat[0]);
1220 int flags = fcntl(heartbeat[1], F_GETFL);
1221 if (flags < 0 || fcntl(heartbeat[1], F_SETFL, flags | O_NONBLOCK) < 0)
1225 .heartbeatFd = heartbeat[1],
1234 pthread_t threads[2];
1235 if (pthread_create(&threads[0], 0, run_process_stop_gate_worker, &workers[0]))
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);
1244 while ((!__atomic_load_n(&probe.ready[0], __ATOMIC_ACQUIRE) ||
1245 !__atomic_load_n(&probe.ready[1], __ATOMIC_ACQUIRE)) &&
1246 attempt++ < processStopGateAttempts) {
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);
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);
1266 close(heartbeat[1]);
1267 _exit(received ==
sizeof(token) && token ==
'x' && !firstJoin && !secondJoin &&
1268 !__atomic_load_n(&probe.failure, __ATOMIC_ACQUIRE)
1273 close(heartbeat[1]);
1276 int flags = fcntl(heartbeat[0], F_GETFL);
1277 if (flags < 0 || fcntl(heartbeat[0], F_SETFL, flags | O_NONBLOCK) < 0)
1281 pid_t waited = waitpid_bounded(child, &statusCode, WUNTRACED);
1282 if (waited != child || !WIFSTOPPED(statusCode) || WSTOPSIG(statusCode) != SIGSTOP)
1285 if (!failed && wait_for_process_stop_gate_quiet(heartbeat[0]))
1289 (kill(child, SIGCONT) || waitpid_bounded(child, &statusCode, WCONTINUED) != child ||
1290 !WIFCONTINUED(statusCode) || wait_for_process_stop_gate_heartbeat(heartbeat[0]))) {
1294 const char token =
'x';
1295 if (!failed && write(release[1], &token,
sizeof(token)) !=
sizeof(token))
1299 waited = waitpid_bounded(child, &statusCode, 0);
1300 if (waited != child || !WIFEXITED(statusCode) || WEXITSTATUS(statusCode))
1303 if (waited != child) {
1304 (void)kill(child, SIGCONT);
1305 (void)kill(child, SIGKILL);
1306 (void)waitpid_bounded(child, &statusCode, 0);
1308 close(heartbeat[0]);
1315static void test_stopped_process_sigkill(
void) {
1316 status(
"Testing SIGKILL of stopped process...");
1318 pid_t child = fork();
1329 pid_t waited = waitpid_bounded(child, &statusCode, WUNTRACED);
1330 if (waited != child || !WIFSTOPPED(statusCode) || WSTOPSIG(statusCode) != SIGSTOP)
1333 if (!failed && kill(child, SIGKILL))
1336 waited = waitpid_bounded(child, &statusCode, 0);
1337 if (waited != child || !WIFSIGNALED(statusCode) || WTERMSIG(statusCode) != SIGKILL)
1340 if (waited != child) {
1341 (void)kill(child, SIGKILL);
1342 (void)kill(child, SIGCONT);
1343 (void)waitpid_bounded(child, &statusCode, 0);
1351static void test_thread_signal_syscalls(
void) {
1352 status(
"Testing thread-directed signal syscalls...");
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))
1360 long tid = syscall(SYS_gettid);
1361 if (tid <= 0 || syscall(SYS_tkill, tid, 0) || syscall(SYS_tgkill, getpid(), tid, 0))
1365 if (syscall(SYS_tkill, tid, SIGUSR1))
1367 for (
size_t attempt = 0; attempt < 1000 && !signalHandled; ++attempt)
1373 if (syscall(SYS_tgkill, getpid(), tid, SIGUSR1))
1375 for (
size_t attempt = 0; attempt < 1000 && !signalHandled; ++attempt)
1381 if (raise(SIGUSR1) || !signalHandled)
1385 if (syscall(SYS_tkill, 0, 0) != -1 || errno != EINVAL)
1388 if (syscall(SYS_tgkill, 0, tid, 0) != -1 || errno != EINVAL)
1391 if (syscall(SYS_tgkill, getpid(), 0, 0) != -1 || errno != EINVAL)
1394 if (syscall(SYS_tkill, INT_MAX, 35) != -1 || errno != ESRCH)
1397 if (syscall(SYS_tkill, tid, 35) != -1 || errno != EINVAL)
1400 if (syscall(SYS_tgkill, getpid(), INT_MAX, 35) != -1 || errno != ESRCH)
1403 if (syscall(SYS_tgkill, INT_MAX, tid, 35) != -1 || errno != ESRCH)
1406 if (sigaction(SIGUSR1, &previousAction, 0))
1411static void test_sigsuspend(
void) {
1412 status(
"Testing sigsuspend temporary mask...");
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))
1423 if (sigemptyset(&blocked) || sigaddset(&blocked, SIGUSR1) ||
1424 sigprocmask(SIG_BLOCK, &blocked, &original))
1426 temporary = original;
1427 if (sigdelset(&temporary, SIGUSR1))
1431 if (kill(getpid(), SIGUSR1))
1434 const int result = sigsuspend(&temporary);
1435 const int suspendError = errno;
1438 if (sigprocmask(SIG_SETMASK, 0, &restored) || sigprocmask(SIG_SETMASK, &original, 0) ||
1439 sigaction(SIGUSR1, &previousAction, 0))
1441 if (result != -1 || suspendError != EINTR || !signalHandled ||
1442 sigismember(&restored, SIGUSR1) != 1)
1448void test_process_stop_contracts(
void) {
1449 test_wait_stop_continue();
1450 test_multithreaded_process_stop_gate();
1451 test_stopped_process_sigkill();
1454void test_process(
const char* program) {
1455 printf(
"Testing process compatibility...\n");
1456 test_proc_self_fd();
1457 test_proc_kernel_threads();
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();