From 40c43bed8e1899db2b9047982e6b2f55757f9688 Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:31:04 -0700 Subject: [PATCH 1/8] Make ELF loading respect program header virtual addresses For non-PIE binaries (ET_EXEC), the page table now maps the code region at the ELF's declared virtual address rather than identity mapping at the GPA. The entrypoint is computed correctly as a GVA. PIE binaries (base_va == 0) continue to use identity mapping, preserving existing behavior. Also adds build infrastructure and integration test for non-PIE guests: - Justfile: build-rust-guests-non-pie target builds simpleguest with static relocation model and --image-base=0x200000 - hyperlight_testing: add simple_guest_non_pie_as_string() path helper - integration_test: non_pie_guest_hello_world exercises full guest lifecycle (init, COW, function call) with absolute addresses Contributes to: #1408 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- Justfile | 12 +++++- .../src/sandbox/snapshot/mod.rs | 41 +++++++++++++++++-- src/hyperlight_host/tests/integration_test.rs | 19 ++++++++- src/hyperlight_testing/src/lib.rs | 31 ++++++++++++++ 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/Justfile b/Justfile index 401897425..aaa50e5a6 100644 --- a/Justfile +++ b/Justfile @@ -45,7 +45,7 @@ build target=default-target: {{ cargo-cmd }} build --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} # build testing guest binaries -guests: build-and-move-rust-guests build-and-move-c-guests +guests: build-and-move-rust-guests-non-pie build-and-move-rust-guests build-and-move-c-guests ensure-cargo-hyperlight: cargo install --locked cargo-hyperlight @@ -69,6 +69,16 @@ build-rust-guests target=default-target features="": (witguest-wit) (ensure-carg build-and-move-rust-guests: (build-rust-guests "debug") (move-rust-guests "debug") (build-rust-guests "release") (move-rust-guests "release") build-and-move-c-guests: (build-c-guests "debug") (move-c-guests "debug") (build-c-guests "release") (move-c-guests "release") +# Build non-PIE variants of rust guests for testing ELF VA mapping +build-rust-guests-non-pie target=default-target: (ensure-cargo-hyperlight) + {{ if os() == "windows" { "$env:RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000';" } else { "" } }} cd src/tests/rust_guests/simpleguest && {{ if os() == "windows" { "" } else { "RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'" } }} cargo hyperlight build --profile={{ if target == "debug" { "dev" } else { target } }} + +@move-rust-guests-non-pie target=default-target: + {{ 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" } }} + cp {{ simpleguest_source }}/{{ target }}/simpleguest {{ rust_guests_bin_dir }}/{{ target }}/non_pie/ + +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") + clean: clean-rust clean-rust: diff --git a/src/hyperlight_host/src/sandbox/snapshot/mod.rs b/src/hyperlight_host/src/sandbox/snapshot/mod.rs index 3d1afcb9c..880af24c6 100644 --- a/src/hyperlight_host/src/sandbox/snapshot/mod.rs +++ b/src/hyperlight_host/src/sandbox/snapshot/mod.rs @@ -28,7 +28,7 @@ use crate::Result; use crate::hypervisor::regs::CommonSpecialRegisters; use crate::mem::exe::{ExeInfo, LoadInfo}; use crate::mem::layout::SandboxMemoryLayout; -use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegion, MemoryRegionFlags}; +use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegion, MemoryRegionFlags, MemoryRegionType}; use crate::mem::mgr::{GuestPageTableBuffer, SnapshotSharedMemory}; use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory}; use crate::sandbox::SandboxConfiguration; @@ -308,6 +308,16 @@ impl Snapshot { let base_va = exe_info.base_va(); let entrypoint_va: u64 = exe_info.entrypoint().into(); + // Determine the virtual base address for the code region. + // For non-PIE binaries (base_va > 0), the code should appear at the + // ELF's declared virtual address. For PIE binaries (base_va == 0), + // we use the physical load address (identity mapping). + let code_virt_base = if base_va > 0 { + base_va + } else { + load_addr + }; + let mut memory = vec![0; layout.get_memory_size()?]; let load_info = exe_info.load( @@ -340,9 +350,26 @@ impl Snapshot { executable, }) }; + + // For the code region, use code_virt_base as the GVA. + // For non-PIE this is the ELF's declared base VA (non-identity mapping). + // For PIE this should equal the GPA (identity mapping). + let virt_base = if rgn.region_type == MemoryRegionType::Code { + if base_va == 0 { + assert_eq!( + code_virt_base, + rgn.guest_region.start as u64, + "PIE code region should be identity-mapped" + ); + } + code_virt_base + } else { + rgn.guest_region.start as u64 + }; + let mapping = Mapping { phys_base: rgn.guest_region.start as u64, - virt_base: rgn.guest_region.start as u64, + virt_base, len: rgn.guest_region.len() as u64, kind, user_accessible: false, @@ -361,13 +388,21 @@ impl Snapshot { - hyperlight_common::layout::SCRATCH_TOP_EXN_STACK_OFFSET + 1; + let entrypoint_offset = entrypoint_va.checked_sub(base_va).ok_or_else(|| { + crate::new_error!( + "ELF entrypoint VA ({:#x}) is below base VA ({:#x})", + entrypoint_va, + base_va + ) + })?; + Ok(Self { memory: ReadonlySharedMemory::from_bytes(&memory, layout.snapshot_size)?, layout, load_info, stack_top_gva: exn_stack_top_gva, sregs: None, - entrypoint: NextAction::Initialise(load_addr + entrypoint_va - base_va), + entrypoint: NextAction::Initialise(code_virt_base + entrypoint_offset), snapshot_generation: 0, host_functions: HostFunctionDetails { host_functions: None, diff --git a/src/hyperlight_host/tests/integration_test.rs b/src/hyperlight_host/tests/integration_test.rs index 6b5a7f8e3..f12b9fa70 100644 --- a/src/hyperlight_host/tests/integration_test.rs +++ b/src/hyperlight_host/tests/integration_test.rs @@ -21,7 +21,7 @@ use std::time::Duration; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::log_level::GuestLogFilter; use hyperlight_host::sandbox::SandboxConfiguration; -use hyperlight_host::{HyperlightError, MultiUseSandbox}; +use hyperlight_host::{HyperlightError, MultiUseSandbox, UninitializedSandbox}; use hyperlight_testing::simplelogger::{LOGGER, SimpleLogger}; use serial_test::serial; use tracing_core::LevelFilter; @@ -1872,3 +1872,20 @@ fn hw_timer_interrupts() { ); }); } + +#[test] +#[serial] +fn non_pie_guest_hello_world() { + let path = + hyperlight_testing::simple_guest_non_pie_as_string().expect("non-PIE guest not found"); + let sandbox = UninitializedSandbox::new( + hyperlight_host::GuestBinary::FilePath(path), + None, + ) + .unwrap(); + let mut multi_use_sandbox: MultiUseSandbox = sandbox.evolve().unwrap(); + let result: i32 = multi_use_sandbox + .call("PrintOutput", "Hello from non-PIE guest!\n".to_string()) + .unwrap(); + assert_eq!(result, 26); +} diff --git a/src/hyperlight_testing/src/lib.rs b/src/hyperlight_testing/src/lib.rs index aa3af3237..b2d1d160d 100644 --- a/src/hyperlight_testing/src/lib.rs +++ b/src/hyperlight_testing/src/lib.rs @@ -88,6 +88,37 @@ pub fn dummy_guest_as_string() -> Result { .ok_or_else(|| anyhow!("couldn't convert dummy guest PathBuf to string")) } +/// Get a fully qualified OS-specific path to the non-PIE simpleguest elf binary +pub fn simple_guest_non_pie_as_string() -> Result { + let buf = rust_guest_non_pie_as_pathbuf("simpleguest"); + buf.to_str() + .map(|s| s.to_string()) + .ok_or_else(|| anyhow!("couldn't convert non-PIE simple guest PathBuf to string")) +} + +/// Get a new `PathBuf` to a specified non-PIE Rust guest +/// $REPO_ROOT/src/tests/rust_guests/bin/${profile}/non_pie/ +fn rust_guest_non_pie_as_pathbuf(guest: &str) -> PathBuf { + let build_dir_selector = if cfg!(debug_assertions) { + "debug" + } else { + "release" + }; + + join_to_path( + MANIFEST_DIR, + vec![ + "..", + "tests", + "rust_guests", + "bin", + build_dir_selector, + "non_pie", + guest, + ], + ) +} + pub fn c_guest_as_pathbuf(guest: &str) -> PathBuf { let build_dir_selector = if cfg!(debug_assertions) { "debug" From 35fef7bb8ddb0e338e5fc38a27f151b14d96b4dd Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Mon, 15 Jun 2026 12:42:48 -0700 Subject: [PATCH 2/8] Fix formatting for CI nightly rustfmt Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- src/hyperlight_host/src/sandbox/snapshot/mod.rs | 13 +++++-------- src/hyperlight_host/tests/integration_test.rs | 7 ++----- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/hyperlight_host/src/sandbox/snapshot/mod.rs b/src/hyperlight_host/src/sandbox/snapshot/mod.rs index 880af24c6..0aa6d83dd 100644 --- a/src/hyperlight_host/src/sandbox/snapshot/mod.rs +++ b/src/hyperlight_host/src/sandbox/snapshot/mod.rs @@ -28,7 +28,9 @@ use crate::Result; use crate::hypervisor::regs::CommonSpecialRegisters; use crate::mem::exe::{ExeInfo, LoadInfo}; use crate::mem::layout::SandboxMemoryLayout; -use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegion, MemoryRegionFlags, MemoryRegionType}; +use crate::mem::memory_region::{ + GuestMemoryRegion, MemoryRegion, MemoryRegionFlags, MemoryRegionType, +}; use crate::mem::mgr::{GuestPageTableBuffer, SnapshotSharedMemory}; use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory}; use crate::sandbox::SandboxConfiguration; @@ -312,11 +314,7 @@ impl Snapshot { // For non-PIE binaries (base_va > 0), the code should appear at the // ELF's declared virtual address. For PIE binaries (base_va == 0), // we use the physical load address (identity mapping). - let code_virt_base = if base_va > 0 { - base_va - } else { - load_addr - }; + let code_virt_base = if base_va > 0 { base_va } else { load_addr }; let mut memory = vec![0; layout.get_memory_size()?]; @@ -357,8 +355,7 @@ impl Snapshot { let virt_base = if rgn.region_type == MemoryRegionType::Code { if base_va == 0 { assert_eq!( - code_virt_base, - rgn.guest_region.start as u64, + code_virt_base, rgn.guest_region.start as u64, "PIE code region should be identity-mapped" ); } diff --git a/src/hyperlight_host/tests/integration_test.rs b/src/hyperlight_host/tests/integration_test.rs index f12b9fa70..703913c93 100644 --- a/src/hyperlight_host/tests/integration_test.rs +++ b/src/hyperlight_host/tests/integration_test.rs @@ -1878,11 +1878,8 @@ fn hw_timer_interrupts() { fn non_pie_guest_hello_world() { let path = hyperlight_testing::simple_guest_non_pie_as_string().expect("non-PIE guest not found"); - let sandbox = UninitializedSandbox::new( - hyperlight_host::GuestBinary::FilePath(path), - None, - ) - .unwrap(); + let sandbox = + UninitializedSandbox::new(hyperlight_host::GuestBinary::FilePath(path), None).unwrap(); let mut multi_use_sandbox: MultiUseSandbox = sandbox.evolve().unwrap(); let result: i32 = multi_use_sandbox .call("PrintOutput", "Hello from non-PIE guest!\n".to_string()) From 97647102896b77f01e1bb7a06ba2ad905eea86e1 Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Tue, 16 Jun 2026 11:02:28 -0700 Subject: [PATCH 3/8] Fix non-PIE guest build to avoid cargo-hyperlight sysroot rebuild Use plain cargo build with the existing sysroot instead of cargo hyperlight for non-PIE guest variants. This avoids triggering a sysroot rebuild with different RUSTFLAGS, which fails on CI runners with stale cargo-hyperlight versions. The non-PIE build now: - Reuses the sysroot already created by the regular guest build - Builds into a separate target-dir to avoid clobbering PIE artifacts - Explicitly passes --cfg=hyperlight, entrypoint, and sysroot flags - Runs after the regular guest build (ordering dependency) Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- .gitignore | 1 + Justfile | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 3aae7b792..27126b8de 100644 --- a/.gitignore +++ b/.gitignore @@ -454,6 +454,7 @@ $RECYCLE.BIN/ # Rust build artifacts **/**target +**/**target-non-pie libhyperlight_host.so libhyperlight_host.d hyperlight_host.dll diff --git a/Justfile b/Justfile index aaa50e5a6..61fa1ae6d 100644 --- a/Justfile +++ b/Justfile @@ -45,7 +45,7 @@ build target=default-target: {{ cargo-cmd }} build --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} # build testing guest binaries -guests: build-and-move-rust-guests-non-pie build-and-move-rust-guests build-and-move-c-guests +guests: build-and-move-rust-guests build-and-move-rust-guests-non-pie build-and-move-c-guests ensure-cargo-hyperlight: cargo install --locked cargo-hyperlight @@ -69,13 +69,18 @@ build-rust-guests target=default-target features="": (witguest-wit) (ensure-carg build-and-move-rust-guests: (build-rust-guests "debug") (move-rust-guests "debug") (build-rust-guests "release") (move-rust-guests "release") build-and-move-c-guests: (build-c-guests "debug") (move-c-guests "debug") (build-c-guests "release") (move-c-guests "release") -# Build non-PIE variants of rust guests for testing ELF VA mapping -build-rust-guests-non-pie target=default-target: (ensure-cargo-hyperlight) - {{ if os() == "windows" { "$env:RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000';" } else { "" } }} cd src/tests/rust_guests/simpleguest && {{ if os() == "windows" { "" } else { "RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'" } }} cargo hyperlight build --profile={{ if target == "debug" { "dev" } else { target } }} +# Build non-PIE variants of rust guests for testing ELF VA mapping. +# Uses the sysroot already created by build-rust-guests (avoids cargo-hyperlight +# sysroot rebuild with different RUSTFLAGS). Builds into a separate target-dir +# to avoid clobbering the normal PIE guest artifacts. +build-rust-guests-non-pie target=default-target: + {{ if os() == "windows" { "$env:RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'; $env:RUSTC_BOOTSTRAP='1';" } else { "RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000' RUSTC_BOOTSTRAP=1" } }} cd src/tests/rust_guests/simpleguest && cargo build --target x86_64-hyperlight-none --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }} + +non_pie_guests_target := "src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none" @move-rust-guests-non-pie target=default-target: {{ 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" } }} - cp {{ simpleguest_source }}/{{ target }}/simpleguest {{ rust_guests_bin_dir }}/{{ target }}/non_pie/ + cp {{ non_pie_guests_target }}/{{ target }}/simpleguest {{ rust_guests_bin_dir }}/{{ target }}/non_pie/ 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") From 2b7223a63a6d19fa8f15c1ca88399d47e4b27105 Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:31:42 -0700 Subject: [PATCH 4/8] Add non-PIE guest build step to CI workflow The dep_build_guests workflow was only building the regular (PIE) Rust guests. The non-PIE variant was never built or uploaded as an artifact, causing the non_pie_guest_hello_world test to fail in the build-test jobs because the binary was missing. Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- .github/workflows/dep_build_guests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/dep_build_guests.yml b/.github/workflows/dep_build_guests.yml index 251852dd5..984810cdf 100644 --- a/.github/workflows/dep_build_guests.yml +++ b/.github/workflows/dep_build_guests.yml @@ -74,6 +74,11 @@ jobs: just build-rust-guests ${{ inputs.config }} just move-rust-guests ${{ inputs.config }} + - name: Build non-PIE Rust guests + run: | + just build-rust-guests-non-pie ${{ inputs.config }} + just move-rust-guests-non-pie ${{ inputs.config }} + - name: Build C guests run: | just build-c-guests ${{ inputs.config }} From 8b92c9b6071781509febd54e97f6f0ccebb5ecb9 Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Wed, 17 Jun 2026 11:46:18 -0700 Subject: [PATCH 5/8] Use RUST_TARGET_PATH for non-PIE guest target spec discovery Cargo on newer Rust versions does not pass RUSTFLAGS during its target-spec probe phase. This caused CI (Rust 1.94) to fail with 'could not find specification for target x86_64-hyperlight-none'. Fix by copying target.json as x86_64-hyperlight-none.json and setting RUST_TARGET_PATH so cargo can discover the spec without relying on --sysroot being available during the probe. Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- Justfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Justfile b/Justfile index 61fa1ae6d..7fab6347c 100644 --- a/Justfile +++ b/Justfile @@ -73,8 +73,9 @@ build-and-move-c-guests: (build-c-guests "debug") (move-c-guests "debug") (build # Uses the sysroot already created by build-rust-guests (avoids cargo-hyperlight # sysroot rebuild with different RUSTFLAGS). Builds into a separate target-dir # to avoid clobbering the normal PIE guest artifacts. +# RUST_TARGET_PATH tells cargo where to find the x86_64-hyperlight-none.json spec. build-rust-guests-non-pie target=default-target: - {{ if os() == "windows" { "$env:RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'; $env:RUSTC_BOOTSTRAP='1';" } else { "RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000' RUSTC_BOOTSTRAP=1" } }} cd src/tests/rust_guests/simpleguest && cargo build --target x86_64-hyperlight-none --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }} + {{ if os() == "windows" { "New-Item -ItemType Directory -Path " + root + "/src/tests/rust_guests/target-non-pie -Force | Out-Null; Copy-Item " + root + "/src/tests/rust_guests/target/sysroot/lib/rustlib/x86_64-hyperlight-none/target.json " + root + "/src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none.json -Force; $env:RUST_TARGET_PATH='" + root + "/src/tests/rust_guests/target-non-pie'; $env:RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'; $env:RUSTC_BOOTSTRAP='1';" } else { "mkdir -p " + root + "/src/tests/rust_guests/target-non-pie && cp " + root + "/src/tests/rust_guests/target/sysroot/lib/rustlib/x86_64-hyperlight-none/target.json " + root + "/src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none.json && RUST_TARGET_PATH='" + root + "/src/tests/rust_guests/target-non-pie' RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000' RUSTC_BOOTSTRAP=1" } }} cd src/tests/rust_guests/simpleguest && cargo build --target x86_64-hyperlight-none --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }} non_pie_guests_target := "src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none" From 5c4502c1607fe9abea855ab158e4d89e6f7c7c3a Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Thu, 18 Jun 2026 08:06:17 -0700 Subject: [PATCH 6/8] Fix RUST_TARGET_PATH scoping in bash for non-PIE build In bash, 'VAR=val cd dir && cargo build' only sets VAR for cd, not for cargo build after &&. Move the env var prefix to directly precede the cargo build command so it is properly in scope. Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- Justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Justfile b/Justfile index 7fab6347c..7179bf852 100644 --- a/Justfile +++ b/Justfile @@ -75,7 +75,7 @@ build-and-move-c-guests: (build-c-guests "debug") (move-c-guests "debug") (build # to avoid clobbering the normal PIE guest artifacts. # RUST_TARGET_PATH tells cargo where to find the x86_64-hyperlight-none.json spec. build-rust-guests-non-pie target=default-target: - {{ if os() == "windows" { "New-Item -ItemType Directory -Path " + root + "/src/tests/rust_guests/target-non-pie -Force | Out-Null; Copy-Item " + root + "/src/tests/rust_guests/target/sysroot/lib/rustlib/x86_64-hyperlight-none/target.json " + root + "/src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none.json -Force; $env:RUST_TARGET_PATH='" + root + "/src/tests/rust_guests/target-non-pie'; $env:RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'; $env:RUSTC_BOOTSTRAP='1';" } else { "mkdir -p " + root + "/src/tests/rust_guests/target-non-pie && cp " + root + "/src/tests/rust_guests/target/sysroot/lib/rustlib/x86_64-hyperlight-none/target.json " + root + "/src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none.json && RUST_TARGET_PATH='" + root + "/src/tests/rust_guests/target-non-pie' RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000' RUSTC_BOOTSTRAP=1" } }} cd src/tests/rust_guests/simpleguest && cargo build --target x86_64-hyperlight-none --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }} + {{ if os() == "windows" { "New-Item -ItemType Directory -Path " + root + "/src/tests/rust_guests/target-non-pie -Force | Out-Null; Copy-Item " + root + "/src/tests/rust_guests/target/sysroot/lib/rustlib/x86_64-hyperlight-none/target.json " + root + "/src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none.json -Force; $env:RUST_TARGET_PATH='" + root + "/src/tests/rust_guests/target-non-pie'; $env:RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'; $env:RUSTC_BOOTSTRAP='1';" } else { "mkdir -p " + root + "/src/tests/rust_guests/target-non-pie && cp " + root + "/src/tests/rust_guests/target/sysroot/lib/rustlib/x86_64-hyperlight-none/target.json " + root + "/src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none.json &&" } }} cd src/tests/rust_guests/simpleguest && {{ if os() == "windows" { "" } else { "RUST_TARGET_PATH='" + root + "/src/tests/rust_guests/target-non-pie' RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000' RUSTC_BOOTSTRAP=1" } }} cargo build --target x86_64-hyperlight-none --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }} non_pie_guests_target := "src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none" From 3850d93a152648d9c643d657c79bb2cc1f3cfa98 Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Thu, 18 Jun 2026 11:10:44 -0700 Subject: [PATCH 7/8] [CI experiment] Use cargo hyperlight for non-PIE build instead of plain cargo Revert to using cargo hyperlight build with only the non-PIE link flags (relocation-model=static, --no-pie, --image-base) appended via RUSTFLAGS. This avoids duplicating cargo-hyperlight internals (sysroot, --cfg=hyperlight, target spec, entrypoint). Uses --target-dir to keep PIE and non-PIE artifacts separate. This is a CI experiment to verify cargo hyperlight v0.1.11 handles the RUSTFLAGS sysroot rebuild correctly. Can be reverted if CI fails. Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- Justfile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Justfile b/Justfile index 7179bf852..f2fd6749b 100644 --- a/Justfile +++ b/Justfile @@ -70,12 +70,10 @@ build-and-move-rust-guests: (build-rust-guests "debug") (move-rust-guests "debug build-and-move-c-guests: (build-c-guests "debug") (move-c-guests "debug") (build-c-guests "release") (move-c-guests "release") # Build non-PIE variants of rust guests for testing ELF VA mapping. -# Uses the sysroot already created by build-rust-guests (avoids cargo-hyperlight -# sysroot rebuild with different RUSTFLAGS). Builds into a separate target-dir +# Uses cargo hyperlight with non-PIE link flags and a separate target-dir # to avoid clobbering the normal PIE guest artifacts. -# RUST_TARGET_PATH tells cargo where to find the x86_64-hyperlight-none.json spec. -build-rust-guests-non-pie target=default-target: - {{ if os() == "windows" { "New-Item -ItemType Directory -Path " + root + "/src/tests/rust_guests/target-non-pie -Force | Out-Null; Copy-Item " + root + "/src/tests/rust_guests/target/sysroot/lib/rustlib/x86_64-hyperlight-none/target.json " + root + "/src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none.json -Force; $env:RUST_TARGET_PATH='" + root + "/src/tests/rust_guests/target-non-pie'; $env:RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'; $env:RUSTC_BOOTSTRAP='1';" } else { "mkdir -p " + root + "/src/tests/rust_guests/target-non-pie && cp " + root + "/src/tests/rust_guests/target/sysroot/lib/rustlib/x86_64-hyperlight-none/target.json " + root + "/src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none.json &&" } }} cd src/tests/rust_guests/simpleguest && {{ if os() == "windows" { "" } else { "RUST_TARGET_PATH='" + root + "/src/tests/rust_guests/target-non-pie' RUSTFLAGS='--sysroot " + root + "/src/tests/rust_guests/target/sysroot --cfg=hyperlight --check-cfg=cfg(hyperlight) -C link-args=-eentrypoint -C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000' RUSTC_BOOTSTRAP=1" } }} cargo build --target x86_64-hyperlight-none --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }} +build-rust-guests-non-pie target=default-target: (ensure-cargo-hyperlight) + {{ if os() == "windows" { "$env:RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000';" } else { "" } }} cd src/tests/rust_guests/simpleguest && {{ if os() == "windows" { "" } else { "RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'" } }} cargo hyperlight build --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }} non_pie_guests_target := "src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none" From 5a635a48686b4b026ea698a126057608f154f212 Mon Sep 17 00:00:00 2001 From: cshung <3410332+cshung@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:09:44 -0700 Subject: [PATCH 8/8] Address review: use ET_DYN for PIE detection, add mapping conflict check - Use ELF e_type (ET_DYN vs ET_EXEC) to determine PIE vs non-PIE instead of checking if base_va is zero. This is more semantically correct as suggested by reviewer. - Add a mapping conflict check that verifies the non-PIE code virtual address range does not overlap with any other memory region (e.g. PEB, stack, heap). Returns an error if a conflict is detected. - Bump image base from 0x200000 to 0x1000000 (16MB) to avoid conflicts with the default memory layout regions. Signed-off-by: cshung <3410332+cshung@users.noreply.github.com> --- Justfile | 2 +- src/hyperlight_host/src/mem/elf.rs | 9 +++++ src/hyperlight_host/src/mem/exe.rs | 6 ++++ .../src/sandbox/snapshot/mod.rs | 33 ++++++++++++++++--- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/Justfile b/Justfile index f2fd6749b..0fda7fff2 100644 --- a/Justfile +++ b/Justfile @@ -73,7 +73,7 @@ build-and-move-c-guests: (build-c-guests "debug") (move-c-guests "debug") (build # Uses cargo hyperlight with non-PIE link flags and a separate target-dir # to avoid clobbering the normal PIE guest artifacts. build-rust-guests-non-pie target=default-target: (ensure-cargo-hyperlight) - {{ if os() == "windows" { "$env:RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000';" } else { "" } }} cd src/tests/rust_guests/simpleguest && {{ if os() == "windows" { "" } else { "RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x200000'" } }} cargo hyperlight build --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }} + {{ if os() == "windows" { "$env:RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x1000000';" } else { "" } }} cd src/tests/rust_guests/simpleguest && {{ if os() == "windows" { "" } else { "RUSTFLAGS='-C relocation-model=static -C link-args=--no-pie -C link-args=--image-base=0x1000000'" } }} cargo hyperlight build --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }} non_pie_guests_target := "src/tests/rust_guests/target-non-pie/x86_64-hyperlight-none" diff --git a/src/hyperlight_host/src/mem/elf.rs b/src/hyperlight_host/src/mem/elf.rs index 16e506eac..5f533d0cc 100644 --- a/src/hyperlight_host/src/mem/elf.rs +++ b/src/hyperlight_host/src/mem/elf.rs @@ -17,6 +17,7 @@ limitations under the License. #[cfg(feature = "mem_profile")] use std::sync::Arc; +use goblin::elf::header::ET_DYN; #[cfg(target_arch = "aarch64")] use goblin::elf::reloc::{R_AARCH64_NONE, R_AARCH64_RELATIVE}; #[cfg(target_arch = "x86_64")] @@ -45,6 +46,8 @@ pub(crate) struct ElfInfo { shdrs: Vec, entry: u64, relocs: Vec, + /// Whether this is a position-independent executable (ET_DYN). + is_pie: bool, /// The hyperlight version string embedded by `hyperlight-guest-bin`, if /// present. Used to detect version/ABI mismatches between guest and host. guest_bin_version: Option, @@ -146,6 +149,7 @@ impl ElfInfo { .collect(), entry: elf.entry, relocs, + is_pie: elf.header.e_type == ET_DYN, guest_bin_version, }) } @@ -171,6 +175,11 @@ impl ElfInfo { self.entry } + /// Returns whether this is a position-independent executable (ET_DYN). + pub(crate) fn is_pie(&self) -> bool { + self.is_pie + } + /// Returns the hyperlight version string embedded in the guest binary, if /// present. Used to detect version/ABI mismatches between guest and host. pub(crate) fn guest_bin_version(&self) -> Option<&str> { diff --git a/src/hyperlight_host/src/mem/exe.rs b/src/hyperlight_host/src/mem/exe.rs index 97874ae6e..7649846ce 100644 --- a/src/hyperlight_host/src/mem/exe.rs +++ b/src/hyperlight_host/src/mem/exe.rs @@ -88,6 +88,12 @@ impl ExeInfo { ExeInfo::Elf(elf) => Offset::from(elf.entrypoint_va()), } } + /// Returns whether this is a position-independent executable (ET_DYN). + pub fn is_pie(&self) -> bool { + match self { + ExeInfo::Elf(elf) => elf.is_pie(), + } + } /// Returns the base virtual address of the loaded binary (lowest PT_LOAD p_vaddr). pub fn base_va(&self) -> u64 { match self { diff --git a/src/hyperlight_host/src/sandbox/snapshot/mod.rs b/src/hyperlight_host/src/sandbox/snapshot/mod.rs index 0aa6d83dd..69b90b3ba 100644 --- a/src/hyperlight_host/src/sandbox/snapshot/mod.rs +++ b/src/hyperlight_host/src/sandbox/snapshot/mod.rs @@ -309,12 +309,14 @@ impl Snapshot { let load_addr = layout.get_guest_code_address() as u64; let base_va = exe_info.base_va(); let entrypoint_va: u64 = exe_info.entrypoint().into(); + let loaded_size = exe_info.loaded_size() as u64; + let is_pie = exe_info.is_pie(); // Determine the virtual base address for the code region. - // For non-PIE binaries (base_va > 0), the code should appear at the - // ELF's declared virtual address. For PIE binaries (base_va == 0), + // For non-PIE binaries (ET_EXEC), the code should appear at the + // ELF's declared virtual address. For PIE binaries (ET_DYN), // we use the physical load address (identity mapping). - let code_virt_base = if base_va > 0 { base_va } else { load_addr }; + let code_virt_base = if !is_pie { base_va } else { load_addr }; let mut memory = vec![0; layout.get_memory_size()?]; @@ -331,6 +333,29 @@ impl Snapshot { // Set up page table entries for the snapshot let pt_buf = GuestPageTableBuffer::new(layout.get_pt_base_gpa() as usize); + // Verify the non-PIE code mapping does not conflict with other mappings. + // PIE uses identity mapping so the code region can't conflict by definition. + if !is_pie { + let code_virt_end = code_virt_base + loaded_size; + for rgn in layout.get_memory_regions_::(())?.iter() { + if rgn.region_type == MemoryRegionType::Code { + continue; + } + let rgn_start = rgn.guest_region.start as u64; + let rgn_end = rgn_start + rgn.guest_region.len() as u64; + if code_virt_base < rgn_end && rgn_start < code_virt_end { + return Err(crate::new_error!( + "Code mapping [{:#x}, {:#x}) conflicts with {:?} region [{:#x}, {:#x})", + code_virt_base, + code_virt_end, + rgn.region_type, + rgn_start, + rgn_end, + )); + } + } + } + // 1. Map the (ideally readonly) pages of snapshot data for rgn in layout.get_memory_regions_::(())?.iter() { let readable = rgn.flags.contains(MemoryRegionFlags::READ); @@ -353,7 +378,7 @@ impl Snapshot { // For non-PIE this is the ELF's declared base VA (non-identity mapping). // For PIE this should equal the GPA (identity mapping). let virt_base = if rgn.region_type == MemoryRegionType::Code { - if base_va == 0 { + if is_pie { assert_eq!( code_virt_base, rgn.guest_region.start as u64, "PIE code region should be identity-mapped"