Hardened paging into a real VMM
This commit is contained in:
+60
-13
@@ -33,17 +33,33 @@ fn check(name: []const u8, ok: bool) void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit the overall result line the harness matches, then the done sentinel.
|
||||
fn result() void {
|
||||
log("DANOS-TEST-RESULT: {s} ({d} passed, {d} failed)\n", .{
|
||||
if (failed == 0) "PASS" else "FAIL",
|
||||
passed,
|
||||
failed,
|
||||
});
|
||||
log("DANOS-TEST-DONE\n", .{});
|
||||
}
|
||||
|
||||
pub fn run(case: []const u8, boot_info: *const BootInfo) void {
|
||||
if (eql(case, "smoke")) {
|
||||
smoke(boot_info);
|
||||
} else if (eql(case, "timer")) {
|
||||
timer();
|
||||
} else if (eql(case, "vmm")) {
|
||||
vmm();
|
||||
} else if (eql(case, "fault-ud")) {
|
||||
faultInvalidOpcode();
|
||||
} else if (eql(case, "fault-pf")) {
|
||||
faultPageFault();
|
||||
} else if (eql(case, "fault-df")) {
|
||||
faultDoubleFault();
|
||||
} else if (eql(case, "fault-nx")) {
|
||||
faultNoExecute();
|
||||
} else if (eql(case, "fault-null")) {
|
||||
faultNull();
|
||||
} else {
|
||||
log("DANOS-TEST-RESULT: FAIL (unknown case '{s}')\n", .{case});
|
||||
}
|
||||
@@ -85,12 +101,7 @@ fn smoke(boot_info: *const BootInfo) void {
|
||||
const cr3 = arch.readCr3();
|
||||
check("paging active (CR3 set)", cr3 != 0 and cr3 % danos.page_size == 0);
|
||||
|
||||
log("DANOS-TEST-RESULT: {s} ({d} passed, {d} failed)\n", .{
|
||||
if (failed == 0) "PASS" else "FAIL",
|
||||
passed,
|
||||
failed,
|
||||
});
|
||||
log("DANOS-TEST-DONE\n", .{});
|
||||
result();
|
||||
}
|
||||
|
||||
/// Verify device interrupts fire and return: the timer tick counter must advance
|
||||
@@ -105,12 +116,26 @@ fn timer() void {
|
||||
while (arch.ticks() == start and spins < 5_000_000_000) spins +%= 1;
|
||||
check("timer interrupts advance the tick count", arch.ticks() > start);
|
||||
|
||||
log("DANOS-TEST-RESULT: {s} ({d} passed, {d} failed)\n", .{
|
||||
if (failed == 0) "PASS" else "FAIL",
|
||||
passed,
|
||||
failed,
|
||||
});
|
||||
log("DANOS-TEST-DONE\n", .{});
|
||||
result();
|
||||
}
|
||||
|
||||
/// Verify the on-demand VMM: map a fresh frame at an unused virtual address, and
|
||||
/// check it's writable and reads back.
|
||||
fn vmm() void {
|
||||
log("DANOS-TEST-BEGIN: vmm\n", .{});
|
||||
const frame = pmm.alloc();
|
||||
check("frame available to map", frame != null);
|
||||
if (frame) |phys| {
|
||||
var virt: u64 = 0x0000_4000_0000_0000; // canonical, well clear of everything mapped
|
||||
arch.mapPage(virt, phys, true);
|
||||
const p: *volatile u64 = @ptrFromInt(virt);
|
||||
p.* = 0xdead_c0de_cafe_babe;
|
||||
check("mapped page is writable and reads back", p.* == 0xdead_c0de_cafe_babe);
|
||||
arch.unmapPage(virt);
|
||||
pmm.free(phys);
|
||||
virt += 0;
|
||||
}
|
||||
result();
|
||||
}
|
||||
|
||||
fn faultInvalidOpcode() void {
|
||||
@@ -118,11 +143,33 @@ fn faultInvalidOpcode() void {
|
||||
asm volatile ("ud2");
|
||||
}
|
||||
|
||||
/// Verify NX: fetching an instruction from a data page (mapped no-execute) faults.
|
||||
fn faultNoExecute() void {
|
||||
log("DANOS-TEST-BEGIN: fault-nx\n", .{});
|
||||
var scratch: u64 = 0xC3; // a lone `ret` — harmless if NX somehow let it run
|
||||
const f: *const fn () void = @ptrFromInt(@intFromPtr(&scratch));
|
||||
f(); // instruction fetch from an NX page -> #PF before it executes
|
||||
log("DANOS-TEST-RESULT: FAIL (NX not enforced)\n", .{});
|
||||
}
|
||||
|
||||
/// Verify the null guard: dereferencing address 0 (page 0 left unmapped) faults.
|
||||
fn faultNull() void {
|
||||
log("DANOS-TEST-BEGIN: fault-null\n", .{});
|
||||
// Launder the address through empty asm so the compiler no longer knows it's
|
||||
// 0 (otherwise it folds a null-pointer safety panic instead of doing the real
|
||||
// access). `allowzero` skips the same null check on the cast. The write then
|
||||
// hits the unmapped page 0 and takes a real hardware #PF.
|
||||
var addr: u64 = 0;
|
||||
addr = asm ("" : [ret] "=r" (-> u64) : [in] "0" (addr));
|
||||
const p: *allowzero volatile u64 = @ptrFromInt(addr);
|
||||
p.* = 1;
|
||||
}
|
||||
|
||||
fn faultPageFault() void {
|
||||
log("DANOS-TEST-BEGIN: fault-pf\n", .{});
|
||||
// Runtime address so the backend emits a register store (not a `mov moffs`,
|
||||
// which the self-hosted x86_64 backend can't encode).
|
||||
var addr: u64 = 0xdeadbeef000; // above our identity-mapped 4 GiB
|
||||
var addr: u64 = 0xdeadbeef000; // well above all mapped RAM
|
||||
const p: *volatile u64 = @ptrFromInt(addr);
|
||||
p.* = 1;
|
||||
addr += 0;
|
||||
|
||||
Reference in New Issue
Block a user