ADR-038 — Identity heap tear after high GlobalAlloc VAs
- Status: Accepted (identity heap unmapped;
GlobalAllocreturns TTBR1 VAs) when the serial / tests pass - Date: 2026-09-12
Context
Track A / A5 continues identity teardown where safe. ADR-037 unmapped identity .data/.bss/linker stacks after SP relocate. Heap stayed identity-mapped: frame::alloc_contiguous handed out PAs and GlobalAlloc returned those identity VAs (ident: heap-stay).
Heap init already runs after the high-VA jump and the .data tear. High twins of the frame pool exist (L2_HIGH_RAM clone). The next honest cut is to install the first-fit pool at the TTBR1 alias, rewrite leftover identity heap pointers, then unmap the identity heap range. PAN enable and umbrella EL0 isolation are not this cut.
Hypotheses
| ID | Option | Notes |
|---|---|---|
| H1 (chosen) | Install the heap free list at to_high_va(pa), rewrite leftover identity heap words, then unmap identity [heap_pa, heap_pa+HEAP_SIZE) from kernel+user TTBR0. High twins stay. Remaining frames after the heap stay identity-mapped. | Smallest honest tear that does not yank live identity VAs. |
| H2 (rejected) | Unmap identity heap while GlobalAlloc still returns identity VAs. | Silent yank would fault. |
| H3 (rejected) | Claim PAN / “EL0 isolated” / “kernel moved” while tearing the heap. | Isolation is not this mile. PAN is ADR-026. _start stays at 0x4008_0000. |
| H4 (rejected) | Remap only new allocations high and leave the existing identity pool mapped. | Would print a stay marker forever. |
Decision
- High allocator first. After
frame::init,heap::inittakes the same 16-frame run and installs the free list at the TTBR1 alias.GlobalAllocreturns high VAs. Serial later printsident: heap-reloc n=N.N == 0is allowed (init already high). - Rewrite leftover identity heap pointers. Walk the heap pool via the high twin (do not walk live stacks or page-table
.bss). Every 8-byte-aligned word that is an identity address in the heap PA range is rewritten to its TTBR1 alias.heap::initalready installed high VAs, son == 0is the expected path. A leakedBoxplantsidentmagic for the high-twin load. - Unmap identity heap. Unmap
[heap_pa, heap_pa+HEAP_SIZE)from kernel TTBR0 (TLBI VAAE1each low VA). User TTBR0 already omits heap; clearing a present slot is fine. High twins stay. Frames after the heap stay identity-mapped. Serialident: heap lo=… hi=… pages=NwithN >= 1. - Keep
_start/ QEMU-kernelat0x4008_0000. Do not change default-cpu. Do not claim PAN. - Fail-closed probe. New
ident: heap-reloc/ident: heap/ident: heap-fault/ident: heap-high. EL1LDRof a torn identity heap VA is a current-EL translation DABORT. EL1LDRof the high twin still returns the planted free-list header size.scripts/qemu-smoke.shgreps those strings and rejectsident: heap-stay. Replace the ADR-032 / ADR-037 stay marker. - Still Planned (at accept time). PAN enable on
-cpu cortex-a57; EL0 entry withoutTLBI VMALLE1; lower-EL IRQ while standing; umbrella EL0 isolation. Remaining identity RAM after the heap (frame bump pool) is not this cut. EL0 entry withoutVMALLE1is superseded by ADR-039. - NFR-10 text is revised in place (ID unchanged). Threat-model v1.18 at accept (v1.19 after ADR-039). Do not mint NFR-15+.
Honesty
Say “identity heap was unmapped while the high twin stayed and GlobalAlloc returned TTBR1 VAs” only when the serial / tests pass. Do not say:
- the kernel has moved to the high half
- identity mappings were fully torn down (
_startstays; frame pool after the heap stays identity-mapped) - “secure OS” / “hardened” / “EL0 isolated”
- PAN (see ADR-026; unimplemented on
-cpu cortex-a57)