Skip to content
Closed
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
7 changes: 0 additions & 7 deletions src/hyperlight_common/src/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,4 @@ pub struct HyperlightPEB {
pub output_stack: GuestMemoryRegion,
pub init_data: GuestMemoryRegion,
pub guest_heap: GuestMemoryRegion,
/// File mappings array descriptor.
/// **Note:** `size` holds the **entry count** (number of valid
/// [`FileMappingInfo`] entries), NOT a byte size. `ptr` holds the
/// guest address of the preallocated array (immediately after the
/// PEB struct).
#[cfg(feature = "nanvix-unstable")]
pub file_mappings: GuestMemoryRegion,
}
37 changes: 33 additions & 4 deletions src/hyperlight_host/src/mem/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ use std::sync::Arc;
use goblin::elf::reloc::{R_AARCH64_NONE, R_AARCH64_RELATIVE};
#[cfg(target_arch = "x86_64")]
use goblin::elf::reloc::{R_X86_64_NONE, R_X86_64_RELATIVE};
use goblin::elf::{Elf, ProgramHeaders, Reloc};
#[cfg(feature = "nanvix-unstable")]
use goblin::elf32::program_header::PT_LOAD;
#[cfg(not(feature = "nanvix-unstable"))]
use goblin::elf::{Elf, ProgramHeaders, Reloc, section_header};
use goblin::elf64::program_header::PT_LOAD;

use super::exe::LoadInfo;
Expand All @@ -45,6 +42,8 @@ pub(crate) struct ElfInfo {
shdrs: Vec<ResolvedSectionHeader>,
entry: u64,
relocs: Vec<Reloc>,
/// (addr, size) of NOBITS sections that need zero-filling (excludes .tbss).
nobits_ranges: Vec<(u64, u64)>,
/// 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 @@ -128,6 +127,20 @@ impl ElfInfo {
// hyperlight-guest-bin.
let guest_bin_version = Self::read_version_note(&elf, bytes);

// Collect NOBITS sections (e.g. .bss) that need zero-filling.
// Skip .tbss (SHF_TLS) since thread-local BSS is allocated per-thread.
let nobits_ranges: Vec<(u64, u64)> = {
elf.section_headers
.iter()
.filter(|sh| {
sh.sh_type == section_header::SHT_NOBITS
&& sh.sh_size > 0
&& (sh.sh_flags & u64::from(section_header::SHF_TLS)) == 0
})
.map(|sh| (sh.sh_addr, sh.sh_size))
.collect()
};

Ok(ElfInfo {
payload: bytes.to_vec(),
phdrs: elf.program_headers,
Expand All @@ -146,6 +159,7 @@ impl ElfInfo {
.collect(),
entry: elf.entry,
relocs,
nobits_ranges,
guest_bin_version,
})
}
Expand Down Expand Up @@ -206,6 +220,21 @@ impl ElfInfo {
.copy_from_slice(&self.payload[payload_offset..payload_offset + payload_len]);
target[start_va + payload_len..start_va + phdr.p_memsz as usize].fill(0);
}
// Zero-fill NOBITS sections (e.g. .bss) that were not already
// covered by the filesz < memsz zeroing above.
for &(addr, size) in &self.nobits_ranges {
let sh_start = (addr - base_va) as usize;
let sh_end = sh_start + size as usize;
if sh_end <= target.len() {
target[sh_start..sh_end].fill(0);
} else {
tracing::warn!(
"NOBITS section at VA {:#x} (size {:#x}) extends past loaded image, skipping zero-fill",
addr,
size
);
}
}
Comment on lines +223 to +237
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addr - base_va can underflow (or wrap in release) for NOBITS sections whose sh_addr is below the first PT_LOAD vaddr (or for non-ALLOC NOBITS sections with sh_addr == 0). This can lead to panics/out-of-bounds slicing and also allows zero-filling the wrong part of target. Filter NOBITS ranges to SHF_ALLOC, and during zero-fill guard addr >= base_va and clamp the [start,end) range to target.len() instead of skipping the whole fill when it extends past the loaded image.

Copilot uses AI. Check for mistakes.
let get_addend = |name, r: &Reloc| {
r.r_addend
.ok_or_else(|| new_error!("{} missing addend", name))
Expand Down
89 changes: 2 additions & 87 deletions src/hyperlight_host/src/mem/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,6 @@ pub(crate) struct SandboxMemoryLayout {
peb_output_data_offset: usize,
peb_init_data_offset: usize,
peb_heap_data_offset: usize,
#[cfg(feature = "nanvix-unstable")]
peb_file_mappings_offset: usize,

guest_heap_buffer_offset: usize,
init_data_offset: usize,
Expand Down Expand Up @@ -281,11 +279,6 @@ impl Debug for SandboxMemoryLayout {
"Guest Heap Offset",
&format_args!("{:#x}", self.peb_heap_data_offset),
);
#[cfg(feature = "nanvix-unstable")]
ff.field(
"File Mappings Offset",
&format_args!("{:#x}", self.peb_file_mappings_offset),
);
ff.field(
"Guest Heap Buffer Offset",
&format_args!("{:#x}", self.guest_heap_buffer_offset),
Expand Down Expand Up @@ -353,29 +346,11 @@ impl SandboxMemoryLayout {
let peb_output_data_offset = peb_offset + offset_of!(HyperlightPEB, output_stack);
let peb_init_data_offset = peb_offset + offset_of!(HyperlightPEB, init_data);
let peb_heap_data_offset = peb_offset + offset_of!(HyperlightPEB, guest_heap);
#[cfg(feature = "nanvix-unstable")]
let peb_file_mappings_offset = peb_offset + offset_of!(HyperlightPEB, file_mappings);

// The following offsets are the actual values that relate to memory layout,
// which are written to PEB struct
let peb_address = Self::BASE_ADDRESS + peb_offset;
// make sure heap buffer starts at 4K boundary.
// The FileMappingInfo array is stored immediately after the PEB struct.
// We statically reserve space for MAX_FILE_MAPPINGS entries so that
// the heap never overlaps the array, even when all slots are used.
// The host writes file mapping metadata here via write_file_mapping_entry;
// the guest only reads the entries. We don't know at layout time how
// many file mappings the host will register, so we reserve space for
// the maximum number.
// The heap starts at the next page boundary after this reserved area.
#[cfg(feature = "nanvix-unstable")]
let file_mappings_array_end = peb_offset
+ size_of::<HyperlightPEB>()
+ hyperlight_common::mem::MAX_FILE_MAPPINGS
* size_of::<hyperlight_common::mem::FileMappingInfo>();
#[cfg(feature = "nanvix-unstable")]
let guest_heap_buffer_offset = file_mappings_array_end.next_multiple_of(PAGE_SIZE_USIZE);
#[cfg(not(feature = "nanvix-unstable"))]
let guest_heap_buffer_offset =
(peb_offset + size_of::<HyperlightPEB>()).next_multiple_of(PAGE_SIZE_USIZE);

Expand All @@ -389,8 +364,6 @@ impl SandboxMemoryLayout {
peb_output_data_offset,
peb_init_data_offset,
peb_heap_data_offset,
#[cfg(feature = "nanvix-unstable")]
peb_file_mappings_offset,
sandbox_memory_config: cfg,
code_size,
guest_heap_buffer_offset,
Expand Down Expand Up @@ -514,32 +487,6 @@ impl SandboxMemoryLayout {
self.peb_heap_data_offset
}

/// Get the offset in guest memory to the file_mappings count field
/// (the `size` field of the `GuestMemoryRegion` in the PEB).
#[cfg(feature = "nanvix-unstable")]
pub(crate) fn get_file_mappings_size_offset(&self) -> usize {
self.peb_file_mappings_offset
}

/// Get the offset in guest memory to the file_mappings pointer field.
#[cfg(feature = "nanvix-unstable")]
fn get_file_mappings_pointer_offset(&self) -> usize {
self.get_file_mappings_size_offset() + size_of::<u64>()
}

/// Get the offset in snapshot memory where the FileMappingInfo array starts
/// (immediately after the PEB struct, within the same page).
#[cfg(feature = "nanvix-unstable")]
pub(crate) fn get_file_mappings_array_offset(&self) -> usize {
self.peb_offset + size_of::<HyperlightPEB>()
}

/// Get the guest address of the FileMappingInfo array.
#[cfg(feature = "nanvix-unstable")]
fn get_file_mappings_array_gva(&self) -> u64 {
(Self::BASE_ADDRESS + self.get_file_mappings_array_offset()) as u64
}

/// Get the offset of the heap pointer in guest memory,
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
fn get_heap_pointer_offset(&self) -> usize {
Expand Down Expand Up @@ -643,19 +590,7 @@ impl SandboxMemoryLayout {
));
}

// PEB + preallocated FileMappingInfo array
#[cfg(feature = "nanvix-unstable")]
let heap_offset = {
let peb_and_array_size = size_of::<HyperlightPEB>()
+ hyperlight_common::mem::MAX_FILE_MAPPINGS
* size_of::<hyperlight_common::mem::FileMappingInfo>();
builder.push_page_aligned(
peb_and_array_size,
MemoryRegionFlags::READ | MemoryRegionFlags::WRITE,
Peb,
)
};
#[cfg(not(feature = "nanvix-unstable"))]
// PEB
let heap_offset =
builder.push_page_aligned(size_of::<HyperlightPEB>(), MemoryRegionFlags::READ, Peb);

Expand Down Expand Up @@ -796,21 +731,6 @@ impl SandboxMemoryLayout {
write_u64(mem, self.get_heap_size_offset(), self.heap_size.try_into()?)?;
write_u64(mem, self.get_heap_pointer_offset(), addr)?;

// Set up the file_mappings descriptor in the PEB.
// - The `size` field holds the number of valid FileMappingInfo
// entries currently written (initially 0 — entries are added
// later by map_file_cow / evolve).
// - The `ptr` field holds the guest address of the preallocated
// FileMappingInfo array
#[cfg(feature = "nanvix-unstable")]
write_u64(mem, self.get_file_mappings_size_offset(), 0)?;
#[cfg(feature = "nanvix-unstable")]
write_u64(
mem,
self.get_file_mappings_pointer_offset(),
self.get_file_mappings_array_gva(),
)?;

// End of setting up the PEB

// The input and output data regions do not have their layout
Expand Down Expand Up @@ -865,12 +785,7 @@ mod tests {
// in order of layout
expected_size += layout.code_size;

// PEB + preallocated FileMappingInfo array
#[cfg(feature = "nanvix-unstable")]
let peb_and_array = size_of::<HyperlightPEB>()
+ hyperlight_common::mem::MAX_FILE_MAPPINGS
* size_of::<hyperlight_common::mem::FileMappingInfo>();
#[cfg(not(feature = "nanvix-unstable"))]
// PEB
let peb_and_array = size_of::<HyperlightPEB>();
expected_size += peb_and_array.next_multiple_of(PAGE_SIZE_USIZE);

Expand Down
63 changes: 0 additions & 63 deletions src/hyperlight_host/src/mem/mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#[cfg(feature = "nanvix-unstable")]
use std::mem::offset_of;

use flatbuffers::FlatBufferBuilder;
use hyperlight_common::flatbuffer_wrappers::function_call::{
Expand Down Expand Up @@ -339,67 +337,6 @@ impl SandboxMemoryManager<ExclusiveSharedMemory> {
}

impl SandboxMemoryManager<HostSharedMemory> {
/// Write a [`FileMappingInfo`] entry into the PEB's preallocated array.
///
/// Reads the current entry count from the PEB, validates that the
/// array isn't full ([`MAX_FILE_MAPPINGS`]), writes the entry at the
/// next available slot, and increments the count.
///
/// This is the **only** place that writes to the PEB file mappings
/// array — both `MultiUseSandbox::map_file_cow` and the evolve loop
/// call through here so the logic is not duplicated.
///
/// # Errors
///
/// Returns an error if [`MAX_FILE_MAPPINGS`] has been reached.
///
/// [`FileMappingInfo`]: hyperlight_common::mem::FileMappingInfo
/// [`MAX_FILE_MAPPINGS`]: hyperlight_common::mem::MAX_FILE_MAPPINGS
#[cfg(feature = "nanvix-unstable")]
pub(crate) fn write_file_mapping_entry(
&mut self,
guest_addr: u64,
size: u64,
label: &[u8; hyperlight_common::mem::FILE_MAPPING_LABEL_MAX_LEN + 1],
) -> Result<()> {
use hyperlight_common::mem::{FileMappingInfo, MAX_FILE_MAPPINGS};

// Read the current entry count from the PEB. This is the source
// of truth — it survives snapshot/restore because the PEB is
// part of shared memory that gets snapshotted.
let current_count =
self.shared_mem
.read::<u64>(self.layout.get_file_mappings_size_offset())? as usize;

if current_count >= MAX_FILE_MAPPINGS {
return Err(crate::new_error!(
"file mapping limit reached ({} of {})",
current_count,
MAX_FILE_MAPPINGS,
));
}

// Write the entry into the next available slot.
let entry_offset = self.layout.get_file_mappings_array_offset()
+ current_count * std::mem::size_of::<FileMappingInfo>();
let guest_addr_offset = offset_of!(FileMappingInfo, guest_addr);
let size_offset = offset_of!(FileMappingInfo, size);
let label_offset = offset_of!(FileMappingInfo, label);
self.shared_mem
.write::<u64>(entry_offset + guest_addr_offset, guest_addr)?;
self.shared_mem
.write::<u64>(entry_offset + size_offset, size)?;
self.shared_mem
.copy_from_slice(label, entry_offset + label_offset)?;

// Increment the entry count.
let new_count = (current_count + 1) as u64;
self.shared_mem
.write::<u64>(self.layout.get_file_mappings_size_offset(), new_count)?;

Ok(())
}

/// Reads a host function call from memory
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_host_function_call(&mut self) -> Result<FunctionCall> {
Expand Down
2 changes: 1 addition & 1 deletion src/hyperlight_host/src/sandbox/file_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub(crate) struct PreparedFileMapping {
/// The page-aligned size of the mapping in bytes.
pub(crate) size: usize,
/// Null-terminated C-style label for this mapping (max 63 chars + null).
#[cfg_attr(not(feature = "nanvix-unstable"), allow(unused))]
#[allow(unused)]
pub(crate) label: [u8; hyperlight_common::mem::FILE_MAPPING_LABEL_MAX_LEN + 1],
/// Host-side OS resources. `None` after successful consumption
Comment on lines 58 to 63
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PreparedFileMapping::label appears to no longer be read anywhere (and is now silenced with #[allow(unused)]). If the label is no longer used after removing PEB file mappings, consider deleting the field (and related label-building work) or wiring it into the replacement mechanism so this data isn’t computed/stored unnecessarily.

Copilot uses AI. Check for mistakes.
/// by the apply step (ownership transferred to the VM layer).
Expand Down
Loading
Loading