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
24 changes: 24 additions & 0 deletions src/hyperlight_host/src/mem/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,30 @@ impl SandboxMemoryLayout {
Ok(())
}

/// Pick a random page-aligned virtual address for ASLR.
///
/// The address is chosen within 47-bit canonical user space:
/// lower bound 16 MiB (above identity-mapped layout regions),
/// upper bound accounts for `loaded_size` so the mapping fits.
pub(crate) fn pick_aslr_address(loaded_size: u64) -> Result<u64> {
use rand::RngExt;
let code_size_pages = loaded_size.div_ceil(PAGE_SIZE as u64);
let min_page = 0x1000_u64; // 0x1000 * PAGE_SIZE = 0x1000000 (16 MiB)
let max_page = 0x7_FFFF_FFFF_u64
.checked_sub(code_size_pages)
.ok_or_else(|| {
new_error!(
"PIE code region too large ({} pages) for ASLR randomization",
code_size_pages
)
})?;
let mut rng = rand::rng();
let page_number = rng.random_range(min_page..max_page);
page_number
.checked_mul(PAGE_SIZE as u64)
.ok_or_else(|| new_error!("ASLR page number overflow"))
}

#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn write_init_data(&self, out: &mut [u8], bytes: &[u8]) -> Result<()> {
out[self.init_data_offset()..self.init_data_offset() + self.init_data_size]
Expand Down
7 changes: 5 additions & 2 deletions src/hyperlight_host/src/sandbox/initialized_multi_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3038,9 +3038,12 @@ mod tests {
/// `read_guest_memory_by_gva`, then assert both views are identical.
#[cfg(feature = "trace_guest")]
fn assert_gva_read_matches(sbox: &mut MultiUseSandbox, gva: u64, len: usize) {
// Guest reads via its own page tables
// Guest reads via its own page tables.
// do_map = false: the code region is already mapped (identity-mapped
// or ASLR-mapped), so we must not remap it with an identity mapping
// that would use the GVA as a physical address.
let expected: Vec<u8> = sbox
.call("ReadMappedBuffer", (gva, len as u64, true))
.call("ReadMappedBuffer", (gva, len as u64, false))
.unwrap();
assert_eq!(expected.len(), len);

Expand Down
9 changes: 8 additions & 1 deletion src/hyperlight_host/src/sandbox/snapshot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,14 @@ impl Snapshot {
let entrypoint_va: u64 = exe_info.entrypoint().into();
let is_pie = exe_info.is_pie();

let code_gva = if is_pie { load_addr } else { base_va };
let code_gva = if is_pie {
SandboxMemoryLayout::pick_aslr_address(exe_info.loaded_size() as u64)?
} else if base_va == load_addr {
// PIE binary at default load address (identity-mapped)
load_addr
} else {
base_va
};
layout.set_code_gva(code_gva)?;
let regions = layout.get_memory_regions()?;

Expand Down
89 changes: 46 additions & 43 deletions src/hyperlight_host/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1671,50 +1671,53 @@ fn exception_handler_installation_and_validation() {
/// This validates that the exception handling path does not require heap allocations.
#[test]
fn fill_heap_and_cause_exception() {
with_rust_sandbox(|mut sandbox| {
let result = sandbox.call::<()>("FillHeapAndCauseException", ());

// The call should fail with an exception error since there's no handler installed
assert!(result.is_err(), "Expected an error from ud2 exception");

let err = result.unwrap_err();
match &err {
HyperlightError::GuestAborted(code, message) => {
assert_eq!(*code, ErrorCode::GuestError as u8, "Full error: {:?}", err);

// Verify the message was properly formatted (proves no-allocation path worked)
// Exception vector 6 is #UD (Invalid Opcode from ud2 instruction)
#[cfg(target_arch = "x86_64")]
let vector = "Exception vector: 6";
#[cfg(target_arch = "aarch64")]
let vector = "Exception vector: CurrentSP0 Synchronous";
assert!(
message.contains(vector),
"Message should contain '{}'\nFull error: {:?}",
vector,
err
);
assert!(
message.contains("Faulting Instruction:"),
"Message should contain 'Faulting Instruction:'\nFull error: {:?}",
err
);
#[cfg(target_arch = "x86_64")]
assert!(
message.contains("Stack Pointer:"),
"Message should contain 'Stack Pointer:'\nFull error: {:?}",
err
);
#[cfg(target_arch = "aarch64")]
assert!(
message.contains("Exception Syndrome:"),
"Message should contain 'Exception Syndrome:'\nFull error: {:?}",
err
);
}
_ => panic!("Expected GuestAborted error, got: {:?}", err),
}
let mut sandbox = build_rust_sandbox(|builder| {
builder.scratch_size(
SandboxConfiguration::DEFAULT_SCRATCH_SIZE + 4 * hyperlight_common::vmem::PAGE_SIZE,
)
});
let result = sandbox.call::<()>("FillHeapAndCauseException", ());

// The call should fail with an exception error since there's no handler installed
assert!(result.is_err(), "Expected an error from ud2 exception");

let err = result.unwrap_err();
match &err {
HyperlightError::GuestAborted(code, message) => {
assert_eq!(*code, ErrorCode::GuestError as u8, "Full error: {:?}", err);

// Verify the message was properly formatted (proves no-allocation path worked)
// Exception vector 6 is #UD (Invalid Opcode from ud2 instruction)
#[cfg(target_arch = "x86_64")]
let vector = "Exception vector: 6";
#[cfg(target_arch = "aarch64")]
let vector = "Exception vector: CurrentSP0 Synchronous";
assert!(
message.contains(vector),
"Message should contain '{}'\nFull error: {:?}",
vector,
err
);
assert!(
message.contains("Faulting Instruction:"),
"Message should contain 'Faulting Instruction:'\nFull error: {:?}",
err
);
#[cfg(target_arch = "x86_64")]
assert!(
message.contains("Stack Pointer:"),
"Message should contain 'Stack Pointer:'\nFull error: {:?}",
err
);
#[cfg(target_arch = "aarch64")]
assert!(
message.contains("Exception Syndrome:"),
"Message should contain 'Exception Syndrome:'\nFull error: {:?}",
err
);
}
_ => panic!("Expected GuestAborted error, got: {:?}", err),
}
}

/// This test is "likely" to catch a race condition where WHvCancelRunVirtualProcessor runs halfway, then the partition is deleted (by drop calling WHvDeletePartition),
Expand Down
Loading