From 4a4ec248f7a24b944bfcc5509bffffbcf4dc7f22 Mon Sep 17 00:00:00 2001 From: Inquisitor-201 <1365605830@qq.com> Date: Tue, 19 May 2026 22:17:25 +0800 Subject: [PATCH 1/2] feat: detect and diagnose hvisor memory region overlap with root zone Add two mitigations for the recurring "memory stomping" problem where hvisor's physical memory falls within root zone MEM_TYPE_RAM regions: 1. Compile-time overlap check (tools/check_hv_mem_overlap.py): Post-link script that reads skernel/__hv_end from the ELF, parses ROOT_ZONE_MEMORY_REGIONS from board.rs, and fails the build if any MEM_TYPE_RAM region overlaps hvisor's range. 2. Runtime diagnostic in aarch64 trap handler: When handle_dabt gets a stage-2 fault whose address falls within hvisor's physical memory range [skernel, __hv_end), it prints the actual cause before panicking instead of the generic error. Closes: #310 Related: #309 Co-Authored-By: Claude Opus 4.7 --- Makefile | 11 +- src/arch/aarch64/trap.rs | 37 +++++++ tools/check_hv_mem_overlap.py | 195 ++++++++++++++++++++++++++++++++++ 3 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 tools/check_hv_mem_overlap.py diff --git a/Makefile b/Makefile index f0034a09..55497c84 100644 --- a/Makefile +++ b/Makefile @@ -87,8 +87,8 @@ COLOR_BOLD := $(shell tput bold) COLOR_RESET := $(shell tput sgr0) # Targets -.PHONY: all elf disa run gdb monitor clean tools rootfs vscode ci-run -all: clean_check gen_cargo_config vscode $(hvisor_bin) +.PHONY: all elf disa run gdb monitor clean tools rootfs vscode ci-run check-hv-mem-overlap +all: clean_check gen_cargo_config vscode $(hvisor_bin) check-hv-mem-overlap @printf "\n" @printf "$(COLOR_GREEN)$(COLOR_BOLD)hvisor build summary:$(COLOR_RESET)\n" @printf "%-10s %s\n" "ARCH =" "$(COLOR_BOLD)$(ARCH)$(COLOR_RESET)" @@ -126,6 +126,13 @@ gen_cargo_config: ./tools/gen_cargo_config.sh @printf "$(COLOR_GREEN)$(COLOR_BOLD)generating .cargo/config.toml success!$(COLOR_RESET)\n" +check-hv-mem-overlap: $(hvisor_bin) platform/$(ARCH)/$(BOARD)/board.rs + @printf "$(COLOR_GREEN)$(COLOR_BOLD)checking hvisor memory vs root zone regions...$(COLOR_RESET)\n" + @python3 tools/check_hv_mem_overlap.py $(hvisor_elf) platform/$(ARCH)/$(BOARD)/board.rs && \ + printf "$(COLOR_GREEN)$(COLOR_BOLD)check passed!$(COLOR_RESET)\n" || \ + (printf "$(COLOR_RED)$(COLOR_BOLD)OVERLAP DETECTED! See details above.$(COLOR_RESET)\n" && \ + false) + vscode: @printf "$(COLOR_GREEN)$(COLOR_BOLD)generating .vscode/settings.json...$(COLOR_RESET)\n" ./tools/gen_vscode_settings.sh diff --git a/src/arch/aarch64/trap.rs b/src/arch/aarch64/trap.rs index ca4bdb5a..4590f075 100644 --- a/src/arch/aarch64/trap.rs +++ b/src/arch/aarch64/trap.rs @@ -212,6 +212,42 @@ fn handle_iabt(_regs: &mut GeneralRegisters) { // arch_skip_instruction(frame); } +/// Returns the start and end of hvisor's physical memory range [hv_start, hv_end). +fn hvisor_mem_range() -> (usize, usize) { + extern "C" { + fn skernel(); + fn __hv_end(); + } + (skernel as usize, __hv_end as usize) +} + +/// Check if `addr` falls within hvisor's own physical memory range. +/// If true, print diagnostic suggesting the guest DTB/config is wrong. +fn check_fault_in_hvisor_mem(fault_addr: usize) -> bool { + let (hv_start, hv_end) = hvisor_mem_range(); + if (hv_start..hv_end).contains(&fault_addr) { + error!( + "FAULT ADDRESS {:#x} is within hvisor's physical memory range [{:#x}, {:#x})", + fault_addr, hv_start, hv_end, + ); + error!( + "LIKELY CAUSE: the guest device tree (DTB) or memory config \ + includes hvisor's physical address range." + ); + error!( + "FIX: ensure the guest's DTB and zone memory_regions exclude \ + the hvisor range [{:#x}, {:#x}).", + hv_start, hv_end, + ); + if is_this_root_zone() { + error!(" For root zone: also check ROOT_ZONE_MEMORY_REGIONS in board.rs."); + } + true + } else { + false + } +} + fn handle_dabt(regs: &mut GeneralRegisters) { let iss = ESR_EL2.read(ESR_EL2::ISS); let is_write = (iss >> 6 & 0x1) != 0; @@ -248,6 +284,7 @@ fn handle_dabt(regs: &mut GeneralRegisters) { } } Err(e) => { + check_fault_in_hvisor_mem(address as usize); error!("mmio_handle_access: {:#x?}", e); zone_error(); } diff --git a/tools/check_hv_mem_overlap.py b/tools/check_hv_mem_overlap.py new file mode 100644 index 00000000..e3ffe761 --- /dev/null +++ b/tools/check_hv_mem_overlap.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +""" +Post-link overlap check for hvisor. + +Verifies that no MEM_TYPE_RAM region in ROOT_ZONE_MEMORY_REGIONS overlaps +with hvisor's own physical memory range [skernel, __hv_end). + +Without this check, the root zone's Linux may allocate and write to physical +pages that hvisor uses for its page tables and heap (memory stomping), +causing "unhandled MMIO fault" panics when those corrupted page tables +are traversed. + +Usage: + python3 tools/check_hv_mem_overlap.py + +Returns exit code 0 if no overlap, 1 if overlap detected or error. +""" + +import re +import subprocess +import sys + + +def get_symbol_value(elf_path: str, symbol: str) -> int | None: + """Read a symbol value from the ELF using rust-nm.""" + result = subprocess.run( + ["rust-nm", elf_path], + capture_output=True, + text=True, + ) + for line in result.stdout.splitlines(): + parts = line.split() + if len(parts) >= 3 and parts[2] == symbol: + return int(parts[0], 16) + return None + + +def strip_rust_comments(text: str) -> str: + """Strip Rust line comments (//) and block comments (/* ... */) from text.""" + # Strip block comments first (non-greedy) + text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL) + # Strip line comments + text = re.sub(r"//[^\n]*", "", text) + return text + + +def parse_root_zone_memory_regions(board_rs_path: str) -> list: + """Parse ROOT_ZONE_MEMORY_REGIONS from board.rs. + + Returns list of (mem_type, physical_start, size) tuples. + """ + with open(board_rs_path) as f: + content = f.read() + + # Strip comments before parsing + content = strip_rust_comments(content) + + # Find the ROOT_ZONE_MEMORY_REGIONS array + match = re.search( + r"pub\s+const\s+ROOT_ZONE_MEMORY_REGIONS\s*:\s*&\[HvConfigMemoryRegion\]\s*=\s*&\[(.*?)\];", + content, + re.DOTALL, + ) + if not match: + print(f"WARN: ROOT_ZONE_MEMORY_REGIONS not found in {board_rs_path}, skipping check", + file=sys.stderr) + return [] + + regions_str = match.group(1) + + regions = [] + # Match each HvConfigMemoryRegion block + for block_match in re.finditer( + r"HvConfigMemoryRegion\s*\{(.*?)\}", regions_str, re.DOTALL + ): + block = block_match.group(1) + + mem_type_m = re.search(r"mem_type\s*:\s*(MEM_TYPE_\w+)", block) + pstart_m = re.search(r"physical_start\s*:\s*(0x[0-9a-fA-F]+)", block) + size_m = re.search(r"size\s*:\s*(0x[0-9a-fA-F]+)", block) + + if mem_type_m and pstart_m and size_m: + regions.append(( + mem_type_m.group(1), + int(pstart_m.group(1), 16), + int(size_m.group(1), 16), + )) + + return regions + + +def do_regions_overlap(start_a: int, end_a: int, start_b: int, end_b: int) -> bool: + """Check if [start_a, end_a) overlaps with [start_b, end_b).""" + return start_a < end_b and start_b < end_a + + +def term_bold(text: str) -> str: + """Wrap text in ANSI bold escape codes.""" + return f"\033[1m{text}\033[22m" + + +def term_red(text: str) -> str: + """Wrap text in ANSI red escape codes.""" + return f"\033[31m{text}\033[39m" + + +def term_bold_red(text: str) -> str: + """Wrap text in ANSI bold+red escape codes.""" + return f"\033[1;31m{text}\033[0m" + + +def main() -> int: + if len(sys.argv) < 3: + print(f"Usage: {sys.argv[0]} ", file=sys.stderr) + return 1 + + elf_path = sys.argv[1] + board_rs_path = sys.argv[2] + + # Read hvisor memory range from ELF + skernel = get_symbol_value(elf_path, "skernel") + hv_end = get_symbol_value(elf_path, "__hv_end") + + if skernel is None: + print(f"WARN: symbol 'skernel' not found in {elf_path}, skipping check", + file=sys.stderr) + return 0 + if hv_end is None: + print(f"WARN: symbol '__hv_end' not found in {elf_path}, skipping check", + file=sys.stderr) + return 0 + + # Parse root zone memory regions + regions = parse_root_zone_memory_regions(board_rs_path) + if not regions: + return 0 + + # Check each MEM_TYPE_RAM region for overlap + found_overlap = False + for mem_type, pstart, size in regions: + pend = pstart + size + if mem_type != "MEM_TYPE_RAM": + continue + if do_regions_overlap(pstart, pend, skernel, hv_end): + overlap_start = max(pstart, skernel) + overlap_end = min(pend, hv_end) + overlap_bytes = overlap_end - overlap_start + + # --- bold/red error header --- + print() + print(term_bold_red("╔══════════════════════════════════════════════════════════════╗")) + print(term_bold_red("║ HVISOR MEMORY REGION OVERLAP! ║")) + print(term_bold_red("╚══════════════════════════════════════════════════════════════╝")) + print() + + # --- what happened --- + print(f" hvisor physical memory: {term_bold(f'[{skernel:#x}, {hv_end:#x})')}") + print(f" overlaps ROOT_ZONE_MEMORY_REGIONS RAM range: " + f"{term_bold(f'[{pstart:#x}, {pend:#x})')}") + print(f" overlap: {term_bold_red(f'{overlap_bytes} bytes')} " + f"({term_bold(f'[{overlap_start:#x}, {overlap_end:#x})')})") + print() + + # --- danger description --- + print(f" {term_bold_red('DANGER')}: Linux in the root zone treats hvisor's") + print(f" physical pages as free memory. The kernel page allocator will") + print(f" hand them out to kernel or user code and write to them,") + print(f" {term_bold_red('corrupting hvisor page tables')} and causing unrecoverable panics.") + print() + + # --- fix instructions --- + print(f" {term_bold('Fix')}:") + print() + print(f" 1. Edit ROOT_ZONE_MEMORY_REGIONS in {term_bold(board_rs_path)}") + print(f" to exclude the hvisor range [{skernel:#x}, {hv_end:#x})") + print(f" from the overlapping MEM_TYPE_RAM region.") + print() + print(f" 2. Reduce hvisor's memory footprint to avoid overlap:") + print(f" - Ensure MODE=release (shrinks binary size)") + print(f" - Reduce HV_MEM_POOL_SIZE (src/consts.rs, currently 64MB)") + print(f" - Adjust BASE_ADDRESS in the linker script") + print() + + found_overlap = True + + if found_overlap: + return 1 + + print(f"OK: hvisor [{term_bold(f'{skernel:#x}')}, {term_bold(f'{hv_end:#x}')}) " + f"does not overlap any ROOT_ZONE_MEMORY_REGIONS RAM region") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 545716771af1d0582922e0eb6765491b969efb89 Mon Sep 17 00:00:00 2001 From: Inquisitor-201 <1365605830@qq.com> Date: Tue, 19 May 2026 22:36:23 +0800 Subject: [PATCH 2/2] fix: exclude hvisor memory range from root zone RAM regions for rk3568 and riscv64 qemu-plic Split overlapping MEM_TYPE_RAM regions in board.rs to prevent memory stomping where Linux in the root zone would write to pages owned by hvisor (page tables, heap), causing unrecoverable panics. Co-Authored-By: Claude Opus 4.7 --- platform/aarch64/rk3568/board.rs | 10 ++++++++-- platform/riscv64/qemu-plic/board.rs | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/platform/aarch64/rk3568/board.rs b/platform/aarch64/rk3568/board.rs index 32ed5d3c..a7618b52 100644 --- a/platform/aarch64/rk3568/board.rs +++ b/platform/aarch64/rk3568/board.rs @@ -139,8 +139,14 @@ pub const ROOT_ZONE_MEMORY_REGIONS: &[HvConfigMemoryRegion] = &[ mem_type: MEM_TYPE_RAM, physical_start: 0x9400000, virtual_start: 0x9400000, - size: 0xe6c00000, - }, // memory + size: 0x56c00000, + }, // memory (before hvisor) + HvConfigMemoryRegion { + mem_type: MEM_TYPE_RAM, + physical_start: 0x64600000, + virtual_start: 0x64600000, + size: 0x8ba00000, + }, // memory (after hvisor) HvConfigMemoryRegion { mem_type: MEM_TYPE_RAM, physical_start: 0x0, diff --git a/platform/riscv64/qemu-plic/board.rs b/platform/riscv64/qemu-plic/board.rs index 80c278c2..6622c121 100644 --- a/platform/riscv64/qemu-plic/board.rs +++ b/platform/riscv64/qemu-plic/board.rs @@ -47,9 +47,9 @@ pub const ROOT_ZONE_NAME: &str = "root-linux"; pub const ROOT_ZONE_MEMORY_REGIONS: &[HvConfigMemoryRegion] = &[ HvConfigMemoryRegion { mem_type: MEM_TYPE_RAM, - physical_start: 0x83000000, - virtual_start: 0x83000000, - size: 0x7D000000, + physical_start: 0x84600000, + virtual_start: 0x84600000, + size: 0x7BA00000, }, // ram HvConfigMemoryRegion { mem_type: MEM_TYPE_IO,