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
6 changes: 6 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ like-ci config=default-target hypervisor="kvm":
@# Ensure up-to-date Cargo.lock
cargo fetch --locked

@# typos
typos

@# check licence headers
just check-license-headers

@# fmt
just fmt-check

Expand Down
13 changes: 9 additions & 4 deletions dev/check-license-headers.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# This script checks for the presence of the required license header in Rust source files.

# Get the repository root
Expand Down Expand Up @@ -32,7 +32,12 @@ MISSING_HEADERS=0
MISSING_FILES=""

# Find all Rust files, excluding target directory
while IFS= read -r file; do
while IFS= read -r -d $'\0' file; do
# Skip some files which appear when the guests are build
if grep -q '^src/tests/rust_guests/[^/]*/target/' <<< "$file"; then
continue
fi

# Skip auto-generated files
if grep -q "@generated" "$file" || grep -q "Automatically generated" "$file"; then
continue
Expand All @@ -44,7 +49,7 @@ while IFS= read -r file; do
MISSING_FILES="$MISSING_FILES\n $file"
MISSING_HEADERS=$((MISSING_HEADERS + 1))
fi
done < <(find src -name "*.rs" -type f)
done < <(find src -name "*.rs" -type f -print0)

if [ $MISSING_HEADERS -gt 0 ]; then
echo "Found $MISSING_HEADERS files with missing or invalid license headers:"
Expand All @@ -57,4 +62,4 @@ if [ $MISSING_HEADERS -gt 0 ]; then
else
echo "All Rust files have the required license header"
exit 0
fi
fi
123 changes: 101 additions & 22 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,19 @@
# for rustfmt and old toolchains to verify MSRV
toolchains = lib.mapAttrs (_: customisedRustChannelOf) {
stable = {
date = "2025-09-18";
date = "2025-12-11";
channel = "stable";
sha256 = "sha256-SJwZ8g0zF2WrKDVmHrVG3pD2RGoQeo24MEXnNx5FyuI=";
sha256 = "sha256-sqSWJDUxc+zaz1nBWMAJKTAGBuGWP25GCftIOlCEAtA=";
};
nightly = {
date = "2025-07-29";
date = "2026-01-19";
channel = "nightly";
sha256 = "sha256-6D2b7glWC3jpbIGCq6Ta59lGCKN9sTexhgixH4Y7Nng=";
sha256 = "sha256-Ye65U/qzilPLte800N5oxFOY96shgG8bST8dbrF6Qh0=";
};
"1.88" = {
date = "2025-06-26";
"1.89" = {
date = "2025-08-07";
channel = "stable";
sha256 = "sha256-Qxt8XAuaUR2OMdKbN4u8dBJOhSHxS+uS06Wl9+flVEk=";
sha256 = "sha256-+9FmLhAOezBZCOziO0Qct1NOrfpjNsXxc/8I0c7BdKE=";
};
};

Expand All @@ -96,39 +96,116 @@
rustc = toolchains.stable.rust;
};

# Script snippet, used in the cargo/rustc wrappers below,
# which creates a number of .cargo/config.toml files in
# order to allow using Nix-fetched dependencies (this must
# be done for the guests, as well as for the main
# workspace). Ideally, we would just use environment
# variables or the --config option to Cargo, but
# unfortunately that tends not to play well with subcommands
# like `cargo clippy` and `cargo hyperlight` (see
# https://github.com/rust-lang/cargo/issues/11031).
materialiseDeps = deps: let
sortedNames = lib.lists.reverseList (builtins.attrNames deps);
matchClause = path: '' */${path}) root="''${manifest%${path}}" ;;'';
matchClauses = lib.strings.concatStringsSep "\n"
(builtins.map matchClause sortedNames);
makeClause = manifest: vendor: let
dir = builtins.dirOf manifest;
gitExclude = builtins.toString (/. + "${dir}/.cargo");
in ''
mkdir -p $root/${dir}/.cargo
cat >$root/${dir}/.cargo/config.toml <<EOF
[source.crates-io]
replace-with = "vendored-sources"

[source.vendored-sources]
directory = "${vendor}"
EOF
printf "# vendor dependency configuration generated by nix\n%s\n" "${gitExclude}" >> $root/.git/info/exclude
'';
makeClauses = lib.strings.concatStringsSep "\n"
(lib.mapAttrsToList makeClause deps);
in ''
manifest=$(''${base}/bin/cargo locate-project --message-format plain --workspace)
case "$manifest" in
${matchClauses}
esac
if [ -f ''${root}/flake.nix ]; then
sed -i '/# vendor dependency configuration generated by nix/{N;d;}' $root/.git/info/exclude
${makeClauses}
fi
'';

# Hyperlight scripts use cargo in a bunch of ways that don't
# make sense for Nix cargo, including the `rustup +toolchain`
# syntax to use a specific toolchain and `cargo install`, so we
# build wrappers for rustc and cargo that enable this. The
# scripts also use `rustup toolchain install` in some cases, in
# order to work in CI, so we provide a fake rustup that does
# nothing as well.
rustup-like-wrapper = name: pkgs.writeShellScriptBin name
rustup-like-wrapper = name: deps: pkgs.writeShellScriptBin name
(let
clause = name: toolchain:
"+${name}) base=\"${toolchain.rust}\"; shift 1; ;;";
clauses = lib.strings.concatStringsSep "\n"
(lib.mapAttrsToList clause toolchains);
in ''
base="${toolchains.stable.rust}"
case "$1" in
${clauses}
install) exit 0; ;;
esac
export PATH="$base/bin:$PATH"
exec "$base/bin/${name}" "$@"
'');
fake-rustup = pkgs.symlinkJoin {
base="${toolchains.stable.rust}"
${materialiseDeps deps}
case "$1" in
${clauses}
install) exit 0; ;;
esac
export PATH="$base/bin:$PATH"
exec "$base/bin/${name}" "$@"
'');
fake-rustup = deps: pkgs.symlinkJoin {
name = "fake-rustup";
paths = [
(pkgs.writeShellScriptBin "rustup" "")
(rustup-like-wrapper "rustc")
(rustup-like-wrapper "cargo")
(rustup-like-wrapper "rustc" deps)
(rustup-like-wrapper "cargo" deps)
];
};

buildRustPackageClang = rust-platform.buildRustPackage.override { stdenv = clangStdenv; };
in (buildRustPackageClang rec {

cargo-hyperlight = buildRustPackageClang rec {
pname = "cargo-hyperlight";
version = "0.1.5";
src = fetchFromGitHub {
owner = "hyperlight-dev";
repo = "cargo-hyperlight";
tag = "v${version}";
hash = "sha256-xq4/c69N0wG/I8WOYVloo0J0JqoSIKiWWtECdSKrsxo=";
};
cargoHash = "sha256-muiMVrK1TydQiMitihfo7xYidqUIIQ+Hw3BIeo5rLFw=";
};
# when building a guest with cargo-hyperlight, we need to
# include any crates.io dependencies of the standard library
# (e.g. rustc-literal-escaper)
stdlibLocks = lib.mapAttrsToList (_: toolchain:
"${toolchain.rust}/lib/rustlib/src/rust/library/Cargo.lock"
) toolchains;
stdlibDeps = builtins.map (lockFile:
rust-platform.importCargoLock { inherit lockFile; }) stdlibLocks;
withStdlibLock = lockFile:
pkgs.symlinkJoin {
name = "cargo-deps";
paths = stdlibDeps ++ [
(rust-platform.importCargoLock {
inherit lockFile;
})
];
};
deps = finalRootVendor: {
"Cargo.toml" = finalRootVendor;
"src/tests/rust_guests/dummyguest/Cargo.toml" = withStdlibLock ./src/tests/rust_guests/dummyguest/Cargo.lock;
"src/tests/rust_guests/simpleguest/Cargo.toml" = withStdlibLock ./src/tests/rust_guests/simpleguest/Cargo.lock;
"src/tests/rust_guests/witguest/Cargo.toml" = withStdlibLock ./src/tests/rust_guests/witguest/Cargo.lock;
};
in (buildRustPackageClang (mkDerivationAttrs: {
pname = "hyperlight";
version = "0.0.0";
src = lib.cleanSource ./.;
Expand All @@ -150,6 +227,8 @@
jaq
gdb
zlib
cargo-hyperlight
typos
];
buildInputs = [
pango
Expand All @@ -167,9 +246,9 @@
# Set this through shellHook rather than nativeBuildInputs to be
# really sure that it overrides the real cargo.
postHook = ''
export PATH="${fake-rustup}/bin:$PATH"
export PATH="${fake-rustup (deps mkDerivationAttrs.cargoDeps)}/bin:$PATH"
'';
}).overrideAttrs(oA: {
})).overrideAttrs(oA: {
hardeningDisable = [ "all" ];
});
};
Expand Down
13 changes: 11 additions & 2 deletions src/hyperlight_common/src/arch/amd64/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

// Keep in mind that the minimum upper half GVA is 0xffff_8000_0000_0000
pub const SNAPSHOT_PT_GVA: usize = 0xffff_ff00_0000_0000;
/// We have this the top of the page below the top of memory in order
/// to make working with start/end ptrs in a few places more
/// convenient (not needing to worry about overflow)
pub const MAX_GVA: usize = 0xffff_ffff_ffff_efff;
pub const SNAPSHOT_PT_GVA: usize = 0xffff_8000_0000_0000;

/// We assume 36-bit IPAs for now, since every amd64 processor
/// supports at least 36 bits. Almost all of them support at least 40
/// bits, so we could consider bumping this in the future if we were
/// ever memory-constrained.
pub const MAX_GPA: usize = 0x0000_00ff_ffff_ffff;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a typo or I don't understand it correctly?
Why is the MAX_GPA is 0x0000_00ff_ffff_ffff (40 bit) when the comment above says 36 bit?

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, thanks for catching that.

11 changes: 9 additions & 2 deletions src/hyperlight_common/src/arch/amd64/vmem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,15 @@ impl<const HIGH_BIT: u8, const LOW_BIT: u8, Op: TableOps> Iterator
let next_vmin = if self.n == 0 {
self.request.vmin
} else {
// Align to the next boundary by adding one entry's worth and masking off lower bits
(self.request.vmin + (self.n << LOW_BIT)) & !lower_bits_mask
// Align to the next boundary by adding one entry's worth
// and masking off lower bits. Masking off before adding
// is safe, since n << LOW_BIT must always have zeros in
// these positions.
let aligned_min = self.request.vmin & !lower_bits_mask;
// Use checked_add here because going past the end of the
// address space counts as "the next one would be out of
// range"
aligned_min.checked_add(self.n << LOW_BIT)?
};

// Check if we've processed the entire requested range
Expand Down
19 changes: 18 additions & 1 deletion src/hyperlight_common/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,21 @@ mod arch;

// The constraint on the feature is temporary and will be removed when other arch i686 is added
#[cfg(feature = "init-paging")]
pub use arch::SNAPSHOT_PT_GVA;
pub use arch::MAX_GPA;
#[cfg(feature = "init-paging")]
pub use arch::{MAX_GVA, SNAPSHOT_PT_GVA};

// offsets down from the top of scratch memory for various things
pub const SCRATCH_TOP_SIZE_OFFSET: u64 = 0x08;
pub const SCRATCH_TOP_USED_OFFSET: u64 = 0x10;
pub const SCRATCH_TOP_ALLOCATOR_OFFSET: u64 = 0x18;
pub const SCRATCH_TOP_EXN_STACK_OFFSET: u64 = 0x20;

#[cfg(feature = "init-paging")]
pub fn scratch_base_gpa(size: usize) -> u64 {
(MAX_GPA - size + 1) as u64
}
#[cfg(feature = "init-paging")]
pub fn scratch_base_gva(size: usize) -> u64 {
(MAX_GVA - size + 1) as u64
}
21 changes: 16 additions & 5 deletions src/hyperlight_host/src/hypervisor/crashdump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ impl GuestView {
let regions = ctx
.regions
.iter()
.filter(|r| !r.host_region.is_empty())
.filter(|r| !r.guest_region.is_empty())
.map(|r| VaRegion {
begin: r.guest_region.start as u64,
end: r.guest_region.end as u64,
offset: r.host_region.start as u64,
offset: <_ as Into<usize>>::into(r.host_region.start) as u64,
Copy link
Member

@andreiltd andreiltd Jan 23, 2026

Choose a reason for hiding this comment

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

This looks like a fix straight from rust-analyzer 😄 can this be just .into()?

protection: VaProtection {
is_private: false,
read: r.flags.contains(MemoryRegionFlags::READ),
Expand Down Expand Up @@ -225,8 +225,8 @@ impl ReadProcessMemory for GuestMemReader {
let offset = base - r.guest_region.start;
let region_slice = unsafe {
std::slice::from_raw_parts(
r.host_region.start as *const u8,
r.host_region.len(),
<_ as Into<usize>>::into(r.host_region.start) as *const u8,
r.guest_region.len(),
)
};

Expand Down Expand Up @@ -463,9 +463,20 @@ mod test {
#[test]
fn test_crashdump_dummy_core_dump() {
let dummy_vec = vec![0; 0x1000];
use crate::mem::memory_region::{HostGuestMemoryRegion, MemoryRegionKind};
#[cfg(target_os = "windows")]
let host_base = crate::mem::memory_region::HostRegionBase {
from_handle: windows::Win32::Foundation::INVALID_HANDLE_VALUE.into(),
handle_base: 0,
handle_size: -1isize as usize,
offset: dummy_vec.as_ptr() as usize,
};
#[cfg(not(target_os = "windows"))]
let host_base = dummy_vec.as_ptr() as usize;
let host_end = <HostGuestMemoryRegion as MemoryRegionKind>::add(host_base, dummy_vec.len());
let regions = vec![MemoryRegion {
guest_region: 0x1000..0x2000,
host_region: dummy_vec.as_ptr() as usize..dummy_vec.as_ptr() as usize + dummy_vec.len(),
host_region: host_base..host_end,
flags: MemoryRegionFlags::READ | MemoryRegionFlags::WRITE,
region_type: crate::mem::memory_region::MemoryRegionType::Code,
}];
Expand Down
9 changes: 4 additions & 5 deletions src/hyperlight_host/src/hypervisor/gdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ impl DebugMemoryAccess {
HyperlightError::TranslateGuestAddress(mem_offset as u64)
})?;

let host_start_ptr = <_ as Into<usize>>::into(reg.host_region.start);
let bytes: &[u8] = unsafe {
slice::from_raw_parts(reg.host_region.start as *const u8, reg.host_region.len())
slice::from_raw_parts(host_start_ptr as *const u8, reg.guest_region.len())
};
data[..read_len].copy_from_slice(&bytes[region_offset..region_offset + read_len]);

Expand Down Expand Up @@ -191,11 +192,9 @@ impl DebugMemoryAccess {
HyperlightError::TranslateGuestAddress(mem_offset as u64)
})?;

let host_start_ptr = <_ as Into<usize>>::into(reg.host_region.start);
let bytes: &mut [u8] = unsafe {
slice::from_raw_parts_mut(
reg.host_region.start as *mut u8,
reg.host_region.len(),
)
slice::from_raw_parts_mut(host_start_ptr as *mut u8, reg.guest_region.len())
};
bytes[region_offset..region_offset + write_len].copy_from_slice(&data[..write_len]);

Expand Down
Loading