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
+20
View File
@@ -57,6 +57,8 @@ ARCHES = {
],
"efi_app": ("EFI/BOOT/BOOTX64.efi", "BOOTX64.efi"), # (dest in ESP, name in zig-out/bin)
"kernel": ("kernel", "kernel"),
# Further files shipped on the ESP: the init user program.
"extra": [("sbin/init", "init")],
# Built as a function so we can splice in per-run paths.
"qemu_args": lambda a, esp, vars_fd, serial: [
"-machine", "q35", "-m", "128M",
@@ -148,6 +150,21 @@ CASES = [
"expect": r"page fault \(vector 14\)",
"fail": r"NX not enforced"},
{"name": "fault-null", "expect": r"page fault \(vector 14\)"},
# Ring 3: a user program runs at CPL 3, makes int 0x80 syscalls, survives
# timer interrupts, and exits back into the kernel.
{"name": "user",
"expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"},
# Isolation: a ring-3 read of a kernel-only page must #PF with error code
# 0x5 (present|user) at the user RIP. ([\s\S] spans lines; `.` doesn't.)
{"name": "user-pf",
"expect": r"page fault \(vector 14\)[\s\S]*error code : 0x5[\s\S]*RIP\s*: 0x00007000000000",
"fail": r"DANOS-TEST-RESULT: FAIL"},
# The real user binary: the bootloader ships sbin/init off the ESP, the
# kernel loads the ELF and runs it in ring 3, and it writes + exits cleanly.
{"name": "init",
"expect": r"DANOS-TEST-RESULT: PASS",
"fail": r"DANOS-TEST-RESULT: FAIL"},
# The ACPI power path succeeds by QEMU *exiting* (S5 off / reset), so match the
# pre-transition marker; the FAIL line only appears if the transition didn't take.
{"name": "poweroff",
@@ -179,6 +196,9 @@ def make_esp(arch):
os.makedirs(os.path.join(esp, os.path.dirname(efi_dest)), exist_ok=True)
shutil.copy(os.path.join(REPO, "zig-out", "bin", efi_name), os.path.join(esp, efi_dest))
shutil.copy(os.path.join(REPO, "zig-out", "bin", kern_name), os.path.join(esp, kern_dest))
for dest, name in arch.get("extra", []):
os.makedirs(os.path.join(esp, os.path.dirname(dest)), exist_ok=True)
shutil.copy(os.path.join(REPO, "zig-out", "bin", name), os.path.join(esp, dest))
return esp