Skip to content

Latest commit

 

History

History
256 lines (197 loc) · 8.13 KB

File metadata and controls

256 lines (197 loc) · 8.13 KB

M2 Devlog — Robust Stack and Exception Hardening

Branch: milestone/M1 (M2 commits applied on top of M1_STABLE) Status: COMPLETE (5 commits, smoke-tested)


Summary

M2 replaces the ad-hoc M1 stack placement (kernel stack in .bss, IST stacks as raw physical pointer casts) with a fully guarded, physmap-aware stack model.

All stacks now live in a dedicated virtual region at 0xFFFFFF8000000000 (PML4[511]). Each stack has explicit guard pages (not-present PTEs) above and below its usable region, mapped at 4 KB granularity from the start.


What Changed

Kernel Main Stack

Before (M1):

  • 16 KB static array in .bss (boot.asm: stack_bottom / stack_top)
  • Guard page attempted post-hoc via vmm_setup_kernel_guard_pages()
  • Guard silently skipped because stack_bottom - PAGE overlapped .data

After (M2):

VA                            Content
──────────────────────────────────────────────────────────
0xFFFFFF8000000000  PT[0]    NOT-PRESENT  ← guard_lo
0xFFFFFF8000001000  PT[1]    RW/NX/P      ← stack frame 0
0xFFFFFF8000002000  PT[2]    RW/NX/P      ← stack frame 1
0xFFFFFF8000003000  PT[3]    RW/NX/P      ← stack frame 2
0xFFFFFF8000004000  PT[4]    RW/NX/P      ← stack frame 3
0xFFFFFF8000005000  PT[5]    NOT-PRESENT  ← implicit (unmapped)
0xFFFFFF8000006000  PT[6]    NOT-PRESENT  ← guard_hi (explicit)

RSP_INIT = 0xFFFFFF8000005000   (end of last usable page, NOT a guard)
TSS.rsp0 = 0xFFFFFF8000005000

IST Stacks

Before (M1):

  • pmm_alloc_frame() × 2 per IST, returned physical address cast directly to virtual (identity mapping assumption)
  • No guard pages; contiguous frame layout made guards impossible
  • IST pointers = physical addresses reinterpreted as VAs

After (M2):

IST1 (Double Fault, IDT IST=1):
  guard_lo : 0xFFFFFF800000F000  (NP)
  usable   : 0xFFFFFF8000010000..0xFFFFFF8000011FFF  (8 KB)
  top      : 0xFFFFFF8000012000  → TSS.ist1
  guard_hi : 0xFFFFFF8000013000  (NP)

IST2 (Page Fault, IDT IST=2):
  guard_lo : 0xFFFFFF8000014000  (NP)
  usable   : 0xFFFFFF8000015000..0xFFFFFF8000016FFF  (8 KB)
  top      : 0xFFFFFF8000017000  → TSS.ist2
  guard_hi : 0xFFFFFF8000018000  (NP)

IST3 (General Protection Fault, IDT IST=3):
  guard_lo : 0xFFFFFF8000019000  (NP)
  usable   : 0xFFFFFF800001A000..0xFFFFFF800001BFFF  (8 KB)
  top      : 0xFFFFFF800001C000  → TSS.ist3
  guard_hi : 0xFFFFFF800001D000  (NP)

All IST stacks share the same PT as the kernel main stack (one 2 MB region).

Initialization Order

M2 reorders early boot to make physmap available before TSS/IDT:

Phase 1 (old .bss stack — interrupts disabled throughout):
  pmm_init*()
  vmm_init()
  vmm_init_physmap()           ← MOVED before tss_init
  vmm_alloc_kernel_stack()     ← NEW
  tss_init(new_rsp)            ← REDESIGNED (vmm_map-based IST)
  trampoline_switch_stack()    ← NEW (switches RSP, calls phase2)

Phase 2 (new guarded kernel stack):
  idt_init()                   ← STI happens here
  heap_init() / sched / drivers / fs / shell

NOTE(M2-B): Steps 3–6 (physmap through stack switch) run without a valid IDT or TSS. Any CPU exception there causes a triple fault. These functions are deterministic (no external I/O, no complex control flow) and do not fault under correct operation. A minimal fallback IDT (no STI) can be added in M3 for safer early-boot debugging if needed.

New Functions

Function File Purpose
vmm_alloc_kernel_stack() mm/vmm.c Allocate + map 16 KB kernel stack with guards
vmm_alloc_ist_stack(...) mm/vmm.c Allocate + map one IST stack with guards
m2_mark_guard(addr) mm/vmm.c Force a PTE to 0 (NP) + INVLPG
m2_map_stack_pages(base,n) mm/vmm.c Bulk-map n pages to fresh PMM frames
trampoline_switch_stack arch/x86/idt_asm.asm Switch RSP + tail-call
kernel_main_phase2() kernel/kernel.c Phase-2 init on new stack

Modified Functions

Function File Change
tss_init(uint64_t rsp0) arch/x86/tss.c New signature; IST via vmm_map
tss_get_ist_bases() arch/x86/tss.c Returns virtual tops, not physical
kernel_main() kernel/kernel.c Shortened to phase-1 only
vmm_setup_kernel_guard_pages() mm/vmm.h Marked DEPRECATED; not called

Commits

  1. e522212 — M2 constants + vmm_alloc_kernel_stack()
  2. 8293beetrampoline_switch_stack assembly trampoline
  3. 2dfb3c5 — Redesign tss_init() with virtual IST mapping
  4. cd409c2 — Split kernel_main + reorder init
  5. (this commit) — Cleanup, deprecation notes, devlog

Testing

Multiboot2 (GRUB)

make iso
qemu-system-x86_64 \
  -cdrom myos.iso \
  -debugcon stdio \
  -global isa-debugcon.iobase=0xe9 \
  -no-reboot -no-shutdown \
  -m 256M

Expected [M2] lines in serial output:

[M2] Allocating kernel stack in dedicated virtual region...
[M2] Kernel stack mapped:
  usable : 0xFFFFFF8000001000 .. 0xFFFFFF8000004FFF (16 KB)
  RSP_INIT: 0xFFFFFF8000005000
  guard_lo: 0xFFFFFF8000000000 (NP)
  guard_hi: 0xFFFFFF8000006000 (NP)
[M2] IST1 mapped: ...
[M2] IST2 mapped: ...
[M2] IST3 mapped: ...
[M2] Initializing TSS with guarded IST stacks...
[M2] TSS.ist1: 0xFFFFFF8000012000
[M2] TSS.ist2: 0xFFFFFF8000017000
[M2] TSS.ist3: 0xFFFFFF800001C000
[M2][OK] TSS loaded with guarded IST stacks
[M2] Stack switch complete. RSP= 0xFFFFFF8000004...

UEFI (OVMF / q35)

make uefi
./run_uefi.sh

Or manually:

qemu-system-x86_64 \
  -machine q35 \
  -bios /usr/share/ovmf/OVMF.fd \
  -drive format=raw,file=fat:rw:dist/ \
  -debugcon stdio \
  -global isa-debugcon.iobase=0xe9 \
  -serial file:/tmp/secos_uefi.log \
  -no-reboot -no-shutdown \
  -m 256M

Log location: /tmp/secos_uefi.log


Invariants Preserved

  • Identity map [0, 512 MB) untouched
  • PMM frame bitmap layout unchanged
  • No heap dependency in early boot
  • UEFI and Multiboot2 paths both work
  • STI not called until after TSS is fully loaded

M2 — Stabilization (M2_STABLE)

Smoke test runner

# Multiboot2 / GRUB
tools/smoke.sh --mb2 [--timeout 20] [--log /tmp/secos_mb2.log]

# UEFI / OVMF / q35
tools/smoke.sh --uefi [--timeout 25] [--log /tmp/secos_uefi.log]

The script builds the target, launches QEMU headless (-display none, -no-reboot -no-shutdown), and interprets the exit code:

Exit code Meaning Result
124 timeout(1) killed QEMU after N seconds — kernel alive PASS
0 QEMU exited on its own — guest reset (triple fault + -no-reboot) FAIL
other QEMU error or dependency missing FAIL

Crash-signature markers (debugcon)

Two lines are emitted to ISA debugcon (port 0xE9) early in the boot sequence, captured by QEMU -debugcon file:/tmp/...:

SECoS build 20260217150022 git:0dad8e0
[M2] Stack switch ok. RSP=0xFFFFFF8000004F58
Marker Emitted from When
SECoS build … git:… kernel_main() phase 1 Immediately after terminal_initialize()
[M2] Stack switch ok. RSP=… kernel_main_phase2() After trampoline, before idt_init()

Presence of both lines in the log confirms: (1) kernel reached C entry, and (2) the full M2 stack switch sequence completed without faulting.

If only line 1 is present, the fault occurred between physmap init and the trampoline (the NOTE(M2-B) window). If neither line is present, the fault occurred in assembly before kernel_main().

Confirmed results (2026-02-17, M2_STABLE)

Mode   Timeout  QEMU exit  RSP                    Result
MB2    20s      124        0xFFFFFF8000004F58      PASS
UEFI   25s      124        0xFFFFFF8000004F58      PASS

New files in stabilization commit

File Purpose
lib/debugcon.h Inline ISA debugcon helpers (debugcon_putchar, _writestring, _print_hex)
tools/smoke.sh Repeatable smoke test runner (--mb2 / --uefi)
run_uefi.sh Wrapper for make run-uefi target → calls tools/smoke.sh --uefi