Skip to content

Commit 117b974

Browse files
cshungCopilot
andauthored
Make ELF loading respect program header virtual addresses for non-PIE binaries (#1530)
## Summary For non-PIE (ET_EXEC) ELF binaries, the guest page table now maps the code region at the ELF's declared virtual address rather than identity-mapping it at the GPA. This allows statically-linked binaries with a fixed load address (e.g., `--image-base=0x200000`) to execute correctly. ## Problem Previously, Hyperlight assumed code GVA == code GPA (identity mapping). Non-PIE binaries that declare a non-zero base virtual address (via program header `p_vaddr`) would triple-fault because the guest CPU jumped to the ELF's declared entrypoint VA, which wasn't mapped in the page tables. ## Solution - Compute `code_virt_base` from the ELF's lowest LOAD segment `p_vaddr` - For non-PIE (`base_va > 0`): map code at the declared VA in the guest page tables - For PIE (`base_va == 0`): preserve existing identity mapping behavior (with assertion to guard the invariant) - Compute entrypoint as `code_virt_base + (entrypoint_va - base_va)` The fix leverages the existing `Mapping` struct's support for `phys_base != virt_base` — no changes to the page table code itself. ## Testing - New `non_pie_guest_hello_world` integration test exercises full guest lifecycle (init, COW, function call, return value) with a non-PIE simpleguest built at `--image-base=0x200000` - All existing PIE guest tests continue to pass (identity mapping preserved) - Tested on Windows/WHP and Linux/KVM ## Build infrastructure - Added `build-rust-guests-non-pie` Justfile targets - Non-PIE build runs first in `guests` recipe to avoid clobbering normal guest binaries - Added `simple_guest_non_pie_as_string()` test helper Contributes to: #1408 --------- Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6f20a05d-6bee-4e2e-b320-12f8d9759bbc
1 parent b1e4185 commit 117b974

18 files changed

Lines changed: 470 additions & 108 deletions

File tree

‎.github/workflows/dep_build_guests.yml‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,15 @@ jobs:
5454
run: |
5555
sudo chown -R $(id -u):$(id -g) /opt/cargo || true
5656
57-
# cargo-hyperlight builds a custom sysroot for x86_64-hyperlight-none target.
57+
# cargo-hyperlight builds a custom sysroot for the Hyperlight guest target.
5858
# rust-cache cleans "anything not a dependency" from target dirs, removing the sysroot.
5959
# We cache sysroot separately to avoid rebuilding it (~10s) on every run.
6060
- name: Sysroot cache
6161
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
6262
with:
6363
path: |
6464
src/tests/rust_guests/target/sysroot
65+
src/tests/rust_guests/target-non-pie/sysroot
6566
key: sysroot-linux-${{ inputs.arch }}-${{ inputs.config }}-${{ hashFiles('rust-toolchain.toml') }}
6667

6768
- name: Rust cache
@@ -87,6 +88,11 @@ jobs:
8788
just build-rust-guests ${{ inputs.config }}
8889
just move-rust-guests ${{ inputs.config }}
8990
91+
- name: Build non-PIE Rust guests
92+
run: |
93+
just build-rust-guests-non-pie ${{ inputs.config }}
94+
just move-rust-guests-non-pie ${{ inputs.config }}
95+
9096
- name: Build C guests
9197
run: |
9298
just build-c-guests ${{ inputs.config }}
@@ -108,4 +114,3 @@ jobs:
108114
path: src/tests/c_guests/bin/${{ inputs.config }}/
109115
retention-days: 1
110116
if-no-files-found: error
111-

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ $RECYCLE.BIN/
454454

455455
# Rust build artifacts
456456
**/**target
457+
**/**target-non-pie
457458
libhyperlight_host.so
458459
libhyperlight_host.d
459460
hyperlight_host.dll

‎Justfile‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ build target=default-target:
5050
{{ cargo-cmd }} build --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }}
5151

5252
# build testing guest binaries
53-
guests: build-and-move-rust-guests build-and-move-c-guests
53+
guests: build-and-move-rust-guests build-and-move-rust-guests-non-pie build-and-move-c-guests
5454

5555
# Ensure the pinned cargo-hyperlight is installed. We compare the *actual*
5656
# installed binary's reported version instead of relying on `cargo install`
@@ -75,6 +75,22 @@ build-rust-guests target=default-target features="": (ensure-cargo-hyperlight)
7575
build-and-move-rust-guests: (build-rust-guests "debug") (move-rust-guests "debug") (build-rust-guests "release") (move-rust-guests "release")
7676
build-and-move-c-guests: (build-c-guests "debug") (move-c-guests "debug") (build-c-guests "release") (move-c-guests "release")
7777

78+
# Build non-PIE variants of rust guests for testing ELF VA mapping.
79+
# Phase 1 builds the sysroot without RUSTFLAGS (avoids RUSTFLAGS leaking
80+
# into the sysroot wrapper build in cargo-hyperlight).
81+
# Phase 2 uses plain cargo with --sysroot and non-PIE link flags.
82+
build-rust-guests-non-pie target=default-target: (ensure-cargo-hyperlight)
83+
cd src/tests/rust_guests/simpleguest && cargo hyperlight build --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }}
84+
{{ if os() == "windows" { "$env:RUSTC_BOOTSTRAP=1; $env:RUSTFLAGS='--sysroot=' + (Resolve-Path src/tests/rust_guests/target-non-pie/sysroot).Path + ' -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x1000000 --cfg=hyperlight --check-cfg=cfg(hyperlight) -Clink-args=-eentrypoint';" } else { "" } }} cd src/tests/rust_guests/simpleguest && {{ if os() == "windows" { "" } else { "RUSTC_BOOTSTRAP=1 RUSTFLAGS=\"--sysroot=$(cd .. && pwd)/target-non-pie/sysroot -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x1000000 --cfg=hyperlight --check-cfg=cfg(hyperlight) -Clink-args=-eentrypoint\"" } }} cargo build --target {{ hyperlight-target }} --target-dir ../target-non-pie/build --profile={{ if target == "debug" { "dev" } else { target } }}
85+
86+
non_pie_guests_target := "src/tests/rust_guests/target-non-pie/build/" + hyperlight-target
87+
88+
@move-rust-guests-non-pie target=default-target:
89+
{{ if os() == "windows" { "New-Item -ItemType Directory -Path " + rust_guests_bin_dir + "/" + target + "/non_pie -Force | Out-Null" } else { "mkdir -p " + rust_guests_bin_dir + "/" + target + "/non_pie" } }}
90+
cp {{ non_pie_guests_target }}/{{ target }}/simpleguest {{ rust_guests_bin_dir }}/{{ target }}/non_pie/
91+
92+
build-and-move-rust-guests-non-pie: (build-rust-guests-non-pie "debug") (move-rust-guests-non-pie "debug") (build-rust-guests-non-pie "release") (move-rust-guests-non-pie "release")
93+
7894
clean: clean-rust
7995

8096
clean-rust:

‎src/hyperlight_host/src/hypervisor/gdb/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ impl<'a> DebugMemoryView<'a> {
9494
}
9595

9696
pub(crate) fn code_section_offset(&self) -> u64 {
97-
self.mem_mgr.layout.get_guest_code_address() as u64
97+
self.mem_mgr.layout.get_guest_code_gva() as u64
9898
}
9999

100100
/// Reads memory from the guest's address space with a maximum length of a PAGE_SIZE

‎src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ mod tests {
874874
use crate::hypervisor::regs::{CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT};
875875
use crate::hypervisor::virtual_machine::VirtualMachine;
876876
use crate::mem::layout::SandboxMemoryLayout;
877-
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
877+
use crate::mem::memory_region::MemoryRegionFlags;
878878
use crate::mem::mgr::{GuestPageTableBuffer, SandboxMemoryManager};
879879
use crate::mem::ptr::RawPtr;
880880
use crate::mem::shared_mem::{ExclusiveSharedMemory, ReadonlySharedMemory};
@@ -1431,16 +1431,12 @@ mod tests {
14311431
let pt_base_gpa = layout.get_pt_base_gpa();
14321432
let pt_buf = GuestPageTableBuffer::new(pt_base_gpa as usize);
14331433

1434-
for rgn in layout
1435-
.get_memory_regions_::<GuestMemoryRegion>(())
1436-
.unwrap()
1437-
.iter()
1438-
{
1434+
for rgn in layout.get_memory_regions().unwrap().iter() {
14391435
let readable = rgn.flags.contains(MemoryRegionFlags::READ);
14401436
let writable = rgn.flags.contains(MemoryRegionFlags::WRITE);
14411437
let executable = rgn.flags.contains(MemoryRegionFlags::EXECUTE);
14421438
let mapping = Mapping {
1443-
phys_base: rgn.guest_region.start as u64,
1439+
phys_base: rgn.host_region.start as u64,
14441440
virt_base: rgn.guest_region.start as u64,
14451441
len: rgn.guest_region.len() as u64,
14461442
kind: MappingKind::Basic(BasicMapping {
@@ -1496,7 +1492,7 @@ mod tests {
14961492
layout,
14971493
ro_mem.to_mgr_snapshot_mem().unwrap(),
14981494
scratch_mem,
1499-
NextAction::Initialise(layout.get_guest_code_address() as u64),
1495+
NextAction::Initialise(layout.get_guest_code_gva() as u64),
15001496
);
15011497

15021498
let (mut hshm, gshm) = mem_mgr.build().unwrap();
@@ -2205,7 +2201,7 @@ mod tests {
22052201
a.fxsave(ptr(rax)).unwrap();
22062202

22072203
// Return dispatch ptr
2208-
a.mov(rax, layout.get_guest_code_address() as u64).unwrap();
2204+
a.mov(rax, layout.get_guest_code_gva() as u64).unwrap();
22092205

22102206
a.hlt().unwrap();
22112207

0 commit comments

Comments
 (0)