From 34d9179a821cbe5631f3578689379873c3fa3172 Mon Sep 17 00:00:00 2001 From: Ben Hillis Date: Thu, 3 Sep 2026 10:17:05 -0700 Subject: [PATCH] loader: keep the page table region out of a large page boundary The paravisor page table region is placed immediately after the relocation region and is deliberately excluded from it, but the boot identity map uses large pages. A loader performing relocation only fixes up a leaf entry when the region it maps overlaps the relocation region (igvm PageTableRelocationBuilder::recurse_fixup uses RangeMap::get_range, which matches on overlap). When the page table region happens to begin exactly on a large page boundary, the leaf entry mapping it does not overlap the relocation region at all and is left identity mapped at its pre-relocation address. cr3 and the page table pages themselves are relocated, so the relocated cr3 has no mapping and the first access to the page tables through the identity map faults with no IDT loaded, triple faulting the VP during boot shim startup. This has been latent and only reproduces when unrelated image growth lands the region on the boundary. Pad by a page so the page table region always shares a large page with the relocation region. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- vm/loader/src/paravisor.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/vm/loader/src/paravisor.rs b/vm/loader/src/paravisor.rs index 3518e36b735..bc801afe160 100644 --- a/vm/loader/src/paravisor.rs +++ b/vm/loader/src/paravisor.rs @@ -409,6 +409,20 @@ where )?; offset += heap_size; + // The page table region lives immediately after the relocation region and + // is deliberately excluded from it, but the identity map built below uses + // large pages. A loader performing relocation only fixes up a leaf entry if + // the region it maps overlaps the relocation region, so if the page table + // region began exactly on a large page boundary the leaf entry mapping it + // would be left identity mapped at its pre-relocation address. The + // relocated cr3 would then be unmapped, and the first access to the page + // tables through the identity map faults with no IDT loaded, triple + // faulting the VP. Pad by a page so the page table region always shares a + // large page with the relocation region. + if offset.is_multiple_of(X64_LARGE_PAGE_SIZE) { + offset += HV_PAGE_SIZE; + } + // The end of memory used by the loader, excluding pagetables. let end_of_underhill_mem = offset; @@ -1155,6 +1169,13 @@ where )?; next_addr += heap_size; + // See the equivalent comment in the x64 loader: the page table region must + // share a large page with the relocation region so that the leaf entry + // mapping it is fixed up when the image is relocated. + if next_addr.is_multiple_of(u64::from(Arm64PageSize::Large)) { + next_addr += HV_PAGE_SIZE; + } + // The end of memory used by the loader, excluding pagetables. let end_of_underhill_mem = next_addr;