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:
co-authored by
Claude Fable 5
parent
7501bd1703
commit
546dd44a2a
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user