]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
kernel: fix execve after pivot_root via vfs_lookup_initrd(); fork execve test
authorTulio A M Mendes <[email protected]>
Mon, 27 Apr 2026 00:28:10 +0000 (21:28 -0300)
committerTulio A M Mendes <[email protected]>
Mon, 27 Apr 2026 00:28:10 +0000 (21:28 -0300)
pivot_root changes the global fs_root and '/' mount entry to a tmpfs,
which breaks subsequent execve calls because vfs_lookup('/bin/echo')
fails in the new root.  This caused the 'echo execve' flaky test
failure (it always failed when run after the pivot_root test).

Fix: save the original initrd overlayfs root at boot and add
vfs_lookup_initrd() which does a direct finddir traversal from that
saved root, bypassing the VFS mount table.  Use it in:
- elf32_load_user_from_initrd (binary lookup)
- elf32_load_interp (ld.so lookup)
- elf32_load_shared_lib_at (libc.so lookup)
- syscall_execve_impl (early ENOENT check)

Also change the execve test to fork a child (like execveat), so PID 1
is not replaced and the test harness can verify the output.

Tests: 120/120 smoke, 33/33 battery, 69/69 host, cppcheck clean.

include/fs.h
src/arch/x86/elf.c
src/kernel/fs.c
src/kernel/init.c
src/kernel/syscall.c
user/cmds/fulltest/fulltest.c

index 5a01cbe846c0cf3975f6b691ee46eb0c444fdfb8..ad378aace46aed4616510e3d0c549c672ba63b8c 100644 (file)
@@ -82,6 +82,10 @@ void vfs_close(fs_node_t* node);
 
 fs_node_t* vfs_lookup(const char* path);
 
+/* Lookup from the saved initrd root (immune to pivot_root). */
+void vfs_set_initrd_root(fs_node_t* root);
+fs_node_t* vfs_lookup_initrd(const char* path);
+
 // Resolve path to (parent_dir, basename).  Returns parent node or NULL.
 fs_node_t* vfs_lookup_parent(const char* path, char* name_out, size_t name_sz);
 
index e3bd091978bdb743502dcf73af23b8b6eb9e4eac..b7b7228e7604228fa37b1e3a06d686646d21b7a4 100644 (file)
@@ -263,7 +263,8 @@ static void elf32_process_relocations(const uint8_t* file, uint32_t file_len,
  * Returns 0 on success, fills *loaded_end with highest mapped address. */
 static int elf32_load_shared_lib_at(const char* path, uintptr_t as,
                                      uintptr_t base, uintptr_t* loaded_end) {
-    fs_node_t* node = vfs_lookup(path);
+    fs_node_t* node = vfs_lookup_initrd(path);
+    if (!node) node = vfs_lookup(path);
     if (!node) return -ENOENT;
 
     uint32_t flen = node->length;
@@ -354,7 +355,8 @@ static int elf32_load_interp(const char* interp_path, uintptr_t as,
                               uintptr_t* interp_entry, uintptr_t* interp_base_out) {
     if (!interp_path || !interp_entry) return -EINVAL;
 
-    fs_node_t* node = vfs_lookup(interp_path);
+    fs_node_t* node = vfs_lookup_initrd(interp_path);
+    if (!node) node = vfs_lookup(interp_path);
     if (!node) {
         kprintf("[ELF] interp not found: %s\n", interp_path);
         return -ENOENT;
@@ -409,7 +411,10 @@ int elf32_load_user_from_initrd(const char* filename, uintptr_t* entry_out, uint
 
     uintptr_t old_as = hal_cpu_get_address_space();
 
-    fs_node_t* node = vfs_lookup(filename);
+    /* Use vfs_lookup_initrd so that pivot_root (which changes the global
+     * fs_root and the "/" mount entry) does not break execve lookups. */
+    fs_node_t* node = vfs_lookup_initrd(filename);
+    if (!node) node = vfs_lookup(filename);
     if (!node) {
         kprintf("[ELF] file not found: %s\n", filename);
         vmm_as_destroy(new_as);
index 646b8b4884888566d095eafa0525b4e4dc8f1db6..843795275ad18d2223607507bb676334bda8e965 100644 (file)
@@ -13,6 +13,7 @@
 #include "errno.h"
 
 fs_node_t* fs_root = NULL;
+static fs_node_t* g_initrd_root = NULL;
 
 struct vfs_mount {
     char mountpoint[128];
@@ -123,6 +124,41 @@ fs_node_t* vfs_lookup(const char* path) {
     return vfs_lookup_depth(path, 0);
 }
 
+void vfs_set_initrd_root(fs_node_t* root) {
+    g_initrd_root = root;
+}
+
+fs_node_t* vfs_lookup_initrd(const char* path) {
+    if (!path || !g_initrd_root) return NULL;
+
+    /* Direct finddir traversal from the saved initrd root.
+     * Bypasses the VFS mount table so that pivot_root (which changes
+     * fs_root and the "/" mount entry) does not break execve lookups. */
+    const char* p = path;
+    while (*p == '/') p++;
+    if (*p == 0) return g_initrd_root;
+
+    fs_node_t* cur = g_initrd_root;
+    char part[128];
+    while (*p != 0) {
+        size_t i = 0;
+        while (*p != 0 && *p != '/') {
+            if (i + 1 < sizeof(part)) part[i++] = *p;
+            p++;
+        }
+        part[i] = 0;
+        while (*p == '/') p++;
+        if (part[0] == 0) continue;
+        if (!cur) return NULL;
+        fs_node_t* (*fn_finddir)(fs_node_t*, const char*) = NULL;
+        if (cur->i_ops && cur->i_ops->lookup) fn_finddir = cur->i_ops->lookup;
+        if (!fn_finddir) return NULL;
+        cur = fn_finddir(cur, part);
+        if (!cur) return NULL;
+    }
+    return cur;
+}
+
 static fs_node_t* vfs_lookup_depth(const char* path, int depth) {
     if (!path || !fs_root) return NULL;
     if (depth > 8) return NULL;
index 1da2670b25455b32f6b415ab0c0140d09b215472..593172502c01593bbe10b142e93b031f53ded2c9 100644 (file)
@@ -209,6 +209,7 @@ int init_start(const struct boot_info* bi) {
             fs_node_t* ovl = overlayfs_create_root(fs_root, upper);
             if (ovl) {
                 (void)vfs_mount("/", ovl);
+                vfs_set_initrd_root(ovl);
             }
         }
     }
index 00450a49d960670ea37e2ee2a07e6fbc1859001c..9881793f7948c2b001bcedc9b464e71bd5ef5fa9 100644 (file)
@@ -2055,7 +2055,8 @@ static int syscall_execve_impl(struct registers* regs, const char* user_path, co
     }
 
     // Distinguish ENOENT early.
-    fs_node_t* node = vfs_lookup(path);
+    fs_node_t* node = vfs_lookup_initrd(path);
+    if (!node) node = vfs_lookup(path);
     if (!node) { ret = -ENOENT; goto out; }
 
     uintptr_t entry = 0;
index 149c02437a9c0b07d9160dcd8838792df6b5b884..8aea99dfa643d94c27876230d1214afffc8fe3b2 100644 (file)
@@ -5256,13 +5256,27 @@ void _start(void) {
                   (uint32_t)(sizeof("[test] pivot_root OK\n") - 1));
     }
 
-    (void)sys_write(1, "[test] execve(/bin/echo)\n",
-                    (uint32_t)(sizeof("[test] execve(/bin/echo)\n") - 1));
-    static const char* const argv[] = {"echo", "[echo]", "hello", "from", "echo", 0};
-    static const char* const envp[] = {"FOO=bar", "HELLO=world", 0};
-    (void)sys_execve("/bin/echo", argv, envp);
-    (void)sys_write(1, "[test] execve returned (unexpected)\n",
-                    (uint32_t)(sizeof("[test] execve returned (unexpected)\n") - 1));
-    sys_exit(1);
+    // execve — fork a child that replaces itself with /bin/echo
+    {
+        int pid = sys_fork();
+        if (pid < 0) {
+            sys_write(1, "[test] execve fork failed\n",
+                      (uint32_t)(sizeof("[test] execve fork failed\n") - 1));
+            sys_exit(1);
+        }
+        if (pid == 0) {
+            static const char* const ev_argv[] = {"echo", "[echo]", "hello", "from", "echo", 0};
+            static const char* const ev_envp[] = {"FOO=bar", "HELLO=world", 0};
+            (void)sys_execve("/bin/echo", ev_argv, ev_envp);
+            sys_exit(1); /* execve should not return */
+        }
+        int st = 0;
+        sys_waitpid(pid, &st, 0);
+        if (st != 0) {
+            sys_write(1, "[test] execve child failed\n",
+                      (uint32_t)(sizeof("[test] execve child failed\n") - 1));
+            sys_exit(1);
+        }
+    }
     sys_exit(0);
 }