Loading...
--- xnu/xnu-12377.101.15/tests/exec_kill_zero_no_eperm_test.c
+++ /dev/null
@@ -1,359 +0,0 @@
-// Copyright (c) 2025 Apple Inc. All rights reserved.
-
-#include <signal.h>
-#include <unistd.h>
-#include <errno.h>
-#include <sys/wait.h>
-#include <sys/time.h>
-#include <pthread.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <spawn.h>
-#include <mach/mach.h>
-#include <mach/semaphore.h>
-
-#include <darwintest.h>
-
-T_GLOBAL_META(T_META_NAMESPACE("xnu.exec"),
- T_META_RADAR_COMPONENT_NAME("xnu"),
- T_META_RADAR_COMPONENT_VERSION("spawn"));
-
-// Test parameters
-#define KILL_ZERO_SPIN_ITERATIONS 5000
-#define KILL_ZERO_SPIN_INTERVAL_USEC 50 // 50 microseconds between kill(pid, 0) calls
-#define EXEC_PROGRAMS_COUNT 3
-
-static volatile int stop_kill_spinning = 0;
-static volatile int kill_zero_eperm_count = 0;
-static volatile int kill_zero_total_count = 0;
-static volatile int kill_zero_esrch_count = 0;
-
-// Different exec programs to test various exec scenarios
-static char *exec_programs[][3] = {
- {"/bin/sleep", "3", NULL}, // Simple sleep
- {"/usr/bin/true", NULL, NULL}, // Quick exit
- {"/bin/echo", "exec_test", NULL}, // Quick with output
-};
-
-// Thread that continuously spins on kill(pid, 0) to check PID validity
-static void *
-kill_zero_spin_thread(void *arg)
-{
- pid_t *child_pid_ptr = (pid_t *)arg;
- pid_t child_pid;
- int result;
-
- T_LOG("Starting kill(pid, 0) spin thread, waiting for pid to be set");
-
- // Wait for child_pid to become non-zero (mimics launchd behavior)
- while (!stop_kill_spinning && *child_pid_ptr == 0) {
- usleep(1); // Brief pause while waiting for spawn
- }
-
- child_pid = *child_pid_ptr;
- if (child_pid == 0) {
- T_LOG("Thread exiting - no pid was set");
- return NULL;
- }
-
- T_LOG("Got pid %d, starting kill(pid, 0) spinning", child_pid);
-
- while (!stop_kill_spinning && kill_zero_total_count < KILL_ZERO_SPIN_ITERATIONS) {
- result = kill(child_pid, 0);
- kill_zero_total_count++;
-
- if (result != 0) {
- if (errno == ESRCH) {
- // Process has exited - this is expected eventually
- kill_zero_esrch_count++;
- T_LOG("Process %d no longer exists after %d kill(0) calls (ESRCH)",
- child_pid, kill_zero_total_count);
- break;
- } else if (errno == EPERM) {
- // This should NEVER happen according to UNIX semantics
- kill_zero_eperm_count++;
- T_LOG("ERROR: EPERM on kill(pid, 0) call %d for pid %d - this violates UNIX semantics!",
- kill_zero_total_count, child_pid);
- } else {
- // Other unexpected errors - these should cause test failure
- T_FAIL("Unexpected error %d (%s) on kill(pid, 0) call %d for pid %d",
- errno, strerror(errno), kill_zero_total_count, child_pid);
- }
- }
-
- // Very short delay between checks to maximize race window
- if (KILL_ZERO_SPIN_INTERVAL_USEC > 0) {
- usleep(KILL_ZERO_SPIN_INTERVAL_USEC);
- }
- }
-
- T_LOG("kill(pid, 0) spin thread completed: %d total calls, %d EPERM errors, %d ESRCH",
- kill_zero_total_count, kill_zero_eperm_count, kill_zero_esrch_count);
- return NULL;
-}
-
-// Thread function for rapid-fire kill(pid, 0) spinning with no delay
-static void *
-rapid_fire_kill_zero_spin_thread(void *arg)
-{
- struct {
- pid_t pid;
- semaphore_t ready_sem;
- } *thread_args = arg;
-
- pid_t pid = thread_args->pid;
- int rapid_fire_calls = 0;
-
- // Signal that the thread is ready to start spinning
- kern_return_t kr = semaphore_signal(thread_args->ready_sem);
- T_ASSERT_MACH_SUCCESS(kr, "semaphore_signal");
-
- while (!stop_kill_spinning && rapid_fire_calls < 10000) {
- int result = kill(pid, 0);
- rapid_fire_calls++;
-
- if (result != 0) {
- if (errno == ESRCH) {
- T_LOG("Process %d exited after %d rapid kill(0) calls", pid, rapid_fire_calls);
- break;
- } else if (errno == EPERM) {
- kill_zero_eperm_count++;
- T_LOG("CRITICAL ERROR: EPERM on rapid kill(pid, 0) call %d", rapid_fire_calls);
- } else {
- // Other unexpected errors - these should cause test failure
- T_FAIL("Unexpected error %d (%s) on rapid kill(pid, 0) call %d for pid %d",
- errno, strerror(errno), rapid_fire_calls, pid);
- }
- }
-
- // NO delay - maximum race condition detection
- }
-
- kill_zero_total_count = rapid_fire_calls;
- T_LOG("Rapid fire spinning completed: %d calls, %d EPERM errors",
- rapid_fire_calls, kill_zero_eperm_count);
- return NULL;
-}
-
-static void
-test_kill_zero_during_spawn(char **exec_args, const char *test_desc)
-{
- T_LOG("Testing kill(pid, 0) during posix_spawn of %s", test_desc);
-
- // Reset counters for this test iteration
- stop_kill_spinning = 0;
- kill_zero_eperm_count = 0;
- kill_zero_total_count = 0;
- kill_zero_esrch_count = 0;
-
- pid_t child_pid = 0; // Initialize to 0 so thread can wait for it
-
- // Start the kill(pid, 0) spinning thread BEFORE spawning to catch even more races
- // This mimics launchd's internal behavior of spinning on the pid memory location
- pthread_t spin_thread;
- int ret = pthread_create(&spin_thread, NULL, kill_zero_spin_thread, &child_pid);
- T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
-
- T_LOG("Started kill(pid, 0) thread, now calling posix_spawn");
-
- // Use posix_spawn to create the process - this avoids fork() issues with libdarwintest
- int spawn_result = posix_spawn(&child_pid, exec_args[0], NULL, NULL, exec_args, NULL);
- T_ASSERT_POSIX_SUCCESS(spawn_result, "posix_spawn");
-
- T_LOG("Spawned process %d, thread should now detect it and start spinning", child_pid);
-
- // Let the spinning continue for a time to catch any spawn/initialization races
- usleep(100000); // 100ms should be enough for process startup
-
- // Stop the kill spinning
- stop_kill_spinning = 1;
-
- // Wait for spin thread to complete
- ret = pthread_join(spin_thread, NULL);
- T_ASSERT_POSIX_SUCCESS(ret, "pthread_join");
-
- // Critical assertion: kill(pid, 0) should NEVER return EPERM during spawn
- T_ASSERT_EQ(kill_zero_eperm_count, 0,
- "kill(pid, 0) should never return EPERM during spawn - found %d EPERM errors in %d calls",
- kill_zero_eperm_count, kill_zero_total_count);
-
- // Verify we actually performed multiple kill(pid, 0) calls
- T_ASSERT_GT(kill_zero_total_count, 10,
- "Should have performed multiple kill(pid, 0) calls during spawn (got %d)",
- kill_zero_total_count);
-
- // Clean up: wait for child to exit
- int status;
- pid_t waited_pid = waitpid(child_pid, &status, 0);
- T_ASSERT_EQ(waited_pid, child_pid, "waitpid");
-
- // Log the status and whether the process exited normally or was killed
- if (WIFEXITED(status)) {
- T_LOG("Process %d exited normally with status %d", child_pid, WEXITSTATUS(status));
- } else if (WIFSIGNALED(status)) {
- T_LOG("Process %d was killed by signal %d", child_pid, WTERMSIG(status));
- } else {
- T_LOG("Process %d terminated with unknown status 0x%x", child_pid, status);
- }
-
- T_LOG("Test completed: %s - %d kill(pid, 0) calls, %d EPERM (should be 0), %d ESRCH",
- test_desc, kill_zero_total_count, kill_zero_eperm_count, kill_zero_esrch_count);
-}
-
-T_DECL(exec_kill_zero_no_eperm,
- "Verify that kill(pid, 0) never returns EPERM during exec",
- T_META_TAG_VM_PREFERRED)
-{
- T_LOG("Testing that kill(pid, 0) never fails with EPERM during exec()");
- T_LOG("UNIX semantics guarantee that kill(pid, 0) can be used to check PID validity");
-
- // Test with different exec scenarios to maximize race window coverage
- for (int i = 0; i < EXEC_PROGRAMS_COUNT; i++) {
- char **exec_args = exec_programs[i];
- char test_desc[256];
- snprintf(test_desc, sizeof(test_desc), "%s", exec_args[0]);
-
- test_kill_zero_during_spawn(exec_args, test_desc);
-
- // Brief pause between test iterations
- usleep(10000);
- }
-
- T_PASS("All exec scenarios completed without EPERM on kill(pid, 0)");
-}
-
-T_DECL(exec_kill_zero_no_eperm_stress,
- "Stress test kill(pid, 0) across many rapid exec operations",
- T_META_TAG_VM_PREFERRED)
-{
- const int stress_iterations = 20;
- int total_kill_calls = 0;
- int total_eperm_errors = 0;
-
- T_LOG("Running stress test with %d rapid exec iterations", stress_iterations);
-
- for (int iteration = 0; iteration < stress_iterations; iteration++) {
- // Alternate between different exec programs for variety
- int prog_idx = iteration % EXEC_PROGRAMS_COUNT;
- char **exec_args = exec_programs[prog_idx];
-
- char test_desc[256];
- snprintf(test_desc, sizeof(test_desc), "iteration_%d_%s",
- iteration + 1, exec_args[0]);
-
- T_LOG("Stress iteration %d/%d: %s", iteration + 1, stress_iterations, test_desc);
-
- test_kill_zero_during_spawn(exec_args, test_desc);
-
- // Accumulate statistics
- total_kill_calls += kill_zero_total_count;
- total_eperm_errors += kill_zero_eperm_count;
-
- // Very brief pause to avoid overwhelming the system
- usleep(5000);
- }
-
- T_LOG("Stress test completed: %d total kill(pid, 0) calls across %d iterations",
- total_kill_calls, stress_iterations);
-
- // Critical verification: NO EPERM errors across all iterations
- T_ASSERT_EQ(total_eperm_errors, 0,
- "kill(pid, 0) should never return EPERM - found %d EPERM errors across %d calls",
- total_eperm_errors, total_kill_calls);
-
- // Verify we actually did significant testing
- T_ASSERT_GT(total_kill_calls, stress_iterations * 10,
- "Should have performed substantial kill(pid, 0) testing");
-
- T_PASS("Stress test passed: %d kill(pid, 0) calls with 0 EPERM errors", total_kill_calls);
-}
-
-T_DECL(exec_kill_zero_very_rapid_spin,
- "Test kill(pid, 0) with very rapid spinning during exec",
- T_META_TAG_VM_PREFERRED)
-{
- T_LOG("Testing kill(pid, 0) with minimal delay between calls during spawn");
-
- // Use /bin/sleep with longer duration to ensure spawn window
- char *long_sleep_args[] = {"/bin/sleep", "5", NULL};
-
- // Reset counters
- stop_kill_spinning = 0;
- kill_zero_eperm_count = 0;
- kill_zero_total_count = 0;
- kill_zero_esrch_count = 0;
-
- // Use posix_spawn instead of fork() + exec()
- pid_t child_pid;
- int spawn_result = posix_spawn(&child_pid, long_sleep_args[0], NULL, NULL, long_sleep_args, NULL);
- T_ASSERT_POSIX_SUCCESS(spawn_result, "posix_spawn");
-
- T_LOG("Starting rapid fire kill(pid, 0) spinning for pid %d", child_pid);
-
- // Create semaphore for thread synchronization
- semaphore_t thread_ready_sem;
- kern_return_t kr = semaphore_create(mach_task_self(), &thread_ready_sem, SYNC_POLICY_FIFO, 0);
- T_ASSERT_MACH_SUCCESS(kr, "semaphore_create");
-
- // Prepare thread arguments
- struct {
- pid_t pid;
- semaphore_t ready_sem;
- } thread_args = {
- .pid = child_pid,
- .ready_sem = thread_ready_sem
- };
-
- // Create spinning thread with no delay
- pthread_t spin_thread;
- int ret = pthread_create(&spin_thread, NULL, rapid_fire_kill_zero_spin_thread, &thread_args);
- T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
-
- // Wait for thread to be ready instead of using usleep
- kr = semaphore_wait(thread_ready_sem);
- if (kr != KERN_SUCCESS) {
- // Ensure thread is joined even on error to prevent access to invalid stack memory
- stop_kill_spinning = 1;
- pthread_join(spin_thread, NULL);
- T_ASSERT_MACH_SUCCESS(kr, "semaphore_wait");
- }
-
- // Let rapid spinning continue during spawn/exec
- usleep(200000); // 200ms
-
- // Stop spinning
- stop_kill_spinning = 1;
-
- // Wait for spinner to complete - must always happen since thread references stack memory
- ret = pthread_join(spin_thread, NULL);
- T_ASSERT_POSIX_SUCCESS(ret, "pthread_join");
-
- // THE CRITICAL TEST: No EPERM should ever occur
- T_ASSERT_EQ(kill_zero_eperm_count, 0,
- "Rapid fire kill(pid, 0) should never return EPERM during exec - got %d errors in %d calls",
- kill_zero_eperm_count, kill_zero_total_count);
-
- T_ASSERT_GT(kill_zero_total_count, 100,
- "Should have performed many rapid fire kill(pid, 0) calls (got %d)",
- kill_zero_total_count);
-
- // Clean up child
- int status;
- pid_t waited_pid = waitpid(child_pid, &status, 0);
- T_ASSERT_EQ(waited_pid, child_pid, "waitpid");
-
- // Log the status and whether the process exited normally or was killed
- if (WIFEXITED(status)) {
- T_LOG("Process %d exited normally with status %d", child_pid, WEXITSTATUS(status));
- } else if (WIFSIGNALED(status)) {
- T_LOG("Process %d was killed by signal %d", child_pid, WTERMSIG(status));
- } else {
- T_LOG("Process %d terminated with unknown status 0x%x", child_pid, status);
- }
-
- // Cleanup semaphore
- semaphore_destroy(mach_task_self(), thread_ready_sem);
-
- T_LOG("Rapid fire test completed: %d kill(pid, 0) calls with %d EPERM errors (should be 0)",
- kill_zero_total_count, kill_zero_eperm_count);
-}