Skip to content

Commit 3fa110a

Browse files
cshungCopilot
andcommitted
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>
1 parent 1d5a9e3 commit 3fa110a

4 files changed

Lines changed: 98 additions & 5 deletions

File tree

Justfile

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

4747
# build testing guest binaries
48-
guests: build-and-move-rust-guests build-and-move-c-guests
48+
guests: build-and-move-rust-guests-non-pie build-and-move-rust-guests build-and-move-c-guests
4949

5050
ensure-cargo-hyperlight:
5151
cargo install --locked --version 0.1.11 cargo-hyperlight
@@ -69,6 +69,16 @@ build-rust-guests target=default-target features="": (witguest-wit) (ensure-carg
6969
build-and-move-rust-guests: (build-rust-guests "debug") (move-rust-guests "debug") (build-rust-guests "release") (move-rust-guests "release")
7070
build-and-move-c-guests: (build-c-guests "debug") (move-c-guests "debug") (build-c-guests "release") (move-c-guests "release")
7171

72+
# Build non-PIE variants of rust guests for testing ELF VA mapping
73+
build-rust-guests-non-pie target=default-target: (ensure-cargo-hyperlight)
74+
{{ 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 } }}
75+
76+
@move-rust-guests-non-pie target=default-target:
77+
{{ 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" } }}
78+
cp {{ simpleguest_source }}/{{ target }}/simpleguest {{ rust_guests_bin_dir }}/{{ target }}/non_pie/
79+
80+
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")
81+
7282
clean: clean-rust
7383

7484
clean-rust:

src/hyperlight_host/src/sandbox/snapshot/mod.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::Result;
2828
use crate::hypervisor::regs::CommonSpecialRegisters;
2929
use crate::mem::exe::{ExeInfo, LoadInfo};
3030
use crate::mem::layout::SandboxMemoryLayout;
31-
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegion, MemoryRegionFlags};
31+
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegion, MemoryRegionFlags, MemoryRegionType};
3232
use crate::mem::mgr::{GuestPageTableBuffer, SnapshotSharedMemory};
3333
use crate::mem::shared_mem::{ReadonlySharedMemory, SharedMemory};
3434
use crate::sandbox::SandboxConfiguration;
@@ -308,6 +308,16 @@ impl Snapshot {
308308
let base_va = exe_info.base_va();
309309
let entrypoint_va: u64 = exe_info.entrypoint().into();
310310

311+
// Determine the virtual base address for the code region.
312+
// For non-PIE binaries (base_va > 0), the code should appear at the
313+
// ELF's declared virtual address. For PIE binaries (base_va == 0),
314+
// we use the physical load address (identity mapping).
315+
let code_virt_base = if base_va > 0 {
316+
base_va
317+
} else {
318+
load_addr
319+
};
320+
311321
let mut memory = vec![0; layout.get_memory_size()?];
312322

313323
let load_info = exe_info.load(
@@ -340,9 +350,26 @@ impl Snapshot {
340350
executable,
341351
})
342352
};
353+
354+
// For the code region, use code_virt_base as the GVA.
355+
// For non-PIE this is the ELF's declared base VA (non-identity mapping).
356+
// For PIE this should equal the GPA (identity mapping).
357+
let virt_base = if rgn.region_type == MemoryRegionType::Code {
358+
if base_va == 0 {
359+
assert_eq!(
360+
code_virt_base,
361+
rgn.guest_region.start as u64,
362+
"PIE code region should be identity-mapped"
363+
);
364+
}
365+
code_virt_base
366+
} else {
367+
rgn.guest_region.start as u64
368+
};
369+
343370
let mapping = Mapping {
344371
phys_base: rgn.guest_region.start as u64,
345-
virt_base: rgn.guest_region.start as u64,
372+
virt_base,
346373
len: rgn.guest_region.len() as u64,
347374
kind,
348375
user_accessible: false,
@@ -361,13 +388,21 @@ impl Snapshot {
361388
- hyperlight_common::layout::SCRATCH_TOP_EXN_STACK_OFFSET
362389
+ 1;
363390

391+
let entrypoint_offset = entrypoint_va.checked_sub(base_va).ok_or_else(|| {
392+
crate::new_error!(
393+
"ELF entrypoint VA ({:#x}) is below base VA ({:#x})",
394+
entrypoint_va,
395+
base_va
396+
)
397+
})?;
398+
364399
Ok(Self {
365400
memory: ReadonlySharedMemory::from_bytes(&memory, layout.snapshot_size)?,
366401
layout,
367402
load_info,
368403
stack_top_gva: exn_stack_top_gva,
369404
sregs: None,
370-
entrypoint: NextAction::Initialise(load_addr + entrypoint_va - base_va),
405+
entrypoint: NextAction::Initialise(code_virt_base + entrypoint_offset),
371406
snapshot_generation: 0,
372407
host_functions: HostFunctionDetails {
373408
host_functions: None,

src/hyperlight_host/tests/integration_test.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::time::Duration;
2121
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
2222
use hyperlight_common::log_level::GuestLogFilter;
2323
use hyperlight_host::sandbox::SandboxConfiguration;
24-
use hyperlight_host::{HyperlightError, MultiUseSandbox};
24+
use hyperlight_host::{HyperlightError, MultiUseSandbox, UninitializedSandbox};
2525
use hyperlight_testing::simplelogger::{LOGGER, SimpleLogger};
2626
use serial_test::serial;
2727
use tracing_core::LevelFilter;
@@ -1872,3 +1872,20 @@ fn hw_timer_interrupts() {
18721872
);
18731873
});
18741874
}
1875+
1876+
#[test]
1877+
#[serial]
1878+
fn non_pie_guest_hello_world() {
1879+
let path =
1880+
hyperlight_testing::simple_guest_non_pie_as_string().expect("non-PIE guest not found");
1881+
let sandbox = UninitializedSandbox::new(
1882+
hyperlight_host::GuestBinary::FilePath(path),
1883+
None,
1884+
)
1885+
.unwrap();
1886+
let mut multi_use_sandbox: MultiUseSandbox = sandbox.evolve().unwrap();
1887+
let result: i32 = multi_use_sandbox
1888+
.call("PrintOutput", "Hello from non-PIE guest!\n".to_string())
1889+
.unwrap();
1890+
assert_eq!(result, 26);
1891+
}

src/hyperlight_testing/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,37 @@ pub fn dummy_guest_as_string() -> Result<String> {
8888
.ok_or_else(|| anyhow!("couldn't convert dummy guest PathBuf to string"))
8989
}
9090

91+
/// Get a fully qualified OS-specific path to the non-PIE simpleguest elf binary
92+
pub fn simple_guest_non_pie_as_string() -> Result<String> {
93+
let buf = rust_guest_non_pie_as_pathbuf("simpleguest");
94+
buf.to_str()
95+
.map(|s| s.to_string())
96+
.ok_or_else(|| anyhow!("couldn't convert non-PIE simple guest PathBuf to string"))
97+
}
98+
99+
/// Get a new `PathBuf` to a specified non-PIE Rust guest
100+
/// $REPO_ROOT/src/tests/rust_guests/bin/${profile}/non_pie/
101+
fn rust_guest_non_pie_as_pathbuf(guest: &str) -> PathBuf {
102+
let build_dir_selector = if cfg!(debug_assertions) {
103+
"debug"
104+
} else {
105+
"release"
106+
};
107+
108+
join_to_path(
109+
MANIFEST_DIR,
110+
vec![
111+
"..",
112+
"tests",
113+
"rust_guests",
114+
"bin",
115+
build_dir_selector,
116+
"non_pie",
117+
guest,
118+
],
119+
)
120+
}
121+
91122
pub fn c_guest_as_pathbuf(guest: &str) -> PathBuf {
92123
let build_dir_selector = if cfg!(debug_assertions) {
93124
"debug"

0 commit comments

Comments
 (0)