From: Tulio A M Mendes Date: Mon, 16 Feb 2026 22:56:49 +0000 (-0300) Subject: test: expand smoke suite to 89 tests + fix SMP orphan reparenting X-Git-Url: https://projects.tadryanom.me/docs/static/gitweb.js?a=commitdiff_plain;h=5dd7bc86d4e77a17b6d30af8d0e5ec418f47490c;p=AdrOS.git test: expand smoke suite to 89 tests + fix SMP orphan reparenting New userspace tests in init.c: - E1: setuid/setgid/seteuid/setegid credential manipulation - E2: fcntl F_GETFL/F_SETFL (O_NONBLOCK toggle) - E3: fcntl F_GETFD/F_SETFD (FD_CLOEXEC) - E4: sigsuspend (block SIGUSR1, self-signal, sigsuspend unblocks) - E5: orphan reparenting (grandchild reparented to init after middle exits) - Boot-time LZ4 Frame decompression pattern check Kernel fix — SMP orphan reparenting: - process_exit_notify() hardcoded parent_pid=1 for reparenting, but with SMP the AP idle processes consume PIDs 1-3 before the init userspace process is created (PID 4+). - Added sched_set_init_pid() to register the actual init process PID. - arch_platform.c calls sched_set_init_pid(current_process->pid) before entering userspace, so orphan reparenting targets the correct process. 89/89 smoke tests pass (9s), cppcheck clean. --- diff --git a/src/arch/x86/arch_platform.c b/src/arch/x86/arch_platform.c index f3a8c53..e89d8bd 100644 --- a/src/arch/x86/arch_platform.c +++ b/src/arch/x86/arch_platform.c @@ -59,6 +59,10 @@ static void userspace_init_thread(void) { current_process->heap_break = heap_brk; vmm_as_activate(user_as); + /* Register this process as "init" for orphan reparenting */ + extern void sched_set_init_pid(uint32_t); + sched_set_init_pid(current_process->pid); + /* Open /dev/console as fd 0, 1, 2 — mirrors Linux kernel_init: * sys_open("/dev/console", O_RDWR, 0); * sys_dup(0); sys_dup(0); */ diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c index b5a8362..504a1b6 100644 --- a/src/kernel/scheduler.c +++ b/src/kernel/scheduler.c @@ -20,6 +20,7 @@ struct process* current_process = NULL; struct process* ready_queue_head = NULL; struct process* ready_queue_tail = NULL; static uint32_t next_pid = 1; +static uint32_t init_pid = 0; /* PID of the first userspace process ("init") */ static spinlock_t sched_lock = {0}; static uintptr_t kernel_as = 0; @@ -488,6 +489,10 @@ int process_waitpid(int pid, int* status_out, uint32_t options) { } } +void sched_set_init_pid(uint32_t pid) { + init_pid = pid; +} + void process_exit_notify(int status) { if (!current_process) return; @@ -497,16 +502,16 @@ void process_exit_notify(int status) { current_process->state = PROCESS_ZOMBIE; alarm_queue_remove(current_process); - /* Reparent children to PID 1 (init) so orphaned zombies can be reaped. + /* Reparent children to the init process so orphaned zombies can be reaped. * If init is waiting on pid==-1, wake it to collect newly-adopted zombies. */ { - struct process* init_proc = process_find_locked(1); + struct process* init_proc = init_pid ? process_find_locked(init_pid) : NULL; struct process* it = ready_queue_head; if (it && init_proc) { const struct process* const start = it; do { if (it->parent_pid == current_process->pid && it != current_process) { - it->parent_pid = 1; + it->parent_pid = init_pid; /* If the child is already a zombie and init is waiting, wake init */ if (it->state == PROCESS_ZOMBIE && init_proc->state == PROCESS_BLOCKED && init_proc->waiting && diff --git a/tests/smoke_test.exp b/tests/smoke_test.exp index 9457718..cd00d52 100755 --- a/tests/smoke_test.exp +++ b/tests/smoke_test.exp @@ -126,6 +126,12 @@ set tests { {"PLT cached" "\\[init\\] PLT cached OK"} {"PING network" "\\[PING\\] .*received.*network OK"} {"echo.elf execve" "\\[echo\\] hello from echo.elf"} + {"setuid/setgid" "\\[init\\] setuid/setgid OK"} + {"fcntl F_GETFL/SETFL" "\\[init\\] fcntl F_GETFL/F_SETFL OK"} + {"fcntl FD_CLOEXEC" "\\[init\\] fcntl FD_CLOEXEC OK"} + {"sigsuspend" "\\[init\\] sigsuspend OK"} + {"orphan reparent" "\\[init\\] orphan reparent OK"} + {"LZ4 Frame decomp" "\\[INITRD\\] LZ4"} } # ---- Poll serial.log for results ---- diff --git a/user/init.c b/user/init.c index b36fa42..01bf586 100644 --- a/user/init.c +++ b/user/init.c @@ -135,9 +135,13 @@ enum { SYSCALL_FLOCK = 87, SYSCALL_GETEUID = 88, SYSCALL_GETEGID = 89, + SYSCALL_SETEUID = 90, + SYSCALL_SETEGID = 91, SYSCALL_SIGSUSPEND = 80, SYSCALL_SIGQUEUE = 95, SYSCALL_POSIX_SPAWN = 96, + SYSCALL_SETUID = 76, + SYSCALL_SETGID = 77, }; enum { @@ -145,10 +149,17 @@ enum { }; enum { + F_GETFD = 1, + F_SETFD = 2, F_GETFL = 3, F_SETFL = 4, F_GETPIPE_SZ = 1032, F_SETPIPE_SZ = 1033, + FD_CLOEXEC = 1, +}; + +enum { + O_CLOEXEC = 0x80000, }; enum { @@ -1272,6 +1283,36 @@ static int sys_posix_spawn(uint32_t* pid_out, const char* path, return __syscall_fix(ret); } +static int sys_setuid(uint32_t uid) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETUID), "b"(uid) : "memory"); + return __syscall_fix(ret); +} + +static int sys_setgid(uint32_t gid) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETGID), "b"(gid) : "memory"); + return __syscall_fix(ret); +} + +static int sys_seteuid(uint32_t euid) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEUID), "b"(euid) : "memory"); + return __syscall_fix(ret); +} + +static int sys_setegid(uint32_t egid) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SETEGID), "b"(egid) : "memory"); + return __syscall_fix(ret); +} + +static int sys_sigsuspend(const uint32_t* mask) { + int ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(SYSCALL_SIGSUSPEND), "b"(mask) : "memory"); + return __syscall_fix(ret); +} + __attribute__((noreturn)) static void sys_exit(int code) { __asm__ volatile( "int $0x80\n" @@ -3668,6 +3709,182 @@ void _start(void) { } } + // E1: setuid/setgid/seteuid/setegid — verify credential manipulation + { + uint32_t orig_uid = sys_getuid(); + uint32_t orig_gid = sys_getgid(); + // Process starts as root (uid=0), set uid to 1000 and back + if (orig_uid == 0) { + int pid = sys_fork(); + if (pid == 0) { + // In child: set uid/gid, verify, then exit + if (sys_setgid(500) < 0) sys_exit(1); + if (sys_getgid() != 500) sys_exit(2); + if (sys_setuid(1000) < 0) sys_exit(3); + if (sys_getuid() != 1000) sys_exit(4); + if (sys_geteuid() != 1000) sys_exit(5); + // Non-root can't change to arbitrary uid + if (sys_setuid(0) >= 0) sys_exit(6); + // seteuid to own uid should work + if (sys_seteuid(1000) < 0) sys_exit(7); + sys_exit(0); + } + if (pid > 0) { + int st = 0; + (void)sys_waitpid(pid, &st, 0); + if (st == 0) { + static const char m[] = "[init] setuid/setgid OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } else { + sys_write(1, "[init] setuid/setgid failed st=", (uint32_t)(sizeof("[init] setuid/setgid failed st=") - 1)); + write_int_dec(st); + sys_write(1, "\n", 1); + sys_exit(1); + } + } + } else { + static const char m[] = "[init] setuid/setgid OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } + (void)orig_gid; + } + + // E2: fcntl F_GETFL / F_SETFL — verify flag operations on pipe + { + int pfds[2]; + if (sys_pipe(pfds) < 0) { + sys_write(1, "[init] fcntl pipe failed\n", (uint32_t)(sizeof("[init] fcntl pipe failed\n") - 1)); + sys_exit(1); + } + int fl = sys_fcntl(pfds[0], F_GETFL, 0); + if (fl < 0) { + sys_write(1, "[init] fcntl F_GETFL failed\n", (uint32_t)(sizeof("[init] fcntl F_GETFL failed\n") - 1)); + sys_exit(1); + } + // Set O_NONBLOCK + if (sys_fcntl(pfds[0], F_SETFL, (uint32_t)fl | O_NONBLOCK) < 0) { + sys_write(1, "[init] fcntl F_SETFL failed\n", (uint32_t)(sizeof("[init] fcntl F_SETFL failed\n") - 1)); + sys_exit(1); + } + int fl2 = sys_fcntl(pfds[0], F_GETFL, 0); + if (!(fl2 & (int)O_NONBLOCK)) { + sys_write(1, "[init] fcntl NONBLOCK not set\n", (uint32_t)(sizeof("[init] fcntl NONBLOCK not set\n") - 1)); + sys_exit(1); + } + (void)sys_close(pfds[0]); + (void)sys_close(pfds[1]); + static const char m[] = "[init] fcntl F_GETFL/F_SETFL OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } + + // E3: fcntl F_GETFD / F_SETFD (FD_CLOEXEC) + { + int fd = sys_open("/bin/init.elf", 0); + if (fd < 0) { + sys_write(1, "[init] fcntl cloexec open failed\n", (uint32_t)(sizeof("[init] fcntl cloexec open failed\n") - 1)); + sys_exit(1); + } + int cloexec = sys_fcntl(fd, F_GETFD, 0); + if (cloexec < 0) { + sys_write(1, "[init] fcntl F_GETFD failed\n", (uint32_t)(sizeof("[init] fcntl F_GETFD failed\n") - 1)); + sys_exit(1); + } + if (sys_fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { + sys_write(1, "[init] fcntl F_SETFD failed\n", (uint32_t)(sizeof("[init] fcntl F_SETFD failed\n") - 1)); + sys_exit(1); + } + int cloexec2 = sys_fcntl(fd, F_GETFD, 0); + if (!(cloexec2 & FD_CLOEXEC)) { + sys_write(1, "[init] fcntl CLOEXEC not set\n", (uint32_t)(sizeof("[init] fcntl CLOEXEC not set\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + static const char m[] = "[init] fcntl FD_CLOEXEC OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } + + // E4: sigsuspend — block until signal delivered + { + int pid = sys_fork(); + if (pid == 0) { + // Child: block SIGUSR1, then sigsuspend with empty mask to unblock it + uint32_t block_mask = (1U << SIGUSR1); + (void)sys_sigprocmask(SIG_BLOCK, block_mask, 0); + + struct sigaction act; + act.sa_handler = (uintptr_t)usr1_handler; + act.sa_sigaction = 0; + act.sa_mask = 0; + act.sa_flags = 0; + (void)sys_sigaction2(SIGUSR1, &act, 0); + + // Signal parent we are ready by exiting a dummy fork + // Actually, just send ourselves SIGUSR1 and then sigsuspend + (void)sys_kill(sys_getpid(), SIGUSR1); + // SIGUSR1 is now pending but blocked + uint32_t empty = 0; // unmask all => SIGUSR1 delivered during suspend + int r = sys_sigsuspend(&empty); + // sigsuspend always returns -1 with errno==EINTR on signal delivery + if (r == -1 && got_usr1) { + sys_exit(0); + } + sys_exit(1); + } + if (pid > 0) { + int st = 0; + (void)sys_waitpid(pid, &st, 0); + if (st == 0) { + static const char m[] = "[init] sigsuspend OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } else { + sys_write(1, "[init] sigsuspend failed\n", (uint32_t)(sizeof("[init] sigsuspend failed\n") - 1)); + sys_exit(1); + } + } + } + + // E5: orphan reparenting — verify zombie grandchild is reaped after middle process exits + { + int mid = sys_fork(); + if (mid == 0) { + // Middle process: fork a grandchild, then exit immediately + int gc = sys_fork(); + if (gc == 0) { + // Grandchild: sleep briefly and exit with known status + struct timespec ts = {0, 200000000}; // 200ms + (void)sys_nanosleep(&ts, 0); + sys_exit(77); + } + // Middle exits immediately — grandchild becomes orphan + sys_exit(0); + } + + // Wait for middle process to finish + int st = 0; + (void)sys_waitpid(mid, &st, 0); + + // Now poll waitpid(-1, WNOHANG) to collect the reparented grandchild. + // It should appear as our child after the middle exits and reparenting occurs. + int found = 0; + for (int attempt = 0; attempt < 30; attempt++) { + int gc_st = 0; + int wp = sys_waitpid(-1, &gc_st, WNOHANG); + if (wp > 0 && gc_st == 77) { + found = 1; + break; + } + struct timespec ts = {0, 50000000}; // 50ms + (void)sys_nanosleep(&ts, 0); + } + if (found) { + static const char m[] = "[init] orphan reparent OK\n"; + (void)sys_write(1, m, (uint32_t)(sizeof(m) - 1)); + } else { + sys_write(1, "[init] orphan reparent failed\n", + (uint32_t)(sizeof("[init] orphan reparent failed\n") - 1)); + } + } + enum { NCHILD = 100 }; int children[NCHILD]; for (int i = 0; i < NCHILD; i++) {