--- /dev/null
+"""
+AdrOS GDB Scripted Integrity Checks
+
+Usage:
+ qemu-system-i386 -s -S ... &
+ gdb adros-x86.bin -x tests/gdb_checks.py
+
+Runs automated checks on kernel state after boot:
+- Heap integrity (magic numbers, free list consistency)
+- PMM bitmap sanity (frame 0 always used, kernel region used)
+- VGA mapping present at 0xC00B8000
+"""
+
+import gdb
+import sys
+
+class AdrOSCheck:
+ def __init__(self):
+ self.passed = 0
+ self.failed = 0
+
+ def check(self, name, condition, detail=""):
+ if condition:
+ print(f" PASS {name}")
+ self.passed += 1
+ else:
+ print(f" FAIL {name} {detail}")
+ self.failed += 1
+
+ def read_u32(self, addr):
+ """Read a uint32_t from a kernel address."""
+ try:
+ val = gdb.parse_and_eval(f"*(uint32_t*){addr:#x}")
+ return int(val) & 0xFFFFFFFF
+ except gdb.error:
+ return None
+
+ def read_u8(self, addr):
+ """Read a uint8_t from a kernel address."""
+ try:
+ val = gdb.parse_and_eval(f"*(uint8_t*){addr:#x}")
+ return int(val) & 0xFF
+ except gdb.error:
+ return None
+
+def run_checks():
+ c = AdrOSCheck()
+
+ print("\n=========================================")
+ print(" AdrOS GDB Integrity Checks")
+ print("=========================================\n")
+
+ # Connect to QEMU
+ try:
+ gdb.execute("target remote :1234", to_string=True)
+ except gdb.error as e:
+ print(f" ERROR: Cannot connect to QEMU: {e}")
+ return 1
+
+ # Load symbols
+ try:
+ gdb.execute("file adros-x86.bin", to_string=True)
+ except gdb.error:
+ pass
+
+ # Set a breakpoint after init completes (on the idle loop)
+ # We'll break on uart_print of "[init]" to know userspace started
+ gdb.execute("break *process_init", to_string=True)
+ gdb.execute("continue", to_string=True)
+
+ # Now we're at process_init — heap and PMM are initialized.
+ # Step past it to ensure scheduler is set up.
+ gdb.execute("finish", to_string=True)
+
+ # ---- Heap Integrity ----
+ try:
+ head_val = gdb.parse_and_eval("(uint32_t)head")
+ head_addr = int(head_val) & 0xFFFFFFFF
+
+ c.check("Heap head is non-NULL", head_addr != 0)
+ c.check("Heap head is in heap range",
+ head_addr >= 0xD0000000 and head_addr < 0xD4000000,
+ f"head={head_addr:#x}")
+
+ if head_addr != 0:
+ magic = c.read_u32(head_addr)
+ c.check("Heap head magic is 0xCAFEBABE",
+ magic == 0xCAFEBABE,
+ f"magic={magic:#x}" if magic else "read failed")
+ except gdb.error as e:
+ c.check("Heap head accessible", False, str(e))
+
+ # ---- PMM Bitmap ----
+ try:
+ # Frame 0 should always be marked as used (never allocate phys addr 0)
+ bitmap_byte0 = gdb.parse_and_eval("memory_bitmap[0]")
+ byte0 = int(bitmap_byte0) & 0xFF
+ c.check("PMM frame 0 is used (bit 0 of bitmap[0])",
+ (byte0 & 1) == 1,
+ f"bitmap[0]={byte0:#x}")
+
+ # max_frames should be > 0
+ mf = gdb.parse_and_eval("max_frames")
+ max_frames = int(mf)
+ c.check("PMM max_frames > 0", max_frames > 0, f"max_frames={max_frames}")
+
+ # total_memory should be > 0
+ tm = gdb.parse_and_eval("total_memory")
+ total_mem = int(tm)
+ c.check("PMM total_memory > 0", total_mem > 0, f"total_memory={total_mem}")
+ except gdb.error as e:
+ c.check("PMM bitmap accessible", False, str(e))
+
+ # ---- VGA Mapping ----
+ try:
+ # VGA buffer should be mapped at 0xC00B8000
+ vga_val = c.read_u32(0xC00B8000)
+ c.check("VGA mapping at 0xC00B8000 is readable",
+ vga_val is not None,
+ "read failed")
+ except gdb.error as e:
+ c.check("VGA mapping accessible", False, str(e))
+
+ # ---- Summary ----
+ total = c.passed + c.failed
+ print(f"\n {c.passed}/{total} passed, {c.failed} failed")
+
+ if c.failed > 0:
+ print(" RESULT: FAIL\n")
+ else:
+ print(" RESULT: PASS\n")
+
+ # Kill QEMU
+ gdb.execute("kill", to_string=True)
+ gdb.execute("quit", to_string=True)
+
+ return 1 if c.failed > 0 else 0
+
+# Auto-run when loaded
+run_checks()
--- /dev/null
+/*
+ * AdrOS Host-Side Unit Tests for Security-Critical Functions
+ *
+ * Tests: user_range_ok, bitmap operations, eflags sanitization logic
+ *
+ * Compile: gcc -m32 -Iinclude -o build/test_security tests/test_security.c
+ * Run: ./build/test_security
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+/* ---- Minimal test framework ---- */
+static int g_tests_run = 0;
+static int g_tests_passed = 0;
+static int g_tests_failed = 0;
+
+#define TEST(name) static void test_##name(void)
+#define RUN(name) do { \
+ g_tests_run++; \
+ printf(" %-44s ", #name); \
+ test_##name(); \
+ printf("PASS\n"); \
+ g_tests_passed++; \
+} while(0)
+
+#define ASSERT_EQ(a, b) do { \
+ if ((a) != (b)) { \
+ printf("FAIL\n %s:%d: %d != %d\n", __FILE__, __LINE__, (int)(a), (int)(b)); \
+ g_tests_failed++; \
+ return; \
+ } \
+} while(0)
+
+#define ASSERT_TRUE(x) ASSERT_EQ(!!(x), 1)
+#define ASSERT_FALSE(x) ASSERT_EQ(!!(x), 0)
+
+/* ---- Functions under test ---- */
+
+/* From src/kernel/uaccess.c (weak default, with the fix applied) */
+#define USER_ADDR_LIMIT 0xC0000000U
+
+static int user_range_ok(const void* user_ptr, size_t len) {
+ uintptr_t uaddr = (uintptr_t)user_ptr;
+ if (len == 0) return 1;
+ if (uaddr == 0) return 0;
+ uintptr_t end = uaddr + len - 1;
+ if (end < uaddr) return 0; /* overflow */
+ if (uaddr >= USER_ADDR_LIMIT) return 0; /* kernel address */
+ if (end >= USER_ADDR_LIMIT) return 0; /* spans into kernel */
+ return 1;
+}
+
+/* From src/mm/pmm.c — bitmap operations */
+static uint8_t test_bitmap[16]; /* 128 bits = 128 frames */
+
+static void bitmap_set(uint64_t bit) {
+ test_bitmap[bit / 8] |= (uint8_t)(1 << (bit % 8));
+}
+
+static void bitmap_unset(uint64_t bit) {
+ test_bitmap[bit / 8] &= (uint8_t)~(1 << (bit % 8));
+}
+
+static int bitmap_test(uint64_t bit) {
+ return test_bitmap[bit / 8] & (1 << (bit % 8));
+}
+
+/* eflags sanitization logic from sigreturn fix */
+static uint32_t sanitize_eflags(uint32_t eflags) {
+ return (eflags & ~0x3000U) | 0x200U;
+}
+
+/* ======== user_range_ok TESTS ======== */
+
+TEST(urange_null_ptr) {
+ ASSERT_FALSE(user_range_ok(NULL, 10));
+}
+
+TEST(urange_zero_len) {
+ ASSERT_TRUE(user_range_ok(NULL, 0));
+ ASSERT_TRUE(user_range_ok((void*)0x1000, 0));
+}
+
+TEST(urange_valid_user) {
+ ASSERT_TRUE(user_range_ok((void*)0x08048000, 4096));
+}
+
+TEST(urange_kernel_addr) {
+ ASSERT_FALSE(user_range_ok((void*)0xC0000000, 1));
+}
+
+TEST(urange_kernel_addr_high) {
+ ASSERT_FALSE(user_range_ok((void*)0xC0100000, 100));
+}
+
+TEST(urange_spans_boundary) {
+ /* Start in user space, end in kernel space */
+ ASSERT_FALSE(user_range_ok((void*)0xBFFFF000, 0x2000));
+}
+
+TEST(urange_just_below_limit) {
+ ASSERT_TRUE(user_range_ok((void*)0xBFFFFFFF, 1));
+}
+
+TEST(urange_at_limit) {
+ ASSERT_FALSE(user_range_ok((void*)0xC0000000, 1));
+}
+
+TEST(urange_overflow) {
+ /* uaddr + len wraps around */
+ ASSERT_FALSE(user_range_ok((void*)0xFFFFFFFF, 2));
+}
+
+TEST(urange_max_user) {
+ /* Largest valid user range: 0x1 to 0xBFFFFFFF */
+ ASSERT_TRUE(user_range_ok((void*)0x1, 0xBFFFFFFF));
+}
+
+TEST(urange_max_user_plus_one) {
+ /* 0x1 + 0xC0000000 - 1 = 0xC0000000 which is kernel */
+ ASSERT_FALSE(user_range_ok((void*)0x1, 0xC0000000));
+}
+
+/* ======== Bitmap TESTS ======== */
+
+TEST(bitmap_set_and_test) {
+ memset(test_bitmap, 0, sizeof(test_bitmap));
+ bitmap_set(0);
+ ASSERT_TRUE(bitmap_test(0));
+ ASSERT_FALSE(bitmap_test(1));
+}
+
+TEST(bitmap_unset) {
+ memset(test_bitmap, 0xFF, sizeof(test_bitmap));
+ bitmap_unset(7);
+ ASSERT_FALSE(bitmap_test(7));
+ ASSERT_TRUE(bitmap_test(6));
+ ASSERT_TRUE(bitmap_test(8));
+}
+
+TEST(bitmap_cross_byte) {
+ memset(test_bitmap, 0, sizeof(test_bitmap));
+ bitmap_set(7); /* last bit of byte 0 */
+ bitmap_set(8); /* first bit of byte 1 */
+ ASSERT_TRUE(bitmap_test(7));
+ ASSERT_TRUE(bitmap_test(8));
+ ASSERT_FALSE(bitmap_test(6));
+ ASSERT_FALSE(bitmap_test(9));
+}
+
+TEST(bitmap_all_bits) {
+ memset(test_bitmap, 0, sizeof(test_bitmap));
+ for (int i = 0; i < 128; i++) {
+ bitmap_set((uint64_t)i);
+ }
+ for (int i = 0; i < 128; i++) {
+ ASSERT_TRUE(bitmap_test((uint64_t)i));
+ }
+ /* Unset every other bit */
+ for (int i = 0; i < 128; i += 2) {
+ bitmap_unset((uint64_t)i);
+ }
+ for (int i = 0; i < 128; i++) {
+ if (i % 2 == 0) {
+ ASSERT_FALSE(bitmap_test((uint64_t)i));
+ } else {
+ ASSERT_TRUE(bitmap_test((uint64_t)i));
+ }
+ }
+}
+
+/* ======== eflags sanitization TESTS ======== */
+
+TEST(eflags_clears_iopl) {
+ /* IOPL=3 (bits 12-13 set) should be cleared */
+ uint32_t dirty = 0x3000 | 0x200; /* IOPL=3, IF=1 */
+ uint32_t clean = sanitize_eflags(dirty);
+ ASSERT_EQ(clean & 0x3000, 0); /* IOPL cleared */
+ ASSERT_TRUE(clean & 0x200); /* IF still set */
+}
+
+TEST(eflags_sets_if) {
+ /* IF=0 should be forced to IF=1 */
+ uint32_t dirty = 0; /* no flags */
+ uint32_t clean = sanitize_eflags(dirty);
+ ASSERT_TRUE(clean & 0x200); /* IF set */
+}
+
+TEST(eflags_preserves_other) {
+ /* CF=1 (bit 0), ZF=1 (bit 6), SF=1 (bit 7) should be preserved */
+ uint32_t dirty = 0x3000 | 0x01 | 0x40 | 0x80; /* IOPL=3, CF, ZF, SF */
+ uint32_t clean = sanitize_eflags(dirty);
+ ASSERT_TRUE(clean & 0x01); /* CF preserved */
+ ASSERT_TRUE(clean & 0x40); /* ZF preserved */
+ ASSERT_TRUE(clean & 0x80); /* SF preserved */
+ ASSERT_EQ(clean & 0x3000, 0); /* IOPL cleared */
+ ASSERT_TRUE(clean & 0x200); /* IF set */
+}
+
+TEST(eflags_iopl1) {
+ /* IOPL=1 should also be cleared */
+ uint32_t dirty = 0x1000 | 0x200;
+ uint32_t clean = sanitize_eflags(dirty);
+ ASSERT_EQ(clean & 0x3000, 0);
+}
+
+/* ======== MAIN ======== */
+int main(void) {
+ printf("\n=========================================\n");
+ printf(" AdrOS Security Unit Tests\n");
+ printf("=========================================\n\n");
+
+ /* user_range_ok */
+ RUN(urange_null_ptr);
+ RUN(urange_zero_len);
+ RUN(urange_valid_user);
+ RUN(urange_kernel_addr);
+ RUN(urange_kernel_addr_high);
+ RUN(urange_spans_boundary);
+ RUN(urange_just_below_limit);
+ RUN(urange_at_limit);
+ RUN(urange_overflow);
+ RUN(urange_max_user);
+ RUN(urange_max_user_plus_one);
+
+ /* bitmap */
+ RUN(bitmap_set_and_test);
+ RUN(bitmap_unset);
+ RUN(bitmap_cross_byte);
+ RUN(bitmap_all_bits);
+
+ /* eflags */
+ RUN(eflags_clears_iopl);
+ RUN(eflags_sets_if);
+ RUN(eflags_preserves_other);
+ RUN(eflags_iopl1);
+
+ printf("\n %d/%d passed, %d failed\n", g_tests_passed, g_tests_run, g_tests_failed);
+
+ if (g_tests_failed > 0) {
+ printf(" RESULT: FAIL\n\n");
+ return 1;
+ }
+ printf(" RESULT: PASS\n\n");
+ return 0;
+}