]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
test: expand smoke suite to 89 tests + fix SMP orphan reparenting
authorTulio A M Mendes <[email protected]>
Mon, 16 Feb 2026 22:56:49 +0000 (19:56 -0300)
committerTulio A M Mendes <[email protected]>
Mon, 16 Feb 2026 22:56:49 +0000 (19:56 -0300)
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.

src/arch/x86/arch_platform.c
src/kernel/scheduler.c
tests/smoke_test.exp
user/init.c

index f3a8c5361274189adff480ad36dacf88ef2df677..e89d8bd930266c84fa053b56cea9cd428e0bd0d4 100644 (file)
@@ -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);                                     */
index b5a836288ced434eed7fddbc94fa35750b247dda..504a1b68ec885e4d1d11dd54982a654b54c892c7 100644 (file)
@@ -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 &&
index 94577183ad11188180c4d1f0e67a423abd16b503..cd00d524695f5520b1d500f87ff9ee96758a2d85 100755 (executable)
@@ -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 ----
index b36fa42b715912d00d397a058b8717f418b98dd6..01bf586b95019756db2cdb2f301d23baaa0cdca0 100644 (file)
@@ -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++) {