From 594086495806ce02d31c473eba963cc3518a5f16 Mon Sep 17 00:00:00 2001 From: Daniel Samson <12231216+daniel-samson@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:46:53 +0100 Subject: [PATCH] test the trampoline is inert (zeroed + NX) when dormant Adds paging.isExecutable and arch.trampolinePage; the smp case now asserts the frame is zeroed and non-executable after bring-up. Teeth-checked against a no-op disarm. --- src/kernel/arch/x86_64/cpu.zig | 10 ++++++++++ src/kernel/arch/x86_64/paging.zig | 15 +++++++++++++++ src/kernel/arch/x86_64/smp.zig | 6 ++++++ src/kernel/tests.zig | 14 ++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/src/kernel/arch/x86_64/cpu.zig b/src/kernel/arch/x86_64/cpu.zig index 2e1ef31..da9181d 100644 --- a/src/kernel/arch/x86_64/cpu.zig +++ b/src/kernel/arch/x86_64/cpu.zig @@ -130,6 +130,16 @@ pub fn testFailNextWakes(n: u32) void { smp.testFailNextWakes(n); } +/// The reserved AP-trampoline frame (0 if none). For tests that check it's inert. +pub fn trampolinePage() u64 { + return smp.trampolinePage(); +} + +/// Whether the page at `virt` is currently mapped executable (present, NX clear). +pub fn pageExecutable(virt: u64) bool { + return paging.isExecutable(virt); +} + /// Kernel tick rate: 1000 Hz (1 ms), the scheduler's time quantum. pub const timer_hz = 1000; diff --git a/src/kernel/arch/x86_64/paging.zig b/src/kernel/arch/x86_64/paging.zig index 05318bb..50ef987 100644 --- a/src/kernel/arch/x86_64/paging.zig +++ b/src/kernel/arch/x86_64/paging.zig @@ -128,6 +128,21 @@ pub fn map(virt: u64, phys: u64, writable_page: bool) void { invalidate(virt); } +/// Whether `virt` is currently mapped **executable** — present with the NX bit +/// clear. Walks the 4-level tables (all danos mappings are 4 KiB, so no huge-page +/// case). Returns false if unmapped. Used for W^X checks in tests. +pub fn isExecutable(virt: u64) bool { + const pml4e = tableAt(kernel_pml4)[(virt >> 39) & 0x1FF]; + if (pml4e & present == 0) return false; + const pdpte = tableAt(pml4e & addr_mask)[(virt >> 30) & 0x1FF]; + if (pdpte & present == 0) return false; + const pde = tableAt(pdpte & addr_mask)[(virt >> 21) & 0x1FF]; + if (pde & present == 0) return false; + const pte = tableAt(pde & addr_mask)[(virt >> 12) & 0x1FF]; + if (pte & present == 0) return false; + return pte & no_execute == 0; +} + /// Make an already-identity-mapped RAM page **executable** (clear its NX bit), /// leaving it present and writable. The blanket RAM mapping is NX for W^X, but the /// application processors fetch the AP trampoline from a low RAM page under paging — diff --git a/src/kernel/arch/x86_64/smp.zig b/src/kernel/arch/x86_64/smp.zig index e1f6ad4..1b849cc 100644 --- a/src/kernel/arch/x86_64/smp.zig +++ b/src/kernel/arch/x86_64/smp.zig @@ -66,6 +66,12 @@ pub fn setTrampolinePage(phys: u64) void { tramp_phys = phys; } +/// The reserved trampoline frame (0 if SMP bring-up never ran). Exposed so a test +/// can verify it's inert — zeroed and non-executable — when dormant. +pub fn trampolinePage() u64 { + return tramp_phys; +} + /// Arm the trampoline for a wake: make its page executable (W^X exception for the /// duration of the climb) and copy the blob in. fn arm() void { diff --git a/src/kernel/tests.zig b/src/kernel/tests.zig index d42f7bf..ebedca9 100644 --- a/src/kernel/tests.zig +++ b/src/kernel/tests.zig @@ -483,6 +483,20 @@ fn smpTest() void { } log("DANOS-SMP: workers ran on {d} distinct core(s)\n", .{cores_seen}); check("tasks ran on multiple cores in parallel", cores_seen >= 2); + + // Bring-up is done, so the trampoline frame must be inert: zeroed (no stale code) + // and non-executable (W^X restored). It's armed only while a core is climbing. + const tramp = arch.trampolinePage(); + check("trampoline frame reserved", tramp != 0); + if (tramp != 0) { + const bytes: [*]const u8 = @ptrFromInt(tramp); + var zeroed = true; + for (0..4096) |b| { + if (bytes[b] != 0) zeroed = false; + } + check("trampoline page zeroed when dormant", zeroed); + check("trampoline page non-executable when dormant", !arch.pageExecutable(tramp)); + } result(); }