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
+30
View File
@@ -145,6 +145,31 @@ pub fn build(b: *std.Build) void {
b.installArtifact(exe);
// --- /sbin/init: the first user-space program ---
// Its own tiny freestanding binary, linked at a fixed address inside the
// kernel's user region (usermode.zig) and started in ring 3 by the kernel's
// user-ELF loader. `.large` because the image base is above 4 GiB — small/
// medium code models emit 32-bit absolute relocations that can't reach.
// Pinned to ReleaseSmall: the user region gives it a 2 MiB budget and its
// size has no reason to track the kernel's optimize mode.
const init_exe = b.addExecutable(.{
.name = "init",
.root_module = b.createModule(.{
.root_source_file = b.path("sbin/init.zig"),
.target = kernel_target,
.optimize = .ReleaseSmall,
.code_model = .large,
.single_threaded = true,
.sanitize_c = .off,
.stack_check = false,
.stack_protector = false,
}),
});
init_exe.setLinkerScript(b.path("sbin/linker.ld"));
init_exe.entry = .{ .symbol_name = "_start" };
init_exe.image_base = 0x7000_0000_0000;
b.installArtifact(init_exe);
// Boot methods live in src/boot/, one per way of getting the kernel running.
// Each is its own binary/entry (a loader is built for its own target); today
// that's UEFI for x86-64, with room for e.g. a device-tree path for the Pis.
@@ -203,6 +228,10 @@ pub fn build(b: *std.Build) void {
const kernel_install = b.addInstallArtifact(exe, .{
.dest_dir = .{ .override = .{ .custom = "esp" } },
});
// The bootloader loads init from sbin/init on the same volume.
const init_install = b.addInstallArtifact(init_exe, .{
.dest_dir = .{ .override = .{ .custom = "esp/sbin" } },
});
// The firmware needs to write NVRAM, so give it a writable copy of the vars.
const vars_copy = b.addSystemCommand(&.{ "cp", "-f", ovmf_vars });
@@ -239,6 +268,7 @@ pub fn build(b: *std.Build) void {
run_efi.addArgs(&.{ "-serial", b.fmt("file:{s}", .{serial_log}) });
run_efi.step.dependOn(&efi_install.step);
run_efi.step.dependOn(&kernel_install.step);
run_efi.step.dependOn(&init_install.step);
const run_efi_step = b.step("run-x86-64", "Boot the x86-64 kernel in QEMU (UEFI/OVMF); serial0 is logged to zig-out/run-x86-64-serial0-<timestamp>.log");
run_efi_step.dependOn(&run_efi.step);