Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions platform/aarch64/rk3568/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions platform/riscv64/qemu-plic/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
37 changes: 37 additions & 0 deletions src/arch/aarch64/trap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
195 changes: 195 additions & 0 deletions tools/check_hv_mem_overlap.py
Original file line number Diff line number Diff line change
@@ -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 <ELF> <BOARD_RS>

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]} <HVISOR_ELF> <BOARD_RS>", 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())
Loading