riscv64: skip satp Bare-mode switch when already Bare (§V.3 item 7 audit fix)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

arch_early_init() unconditionally cleared satp on every boot, justified
only by behavior observed under QEMU/EDK2 firmware (satp.MODE=10/Sv57,
kernel identity-mapped within it). That reasoning never applied to the
native U-Boot+OpenSBI boot path, where satp is conventionally already 0
at S-mode handoff -- the unconditional clear was likely a harmless no-op
there, but on an unverified assumption.

Fix: read satp.MODE first and return early when it's already 0 (nothing
to switch away from, no safety argument needed). The unconditional
csrw/sfence pair still runs unchanged for the confirmed QEMU/EDK2 case.
No Sv39/Sv48/Sv57 page-table walker built -- out of proportion to this
finding's severity.

3-arch acceptance: amd64/aarch64 don't compile this file, so their runs
are non-regression on untouched files only. riscv64's own boot log
confirms satp.MODE = 0xa at entry, so the mode != 0 branch ran and
"satp cleared -- Bare mode, explicit" printed exactly as before the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
This commit is contained in:
Robert Allan James
2026-09-05 00:17:35 -04:00
co-authored by Claude Sonnet 5
parent 350287850f
commit 90ee8deb6d
10 changed files with 27648 additions and 21 deletions
+47 -8
View File
@@ -26,7 +26,10 @@ extern char __kernel_start[]; /* same pattern pmm.c:67 already uses */
* top past the PMM-free region -- consistent with the kernel's own running
* range being identity-mapped (VA==PA) under firmware's Sv57 table, not
* some high canonical virtual range. That is the basis for the Bare-mode
* switch below being safe. */
* switch below being safe *when firmware leaves a translation active at
* all* -- see arch_early_init()'s own comment (FABRIC-3.md §V.3 item 7
* fix, 2026-09-04) for why that no longer covers every boot path this
* kernel now has. */
static void print_hex64(uint64_t val)
{
char buf[19];
@@ -49,6 +52,27 @@ static void print_hex64(uint64_t val)
* via @c riscv64_install_vectors(). This stub satisfies the common
* @c arch_early_init() call site in @c kernel_main() without any
* RISC-V-specific action.
*
* **Correction, 2026-09-04 (FABRIC-3.md §V.3 item 7's code audit).** The
* unconditional Bare-mode switch this function used to perform was
* justified entirely by behaviour observed under this system's QEMU/EDK2
* RISC-V firmware (@c satp.MODE=10/Sv57, kernel identity-mapped within
* it — see the comment on @c print_hex64() above). That observation
* cannot apply to the native boot path (U-Boot+OpenSBI, no EDK2 at all,
* FABRIC-3.md §V.1) this kernel now also has, and OpenSBI's own S-mode
* handoff convention leaves @c satp already at 0 (Bare) in the common
* case — meaning the switch below was very likely running as a harmless
* no-op there, but on an unverified assumption rather than a checked one.
* Rather than build a general Sv39/Sv48/Sv57 page-table walker just to
* *prove* the running address is identity-mapped before switching (out of
* proportion to this item's real scope), the fix narrows what actually
* needs the QEMU-specific safety argument: **skip the switch entirely
* when @c satp is already 0.** That is the expected case on both a
* from-Bare native boot and any firmware that already left translation
* off, and it needs no safety argument at all — there is nothing to
* switch away from. The unconditional-switch path still exists for the
* confirmed QEMU/EDK2 case (@c mode != 0), where the identity-mapping
* argument above remains the actual justification, unchanged.
*/
void arch_early_init(void)
{
@@ -64,18 +88,33 @@ void arch_early_init(void)
console_puts(" satp.PPN = "); print_hex64(ppn); console_puts("\n");
console_puts(" __kernel_start = "); print_hex64((uint64_t)__kernel_start); console_puts("\n");
if (mode == 0) {
/* Already Bare -- the expected case on a native (OpenSBI) boot.
* Nothing to switch away from, so no identity-mapping assumption
* is needed at all: skipping is strictly safer than an
* unconditional csrw+sfence that would just write back the same
* zero value. */
console_println("riscv64: satp already Bare -- no switch needed (item 4.3.5a)");
return;
}
/* Explicit switch to Bare mode (satp.MODE=0): this kernel builds no
* riscv64 page table of its own (vmm.c's is x86-64-shaped and never
* activated here, per its own load_cr3() no-op outside __x86_64__) and
* has no present use for virtual memory on this ISA, so there is no
* reason to inherit firmware's Sv57 mapping -- which is confirmed to
* have at least one hole (PLIC_THRESHOLD, FABRIC-0.md item 4.3.5a).
* ExitBootServices() has already completed several checkpoints before
* this function runs (ConOut/GOP done, BootServices exited per the
* reason to inherit firmware's mapping -- which is confirmed, for the
* QEMU/EDK2 case this branch was written for, to have at least one
* hole (PLIC_THRESHOLD, FABRIC-0.md item 4.3.5a). ExitBootServices()
* has already completed several checkpoints before this function
* runs on that path (ConOut/GOP done, BootServices exited per the
* "[CKPT 008]" trace), so nothing downstream depends on firmware's
* table surviving. Single asm block: the CSR write and SFENCE.VMA
* must not be separated by a compiler-scheduled memory access (RISC-V
* Privileged Spec §4.2.1, SFENCE.VMA ordering). */
* table surviving there. On the native boot path, reaching this
* branch at all means OpenSBI's handoff left satp non-zero -- not
* the case this kernel has confirmed, so the identity-mapping safety
* argument is unverified here; flagged, not guessed past. Single asm
* block: the CSR write and SFENCE.VMA must not be separated by a
* compiler-scheduled memory access (RISC-V Privileged Spec §4.2.1,
* SFENCE.VMA ordering). */
__asm__ volatile (
"csrw satp, x0\n"
"sfence.vma\n"