]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
tests: expand host unit tests from 47 to 101 checks
authorTulio A M Mendes <[email protected]>
Mon, 27 Apr 2026 16:58:38 +0000 (13:58 -0300)
committerTulio A M Mendes <[email protected]>
Mon, 4 May 2026 23:52:08 +0000 (20:52 -0300)
test_utils.c (28→63):
- tar_parse_octal: zero, simple, large, empty, null-term, non-octal skip
- path_is_mountpoint_prefix: root, exact, child, no-match, partial, empty
- normalize_mountpoint: root, simple, trailing-slash, empty, null, normalized
- vfs_check_permission: root, owner/group/other read/write, mode-0, 0644
- elf32_validate: valid ET_EXEC/ET_DYN, bad magic/class/type/machine,
  no phnum, truncated, null

test_security.c (19→38):
- Signal mask: valid excludes KILL/STOP, includes USR1,
  pending&blocked, deliverable/blocked, KILL/STOP always deliverable
- chmod symbolic: u+x, go-w, a+x, a=rw, u+s, g+s, +t, o-x,
  u=rw,go=r, no-change

gdb_checks.py (6→10):
- Process table PID 1 state check
- Scheduler active runqueue bitmap
- Mount table count
- Frame 0 refcount

Test results: 63/63 + 38/38 + 111/111 = 212 host PASS

tests/gdb_checks.py
tests/test_security.c
tests/test_utils.c

index 5a94de58c635b9ec67d171f4cd11f4f7d8ad6d2c..336330c8e0194ba0c49d9d39347f8e7b69b6ae07 100644 (file)
@@ -121,6 +121,52 @@ def run_checks():
     except gdb.error as e:
         c.check("VGA mapping accessible", False, str(e))
 
+    # ---- Process Table ----
+    try:
+        # PID 1 (init/fulltest) should exist and be in RUNNING state
+        proc_ptr = gdb.parse_and_eval("(struct process*)process_table")
+        if proc_ptr:
+            p1_state = c.read_u32(int(proc_ptr) + 0)  # state is first field
+            # State values: RUNNING=1, READY=2, BLOCKED=3, SLEEPING=4, ZOMBIE=5
+            c.check("PID 1 state is RUNNING or READY",
+                    p1_state is not None and (p1_state == 1 or p1_state == 2),
+                    f"state={p1_state}")
+    except gdb.error as e:
+        c.check("Process table accessible", False, str(e))
+
+    # ---- Scheduler State ----
+    try:
+        # active_runqueue bitmap should be non-zero (at least one process ready)
+        rq_bitmap = gdb.parse_and_eval("active_rq.bitmap")
+        bitmap_val = int(rq_bitmap) & 0xFFFFFFFF
+        c.check("Active runqueue bitmap non-zero (processes ready)",
+                bitmap_val != 0,
+                f"bitmap={bitmap_val:#x}")
+    except gdb.error as e:
+        c.check("Scheduler runqueue accessible", False, str(e))
+
+    # ---- Mount Table ----
+    try:
+        # g_mount_count should be > 0 (at least root mounted)
+        mc = gdb.parse_and_eval("g_mount_count")
+        mount_count = int(mc)
+        c.check("Mount count > 0",
+                mount_count > 0,
+                f"mount_count={mount_count}")
+    except gdb.error as e:
+        c.check("Mount table accessible", False, str(e))
+
+    # ---- Frame Reference Counts ----
+    try:
+        # frame_refcount[0] should be > 0 (frame 0 is never freed)
+        ref0 = gdb.parse_and_eval("frame_refcount[0]")
+        ref0_val = int(ref0) & 0xFFFFFFFF
+        c.check("Frame 0 refcount > 0",
+                ref0_val > 0,
+                f"refcount[0]={ref0_val}")
+    except gdb.error as e:
+        c.check("Frame refcounts accessible", False, str(e))
+
     # ---- Summary ----
     total = c.passed + c.failed
     print(f"\n  {c.passed}/{total} passed, {c.failed} failed")
index 3a4a222119590d8bd612a64c67fe42ba33a4943e..308ad7485ef7f1f37d85921a0a571ce78d74679e 100644 (file)
@@ -82,6 +82,104 @@ static uint32_t sanitize_eflags(uint32_t eflags) {
     return (eflags & ~0x3000U) | 0x200U;
 }
 
+/* Signal mask logic from kernel/scheduler.c and kernel/syscall.c */
+#define SIGKILL  9
+#define SIGSTOP  19
+#define PROCESS_MAX_SIG 32
+
+static uint32_t sig_valid_mask(void) {
+    /* SIGKILL and SIGSTOP cannot be blocked */
+    uint32_t all = 0;
+    for (int i = 1; i < PROCESS_MAX_SIG; i++) {
+        if (i != SIGKILL && i != SIGSTOP)
+            all |= (1U << (uint32_t)i);
+    }
+    return all;
+}
+
+static uint32_t sig_pending_and_blocked(uint32_t pending, uint32_t blocked) {
+    /* sigpending returns: pending & blocked */
+    return pending & blocked;
+}
+
+static int sig_is_deliverable(uint32_t sig, uint32_t blocked) {
+    /* Signal is deliverable if not blocked (except KILL/STOP always deliverable) */
+    if (sig == (uint32_t)SIGKILL || sig == (uint32_t)SIGSTOP) return 1;
+    return (blocked & (1U << sig)) == 0;
+}
+
+/* chmod symbolic mode parsing from user/cmds/chmod/chmod.c */
+static unsigned int parse_symbolic(const char* mode, unsigned int old) {
+    unsigned int result = old;
+    const char* p = mode;
+    while (*p) {
+        unsigned int who_bits = 0;
+        int has_u = 0, has_g = 0, has_o = 0;
+        while (*p == 'u' || *p == 'g' || *p == 'o' || *p == 'a') {
+            switch (*p) {
+            case 'u': has_u = 1; break;
+            case 'g': has_g = 1; break;
+            case 'o': has_o = 1; break;
+            case 'a': has_u = has_g = has_o = 1; break;
+            }
+            p++;
+        }
+        if (!has_u && !has_g && !has_o) has_u = has_g = has_o = 1;
+
+        if (has_u) who_bits |= 04700;
+        if (has_g) who_bits |= 02070;
+        if (has_o) who_bits |= 00007;
+
+        while (*p) {
+            char op = *p;
+            if (op != '+' && op != '-' && op != '=') break;
+            p++;
+
+            unsigned int perm = 0;
+            while (*p == 'r' || *p == 'w' || *p == 'x' ||
+                   *p == 's' || *p == 't') {
+                switch (*p) {
+                case 'r':
+                    if (has_u) perm |= 0400;
+                    if (has_g) perm |= 0040;
+                    if (has_o) perm |= 0004;
+                    break;
+                case 'w':
+                    if (has_u) perm |= 0200;
+                    if (has_g) perm |= 0020;
+                    if (has_o) perm |= 0002;
+                    break;
+                case 'x':
+                    if (has_u) perm |= 0100;
+                    if (has_g) perm |= 0010;
+                    if (has_o) perm |= 0001;
+                    break;
+                case 's':
+                    if (has_u) perm |= 04000;
+                    if (has_g) perm |= 02000;
+                    break;
+                case 't':
+                    perm |= 01000;
+                    break;
+                }
+                p++;
+            }
+
+            if (op == '+') {
+                result |= perm;
+            } else if (op == '-') {
+                result &= ~perm;
+            } else {
+                result &= ~who_bits;
+                result |= perm;
+            }
+
+            if (*p == ',') { p++; break; }
+        }
+    }
+    return result & 07777;
+}
+
 /* ======== user_range_ok TESTS ======== */
 
 TEST(urange_null_ptr) {
@@ -216,6 +314,114 @@ TEST(eflags_iopl1) {
     ASSERT_EQ(clean & 0x3000, 0);
 }
 
+/* ======== Signal mask TESTS ======== */
+
+TEST(sigmask_valid_excludes_kill) {
+    /* SIGKILL (bit 9) must not be in valid mask */
+    uint32_t mask = sig_valid_mask();
+    ASSERT_EQ(mask & (1U << SIGKILL), 0);
+}
+
+TEST(sigmask_valid_excludes_stop) {
+    /* SIGSTOP (bit 19) must not be in valid mask */
+    uint32_t mask = sig_valid_mask();
+    ASSERT_EQ(mask & (1U << SIGSTOP), 0);
+}
+
+TEST(sigmask_valid_includes_usr1) {
+    /* SIGUSR1 (bit 10) must be in valid mask */
+    uint32_t mask = sig_valid_mask();
+    ASSERT_TRUE(mask & (1U << 10));
+}
+
+TEST(sigmask_pending_and_blocked) {
+    /* sigpending = pending & blocked */
+    uint32_t pending = (1U << 10) | (1U << 12); /* SIGUSR1, SIGUSR2 */
+    uint32_t blocked = (1U << 10);              /* only SIGUSR1 blocked */
+    uint32_t result = sig_pending_and_blocked(pending, blocked);
+    ASSERT_EQ(result, (1U << 10));               /* only SIGUSR1 pending+blocked */
+}
+
+TEST(sigmask_pending_not_blocked) {
+    /* Signal pending but not blocked → not in sigpending result */
+    uint32_t pending = (1U << 10);
+    uint32_t blocked = 0;
+    uint32_t result = sig_pending_and_blocked(pending, blocked);
+    ASSERT_EQ(result, 0);
+}
+
+TEST(sigmask_deliverable_normal) {
+    /* Normal signal not blocked → deliverable */
+    ASSERT_TRUE(sig_is_deliverable(10, 0));
+}
+
+TEST(sigmask_deliverable_blocked) {
+    /* Normal signal blocked → not deliverable */
+    ASSERT_FALSE(sig_is_deliverable(10, (1U << 10)));
+}
+
+TEST(sigmask_kill_always_deliverable) {
+    /* SIGKILL always deliverable even if blocked */
+    ASSERT_TRUE(sig_is_deliverable(SIGKILL, (1U << SIGKILL)));
+}
+
+TEST(sigmask_stop_always_deliverable) {
+    /* SIGSTOP always deliverable even if blocked */
+    ASSERT_TRUE(sig_is_deliverable(SIGSTOP, (1U << SIGSTOP)));
+}
+
+/* ======== chmod symbolic mode TESTS ======== */
+
+TEST(chmod_u_plus_x) {
+    /* u+x on 0644 → 0744 (only user gets execute) */
+    ASSERT_EQ(parse_symbolic("u+x", 0644), 0744);
+}
+
+TEST(chmod_go_minus_w) {
+    /* go-w on 0666 → 0644 */
+    ASSERT_EQ(parse_symbolic("go-w", 0666), 0644);
+}
+
+TEST(chmod_a_plus_x) {
+    /* a+x on 0644 → 0755 */
+    ASSERT_EQ(parse_symbolic("a+x", 0644), 0755);
+}
+
+TEST(chmod_a_eq_rw) {
+    /* a=rw on 0755 → 0666 */
+    ASSERT_EQ(parse_symbolic("a=rw", 0755), 0666);
+}
+
+TEST(chmod_u_plus_s) {
+    /* u+s on 0755 → 4755 (setuid) */
+    ASSERT_EQ(parse_symbolic("u+s", 0755), 04755);
+}
+
+TEST(chmod_g_plus_s) {
+    /* g+s on 0755 → 2755 (setgid) */
+    ASSERT_EQ(parse_symbolic("g+s", 0755), 02755);
+}
+
+TEST(chmod_plus_t) {
+    /* +t on 0777 → 1777 (sticky) */
+    ASSERT_EQ(parse_symbolic("+t", 0777), 01777);
+}
+
+TEST(chmod_o_minus_x) {
+    /* o-x on 0755 → 0754 */
+    ASSERT_EQ(parse_symbolic("o-x", 0755), 0754);
+}
+
+TEST(chmod_u_eq_rw_go_eq_r) {
+    /* u=rw,go=r on 0755 → 0644 */
+    ASSERT_EQ(parse_symbolic("u=rw,go=r", 0755), 0644);
+}
+
+TEST(chmod_no_change) {
+    /* u+r on already 0755 → 0755 */
+    ASSERT_EQ(parse_symbolic("u+r", 0755), 0755);
+}
+
 /* ======== MAIN ======== */
 int main(void) {
     printf("\n=========================================\n");
@@ -247,6 +453,29 @@ int main(void) {
     RUN(eflags_preserves_other);
     RUN(eflags_iopl1);
 
+    /* signal mask */
+    RUN(sigmask_valid_excludes_kill);
+    RUN(sigmask_valid_excludes_stop);
+    RUN(sigmask_valid_includes_usr1);
+    RUN(sigmask_pending_and_blocked);
+    RUN(sigmask_pending_not_blocked);
+    RUN(sigmask_deliverable_normal);
+    RUN(sigmask_deliverable_blocked);
+    RUN(sigmask_kill_always_deliverable);
+    RUN(sigmask_stop_always_deliverable);
+
+    /* chmod symbolic mode */
+    RUN(chmod_u_plus_x);
+    RUN(chmod_go_minus_w);
+    RUN(chmod_a_plus_x);
+    RUN(chmod_a_eq_rw);
+    RUN(chmod_u_plus_s);
+    RUN(chmod_g_plus_s);
+    RUN(chmod_plus_t);
+    RUN(chmod_o_minus_x);
+    RUN(chmod_u_eq_rw_go_eq_r);
+    RUN(chmod_no_change);
+
     printf("\n  %d/%d passed, %d failed\n", g_tests_passed, g_tests_run, g_tests_failed);
 
     if (g_tests_failed > 0) {
index 78d98ce0858b205897544a95329e283c32c53743..aae92f8def6ea06832d30624856e813ee0371c06 100644 (file)
@@ -196,6 +196,127 @@ static uint64_t align_up(uint64_t value, uint64_t align) {
     return (value + align - 1) & ~(align - 1);
 }
 
+/* From src/drivers/initrd.c — tar octal parsing */
+static uint32_t tar_parse_octal(const char* s, size_t n) {
+    uint32_t v = 0;
+    for (size_t i = 0; i < n; i++) {
+        if (s[i] == 0) break;
+        if (s[i] < '0' || s[i] > '7') continue;
+        v = (v << 3) + (uint32_t)(s[i] - '0');
+    }
+    return v;
+}
+
+/* From src/kernel/fs.c — mount point prefix check */
+static int path_is_mountpoint_prefix(const char* mp, const char* path) {
+    size_t mpl = strlen(mp);
+    if (mpl == 0) return 0;
+    if (strcmp(mp, "/") == 0) return 1;
+    if (strncmp(path, mp, mpl) != 0) return 0;
+    if (path[mpl] == 0) return 1;
+    if (path[mpl] == '/') return 1;
+    return 0;
+}
+
+/* From src/kernel/fs.c — mountpoint normalization */
+static void normalize_mountpoint(const char* in, char* out, size_t out_sz) {
+    if (!out || out_sz == 0) return;
+    out[0] = 0;
+    if (!in || in[0] == 0) {
+        strcpy(out, "/");
+        return;
+    }
+    size_t i = 0;
+    if (in[0] != '/') {
+        out[i++] = '/';
+    }
+    for (size_t j = 0; in[j] != 0 && i + 1 < out_sz; j++) {
+        out[i++] = in[j];
+    }
+    out[i] = 0;
+    size_t l = strlen(out);
+    while (l > 1 && out[l - 1] == '/') {
+        out[l - 1] = 0;
+        l--;
+    }
+}
+
+/* From src/kernel/syscall.c — permission check (simplified for host test) */
+#define EACCES 13
+static int vfs_check_permission(uint32_t mode, uint32_t euid, uint32_t egid,
+                                 uint32_t file_uid, uint32_t file_gid, int want) {
+    if (euid == 0) return 0;       /* root — allow all */
+    if (mode == 0) return 0;        /* mode not set — permissive */
+    uint32_t perm;
+    if (euid == file_uid) {
+        perm = (mode >> 6) & 7;
+    } else if (egid == file_gid) {
+        perm = (mode >> 3) & 7;
+    } else {
+        perm = mode & 7;
+    }
+    if (((uint32_t)want & perm) != (uint32_t)want) return -EACCES;
+    return 0;
+}
+
+/* From src/arch/x86/elf.c — ELF header validation (simplified for host) */
+#define ELF_MAGIC0 0x7F
+#define ELF_MAGIC1 'E'
+#define ELF_MAGIC2 'L'
+#define ELF_MAGIC3 'F'
+#define ELFCLASS32 1
+#define ELFDATA2LSB 1
+#define ET_EXEC 2
+#define ET_DYN 3
+#define EM_386 3
+#define EINVAL 22
+#define EFAULT 14
+
+typedef struct {
+    uint8_t  e_ident[16];
+    uint16_t e_type;
+    uint16_t e_machine;
+    uint32_t e_version;
+    uint32_t e_entry;
+    uint32_t e_phoff;
+    uint32_t e_shoff;
+    uint32_t e_flags;
+    uint16_t e_ehsize;
+    uint16_t e_phentsize;
+    uint16_t e_phnum;
+    uint16_t e_shentsize;
+    uint16_t e_shnum;
+    uint16_t e_shstrndx;
+} test_elf32_ehdr_t;
+
+typedef struct {
+    uint32_t p_type;
+    uint32_t p_offset;
+    uint32_t p_vaddr;
+    uint32_t p_paddr;
+    uint32_t p_filesz;
+    uint32_t p_memsz;
+    uint32_t p_flags;
+    uint32_t p_align;
+} test_elf32_phdr_t;
+
+static int elf32_validate(const test_elf32_ehdr_t* eh, size_t file_len) {
+    if (!eh) return -EFAULT;
+    if (file_len < sizeof(*eh)) return -EINVAL;
+    if (eh->e_ident[0] != ELF_MAGIC0 ||
+        eh->e_ident[1] != ELF_MAGIC1 ||
+        eh->e_ident[2] != ELF_MAGIC2 ||
+        eh->e_ident[3] != ELF_MAGIC3)
+        return -EINVAL;
+    if (eh->e_ident[4] != ELFCLASS32) return -EINVAL;
+    if (eh->e_ident[5] != ELFDATA2LSB) return -EINVAL;
+    if (eh->e_type != ET_EXEC && eh->e_type != ET_DYN) return -EINVAL;
+    if (eh->e_machine != EM_386) return -EINVAL;
+    if (eh->e_phentsize != sizeof(test_elf32_phdr_t)) return -EINVAL;
+    if (eh->e_phnum == 0) return -EINVAL;
+    return 0;
+}
+
 /* ======== TESTS ======== */
 
 /* --- itoa tests --- */
@@ -354,6 +475,255 @@ TEST(align_up_zero) {
     ASSERT_EQ(align_up(0, 4096), 0);
 }
 
+/* --- tar_parse_octal tests --- */
+TEST(tar_octal_zero) {
+    ASSERT_EQ(tar_parse_octal("0", 2), 0);
+}
+
+TEST(tar_octal_simple) {
+    ASSERT_EQ(tar_parse_octal("644", 4), 0644);
+}
+
+TEST(tar_octal_large) {
+    ASSERT_EQ(tar_parse_octal("100644", 7), 0100644);
+}
+
+TEST(tar_octal_empty) {
+    ASSERT_EQ(tar_parse_octal("", 1), 0);
+}
+
+TEST(tar_octal_null_term) {
+    /* Stops at NUL even if n is larger */
+    ASSERT_EQ(tar_parse_octal("755\0ignored", 10), 0755);
+}
+
+TEST(tar_octal_skips_non_octal) {
+    /* Space padding is common in tar headers */
+    ASSERT_EQ(tar_parse_octal(" 644 ", 6), 0644);
+}
+
+/* --- path_is_mountpoint_prefix tests --- */
+TEST(mountprefix_root) {
+    ASSERT_EQ(path_is_mountpoint_prefix("/", "/anything"), 1);
+}
+
+TEST(mountprefix_exact) {
+    ASSERT_EQ(path_is_mountpoint_prefix("/disk", "/disk"), 1);
+}
+
+TEST(mountprefix_child) {
+    ASSERT_EQ(path_is_mountpoint_prefix("/disk", "/disk/file"), 1);
+}
+
+TEST(mountprefix_no_match) {
+    ASSERT_EQ(path_is_mountpoint_prefix("/disk", "/other"), 0);
+}
+
+TEST(mountprefix_partial) {
+    /* /dis should not match /disk */
+    ASSERT_EQ(path_is_mountpoint_prefix("/dis", "/disk"), 0);
+}
+
+TEST(mountprefix_empty) {
+    ASSERT_EQ(path_is_mountpoint_prefix("", "/anything"), 0);
+}
+
+/* --- normalize_mountpoint tests --- */
+TEST(normmount_root) {
+    char out[128];
+    normalize_mountpoint("/", out, sizeof(out));
+    ASSERT_STR_EQ(out, "/");
+}
+
+TEST(normmount_simple) {
+    char out[128];
+    normalize_mountpoint("disk", out, sizeof(out));
+    ASSERT_STR_EQ(out, "/disk");
+}
+
+TEST(normmount_trailing_slash) {
+    char out[128];
+    normalize_mountpoint("/disk/", out, sizeof(out));
+    ASSERT_STR_EQ(out, "/disk");
+}
+
+TEST(normmount_empty) {
+    char out[128];
+    normalize_mountpoint("", out, sizeof(out));
+    ASSERT_STR_EQ(out, "/");
+}
+
+TEST(normmount_null) {
+    char out[128];
+    normalize_mountpoint(NULL, out, sizeof(out));
+    ASSERT_STR_EQ(out, "/");
+}
+
+TEST(normmount_already_normalized) {
+    char out[128];
+    normalize_mountpoint("/disk", out, sizeof(out));
+    ASSERT_STR_EQ(out, "/disk");
+}
+
+/* --- vfs_check_permission tests --- */
+TEST(perm_root_allows) {
+    /* Root can do anything */
+    ASSERT_EQ(vfs_check_permission(0100, 0, 0, 1000, 1000, 4), 0);
+}
+
+TEST(perm_owner_read) {
+    /* mode=0400, owner wants read → allowed */
+    ASSERT_EQ(vfs_check_permission(0400, 1000, 100, 1000, 100, 4), 0);
+}
+
+TEST(perm_owner_no_write) {
+    /* mode=0400, owner wants write → denied */
+    ASSERT_EQ(vfs_check_permission(0400, 1000, 100, 1000, 100, 2), -EACCES);
+}
+
+TEST(perm_group_read) {
+    /* mode=0040, group member wants read → allowed */
+    ASSERT_EQ(vfs_check_permission(0040, 2000, 100, 1000, 100, 4), 0);
+}
+
+TEST(perm_other_read) {
+    /* mode=0004, other wants read → allowed */
+    ASSERT_EQ(vfs_check_permission(0004, 3000, 200, 1000, 100, 4), 0);
+}
+
+TEST(perm_other_no_write) {
+    /* mode=0004, other wants write → denied */
+    ASSERT_EQ(vfs_check_permission(0004, 3000, 200, 1000, 100, 2), -EACCES);
+}
+
+TEST(perm_mode_zero_permissive) {
+    /* mode=0 → permissive (allow all) */
+    ASSERT_EQ(vfs_check_permission(0, 1000, 100, 1000, 100, 7), 0);
+}
+
+TEST(perm_rw_file) {
+    /* mode=0644: owner rw, group/other read */
+    ASSERT_EQ(vfs_check_permission(0644, 1000, 100, 1000, 100, 6), 0); /* owner rw */
+    ASSERT_EQ(vfs_check_permission(0644, 2000, 100, 1000, 100, 4), 0); /* group read */
+    ASSERT_EQ(vfs_check_permission(0644, 2000, 100, 1000, 100, 2), -EACCES); /* group no write */
+    ASSERT_EQ(vfs_check_permission(0644, 3000, 200, 1000, 100, 4), 0); /* other read */
+    ASSERT_EQ(vfs_check_permission(0644, 3000, 200, 1000, 100, 2), -EACCES); /* other no write */
+}
+
+/* --- elf32_validate tests --- */
+TEST(elf_valid_exec) {
+    test_elf32_ehdr_t eh;
+    memset(&eh, 0, sizeof(eh));
+    eh.e_ident[0] = 0x7F;
+    eh.e_ident[1] = 'E';
+    eh.e_ident[2] = 'L';
+    eh.e_ident[3] = 'F';
+    eh.e_ident[4] = ELFCLASS32;
+    eh.e_ident[5] = ELFDATA2LSB;
+    eh.e_type = ET_EXEC;
+    eh.e_machine = EM_386;
+    eh.e_phentsize = sizeof(test_elf32_phdr_t);
+    eh.e_phnum = 1;
+    ASSERT_EQ(elf32_validate(&eh, sizeof(eh)), 0);
+}
+
+TEST(elf_valid_dyn) {
+    test_elf32_ehdr_t eh;
+    memset(&eh, 0, sizeof(eh));
+    eh.e_ident[0] = 0x7F;
+    eh.e_ident[1] = 'E';
+    eh.e_ident[2] = 'L';
+    eh.e_ident[3] = 'F';
+    eh.e_ident[4] = ELFCLASS32;
+    eh.e_ident[5] = ELFDATA2LSB;
+    eh.e_type = ET_DYN;
+    eh.e_machine = EM_386;
+    eh.e_phentsize = sizeof(test_elf32_phdr_t);
+    eh.e_phnum = 1;
+    ASSERT_EQ(elf32_validate(&eh, sizeof(eh)), 0);
+}
+
+TEST(elf_bad_magic) {
+    test_elf32_ehdr_t eh;
+    memset(&eh, 0, sizeof(eh));
+    eh.e_ident[0] = 'X';
+    ASSERT_EQ(elf32_validate(&eh, sizeof(eh)), -EINVAL);
+}
+
+TEST(elf_bad_class) {
+    test_elf32_ehdr_t eh;
+    memset(&eh, 0, sizeof(eh));
+    eh.e_ident[0] = 0x7F;
+    eh.e_ident[1] = 'E';
+    eh.e_ident[2] = 'L';
+    eh.e_ident[3] = 'F';
+    eh.e_ident[4] = 2; /* ELFCLASS64 */
+    eh.e_ident[5] = ELFDATA2LSB;
+    eh.e_type = ET_EXEC;
+    eh.e_machine = EM_386;
+    eh.e_phentsize = sizeof(test_elf32_phdr_t);
+    eh.e_phnum = 1;
+    ASSERT_EQ(elf32_validate(&eh, sizeof(eh)), -EINVAL);
+}
+
+TEST(elf_bad_type) {
+    test_elf32_ehdr_t eh;
+    memset(&eh, 0, sizeof(eh));
+    eh.e_ident[0] = 0x7F;
+    eh.e_ident[1] = 'E';
+    eh.e_ident[2] = 'L';
+    eh.e_ident[3] = 'F';
+    eh.e_ident[4] = ELFCLASS32;
+    eh.e_ident[5] = ELFDATA2LSB;
+    eh.e_type = 0; /* ET_NONE */
+    eh.e_machine = EM_386;
+    eh.e_phentsize = sizeof(test_elf32_phdr_t);
+    eh.e_phnum = 1;
+    ASSERT_EQ(elf32_validate(&eh, sizeof(eh)), -EINVAL);
+}
+
+TEST(elf_bad_machine) {
+    test_elf32_ehdr_t eh;
+    memset(&eh, 0, sizeof(eh));
+    eh.e_ident[0] = 0x7F;
+    eh.e_ident[1] = 'E';
+    eh.e_ident[2] = 'L';
+    eh.e_ident[3] = 'F';
+    eh.e_ident[4] = ELFCLASS32;
+    eh.e_ident[5] = ELFDATA2LSB;
+    eh.e_type = ET_EXEC;
+    eh.e_machine = 0x3E; /* EM_X86_64 */
+    eh.e_phentsize = sizeof(test_elf32_phdr_t);
+    eh.e_phnum = 1;
+    ASSERT_EQ(elf32_validate(&eh, sizeof(eh)), -EINVAL);
+}
+
+TEST(elf_no_phnum) {
+    test_elf32_ehdr_t eh;
+    memset(&eh, 0, sizeof(eh));
+    eh.e_ident[0] = 0x7F;
+    eh.e_ident[1] = 'E';
+    eh.e_ident[2] = 'L';
+    eh.e_ident[3] = 'F';
+    eh.e_ident[4] = ELFCLASS32;
+    eh.e_ident[5] = ELFDATA2LSB;
+    eh.e_type = ET_EXEC;
+    eh.e_machine = EM_386;
+    eh.e_phentsize = sizeof(test_elf32_phdr_t);
+    eh.e_phnum = 0;
+    ASSERT_EQ(elf32_validate(&eh, sizeof(eh)), -EINVAL);
+}
+
+TEST(elf_truncated) {
+    test_elf32_ehdr_t eh;
+    memset(&eh, 0, sizeof(eh));
+    ASSERT_EQ(elf32_validate(&eh, 10), -EINVAL);
+}
+
+TEST(elf_null) {
+    ASSERT_EQ(elf32_validate(NULL, 100), -EFAULT);
+}
+
 /* ======== MAIN ======== */
 int main(void) {
     printf("\n=========================================\n");
@@ -398,6 +768,51 @@ int main(void) {
     RUN(align_up_exact);
     RUN(align_up_zero);
 
+    /* tar_parse_octal */
+    RUN(tar_octal_zero);
+    RUN(tar_octal_simple);
+    RUN(tar_octal_large);
+    RUN(tar_octal_empty);
+    RUN(tar_octal_null_term);
+    RUN(tar_octal_skips_non_octal);
+
+    /* path_is_mountpoint_prefix */
+    RUN(mountprefix_root);
+    RUN(mountprefix_exact);
+    RUN(mountprefix_child);
+    RUN(mountprefix_no_match);
+    RUN(mountprefix_partial);
+    RUN(mountprefix_empty);
+
+    /* normalize_mountpoint */
+    RUN(normmount_root);
+    RUN(normmount_simple);
+    RUN(normmount_trailing_slash);
+    RUN(normmount_empty);
+    RUN(normmount_null);
+    RUN(normmount_already_normalized);
+
+    /* vfs_check_permission */
+    RUN(perm_root_allows);
+    RUN(perm_owner_read);
+    RUN(perm_owner_no_write);
+    RUN(perm_group_read);
+    RUN(perm_other_read);
+    RUN(perm_other_no_write);
+    RUN(perm_mode_zero_permissive);
+    RUN(perm_rw_file);
+
+    /* elf32_validate */
+    RUN(elf_valid_exec);
+    RUN(elf_valid_dyn);
+    RUN(elf_bad_magic);
+    RUN(elf_bad_class);
+    RUN(elf_bad_type);
+    RUN(elf_bad_machine);
+    RUN(elf_no_phnum);
+    RUN(elf_truncated);
+    RUN(elf_null);
+
     printf("\n  %d/%d passed, %d failed\n", g_tests_passed, g_tests_run, g_tests_failed);
 
     if (g_tests_failed > 0) {