isolation M1: ring 3 + a real /sbin/init, end to end

Ring 3 works: user GDT descriptors (sysret-ready layout), TSS.rsp0,
U/S-bit user mappings (W^X preserved), an int 0x80 syscall gate with a
mutable trap frame, and a setjmp-style enter/exit path. /sbin/init is a
real freestanding Zig binary built from sbin/, shipped on the ESP,
loaded by the bootloader (BootInfo.init_base/len), validated and mapped
by an in-kernel user-ELF loader, and run at CPL 3 — syscalls: exit,
ping, write. Tests: user, user-pf (U/S isolation proof, error code
0x5), init. Suite 27/27.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Daniel Samson
2026-07-08 22:15:07 +01:00
co-authored by Claude Fable 5
parent 7501bd1703
commit 546dd44a2a
17 changed files with 838 additions and 25 deletions
+68
View File
@@ -17,6 +17,7 @@ const pmm = @import("pmm.zig");
const heap = @import("heap.zig");
const sched = @import("scheduler.zig");
const ipc = @import("ipc.zig");
const usermode = @import("usermode.zig");
/// Formatted write straight to serial, independent of the framebuffer console.
fn log(comptime fmt: []const u8, args: anytype) void {
@@ -92,6 +93,12 @@ pub fn run(case: []const u8, boot_info: *const BootInfo) void {
faultNoExecute();
} else if (eql(case, "fault-null")) {
faultNull();
} else if (eql(case, "user")) {
userTest();
} else if (eql(case, "user-pf")) {
userPfTest();
} else if (eql(case, "init")) {
initTest(boot_info);
} else if (eql(case, "poweroff")) {
powerTest(.off);
} else if (eql(case, "reboot")) {
@@ -702,6 +709,67 @@ fn smpRetryTest() void {
result();
}
// --- ring 3 (user mode) -----------------------------------------------------
/// The full ring-3 round trip: enter user mode, take syscalls and timer
/// interrupts from CPL 3, and come back. Preemption is disabled for the run —
/// enter_user publishes TSS.rsp0 on *this* core, so the task must not migrate
/// (interrupts still fire and iretq back into ring 3, which is the point).
fn userTest() void {
log("DANOS-TEST-BEGIN: user\n", .{});
sched.setPreemption(false);
const ran = if (usermode.run(usermode.helloBlob())) true else |err| blk: {
log("DANOS-USER: run failed: {s}\n", .{@errorName(err)});
break :blk false;
};
sched.setPreemption(true);
check("user program ran and exited (ring-3 round trip)", ran);
check("two ping syscalls received", usermode.ping_count == 2);
check("syscall args passed in registers (0xC0DE, 0xBEEF)", usermode.pings[0].value == 0xC0DE and usermode.pings[1].value == 0xBEEF);
check("syscalls came from CPL 3 (CS = user selector | RPL 3)", usermode.pings[0].cs == 0x23 and usermode.pings[1].cs == 0x23);
check("timer ticks advanced while in ring 3", usermode.pings[1].ticks > usermode.pings[0].ticks);
result();
}
/// Isolation: a ring-3 read of a kernel-only page (the LAPIC page — present,
/// supervisor) must page-fault with error code 0x5 (present | user) at the user
/// RIP. The fault report is the pass signal (matched by the harness); if the
/// read is somehow allowed the blob spins and the harness times out.
fn userPfTest() void {
log("DANOS-TEST-BEGIN: user-pf\n", .{});
sched.setPreemption(false);
_ = usermode.run(usermode.pfBlob()) catch {};
log("DANOS-TEST-RESULT: FAIL (user read of kernel memory did not fault)\n", .{});
}
/// The full user-binary path: the bootloader read sbin/init off the boot
/// volume and handed it over; load it as a user ELF and run it in ring 3. The
/// same call the normal boot path makes — here with teeth.
fn initTest(boot_info: *const BootInfo) void {
log("DANOS-TEST-BEGIN: init\n", .{});
check("bootloader handed over sbin/init", boot_info.init_len != 0);
if (boot_info.init_len == 0) {
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_info.init_base))[0..boot_info.init_len];
sched.setPreemption(false); // see userTest: pins the run to this core's rsp0
const code = usermode.runInitElf(image);
sched.setPreemption(true);
if (code) |c| {
check("init loaded, ran, and exited (user ELF path)", true);
check("init exited cleanly (code 0)", c == 0);
} else |err| {
log("DANOS-INIT-ERR: {s}\n", .{@errorName(err)});
check("init loaded, ran, and exited (user ELF path)", false);
}
check("init's write arrived intact", eql(usermode.write_buf[0..usermode.write_len], "init: hello from user space\n"));
check("write came from CPL 3 (CS = user selector | RPL 3)", usermode.write_cs == 0x23);
result();
}
fn faultInvalidOpcode() void {
log("DANOS-TEST-BEGIN: fault-ud\n", .{});
asm volatile ("ud2");