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
9 changes: 7 additions & 2 deletions .github/workflows/dep_build_guests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@ jobs:
run: |
sudo chown -R $(id -u):$(id -g) /opt/cargo || true

# cargo-hyperlight builds a custom sysroot for x86_64-hyperlight-none target.
# cargo-hyperlight builds a custom sysroot for the Hyperlight guest target.
# rust-cache cleans "anything not a dependency" from target dirs, removing the sysroot.
# We cache sysroot separately to avoid rebuilding it (~10s) on every run.
- name: Sysroot cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
src/tests/rust_guests/target/sysroot
src/tests/rust_guests/target-non-pie/sysroot
key: sysroot-linux-${{ inputs.arch }}-${{ inputs.config }}-${{ hashFiles('rust-toolchain.toml') }}

- name: Rust cache
Expand All @@ -87,6 +88,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 }}
Expand All @@ -108,4 +114,3 @@ jobs:
path: src/tests/c_guests/bin/${{ inputs.config }}/
retention-days: 1
if-no-files-found: error

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ $RECYCLE.BIN/

# Rust build artifacts
**/**target
**/**target-non-pie
libhyperlight_host.so
libhyperlight_host.d
hyperlight_host.dll
Expand Down
18 changes: 17 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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 build-and-move-rust-guests-non-pie build-and-move-c-guests

# Ensure the pinned cargo-hyperlight is installed. We compare the *actual*
# installed binary's reported version instead of relying on `cargo install`
Expand All @@ -75,6 +75,22 @@ build-rust-guests target=default-target features="": (ensure-cargo-hyperlight)
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.
# Phase 1 builds the sysroot without RUSTFLAGS (avoids RUSTFLAGS leaking
# into the sysroot wrapper build in cargo-hyperlight).
# Phase 2 uses plain cargo with --sysroot and non-PIE link flags.
build-rust-guests-non-pie target=default-target: (ensure-cargo-hyperlight)
cd src/tests/rust_guests/simpleguest && cargo hyperlight build --target-dir ../target-non-pie --profile={{ if target == "debug" { "dev" } else { target } }}
{{ 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 } }}

non_pie_guests_target := "src/tests/rust_guests/target-non-pie/build/" + hyperlight-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 {{ 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")

clean: clean-rust

clean-rust:
Expand Down
2 changes: 1 addition & 1 deletion src/hyperlight_host/src/hypervisor/gdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ impl<'a> DebugMemoryView<'a> {
}

pub(crate) fn code_section_offset(&self) -> u64 {
self.mem_mgr.layout.get_guest_code_address() as u64
self.mem_mgr.layout.get_guest_code_gva() as u64
}

/// Reads memory from the guest's address space with a maximum length of a PAGE_SIZE
Expand Down
14 changes: 5 additions & 9 deletions src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ mod tests {
use crate::hypervisor::regs::{CommonSegmentRegister, CommonTableRegister, MXCSR_DEFAULT};
use crate::hypervisor::virtual_machine::VirtualMachine;
use crate::mem::layout::SandboxMemoryLayout;
use crate::mem::memory_region::{GuestMemoryRegion, MemoryRegionFlags};
use crate::mem::memory_region::MemoryRegionFlags;
use crate::mem::mgr::{GuestPageTableBuffer, SandboxMemoryManager};
use crate::mem::ptr::RawPtr;
use crate::mem::shared_mem::{ExclusiveSharedMemory, ReadonlySharedMemory};
Expand Down Expand Up @@ -1425,16 +1425,12 @@ mod tests {
let pt_base_gpa = layout.get_pt_base_gpa();
let pt_buf = GuestPageTableBuffer::new(pt_base_gpa as usize);

for rgn in layout
.get_memory_regions_::<GuestMemoryRegion>(())
.unwrap()
.iter()
{
for rgn in layout.get_memory_regions().unwrap().iter() {
let readable = rgn.flags.contains(MemoryRegionFlags::READ);
let writable = rgn.flags.contains(MemoryRegionFlags::WRITE);
let executable = rgn.flags.contains(MemoryRegionFlags::EXECUTE);
let mapping = Mapping {
phys_base: rgn.guest_region.start as u64,
phys_base: rgn.host_region.start as u64,
virt_base: rgn.guest_region.start as u64,
len: rgn.guest_region.len() as u64,
kind: MappingKind::Basic(BasicMapping {
Expand Down Expand Up @@ -1480,7 +1476,7 @@ mod tests {
layout,
ro_mem.to_mgr_snapshot_mem().unwrap(),
scratch_mem,
NextAction::Initialise(layout.get_guest_code_address() as u64),
NextAction::Initialise(layout.get_guest_code_gva() as u64),
);

let (mut hshm, gshm) = mem_mgr.build().unwrap();
Expand Down Expand Up @@ -2185,7 +2181,7 @@ mod tests {
a.fxsave(ptr(rax)).unwrap();

// Return dispatch ptr
a.mov(rax, layout.get_guest_code_address() as u64).unwrap();
a.mov(rax, layout.get_guest_code_gva() as u64).unwrap();

a.hlt().unwrap();

Expand Down
105 changes: 99 additions & 6 deletions src/hyperlight_host/src/mem/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#[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")]
Expand All @@ -14,6 +15,53 @@ use goblin::elf64::program_header::PT_LOAD;
use super::exe::LoadInfo;
use crate::{Result, log_then_return, new_error};

fn apply_relative_relocation(
name: &str,
relocation_va: u64,
addend: i64,
base_va: u64,
load_gva: u64,
target: &mut [u8],
) -> Result<()> {
let offset = relocation_va.checked_sub(base_va).ok_or_else(|| {
new_error!(
"{} target VA ({:#x}) is below ELF base VA ({:#x})",
name,
relocation_va,
base_va
)
})?;
let offset: usize = offset.try_into()?;
let end = offset
.checked_add(size_of::<u64>())
.ok_or_else(|| new_error!("{} target offset overflow", name))?;
let target_len = target.len();
let destination = target.get_mut(offset..end).ok_or_else(|| {
new_error!(
"{} target range [{:#x}, {:#x}) exceeds loaded image size ({:#x})",
name,
offset,
end,
target_len
)
})?;

let load_bias = i128::from(load_gva) - i128::from(base_va);
let value = i128::from(addend)
.checked_add(load_bias)
.and_then(|value| u64::try_from(value).ok())
.ok_or_else(|| {
new_error!(
"{} result does not fit in u64: addend ({:#x}) + load bias ({:#x})",
name,
addend,
load_bias
)
})?;
destination.copy_from_slice(&value.to_le_bytes());
Ok(())
}

#[cfg(feature = "mem_profile")]
struct ResolvedSectionHeader {
name: String,
Expand All @@ -29,6 +77,8 @@ pub(crate) struct ElfInfo {
shdrs: Vec<ResolvedSectionHeader>,
entry: u64,
relocs: Vec<Reloc>,
/// 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<String>,
Expand Down Expand Up @@ -130,6 +180,7 @@ impl ElfInfo {
.collect(),
entry: elf.entry,
relocs,
is_pie: elf.header.e_type == ET_DYN,
guest_bin_version,
})
}
Expand All @@ -155,6 +206,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> {
Expand All @@ -180,7 +236,7 @@ impl ElfInfo {
.unwrap();
(max_phdr.p_vaddr + max_phdr.p_memsz - self.get_base_va()) as usize
}
pub(crate) fn load_at(self, load_addr: usize, target: &mut [u8]) -> Result<LoadInfo> {
pub(crate) fn load_at(self, load_gva: u64, target: &mut [u8]) -> Result<LoadInfo> {
let base_va = self.get_base_va();
for phdr in self.phdrs.iter().filter(|phdr| phdr.p_type == PT_LOAD) {
let start_va = (phdr.p_vaddr - base_va) as usize;
Expand All @@ -199,8 +255,14 @@ impl ElfInfo {
match r.r_type {
R_AARCH64_RELATIVE => {
let addend = get_addend("R_AARCH64_RELATIVE", r)?;
target[r.r_offset as usize..r.r_offset as usize + 8]
.copy_from_slice(&(load_addr as i64 + addend).to_le_bytes());
apply_relative_relocation(
"R_AARCH64_RELATIVE",
r.r_offset,
addend,
base_va,
load_gva,
target,
)?;
}
R_AARCH64_NONE => {}
_ => {
Expand All @@ -211,8 +273,14 @@ impl ElfInfo {
match r.r_type {
R_X86_64_RELATIVE => {
let addend = get_addend("R_X86_64_RELATIVE", r)?;
target[r.r_offset as usize..r.r_offset as usize + 8]
.copy_from_slice(&(load_addr as i64 + addend).to_le_bytes());
apply_relative_relocation(
"R_X86_64_RELATIVE",
r.r_offset,
addend,
base_va,
load_gva,
target,
)?;
}
R_X86_64_NONE => {}
_ => {
Expand All @@ -227,7 +295,7 @@ impl ElfInfo {
Ok(LoadInfo {
info: Arc::new(UnwindInfo {
payload: self.payload,
load_addr: load_addr as u64,
load_addr: load_gva,
va_size,
base_svma,
shdrs: self.shdrs,
Expand All @@ -239,3 +307,28 @@ impl ElfInfo {
}
}
}

#[cfg(test)]
mod tests {
use super::apply_relative_relocation;

#[test]
fn relative_relocation_uses_link_base() {
let mut target = [0u8; 16];

apply_relative_relocation("R_RELATIVE", 0x1008, 0x1010, 0x1000, 0x3000, &mut target)
.unwrap();

assert_eq!(u64::from_le_bytes(target[8..].try_into().unwrap()), 0x3010);
}

#[test]
fn relative_relocation_supports_negative_load_bias() {
let mut target = [0u8; 8];

apply_relative_relocation("R_RELATIVE", 0x1000, 0x1010, 0x1000, 0x800, &mut target)
.unwrap();

assert_eq!(u64::from_le_bytes(target), 0x810);
}
}
10 changes: 8 additions & 2 deletions src/hyperlight_host/src/mem/exe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,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 {
Expand All @@ -100,9 +106,9 @@ impl ExeInfo {
// copying into target, but the PE loader chooses to apply
// relocations in its owned representation of the PE contents,
// which requires it to be &mut.
pub fn load(self, load_addr: usize, target: &mut [u8]) -> Result<LoadInfo> {
pub fn load(self, load_gva: u64, target: &mut [u8]) -> Result<LoadInfo> {
match self {
ExeInfo::Elf(elf) => elf.load_at(load_addr, target),
ExeInfo::Elf(elf) => elf.load_at(load_gva, target),
}
}
}
Expand Down
Loading
Loading