diff --git a/Justfile b/Justfile index e25900647..4693f4690 100644 --- a/Justfile +++ b/Justfile @@ -164,10 +164,17 @@ run-examples-like-ci config=default-target hypervisor="kvm": @# Run Rust examples - linux {{ if os() == "linux" { "just run-rust-examples-linux " + config + " " } else { "" } }} -benchmarks-like-ci config=default-target hypervisor="$vm": +benchmarks-like-ci config=default-target hypervisor="kvm": @# Run benchmarks {{ if config == "release" { "just bench-ci main" } else { "" } }} +fuzz-like-ci target config=default-target hypervisor="kvm": + @# Run Fuzzing + # Use a much shorter time limit (1 vs 300 seconds), because the + # local version of this step is mostly intended just for making + # sure that the fuzz harnesses compile + {{ if config == "release" { "just fuzz-timed " + target + " 1" } else { "" } }} + like-ci config=default-target hypervisor="kvm": @# .github/workflows/dep_code_checks.yml just code-checks-like-ci {{config}} {{hypervisor}} @@ -184,7 +191,12 @@ like-ci config=default-target hypervisor="kvm": @# .github/workflows/dep_benchmarks.yml just benchmarks-like-ci {{config}} {{hypervisor}} - @# can't run fuzzing locally + @# .github/workflows/dep_fuzzing.yml + just fuzz-like-ci fuzz_host_print {{config}} {{hypervisor}} + just fuzz-like-ci fuzz_guest_call {{config}} {{hypervisor}} + just fuzz-like-ci fuzz_host_call {{config}} {{hypervisor}} + just fuzz-like-ci fuzz_guest_estimate_trace_event {{config}} {{hypervisor}} + just fuzz-like-ci fuzz_guest_trace {{config}} {{hypervisor}} @# spelling typos diff --git a/flake.lock b/flake.lock index 4efc77506..487dd3b5c 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1752950548, - "narHash": "sha256-NS6BLD0lxOrnCiEOcvQCDVPXafX1/ek1dfJHX1nUIzc=", + "lastModified": 1770115704, + "narHash": "sha256-KHFT9UWOF2yRPlAnSXQJh6uVcgNcWlFqqiAZ7OVlHNc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c87b95e25065c028d31a94f06a62927d18763fdf", + "rev": "e6eae2ee2110f3d31110d5c222cd395303343b08", "type": "github" }, "original": { @@ -18,11 +18,11 @@ }, "nixpkgs-mozilla": { "locked": { - "lastModified": 1744624473, - "narHash": "sha256-S6zT/w5SyAkJ//dYdjbrXgm+6Vkd/k7qqUl4WgZ6jjk=", + "lastModified": 1762256096, + "narHash": "sha256-Hzj/d8eRpfRfjHSRX6gGRf0jSg2zIJMXl6S5opuKsHc=", "owner": "mozilla", "repo": "nixpkgs-mozilla", - "rev": "2292d4b35aa854e312ad2e95c4bb5c293656f21a", + "rev": "80c058cf774c198fb838fc3549806b232dd3e320", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6e4c845aa..61dc28ff0 100644 --- a/flake.nix +++ b/flake.nix @@ -2,65 +2,77 @@ inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; inputs.nixpkgs-mozilla.url = "github:mozilla/nixpkgs-mozilla/master"; outputs = { self, nixpkgs, nixpkgs-mozilla, ... } @ inputs: - { + rec { + overlays.fix-rust = self: super: { + # Work around the nixpkgs-mozilla equivalent of + # https://github.com/NixOS/nixpkgs/issues/278508 and an + # incompatibility between nixpkgs-mozilla and makeRustPlatform + rustChannelOf = args: let + orig = super.rustChannelOf args; + patchRustPkg = pkg: (pkg.overrideAttrs (oA: { + buildCommand = (builtins.replaceStrings + [ "rustc,rustdoc" ] + [ "rustc,rustdoc,clippy-driver,cargo-clippy,miri,cargo-miri" ] + oA.buildCommand) + (let + wrapperPath = self.path + "/pkgs/build-support/bintools-wrapper/ld-wrapper.sh"; + baseOut = self.clangStdenv.cc.bintools.out; + getStdenvAttrs = drv: (drv.overrideAttrs (oA: { + passthru.origAttrs = oA; + })).origAttrs; + baseEnv = (getStdenvAttrs self.clangStdenv.cc.bintools).env; + baseSubstitutedWrapper = self.replaceVars wrapperPath + { + inherit (baseEnv) + shell coreutils_bin suffixSalt mktemp rm; + use_response_file_by_default = "0"; + prog = null; + out = null; + }; + in '' + # work around a bug in the overlay + ${oA.postInstall} + + # copy over helper scripts that the wrapper needs + (cd "${baseOut}"; find . -type f \( -name '*.sh' -or -name '*.bash' \) -print0) | while read -d $'\0' script; do + mkdir -p "$out/$(dirname "$script")" + substitute "${baseOut}/$script" "$out/$script" --replace-quiet "${baseOut}" "$out" + done + + # TODO: Work out how to make this work with cross builds + ldlld="$out/lib/rustlib/${self.clangStdenv.targetPlatform.config}/bin/gcc-ld/ld.lld"; + if [ -e "$ldlld" ]; then + export prog="$(readlink -f "$ldlld")" + rm "$ldlld" + substitute ${baseSubstitutedWrapper} "$ldlld" --subst-var "out" --subst-var "prog" + chmod +x "$ldlld" + fi + ''); + })) // { + targetPlatforms = [ "x86_64-linux" ]; + badTargetPlatforms = [ ]; + }; + overrideRustPkg = pkg: self.lib.makeOverridable (origArgs: + patchRustPkg (pkg.override origArgs) + ) {}; + in builtins.mapAttrs (_: overrideRustPkg) orig; + }; + gcroots = + let gcrootForShell = pkg: pkg // derivation (pkg.drvAttrs // { + origArgs = pkg.drvAttrs.args; + # assume the builder is bash for now (it always is for + # stdenv, which is the only thing that we will encounter + # in this flake). + args = [ "-c" "declare > $out" ]; + }); + in { + shells.default = gcrootForShell devShells.x86_64-linux.default; + }; devShells.x86_64-linux.default = let pkgs = import nixpkgs { system = "x86_64-linux"; - overlays = [ (import (nixpkgs-mozilla + "/rust-overlay.nix")) ]; + overlays = [ (import (nixpkgs-mozilla + "/rust-overlay.nix")) overlays.fix-rust ]; }; in with pkgs; let - # Work around the nixpkgs-mozilla equivalent of - # https://github.com/NixOS/nixpkgs/issues/278508 and an - # incompatibility between nixpkgs-mozilla and makeRustPlatform - rustChannelOf = args: let - orig = pkgs.rustChannelOf args; - patchRustPkg = pkg: (pkg.overrideAttrs (oA: { - buildCommand = (builtins.replaceStrings - [ "rustc,rustdoc" ] - [ "rustc,rustdoc,clippy-driver,cargo-clippy,miri,cargo-miri" ] - oA.buildCommand) + (let - wrapperPath = pkgs.path + "/pkgs/build-support/bintools-wrapper/ld-wrapper.sh"; - baseOut = pkgs.clangStdenv.cc.bintools.out; - getStdenvAttrs = drv: (drv.overrideAttrs (oA: { - passthru.origAttrs = oA; - })).origAttrs; - baseEnv = (getStdenvAttrs pkgs.clangStdenv.cc.bintools).env; - baseSubstitutedWrapper = pkgs.replaceVars wrapperPath - { - inherit (baseEnv) - shell coreutils_bin suffixSalt mktemp rm; - use_response_file_by_default = "0"; - prog = null; - out = null; - }; - in '' - # work around a bug in the overlay - ${oA.postInstall} - - # copy over helper scripts that the wrapper needs - (cd "${baseOut}"; find . -type f \( -name '*.sh' -or -name '*.bash' \) -print0) | while read -d $'\0' script; do - mkdir -p "$out/$(dirname "$script")" - substitute "${baseOut}/$script" "$out/$script" --replace-quiet "${baseOut}" "$out" - done - - # TODO: Work out how to make this work with cross builds - ldlld="$out/lib/rustlib/${pkgs.clangStdenv.targetPlatform.config}/bin/gcc-ld/ld.lld"; - if [ -e "$ldlld" ]; then - export prog="$(readlink -f "$ldlld")" - rm "$ldlld" - substitute ${baseSubstitutedWrapper} "$ldlld" --subst-var "out" --subst-var "prog" - chmod +x "$ldlld" - fi - ''); - })) // { - targetPlatforms = [ "x86_64-linux" ]; - badTargetPlatforms = [ ]; - }; - overrideRustPkg = pkg: lib.makeOverridable (origArgs: - patchRustPkg (pkg.override origArgs) - ) {}; - in builtins.mapAttrs (_: overrideRustPkg) orig; - customisedRustChannelOf = args: lib.flip builtins.mapAttrs (rustChannelOf args) (_: pkg: pkg.override { targets = [ @@ -246,6 +258,8 @@ zlib cargo-hyperlight typos + flatbuffers + cargo-fuzz ]; buildInputs = [ pango diff --git a/fuzz/fuzz_targets/guest_trace.rs b/fuzz/fuzz_targets/guest_trace.rs index defa45e40..3dfb61c95 100644 --- a/fuzz/fuzz_targets/guest_trace.rs +++ b/fuzz/fuzz_targets/guest_trace.rs @@ -70,7 +70,7 @@ fuzz_target!( init: { let mut cfg = SandboxConfiguration::default(); // In local tests, 256 KiB seemed sufficient for deep recursion - cfg.set_stack_size(256 * 1024); + cfg.set_scratch_size(256 * 1024); let path = simple_guest_for_fuzzing_as_string().expect("Guest Binary Missing"); let u_sbox = UninitializedSandbox::new( GuestBinary::FilePath(path), diff --git a/src/hyperlight_common/src/arch/amd64/layout.rs b/src/hyperlight_common/src/arch/amd64/layout.rs index c98d4b06d..b1471b8ce 100644 --- a/src/hyperlight_common/src/arch/amd64/layout.rs +++ b/src/hyperlight_common/src/arch/amd64/layout.rs @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +// The addresses in this file should be coordinated with +// src/hyperlight_guest/src/arch/amd64/layout.rs and +// src/hyperlight_guest_bin/src/arch/amd64/layout.rs + /// 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) @@ -26,3 +30,13 @@ pub const SNAPSHOT_PT_GVA_MAX: usize = 0xffff_80ff_ffff_ffff; /// bits, so we could consider bumping this in the future if we were /// ever memory-constrained. pub const MAX_GPA: usize = 0x0000_000f_ffff_ffff; + +/// On amd64, this is: +/// - Two pages for the TSS and IDT +/// - (up to) 4 pages for the PTEs for mapping that (including CoW'ing the root PT) +/// - A page for the smallest possible non-exception stack +/// - (up to) 3 pages for mapping that +/// - Two pages for the exception stack and metadata +pub fn min_scratch_size() -> usize { + 12 * crate::vmem::PAGE_SIZE +} diff --git a/src/hyperlight_common/src/arch/i686/layout.rs b/src/hyperlight_common/src/arch/i686/layout.rs index e46a7c8ff..68cc2cf1b 100644 --- a/src/hyperlight_common/src/arch/i686/layout.rs +++ b/src/hyperlight_common/src/arch/i686/layout.rs @@ -21,3 +21,7 @@ pub const MAX_GVA: usize = 0xffff_efff; pub const SNAPSHOT_PT_GVA_MIN: usize = 0xef00_0000; pub const SNAPSHOT_PT_GVA_MAX: usize = 0xefff_efff; pub const MAX_GPA: usize = 0xffff_ffff; + +pub fn min_scratch_size() -> usize { + 1 * crate::vmem::PAGE_SIZE +} diff --git a/src/hyperlight_common/src/flatbuffer_wrappers/guest_error.rs b/src/hyperlight_common/src/flatbuffer_wrappers/guest_error.rs index b3d1f457e..3625f2fb8 100644 --- a/src/hyperlight_common/src/flatbuffer_wrappers/guest_error.rs +++ b/src/hyperlight_common/src/flatbuffer_wrappers/guest_error.rs @@ -35,7 +35,6 @@ pub enum ErrorCode { GispatchFunctionPointerNotSet = 6, OutbError = 7, UnknownError = 8, - StackOverflow = 9, GsCheckFailed = 10, TooManyGuestFunctions = 11, FailureInDlmalloc = 12, @@ -59,7 +58,6 @@ impl From for FbErrorCode { ErrorCode::GispatchFunctionPointerNotSet => Self::GispatchFunctionPointerNotSet, ErrorCode::OutbError => Self::OutbError, ErrorCode::UnknownError => Self::UnknownError, - ErrorCode::StackOverflow => Self::StackOverflow, ErrorCode::GsCheckFailed => Self::GsCheckFailed, ErrorCode::TooManyGuestFunctions => Self::TooManyGuestFunctions, ErrorCode::FailureInDlmalloc => Self::FailureInDlmalloc, @@ -86,7 +84,6 @@ impl From for ErrorCode { } FbErrorCode::GispatchFunctionPointerNotSet => Self::GispatchFunctionPointerNotSet, FbErrorCode::OutbError => Self::OutbError, - FbErrorCode::StackOverflow => Self::StackOverflow, FbErrorCode::GsCheckFailed => Self::GsCheckFailed, FbErrorCode::TooManyGuestFunctions => Self::TooManyGuestFunctions, FbErrorCode::FailureInDlmalloc => Self::FailureInDlmalloc, @@ -113,7 +110,6 @@ impl From for ErrorCode { 6 => Self::GispatchFunctionPointerNotSet, 7 => Self::OutbError, 8 => Self::UnknownError, - 9 => Self::StackOverflow, 10 => Self::GsCheckFailed, 11 => Self::TooManyGuestFunctions, 12 => Self::FailureInDlmalloc, @@ -138,7 +134,6 @@ impl From for u64 { ErrorCode::GispatchFunctionPointerNotSet => 6, ErrorCode::OutbError => 7, ErrorCode::UnknownError => 8, - ErrorCode::StackOverflow => 9, ErrorCode::GsCheckFailed => 10, ErrorCode::TooManyGuestFunctions => 11, ErrorCode::FailureInDlmalloc => 12, @@ -164,7 +159,6 @@ impl From for String { ErrorCode::GispatchFunctionPointerNotSet => "GispatchFunctionPointerNotSet".to_string(), ErrorCode::OutbError => "OutbError".to_string(), ErrorCode::UnknownError => "UnknownError".to_string(), - ErrorCode::StackOverflow => "StackOverflow".to_string(), ErrorCode::GsCheckFailed => "GsCheckFailed".to_string(), ErrorCode::TooManyGuestFunctions => "TooManyGuestFunctions".to_string(), ErrorCode::FailureInDlmalloc => "FailureInDlmalloc".to_string(), diff --git a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/error_code_generated.rs b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/error_code_generated.rs index b299358e3..f26d38db0 100644 --- a/src/hyperlight_common/src/flatbuffers/hyperlight/generated/error_code_generated.rs +++ b/src/hyperlight_common/src/flatbuffers/hyperlight/generated/error_code_generated.rs @@ -25,7 +25,7 @@ pub const ENUM_MAX_ERROR_CODE: u64 = 17; note = "Use associated constants instead. This will no longer be generated in 2021." )] #[allow(non_camel_case_types)] -pub const ENUM_VALUES_ERROR_CODE: [ErrorCode; 17] = [ +pub const ENUM_VALUES_ERROR_CODE: [ErrorCode; 16] = [ ErrorCode::NoError, ErrorCode::UnsupportedParameterType, ErrorCode::GuestFunctionNameNotProvided, @@ -34,7 +34,6 @@ pub const ENUM_VALUES_ERROR_CODE: [ErrorCode; 17] = [ ErrorCode::GispatchFunctionPointerNotSet, ErrorCode::OutbError, ErrorCode::UnknownError, - ErrorCode::StackOverflow, ErrorCode::GsCheckFailed, ErrorCode::TooManyGuestFunctions, ErrorCode::FailureInDlmalloc, @@ -58,7 +57,6 @@ impl ErrorCode { pub const GispatchFunctionPointerNotSet: Self = Self(6); pub const OutbError: Self = Self(7); pub const UnknownError: Self = Self(8); - pub const StackOverflow: Self = Self(9); pub const GsCheckFailed: Self = Self(10); pub const TooManyGuestFunctions: Self = Self(11); pub const FailureInDlmalloc: Self = Self(12); @@ -79,7 +77,6 @@ impl ErrorCode { Self::GispatchFunctionPointerNotSet, Self::OutbError, Self::UnknownError, - Self::StackOverflow, Self::GsCheckFailed, Self::TooManyGuestFunctions, Self::FailureInDlmalloc, @@ -102,7 +99,6 @@ impl ErrorCode { Self::GispatchFunctionPointerNotSet => Some("GispatchFunctionPointerNotSet"), Self::OutbError => Some("OutbError"), Self::UnknownError => Some("UnknownError"), - Self::StackOverflow => Some("StackOverflow"), Self::GsCheckFailed => Some("GsCheckFailed"), Self::TooManyGuestFunctions => Some("TooManyGuestFunctions"), Self::FailureInDlmalloc => Some("FailureInDlmalloc"), diff --git a/src/hyperlight_common/src/layout.rs b/src/hyperlight_common/src/layout.rs index a1a6d2661..215a80d87 100644 --- a/src/hyperlight_common/src/layout.rs +++ b/src/hyperlight_common/src/layout.rs @@ -32,3 +32,6 @@ pub fn scratch_base_gpa(size: usize) -> u64 { pub fn scratch_base_gva(size: usize) -> u64 { (MAX_GVA - size + 1) as u64 } + +/// Compute the minimum scratch region size needed for a sandbox. +pub use arch::min_scratch_size; diff --git a/src/hyperlight_common/src/mem.rs b/src/hyperlight_common/src/mem.rs index d2807c107..35fa9c183 100644 --- a/src/hyperlight_common/src/mem.rs +++ b/src/hyperlight_common/src/mem.rs @@ -28,24 +28,12 @@ pub struct GuestMemoryRegion { pub ptr: u64, } -/// A memory region in the guest address space that is used for the stack -#[derive(Debug, Clone, Copy)] -#[repr(C)] -pub struct GuestStack { - /// The top of the user stack - pub min_user_stack_address: u64, - /// The user stack pointer - pub user_stack_address: u64, -} - #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct HyperlightPEB { - pub security_cookie_seed: u64, pub guest_function_dispatch_ptr: u64, pub input_stack: GuestMemoryRegion, pub output_stack: GuestMemoryRegion, pub init_data: GuestMemoryRegion, pub guest_heap: GuestMemoryRegion, - pub guest_stack: GuestStack, } diff --git a/src/hyperlight_guest/src/arch/amd64/layout.rs b/src/hyperlight_guest/src/arch/amd64/layout.rs index 9f83382a4..fbf29e461 100644 --- a/src/hyperlight_guest/src/arch/amd64/layout.rs +++ b/src/hyperlight_guest/src/arch/amd64/layout.rs @@ -14,7 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -pub const MAIN_STACK_TOP_GVA: usize = 0xffff_feff_ffff_f000; +// The addresses in this file should be coordinated with +// src/hyperlight_common/src/arch/amd64/layout.rs and +// src/hyperlight_guest_bin/src/arch/amd64/layout.rs + +/// Note that the x86-64 ELF psABI requires that the stack be 16-byte +/// aligned before a call instruction; we use the aligned version +/// here, even though this requires adjusting the pointer by 8 bytes +/// when entering the guest without a call instruction to push a +/// return address. +pub const MAIN_STACK_TOP_GVA: u64 = 0xffff_ff00_0000_0000; +pub const MAIN_STACK_LIMIT_GVA: u64 = 0xffff_fe00_0000_0000; pub fn scratch_size() -> u64 { let addr = crate::layout::scratch_size_gva(); diff --git a/src/hyperlight_guest/src/arch/amd64/prim_alloc.rs b/src/hyperlight_guest/src/arch/amd64/prim_alloc.rs index 049d92b7f..cfaad9a0b 100644 --- a/src/hyperlight_guest/src/arch/amd64/prim_alloc.rs +++ b/src/hyperlight_guest/src/arch/amd64/prim_alloc.rs @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; + // There are no notable architecture-specific safety considerations // here, and the general conditions are documented in the // architecture-independent re-export in prim_alloc.rs @@ -29,8 +31,18 @@ pub unsafe fn alloc_phys_pages(n: u64) -> u64 { x = inout(reg) x ); } - if x.checked_add(nbytes).is_none() { - panic!("Out of physical memory!") + // Set aside two pages at the top of the scratch region for the + // exception stack, shared state, etc + let max_avail = hyperlight_common::layout::MAX_GPA - hyperlight_common::vmem::PAGE_SIZE * 2; + if x.checked_add(nbytes) + .is_none_or(|xx| xx >= max_avail as u64) + { + unsafe { + crate::exit::abort_with_code_and_message( + &[ErrorCode::MallocFailed as u8], + c"Out of physical memory".as_ptr(), + ) + } } x } diff --git a/src/hyperlight_guest/src/arch/i686/layout.rs b/src/hyperlight_guest/src/arch/i686/layout.rs index 7ca2f4fcf..193a70948 100644 --- a/src/hyperlight_guest/src/arch/i686/layout.rs +++ b/src/hyperlight_guest/src/arch/i686/layout.rs @@ -18,6 +18,7 @@ limitations under the License. // allow compiling the guest for real mode boot scenarios. pub const MAIN_STACK_TOP_GVA: usize = 0xdfff_efff; +pub const MAIN_STACK_LIMIT_GVA: usize = 0xdf00_0000; pub fn scratch_size() -> u64 { hyperlight_common::vmem::PAGE_SIZE as u64 diff --git a/src/hyperlight_guest/src/layout.rs b/src/hyperlight_guest/src/layout.rs index 929fe7770..431a17c67 100644 --- a/src/hyperlight_guest/src/layout.rs +++ b/src/hyperlight_guest/src/layout.rs @@ -18,7 +18,7 @@ limitations under the License. #[cfg_attr(target_arch = "x86", path = "arch/i686/layout.rs")] mod arch; -pub use arch::MAIN_STACK_TOP_GVA; +pub use arch::{MAIN_STACK_LIMIT_GVA, MAIN_STACK_TOP_GVA}; pub fn scratch_size_gva() -> *mut u64 { use hyperlight_common::layout::{MAX_GVA, SCRATCH_TOP_SIZE_OFFSET}; (MAX_GVA as u64 - SCRATCH_TOP_SIZE_OFFSET + 1) as *mut u64 diff --git a/src/hyperlight_guest_bin/src/arch/amd64/context.rs b/src/hyperlight_guest_bin/src/arch/amd64/context.rs new file mode 100644 index 000000000..7071883fb --- /dev/null +++ b/src/hyperlight_guest_bin/src/arch/amd64/context.rs @@ -0,0 +1,118 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. + */ + +use super::machine::ExceptionInfo; + +#[repr(C)] +/// Saved context, pushed onto the stack by exception entry code +pub struct Context { + /// in order: ds, gs, fs, es + pub segments: [u64; 4], + pub fxsave: [u8; 512], + /// no `rsp`, since the processor saved it + /// `rax` is at the top, `r15` the bottom + pub gprs: [u64; 15], + _padding: u64, +} +const _: () = assert!(size_of::() == 32 + 512 + 120 + 8); +// The combination of the ExceptionInfo (pushed by the CPU) and the +// register Context that we save to the stack must be 16byte aligned +// before calling the hl_exception_handler as specified in the x86-64 +// ELF System V psABI specification, Section 3.2.2: +// +// https://gitlab.com/x86-psABIs/x86-64-ABI/-/jobs/artifacts/master/raw/x86-64-ABI/abi.pdf?job=build +const _: () = assert!((size_of::() + size_of::()).is_multiple_of(16)); + +// Defines `context_save` and `context_restore` +macro_rules! save { + () => { + concat!( + // Save general-purpose registers + " sub rsp, 8\n", + " push rax\n", + " push rbx\n", + " push rcx\n", + " push rdx\n", + " push rsi\n", + " push rdi\n", + " push rbp\n", + " push r8\n", + " push r9\n", + " push r10\n", + " push r11\n", + " push r12\n", + " push r13\n", + " push r14\n", + " push r15\n", + // Save floating-point/SSE registers + // TODO: Don't do this unconditionally: get the exn + // handlers compiled without sse + // TODO: Check if we ever generate code with ymm/zmm in + // the handlers and save/restore those as well + " sub rsp, 512\n", + " mov rax, rsp\n", + " fxsave [rax]\n", + // Save the rest of the segment registers + " mov rax, es\n", + " push rax\n", + " mov rax, fs\n", + " push rax\n", + " mov rax, gs\n", + " push rax\n", + " mov rax, ds\n", + " push rax\n", + ) + }; +} +pub(super) use save; + +macro_rules! restore { + () => { + concat!( + // Restore most segment registers + " pop rax\n", + " mov ds, rax\n", + " pop rax\n", + " mov gs, rax\n", + " pop rax\n", + " mov fs, rax\n", + " pop rax\n", + " mov es, rax\n", + // Restore floating-point/SSE registers + " mov rax, rsp\n", + " fxrstor [rax]\n", + " add rsp, 512\n", + // Restore general-purpose registers + " pop r15\n", + " pop r14\n", + " pop r13\n", + " pop r12\n", + " pop r11\n", + " pop r10\n", + " pop r9\n", + " pop r8\n", + " pop rbp\n", + " pop rdi\n", + " pop rsi\n", + " pop rdx\n", + " pop rcx\n", + " pop rbx\n", + " pop rax\n", + " add rsp, 8\n", + ) + }; +} +pub(super) use restore; diff --git a/src/hyperlight_guest_bin/src/arch/amd64/exception/entry.rs b/src/hyperlight_guest_bin/src/arch/amd64/exception/entry.rs new file mode 100644 index 000000000..87f89f15c --- /dev/null +++ b/src/hyperlight_guest_bin/src/arch/amd64/exception/entry.rs @@ -0,0 +1,217 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +// Note: this code takes reference from +// https://github.com/nanvix/nanvix/blob/dev/src/kernel/src/hal/arch/x86/hooks.S + +use core::arch::{asm, global_asm}; + +use hyperlight_common::outb::Exception; + +use super::super::context; +use super::super::machine::{IDT, IdtEntry, IdtPointer, ProcCtrl}; + +unsafe extern "C" { + // Exception handlers + fn _do_excp0(); + fn _do_excp1(); + fn _do_excp2(); + fn _do_excp3(); + fn _do_excp4(); + fn _do_excp5(); + fn _do_excp6(); + fn _do_excp7(); + fn _do_excp8(); + fn _do_excp9(); + fn _do_excp10(); + fn _do_excp11(); + fn _do_excp12(); + fn _do_excp13(); + fn _do_excp14(); + fn _do_excp15(); + fn _do_excp16(); + fn _do_excp17(); + fn _do_excp18(); + fn _do_excp19(); + fn _do_excp20(); + fn _do_excp30(); +} + +// Macro to generate exception handlers +// that satisfy the `extern`s at the top of the file. +// +// - Example output from this macro for generate_excp!(0) call: +// ```assembly +// .global _do_excp0 +// _do_excp0: +// context_save!() +// mov rsi, 0 +// mov rdx, 0 +// jmp _do_excp_common +// ``` +macro_rules! generate_excp { + ($num:expr) => { + concat!( + ".global _do_excp", + stringify!($num), + "\n", + "_do_excp", + stringify!($num), + ":\n", + context::save!(), + // rsi is the exception number. + " mov rsi, ", + stringify!($num), + "\n", + // rdx is only used for pagefault exception and + // contains the address that caused the pagefault. + " mov rdx, 0\n", + " jmp _do_excp_common\n" + ) + }; + ($num:expr, pusherrcode) => { + concat!( + ".global _do_excp", + stringify!($num), + "\n", + "_do_excp", + stringify!($num), + ":\n", + // Some exceptions push an error code onto the stack. + // For the ones that don't, we push a 0 to keep the + // stack aligned. + " push 0\n", + context::save!(), + // rsi is the exception number. + " mov rsi, ", + stringify!($num), + "\n", + // rdx is only used for pagefault exception and + // contains the address that caused the pagefault. + " mov rdx, 0\n", + " jmp _do_excp_common\n" + ) + }; + ($num:expr, pagefault) => { + concat!( + ".global _do_excp", + stringify!($num), + "\n", + "_do_excp", + stringify!($num), + ":\n", + context::save!(), + " mov rsi, ", + stringify!($num), + "\n", + // In a page fault exception, the cr2 register + // contains the address that caused the page fault. + " mov rdx, cr2\n", + " jmp _do_excp_common\n" + ) + }; +} + +// Generates exception handlers +macro_rules! generate_exceptions { + () => { + concat!( + // Common exception handler + ".global _do_excp_common\n", + "_do_excp_common:\n", + // the first argument to the Rust handler points to the + // bottom of the context struct, which happens to be the + // stack pointer just before it was called. + " mov rdi, rsp\n", + " call {hl_exception_handler}\n", + context::restore!(), + " add rsp, 8\n", // error code + " iretq\n", // iretq is used to return from exception in x86_64 + generate_excp!(0, pusherrcode), + generate_excp!(1, pusherrcode), + generate_excp!(2, pusherrcode), + generate_excp!(3, pusherrcode), + generate_excp!(4, pusherrcode), + generate_excp!(5, pusherrcode), + generate_excp!(6, pusherrcode), + generate_excp!(7, pusherrcode), + generate_excp!(8), + generate_excp!(9, pusherrcode), + generate_excp!(10), + generate_excp!(11), + generate_excp!(12), + generate_excp!(13), + generate_excp!(14, pagefault), + generate_excp!(15, pusherrcode), + generate_excp!(16, pusherrcode), + generate_excp!(17), + generate_excp!(18, pusherrcode), + generate_excp!(19, pusherrcode), + generate_excp!(20, pusherrcode), + generate_excp!(30), + ) + }; +} + +// Output the assembly code +global_asm!( + generate_exceptions!(), + hl_exception_handler = sym super::handle::hl_exception_handler, +); + +pub(in super::super) fn init_idt(pc: *mut ProcCtrl) { + let idt = unsafe { &raw mut (*pc).idt }; + let set_idt_entry = |idx, handler: unsafe extern "C" fn()| { + let handler_addr = handler as *const () as u64; + unsafe { + (&raw mut (*idt).entries[idx as usize]).write_volatile(IdtEntry::new(handler_addr)); + } + }; + set_idt_entry(Exception::DivideByZero, _do_excp0); // Divide by zero + set_idt_entry(Exception::Debug, _do_excp1); // Debug + set_idt_entry(Exception::NonMaskableInterrupt, _do_excp2); // Non-maskable interrupt + set_idt_entry(Exception::Breakpoint, _do_excp3); // Breakpoint + set_idt_entry(Exception::Overflow, _do_excp4); // Overflow + set_idt_entry(Exception::BoundRangeExceeded, _do_excp5); // Bound Range Exceeded + set_idt_entry(Exception::InvalidOpcode, _do_excp6); // Invalid Opcode + set_idt_entry(Exception::DeviceNotAvailable, _do_excp7); // Device Not Available + set_idt_entry(Exception::DoubleFault, _do_excp8); // Double Fault + set_idt_entry(Exception::CoprocessorSegmentOverrun, _do_excp9); // Coprocessor Segment Overrun + set_idt_entry(Exception::InvalidTSS, _do_excp10); // Invalid TSS + set_idt_entry(Exception::SegmentNotPresent, _do_excp11); // Segment Not Present + set_idt_entry(Exception::StackSegmentFault, _do_excp12); // Stack-Segment Fault + set_idt_entry(Exception::GeneralProtectionFault, _do_excp13); // General Protection Fault + set_idt_entry(Exception::PageFault, _do_excp14); // Page Fault + set_idt_entry(Exception::Reserved, _do_excp15); // Reserved + set_idt_entry(Exception::X87FloatingPointException, _do_excp16); // x87 Floating-Point Exception + set_idt_entry(Exception::AlignmentCheck, _do_excp17); // Alignment Check + set_idt_entry(Exception::MachineCheck, _do_excp18); // Machine Check + set_idt_entry(Exception::SIMDFloatingPointException, _do_excp19); // SIMD Floating-Point Exception + set_idt_entry(Exception::VirtualizationException, _do_excp20); // Virtualization Exception + set_idt_entry(Exception::SecurityException, _do_excp30); // Security Exception + + let idtr = IdtPointer { + limit: (core::mem::size_of::() - 1) as u16, + base: idt as u64, + }; + unsafe { + asm!( + "lidt [{}]", + in(reg) &idtr, + options(readonly, nostack, preserves_flags) + ); + } +} diff --git a/src/hyperlight_guest_bin/src/exceptions/handler.rs b/src/hyperlight_guest_bin/src/arch/amd64/exception/handle.rs similarity index 61% rename from src/hyperlight_guest_bin/src/exceptions/handler.rs rename to src/hyperlight_guest_bin/src/arch/amd64/exception/handle.rs index b24324391..91fad866a 100644 --- a/src/hyperlight_guest_bin/src/exceptions/handler.rs +++ b/src/hyperlight_guest_bin/src/arch/amd64/exception/handle.rs @@ -1,5 +1,5 @@ /* -Copyright 2025 The Hyperlight Authors. +Copyright 2025 The Hyperlight Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,68 +12,17 @@ distributed under the License is distributed on an "AS IS" BASIS, 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. -*/ + */ use core::fmt::Write; -use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::outb::Exception; use hyperlight_guest::exit::write_abort; +use hyperlight_guest::layout::{MAIN_STACK_LIMIT_GVA, MAIN_STACK_TOP_GVA}; -use crate::HyperlightAbortWriter; - -/// Exception information pushed onto the stack by the CPU during an excpection. -/// -/// See AMD64 Architecture Programmer's Manual, Volume 2 -/// §8.9.3 Interrupt Stack Frame, pp. 283--284 -/// Figure 8-14: Long-Mode Stack After Interrupt---Same Privilege, -/// Figure 8-15: Long-Mode Stack After Interrupt---Higher Privilege -/// Note: For exceptions that don't provide an error code, we push a dummy value of 0. -#[repr(C)] -pub struct ExceptionInfo { - /// Error code provided by the processor (or 0 if not applicable). - pub error_code: u64, - /// Instruction pointer at the time of the exception. - pub rip: u64, - /// Code segment selector. - pub cs: u64, - /// CPU flags register. - pub rflags: u64, - /// Stack pointer at the time of the exception. - pub rsp: u64, - /// Stack segment selector. - pub ss: u64, -} -const _: () = assert!(core::mem::offset_of!(ExceptionInfo, rip) == 8); -const _: () = assert!(core::mem::offset_of!(ExceptionInfo, rsp) == 32); - -/// Saved CPU context pushed onto the stack by exception entry code. -/// -/// This structure contains all the saved CPU state needed to resume execution -/// after handling an exception. It includes segment registers, floating-point state, -/// and general-purpose registers. -#[repr(C)] -pub struct Context { - /// Segment registers in order: GS, FS, ES, DS. - pub segments: [u64; 4], - /// FPU/SSE state saved via FXSAVE instruction (512 bytes). - pub fxsave: [u8; 512], - /// General-purpose registers (RAX through R15, excluding RSP). - /// - /// The stack pointer (RSP) is not included here since it's saved - /// by the processor in the `ExceptionInfo` structure. - /// R15 is at index 0, RAX is at index 14. - pub gprs: [u64; 15], - /// Padding to ensure 16-byte alignment when combined with ExceptionInfo. - padding: [u64; 1], -} -const _: () = assert!(size_of::() == 32 + 512 + 120 + 8); -// The combination of the ExceptionInfo (pushed by the CPU) and the register Context -// that we save to the stack must be 16byte aligned before calling the hl_exception_handler -// as specified in the x86-64 ELF System V psABI specification, Section 3.2.2: -// -// https://gitlab.com/x86-psABIs/x86-64-ABI/-/jobs/artifacts/master/raw/x86-64-ABI/abi.pdf?job=build -const _: () = assert!((size_of::() + size_of::()).is_multiple_of(16)); +use super::super::context::Context; +use super::super::machine::ExceptionInfo; +use crate::{ErrorCode, HyperlightAbortWriter}; /// Array of installed exception handlers for vectors 0-30. /// @@ -118,6 +67,9 @@ pub(crate) extern "C" fn hl_exception_handler( exception_number: u64, page_fault_address: u64, ) { + // TODO: is this always needed? surely only needed if CoW + crate::paging::flush_tlb(); + let ctx = stack_pointer as *mut Context; let exn_info = (stack_pointer + size_of::() as u64) as *mut ExceptionInfo; @@ -126,6 +78,23 @@ pub(crate) extern "C" fn hl_exception_handler( let saved_rip = unsafe { (&raw const (*exn_info).rip).read_volatile() }; let error_code = unsafe { (&raw const (*exn_info).error_code).read_volatile() }; + if exception_number == 14 + && (MAIN_STACK_LIMIT_GVA..MAIN_STACK_TOP_GVA).contains(&page_fault_address) + { + // TODO: perhaps we should have a sanity check that the + // stack grows only one page at a time, which should be + // ensured by our stack probing discipline? + unsafe { + let new_page = hyperlight_guest::prim_alloc::alloc_phys_pages(1); + crate::paging::map_region( + new_page, + (page_fault_address & !0xfff) as *mut u8, + hyperlight_common::vmem::PAGE_SIZE as u64, + ); + return; + } + } + // Check for registered user handlers (only for architecture-defined vectors 0-30) if exception_number < 31 { let handler = @@ -141,6 +110,8 @@ pub(crate) extern "C" fn hl_exception_handler( } } + let bytes_at_rip = unsafe { (saved_rip as *const [u8; 8]).read_volatile() }; + // begin abort sequence by writing the error code let mut w = HyperlightAbortWriter; write_abort(&[ErrorCode::GuestError as u8, exception as u8]); @@ -148,10 +119,11 @@ pub(crate) extern "C" fn hl_exception_handler( w, "Exception vector: {}\n\ Faulting Instruction: {:#x}\n\ + Bytes At Faulting Instruction: {:?}\n\ Page Fault Address: {:#x}\n\ Error code: {:#x}\n\ Stack Pointer: {:#x}", - exception_number, saved_rip, page_fault_address, error_code, stack_pointer + exception_number, saved_rip, bytes_at_rip, page_fault_address, error_code, stack_pointer ); if write_res.is_err() { write_abort("exception message format failed".as_bytes()); diff --git a/src/hyperlight_guest_bin/src/arch/amd64/exception/mod.rs b/src/hyperlight_guest_bin/src/arch/amd64/exception/mod.rs new file mode 100644 index 000000000..e4d8a97bd --- /dev/null +++ b/src/hyperlight_guest_bin/src/arch/amd64/exception/mod.rs @@ -0,0 +1,18 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. + */ + +pub(super) mod entry; +pub mod handle; diff --git a/src/hyperlight_guest_bin/src/arch/amd64/init.rs b/src/hyperlight_guest_bin/src/arch/amd64/init.rs new file mode 100644 index 000000000..61e4da900 --- /dev/null +++ b/src/hyperlight_guest_bin/src/arch/amd64/init.rs @@ -0,0 +1,159 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. + */ + +use core::arch::asm; +use core::mem; + +use super::exception::entry::init_idt; +use super::machine::{GDT, GdtEntry, GdtPointer, ProcCtrl, TSS}; + +/// See AMD64 Architecture Programmer's Manual, Volume 2: System Programming +/// Section 4: Segmented Virtual Memory +/// §4.6: Descriptor Tables +/// for the functions of the GDT. +/// +/// Hyperlight's GDT consists of: +/// - A null first entry, which is architecturally required +/// - A single code segment descriptor, used for all code accesses +/// - A single data segment descriptor, used for all data accesses +/// - A TSS System descriptor that outlines the location of the TSS +/// (see [`init_tss`], below) +#[repr(C)] +struct HyperlightGDT { + null: GdtEntry, + kernel_code: GdtEntry, + kernel_data: GdtEntry, + tss: [GdtEntry; 2], +} +const _: () = assert!(mem::size_of::() == mem::size_of::()); +const _: () = assert!(mem::offset_of!(HyperlightGDT, null) == 0x00); +const _: () = assert!(mem::offset_of!(HyperlightGDT, kernel_code) == 0x08); +const _: () = assert!(mem::offset_of!(HyperlightGDT, kernel_data) == 0x10); +const _: () = assert!(mem::offset_of!(HyperlightGDT, tss) == 0x18); + +unsafe fn init_gdt(pc: *mut ProcCtrl) { + unsafe { + let gdt_ptr = &raw mut (*pc).gdt as *mut HyperlightGDT; + (&raw mut (*gdt_ptr).null).write_volatile(GdtEntry::new(0, 0, 0, 0)); + (&raw mut (*gdt_ptr).kernel_code).write_volatile(GdtEntry::new(0, 0, 0x9A, 0xA)); + (&raw mut (*gdt_ptr).kernel_data).write_volatile(GdtEntry::new(0, 0, 0x92, 0xC)); + (&raw mut (*gdt_ptr).tss).write_volatile(GdtEntry::tss( + &raw mut (*pc).tss as u64, + mem::size_of::() as u32, + )); + let gdtr = GdtPointer { + limit: (core::mem::size_of::<[GdtEntry; 5]>() - 1) as u16, + base: gdt_ptr as u64, + }; + asm!( + "lgdt [{0}]", + "mov ds, ax", + "mov es, ax", + "mov fs, ax", + "mov gs, ax", + "mov ss, ax", + "push rcx", + "lea rax, [2f + rip]", + "push rax", + "retfq", + "2:", + in(reg) &gdtr, + in("ax") mem::offset_of!(HyperlightGDT, kernel_data), + in("rcx") mem::offset_of!(HyperlightGDT, kernel_code), + lateout("rax") _, + options(nostack, preserves_flags) + ); + } +} + +/// Hyperlight's TSS contains only a single IST entry, which is used +/// to set up the stack switch to the exception stack whenever we take +/// an exception (including page faults, which are important, since +/// the fault might be due to needing to grow the stack!) +/// +/// This function sets up the TSS and then points the processor at the +/// system segment descriptor, initialized in [`init_gdt`] above, +/// which describes the location of the TSS. +unsafe fn init_tss(pc: *mut ProcCtrl) { + unsafe { + let tss_ptr = &raw mut (*pc).tss; + // copy byte by byte to avoid alignment issues + let ist1_ptr = &raw mut (*tss_ptr).ist1 as *mut [u8; 8]; + let exn_stack = hyperlight_common::layout::MAX_GVA as u64 + - hyperlight_common::layout::SCRATCH_TOP_EXN_STACK_OFFSET + + 1; + ist1_ptr.write_volatile(exn_stack.to_ne_bytes()); + asm!( + "ltr ax", + in("ax") core::mem::offset_of!(HyperlightGDT, tss), + options(nostack, preserves_flags) + ); + } +} + +/// To initialise the main stack, we just pre-emptively map the first +/// page of it. +unsafe fn init_stack() -> u64 { + use hyperlight_guest::layout::MAIN_STACK_TOP_GVA; + let stack_top_page_base = (MAIN_STACK_TOP_GVA - 1) & !0xfff; + unsafe { + crate::paging::map_region( + hyperlight_guest::prim_alloc::alloc_phys_pages(1), + stack_top_page_base as *mut u8, + hyperlight_common::vmem::PAGE_SIZE as u64, + ); + } + MAIN_STACK_TOP_GVA +} + +/// Machine-specific initialisation; calls [`crate::generic_init`] +/// once stack, CoW, etc have been set up. +#[unsafe(no_mangle)] +pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_level: u64) { + unsafe { + // Allocate a VA for processor control structures which must + // survive snapshotting at the same VA. + let pc = ProcCtrl::init(); + + init_gdt(pc); + init_tss(pc); + init_idt(pc); + let stack_top = init_stack(); + + // Architecture early init is complete! We pivot now to + // executing on the main stack, and jump into generic + // initialisation code in lib.rs + pivot_stack(peb_address, seed, ops, max_log_level, stack_top); + } +} + +unsafe extern "C" { + unsafe fn pivot_stack( + peb_address: u64, + seed: u64, + ops: u64, + max_log_level: u64, + stack_top: u64, + ) -> !; +} + +core::arch::global_asm!(" + .global pivot_stack\n + pivot_stack:\n + mov rsp, r8\n + call {generic_init}\n + hlt\n +", generic_init = sym crate::generic_init); diff --git a/src/hyperlight_guest_bin/src/arch/amd64/layout.rs b/src/hyperlight_guest_bin/src/arch/amd64/layout.rs new file mode 100644 index 000000000..e23bb0761 --- /dev/null +++ b/src/hyperlight_guest_bin/src/arch/amd64/layout.rs @@ -0,0 +1,25 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. + */ + +// The addresses in this file should be coordinated with +// src/hyperlight_common/src/arch/amd64/layout.rs and +// src/hyperlight_guest/src/arch/amd64/layout.rs + +/// On amd64, since the processor is told the VAs of control +/// structures like the GDT/IDT/TSS, we need to map them somewhere to +/// a VA that will survive the snapshot process. Since we don't have a +/// useful virtual allocator yet, we just put them here... +pub const PROC_CONTROL_GVA: u64 = 0xffff_fd00_0000_0000; diff --git a/src/hyperlight_guest_bin/src/arch/amd64/machine.rs b/src/hyperlight_guest_bin/src/arch/amd64/machine.rs new file mode 100644 index 000000000..79bc7cab8 --- /dev/null +++ b/src/hyperlight_guest_bin/src/arch/amd64/machine.rs @@ -0,0 +1,253 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. +*/ + +use core::mem; + +use hyperlight_common::vmem::PAGE_SIZE; + +use super::layout::PROC_CONTROL_GVA; + +/// Entry in the Global Descriptor Table (GDT) +/// For reference, see page 3-10 Vol. 3A of Intel 64 and IA-32 +/// Architectures Software Developer's Manual, figure 3-8 +/// (https://i.imgur.com/1i9xUmx.png). +/// From the bottom, we have: +/// - segment limit 15..0 = limit_low +/// - base address 31..16 = base_low +/// - base 23..16 = base_middle +/// - p dpl s type 15..8 = access +/// - p d/b l avl seg. limit 23..16 = flags_limit +/// - base 31..24 = base_high +#[derive(Copy, Clone)] +#[repr(C, align(8))] +pub(super) struct GdtEntry { + limit_low: u16, + base_low: u16, + base_middle: u8, + access: u8, + flags_limit: u8, + base_high: u8, +} +const _: () = assert!(mem::size_of::() == 0x8); + +impl GdtEntry { + /// Creates a new GDT entry. + pub const fn new(base: u32, limit: u32, access: u8, flags: u8) -> Self { + Self { + base_low: (base & 0xffff) as u16, + base_middle: ((base >> 16) & 0xff) as u8, + base_high: ((base >> 24) & 0xff) as u8, + limit_low: (limit & 0xffff) as u16, + flags_limit: (((limit >> 16) & 0x0f) as u8) | ((flags & 0x0f) << 4), + access, + } + } + + /// Create a new entry that describes the Task State Segment + /// (TSS). + /// + /// The segment descriptor for the TSS needs to be wider than + /// other segments, because its base address is actually used & + /// must therefore be able to encode an entire 64-bit VA. Because + /// of this, it uses two adjacent descriptor entries. + /// + /// See AMD64 Architecture Programmer's Manual, Volume 2: System Programming + /// Section 4: Segmented Virtual Memory + /// §4.8: Long-Mod Segment Descriptors + /// §4.8.3: System Descriptors + /// for details of the layout + pub const fn tss(base: u64, limit: u32) -> [Self; 2] { + [ + Self { + limit_low: (limit & 0xffff) as u16, + base_low: (base & 0xffff) as u16, + base_middle: ((base >> 16) & 0xff) as u8, + access: 0x89, + flags_limit: ((limit >> 16) & 0x0f) as u8, + base_high: ((base >> 24) & 0xff) as u8, + }, + Self { + limit_low: ((base >> 32) & 0xffff) as u16, + base_low: ((base >> 48) & 0xffff) as u16, + base_middle: 0, + access: 0, + flags_limit: 0, + base_high: 0, + }, + ] + } +} + +/// GDTR (GDT pointer) +/// +/// This contains the virtual address of the GDT. The GDT that it +/// points to needs to remain mapped in memory at that address, but +/// this structure itself does not. +#[repr(C, packed)] +pub(super) struct GdtPointer { + pub(super) limit: u16, + pub(super) base: u64, +} + +/// Task State Segment +/// +/// See AMD64 Architecture Programmer's Manual, Volume 2: System Programming +/// Section 12: Task Management +/// §12.2: Task-Management Resources +/// §12.2.5: 64-bit Task State Segment +#[allow(clippy::upper_case_acronyms)] +#[repr(C, packed)] +pub(super) struct TSS { + _rsvd0: [u8; 4], + _rsp0: u64, + _rsp1: u64, + _rsp2: u64, + _rsvd1: [u8; 8], + pub(super) ist1: u64, + _ist2: u64, + _ist3: u64, + _ist4: u64, + _ist5: u64, + _ist6: u64, + _ist7: u64, + _rsvd2: [u8; 8], +} +const _: () = assert!(mem::size_of::() == 0x64); +const _: () = assert!(mem::offset_of!(TSS, ist1) == 0x24); + +/// An entry in the Interrupt Descriptor Table (IDT) +/// For reference, see page 7-20 Vol. 3A of Intel 64 and IA-32 +/// Architectures Software Developer's Manual, figure 7-8 +/// (i.e., https://i.imgur.com/N4rEjHj.png). +/// From the bottom, we have: +/// - offset 15..0 = offset_low +/// - segment selector 31..16 = selector +/// - 000 0 0 Interrupt Stack Table 7..0 = interrupt_stack_table_offset +/// - p dpl 0 type 15..8 = type_attr +/// - offset 31..16 = offset_mid +/// - offset 63..32 = offset_high +/// - reserved 31..0 = zero +#[repr(C, align(16))] +pub(crate) struct IdtEntry { + offset_low: u16, // Lower 16 bits of handler address + selector: u16, // code segment selector in GDT + interrupt_stack_table_offset: u8, // Interrupt Stack Table offset + type_attr: u8, // Gate type and flags + offset_mid: u16, // Middle 16 bits of handler address + offset_high: u32, // High 32 bits of handler address + _rsvd: u32, // Reserved, ignored +} +const _: () = assert!(mem::size_of::() == 0x10); + +impl IdtEntry { + pub(super) fn new(handler: u64) -> Self { + Self { + offset_low: (handler & 0xFFFF) as u16, + selector: 0x08, // Kernel Code Segment + interrupt_stack_table_offset: 1, + type_attr: 0x8E, + // 0x8E = 10001110b + // 1 00 0 1101 + // 1 = Present + // 00 = Descriptor Privilege Level (0) + // 0 = Storage Segment (0) + // 1110 = Gate Type (0b1110 = 14 = 0xE) + // 0xE means it's an interrupt gate + offset_mid: ((handler >> 16) & 0xFFFF) as u16, + offset_high: ((handler >> 32) & 0xFFFFFFFF) as u32, + _rsvd: 0, + } + } +} + +#[repr(C, packed)] +pub(super) struct IdtPointer { + pub limit: u16, + pub base: u64, +} +const _: () = assert!(mem::size_of::() == 10); + +#[allow(clippy::upper_case_acronyms)] +pub(super) type GDT = [GdtEntry; 5]; +#[allow(clippy::upper_case_acronyms)] +#[repr(align(0x1000))] +pub(super) struct IDT { + pub(super) entries: [IdtEntry; 256], +} +const _: () = assert!(mem::size_of::() == 0x1000); + +const PADDING_BEFORE_TSS: usize = 64 - mem::size_of::(); +/// A single structure containing all of the processor control +/// structures that we use during early initialization, making it easy +/// to keep them in an early-allocated physical page. Field alignment +/// is chosen partly to lineup nicely with likely cache line +/// boundaries (gdt, tss) and to keep the idt (which is 4k in size) on +/// its own page. +#[repr(C, align(0x1000))] +pub(super) struct ProcCtrl { + pub(super) gdt: GDT, + _pad: mem::MaybeUninit<[u8; PADDING_BEFORE_TSS]>, + pub(super) tss: TSS, + pub(super) idt: IDT, +} +const _: () = assert!(mem::size_of::() == 0x2000); +const _: () = assert!(mem::size_of::() <= PAGE_SIZE * 2); +const _: () = assert!(mem::offset_of!(ProcCtrl, gdt) == 0); +const _: () = assert!(mem::offset_of!(ProcCtrl, tss) == 64); +const _: () = assert!(mem::offset_of!(ProcCtrl, idt) == 0x1000); + +impl ProcCtrl { + /// Create a copy of the ProcCtrl structure at its known + /// mapping. + /// + /// # Safety + /// This should only be called once, and before any of the + /// gdtr/tr/idtr pointing at its address have been loaded. + pub(super) unsafe fn init() -> *mut Self { + unsafe { + let ptr = PROC_CONTROL_GVA as *mut u8; + crate::paging::map_region( + hyperlight_guest::prim_alloc::alloc_phys_pages(2), + ptr, + PAGE_SIZE as u64 * 2, + ); + let ptr = ptr as *mut Self; + (&raw mut (*ptr).gdt).write_bytes(0u8, 1); + (&raw mut (*ptr).tss).write_bytes(0u8, 1); + (&raw mut (*ptr).idt).write_bytes(0u8, 1); + ptr + } + } +} + +/// See AMD64 Architecture Programmer's Manual, Volume 2 +/// §8.9.3 Interrupt Stack Frame, pp. 283--284 +/// Figure 8-14: Long-Mode Stack After Interrupt---Same Privilege, +/// Figure 8-15: Long-Mode Stack After Interrupt---Higher Privilege +/// Subject to the proviso that we push a dummy error code of 0 for exceptions +/// for which the processor does not provide one +#[repr(C)] +pub struct ExceptionInfo { + pub error_code: u64, + pub rip: u64, + pub cs: u64, + pub rflags: u64, + pub rsp: u64, + pub ss: u64, +} +const _: () = assert!(size_of::() == 8 * 6); +const _: () = assert!(mem::offset_of!(ExceptionInfo, rip) == 8); +const _: () = assert!(mem::offset_of!(ExceptionInfo, rsp) == 32); diff --git a/src/hyperlight_guest_bin/src/arch/amd64/mod.rs b/src/hyperlight_guest_bin/src/arch/amd64/mod.rs new file mode 100644 index 000000000..9ba50286d --- /dev/null +++ b/src/hyperlight_guest_bin/src/arch/amd64/mod.rs @@ -0,0 +1,21 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. + */ + +pub(crate) mod context; +pub mod exception; +mod init; +mod layout; +pub(crate) mod machine; diff --git a/src/hyperlight_guest_bin/src/exception.rs b/src/hyperlight_guest_bin/src/exception.rs new file mode 100644 index 000000000..a233cb60e --- /dev/null +++ b/src/hyperlight_guest_bin/src/exception.rs @@ -0,0 +1,21 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +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. + */ + +pub mod arch { + pub use crate::arch::context::Context; + pub use crate::arch::exception::handle::HANDLERS; + pub use crate::arch::machine::ExceptionInfo; +} diff --git a/src/hyperlight_guest_bin/src/exceptions/gdt.rs b/src/hyperlight_guest_bin/src/exceptions/gdt.rs deleted file mode 100644 index 0a3b2cfb6..000000000 --- a/src/hyperlight_guest_bin/src/exceptions/gdt.rs +++ /dev/null @@ -1,99 +0,0 @@ -/* -Copyright 2025 The Hyperlight Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -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. -*/ - -use core::arch::asm; -use core::ptr::addr_of; - -/// Entry in the Global Descriptor Table (GDT) -/// For reference, see page 3-10 Vol. 3A of Intel 64 and IA-32 -/// Architectures Software Developer's Manual, figure 3-8 -/// (https://i.imgur.com/1i9xUmx.png). -/// From the bottom, we have: -/// - segment limit 15..0 = limit_low -/// - base address 31..16 = base_low -/// - base 23..16 = base_middle -/// - p dpl s type 15..8 = access -/// - p d/b l avl seg. limit 23..16 = flags_limit -/// - base 31..24 = base_high -#[repr(C, align(8))] -pub struct GdtEntry { - limit_low: u16, - base_low: u16, - base_middle: u8, - access: u8, - flags_limit: u8, - base_high: u8, -} - -impl GdtEntry { - /// Creates a new GDT entry. - pub const fn new(base: u32, limit: u32, access: u8, flags: u8) -> Self { - Self { - base_low: (base & 0xffff) as u16, - base_middle: ((base >> 16) & 0xff) as u8, - base_high: ((base >> 24) & 0xff) as u8, - limit_low: (limit & 0xffff) as u16, - flags_limit: (((limit >> 16) & 0x0f) as u8) | ((flags & 0x0f) << 4), - access, - } - } -} - -// Global Descriptor Table (GDT) -// For reference, see page 2-3 Vol. 3A of Intel 64 and IA-32 -// Architectures Software Developer's Manual. -static mut GDT: [GdtEntry; 3] = [ - // Null descriptor - GdtEntry::new(0, 0, 0, 0), - // Kernel Code Segment (0x08) - GdtEntry::new(0, 0, 0x9A, 0xA), - // Kernel Data Segment (0x10) - GdtEntry::new(0, 0, 0x92, 0xC), -]; - -/// GDTR (GDT pointer) -#[repr(C, packed)] -struct GdtPointer { - size: u16, - base: u64, -} - -/// Load the GDT -pub unsafe fn load_gdt() { - unsafe { - let gdt_ptr = GdtPointer { - size: (core::mem::size_of::<[GdtEntry; 3]>() - 1) as u16, - base: addr_of!(GDT) as *const _ as u64, - }; - - asm!( - "lgdt [{0}]", - "mov ax, 0x10", // Load data segment registers - "mov ds, ax", - "mov es, ax", - "mov fs, ax", - "mov gs, ax", - "mov ss, ax", - "push 0x08", // Push CS (kernel code segment) - "lea rax, [2f + rip]", // Load the next instruction's address - "push rax", // Push address onto stack - "retfq", // Far return to update CS - "2:", // Label for continued execution - in(reg) &gdt_ptr, - options(nostack, preserves_flags) - ); - } -} diff --git a/src/hyperlight_guest_bin/src/exceptions/idt.rs b/src/hyperlight_guest_bin/src/exceptions/idt.rs deleted file mode 100644 index 3107c4df4..000000000 --- a/src/hyperlight_guest_bin/src/exceptions/idt.rs +++ /dev/null @@ -1,104 +0,0 @@ -/* -Copyright 2025 The Hyperlight Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -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. -*/ - -use hyperlight_common::outb::Exception; - -use crate::exceptions::interrupt_entry::{ - _do_excp0, _do_excp1, _do_excp2, _do_excp3, _do_excp4, _do_excp5, _do_excp6, _do_excp7, - _do_excp8, _do_excp9, _do_excp10, _do_excp11, _do_excp12, _do_excp13, _do_excp14, _do_excp15, - _do_excp16, _do_excp17, _do_excp18, _do_excp19, _do_excp20, _do_excp30, -}; - -// An entry in the Interrupt Descriptor Table (IDT) -// For reference, see page 7-20 Vol. 3A of Intel 64 and IA-32 -// Architectures Software Developer's Manual, figure 7-8 -// (i.e., https://i.imgur.com/N4rEjHj.png). -// From the bottom, we have: -// - offset 15..0 = offset_low -// - segment selector 31..16 = selector -// - 000 0 0 Interrupt Stack Table 7..0 = interrupt_stack_table_offset -// - p dpl 0 type 15..8 = type_attr -// - offset 31..16 = offset_mid -// - offset 63..32 = offset_high -// - reserved 31..0 = zero -#[repr(C, align(16))] -pub(crate) struct IdtEntry { - offset_low: u16, // Lower 16 bits of handler address - selector: u16, // code segment selector in GDT - interrupt_stack_table_offset: u8, // Interrupt Stack Table offset - type_attr: u8, // Gate type and flags - offset_mid: u16, // Middle 16 bits of handler address - offset_high: u32, // High 32 bits of handler address - zero: u32, // Reserved (always 0) -} - -impl IdtEntry { - fn new(handler: u64) -> Self { - Self { - offset_low: (handler & 0xFFFF) as u16, - selector: 0x08, // Kernel Code Segment - interrupt_stack_table_offset: 0, // No interrupt stack table used - type_attr: 0x8E, - // 0x8E = 10001110b - // 1 00 0 1110 - // 1 = Present - // 00 = Descriptor Privilege Level (0) - // 0 = Storage Segment (0) - // 1110 = Gate Type (0b1110 = 14 = 0xE) - // 0xE means it's an interrupt gate - offset_mid: ((handler >> 16) & 0xFFFF) as u16, - offset_high: ((handler >> 32) & 0xFFFFFFFF) as u32, - zero: 0, - } - } -} - -// The IDT is an array of 256 IDT entries -// (for reference, see page 7-9 Vol. 3A of Intel 64 and IA-32 -// Architectures Software Developer's Manual). -pub(crate) static mut IDT: [IdtEntry; 256] = unsafe { core::mem::zeroed() }; - -pub(crate) fn init_idt() { - set_idt_entry(Exception::DivideByZero as usize, _do_excp0); // Divide by zero - set_idt_entry(Exception::Debug as usize, _do_excp1); // Debug - set_idt_entry(Exception::NonMaskableInterrupt as usize, _do_excp2); // Non-maskable interrupt - set_idt_entry(Exception::Breakpoint as usize, _do_excp3); // Breakpoint - set_idt_entry(Exception::Overflow as usize, _do_excp4); // Overflow - set_idt_entry(Exception::BoundRangeExceeded as usize, _do_excp5); // Bound Range Exceeded - set_idt_entry(Exception::InvalidOpcode as usize, _do_excp6); // Invalid Opcode - set_idt_entry(Exception::DeviceNotAvailable as usize, _do_excp7); // Device Not Available - set_idt_entry(Exception::DoubleFault as usize, _do_excp8); // Double Fault - set_idt_entry(Exception::CoprocessorSegmentOverrun as usize, _do_excp9); // Coprocessor Segment Overrun - set_idt_entry(Exception::InvalidTSS as usize, _do_excp10); // Invalid TSS - set_idt_entry(Exception::SegmentNotPresent as usize, _do_excp11); // Segment Not Present - set_idt_entry(Exception::StackSegmentFault as usize, _do_excp12); // Stack-Segment Fault - set_idt_entry(Exception::GeneralProtectionFault as usize, _do_excp13); // General Protection Fault - set_idt_entry(Exception::PageFault as usize, _do_excp14); // Page Fault - set_idt_entry(Exception::Reserved as usize, _do_excp15); // Reserved - set_idt_entry(Exception::X87FloatingPointException as usize, _do_excp16); // x87 Floating-Point Exception - set_idt_entry(Exception::AlignmentCheck as usize, _do_excp17); // Alignment Check - set_idt_entry(Exception::MachineCheck as usize, _do_excp18); // Machine Check - set_idt_entry(Exception::SIMDFloatingPointException as usize, _do_excp19); // SIMD Floating-Point Exception - set_idt_entry(Exception::VirtualizationException as usize, _do_excp20); // Virtualization Exception - set_idt_entry(Exception::SecurityException as usize, _do_excp30); // Security Exception -} - -fn set_idt_entry(index: usize, handler: unsafe extern "C" fn()) { - let handler_addr = handler as *const () as u64; - unsafe { - IDT[index] = IdtEntry::new(handler_addr); - } -} diff --git a/src/hyperlight_guest_bin/src/exceptions/idtr.rs b/src/hyperlight_guest_bin/src/exceptions/idtr.rs deleted file mode 100644 index d1d54830b..000000000 --- a/src/hyperlight_guest_bin/src/exceptions/idtr.rs +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2025 The Hyperlight Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -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. -*/ - -use core::mem::size_of; -use core::ptr::addr_of; - -use crate::exceptions::idt::{IDT, IdtEntry, init_idt}; - -#[repr(C, packed)] -pub struct Idtr { - pub limit: u16, - pub base: u64, -} - -static mut IDTR: Idtr = Idtr { limit: 0, base: 0 }; - -impl Idtr { - pub unsafe fn init(&mut self, base: u64, size: u16) { - self.limit = size - 1; - self.base = base; - } - - pub unsafe fn load(&self) { - unsafe { - core::arch::asm!("lidt [{}]", in(reg) self, options(readonly, nostack, preserves_flags)); - } - } -} - -pub(crate) unsafe fn load_idt() { - unsafe { - init_idt(); - - let idt_size = 256 * size_of::(); - let expected_base = addr_of!(IDT) as *const _ as u64; - - // Use &raw mut to get a mutable raw pointer, then dereference it - // this is to avoid the clippy warning "shared reference to mutable static" - #[allow(clippy::deref_addrof)] - let idtr = &mut *(&raw mut IDTR); - idtr.init(expected_base, idt_size as u16); - idtr.load(); - } -} diff --git a/src/hyperlight_guest_bin/src/exceptions/interrupt_entry.rs b/src/hyperlight_guest_bin/src/exceptions/interrupt_entry.rs deleted file mode 100644 index 818283ac7..000000000 --- a/src/hyperlight_guest_bin/src/exceptions/interrupt_entry.rs +++ /dev/null @@ -1,285 +0,0 @@ -/* -Copyright 2025 The Hyperlight Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -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. -*/ - -// Note: this code takes reference from -// https://github.com/nanvix/nanvix/blob/dev/src/kernel/src/hal/arch/x86/hooks.S - -use core::arch::global_asm; - -use crate::exceptions::handler::hl_exception_handler; - -unsafe extern "C" { - // Exception handlers - pub(crate) fn _do_excp0(); - pub(crate) fn _do_excp1(); - pub(crate) fn _do_excp2(); - pub(crate) fn _do_excp3(); - pub(crate) fn _do_excp4(); - pub(crate) fn _do_excp5(); - pub(crate) fn _do_excp6(); - pub(crate) fn _do_excp7(); - pub(crate) fn _do_excp8(); - pub(crate) fn _do_excp9(); - pub(crate) fn _do_excp10(); - pub(crate) fn _do_excp11(); - pub(crate) fn _do_excp12(); - pub(crate) fn _do_excp13(); - pub(crate) fn _do_excp14(); - pub(crate) fn _do_excp15(); - pub(crate) fn _do_excp16(); - pub(crate) fn _do_excp17(); - pub(crate) fn _do_excp18(); - pub(crate) fn _do_excp19(); - pub(crate) fn _do_excp20(); - pub(crate) fn _do_excp30(); -} - -// Defines `context_save` and `context_restore` -macro_rules! context_save { - () => { - concat!( - // Push padding to match Context struct (8 bytes) - " push 0\n", - // Save general-purpose registers - " push rax\n", - " push rbx\n", - " push rcx\n", - " push rdx\n", - " push rsi\n", - " push rdi\n", - " push rbp\n", - " push r8\n", - " push r9\n", - " push r10\n", - " push r11\n", - " push r12\n", - " push r13\n", - " push r14\n", - " push r15\n", - // Save floating-point/SSE registers - // TODO: Don't do this unconditionally: get the exn - // handlers compiled without sse - // TODO: Check if we ever generate code with ymm/zmm in - // the handlers and save/restore those as well - " sub rsp, 512\n", - " mov rax, rsp\n", - " fxsave [rax]\n", - // Save all segment registers - " mov rax, ds\n", - " push rax\n", - " mov rax, es\n", - " push rax\n", - " mov rax, fs\n", - " push rax\n", - " mov rax, gs\n", - " push rax\n", - ) - }; -} - -macro_rules! context_restore { - () => { - concat!( - // Restore all segment registers - " pop rax\n", - " mov gs, rax\n", - " pop rax\n", - " mov fs, rax\n", - " pop rax\n", - " mov es, rax\n", - " pop rax\n", - " mov ds, rax\n", - // Restore floating-point/SSE registers - " mov rax, rsp\n", - " fxrstor [rax]\n", - " add rsp, 512\n", - // Restore general-purpose registers - " pop r15\n", - " pop r14\n", - " pop r13\n", - " pop r12\n", - " pop r11\n", - " pop r10\n", - " pop r9\n", - " pop r8\n", - " pop rbp\n", - " pop rdi\n", - " pop rsi\n", - " pop rdx\n", - " pop rcx\n", - " pop rbx\n", - " pop rax\n", - // Skip padding (8 bytes) - " add rsp, 8\n", - ) - }; -} - -// Generates exception handlers -macro_rules! generate_exceptions { - () => { - concat!( - // Common exception handler - ".global _do_excp_common\n", - "_do_excp_common:\n", - // rdi is the stack pointer. - " mov rdi, rsp\n", - " call {hl_exception_handler}\n", - context_restore!(), - " add rsp, 8\n", // error code - " iretq\n", // iretq is used to return from exception in x86_64 - generate_excp!(0, pusherrcode), - generate_excp!(1, pusherrcode), - generate_excp!(2, pusherrcode), - generate_excp!(3, pusherrcode), - generate_excp!(4, pusherrcode), - generate_excp!(5, pusherrcode), - generate_excp!(6, pusherrcode), - generate_excp!(7, pusherrcode), - generate_excp!(8), - generate_excp!(9, pusherrcode), - generate_excp!(10), - generate_excp!(11), - generate_excp!(12), - generate_excp!(13), - generate_excp!(14, pagefault), - generate_excp!(15, pusherrcode), - generate_excp!(16, pusherrcode), - generate_excp!(17), - generate_excp!(18, pusherrcode), - generate_excp!(19, pusherrcode), - generate_excp!(20, pusherrcode), - generate_excp!(30), - ) - }; -} - -// Macro to generate exception handlers -// that satisfy the `extern`s at the top of the file. -// -// - Example output from this macro for generate_excp!(0) call: -// ```assembly -// .global _do_excp0 -// _do_excp0: -// context_save!() -// mov rsi, 0 -// mov rdx, 0 -// jmp _do_excp_common -// ``` -// -// Stack layout after context_save!() (from high to low addresses): -// ``` -// +------------------+ <-- Higher addresses -// | SS | (Pushed by CPU on exception) -// | RSP | (Pushed by CPU on exception) -// | RFLAGS | (Pushed by CPU on exception) -// | CS | (Pushed by CPU on exception) -// | RIP | (Pushed by CPU on exception) -// | Error Code | (Pushed by CPU or by handler) <-- ExceptionInfo struct starts here -// +------------------+ -// | Padding (8) | (Pushed by context_save!) -// +------------------+ -// | RAX | gprs[14] -// | RBX | gprs[13] -// | RCX | gprs[12] -// | RDX | gprs[11] -// | RSI | gprs[10] -// | RDI | gprs[9] -// | RBP | gprs[8] -// | R8 | gprs[7] -// | R9 | gprs[6] -// | R10 | gprs[5] -// | R11 | gprs[4] -// | R12 | gprs[3] -// | R13 | gprs[2] -// | R14 | gprs[1] -// | R15 | gprs[0] (15 GPRs total, 120 bytes) -// +------------------+ -// | FXSAVE area | (512 bytes for FPU/SSE state) -// +------------------+ -// | GS | segments[3] -// | FS | segments[2] -// | ES | segments[1] -// | DS | segments[0] (4 segment registers, 32 bytes) <-- Context struct starts here -// ``` -macro_rules! generate_excp { - ($num:expr) => { - concat!( - ".global _do_excp", - stringify!($num), - "\n", - "_do_excp", - stringify!($num), - ":\n", - context_save!(), - // rsi is the exception number. - " mov rsi, ", - stringify!($num), - "\n", - // rdx is only used for pagefault exception and - // contains the address that caused the pagefault. - " mov rdx, 0\n", - " jmp _do_excp_common\n" - ) - }; - ($num:expr, pusherrcode) => { - concat!( - ".global _do_excp", - stringify!($num), - "\n", - "_do_excp", - stringify!($num), - ":\n", - // Some exceptions push an error code onto the stack. - // For the ones that don't, we push a 0 to keep the - // stack aligned. - " push 0\n", - context_save!(), - // rsi is the exception number. - " mov rsi, ", - stringify!($num), - "\n", - // rdx is only used for pagefault exception and - // contains the address that caused the pagefault. - " mov rdx, 0\n", - " jmp _do_excp_common\n" - ) - }; - ($num:expr, pagefault) => { - concat!( - ".global _do_excp", - stringify!($num), - "\n", - "_do_excp", - stringify!($num), - ":\n", - context_save!(), - " mov rsi, ", - stringify!($num), - "\n", - // In a page fault exception, the cr2 register - // contains the address that caused the page fault. - " mov rdx, cr2\n", - " jmp _do_excp_common\n" - ) - }; -} - -// Output the assembly code -global_asm!( - generate_exceptions!(), - hl_exception_handler = sym hl_exception_handler, -); diff --git a/src/hyperlight_guest_bin/src/lib.rs b/src/hyperlight_guest_bin/src/lib.rs index d692e0b26..69ffc37af 100644 --- a/src/hyperlight_guest_bin/src/lib.rs +++ b/src/hyperlight_guest_bin/src/lib.rs @@ -21,8 +21,6 @@ extern crate alloc; use core::fmt::Write; use buddy_system_allocator::LockedHeap; -#[cfg(target_arch = "x86_64")] -use exceptions::{gdt::load_gdt, idtr::load_idt}; use guest_function::call::dispatch_function; use guest_function::register::GuestFunctionRegister; use guest_logger::init_logger; @@ -30,20 +28,18 @@ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::mem::HyperlightPEB; #[cfg(feature = "mem_profile")] use hyperlight_common::outb::OutBAction; -use hyperlight_guest::exit::{halt, write_abort}; +use hyperlight_guest::exit::write_abort; use hyperlight_guest::guest_handle::handle::GuestHandle; use log::LevelFilter; -use spin::Once; // === Modules === +#[cfg_attr(target_arch = "x86_64", path = "arch/amd64/mod.rs")] +mod arch; +// temporarily expose the architecture-specific exception interface; +// this should be replaced with something a bit more abstract in the +// near future. #[cfg(target_arch = "x86_64")] -pub mod exceptions { - pub(super) mod gdt; - pub mod handler; - mod idt; - pub(super) mod idtr; - mod interrupt_entry; -} +pub mod exception; pub mod guest_function { pub(super) mod call; pub mod definition; @@ -123,8 +119,10 @@ pub static mut GUEST_HANDLE: GuestHandle = GuestHandle::new(); pub(crate) static mut REGISTERED_GUEST_FUNCTIONS: GuestFunctionRegister = GuestFunctionRegister::new(); -pub static mut MIN_STACK_ADDRESS: u64 = 0; - +/// The size of one page in the host OS, which may have some impacts +/// on how buffers for host consumption should be aligned. Code only +/// working with the guest page tables should use +/// [`hyperlight_common::vm::PAGE_SIZE`] instead. pub static mut OS_PAGE_SIZE: u32 = 0; // === Panic Handler === @@ -177,78 +175,62 @@ unsafe extern "C" { fn srand(seed: u32); } -static INIT: Once = Once::new(); +/// Architecture-nonspecific initialisation: set up the heap, +/// coordinate some addresses and configuration with the host, and run +/// user initialisation +pub(crate) extern "C" fn generic_init(peb_address: u64, seed: u64, ops: u64, max_log_level: u64) { + let peb_ptr = unsafe { + GUEST_HANDLE = GuestHandle::init(peb_address as *mut HyperlightPEB); + #[allow(static_mut_refs)] + let peb_ptr = GUEST_HANDLE.peb().unwrap(); + + let heap_start = (*peb_ptr).guest_heap.ptr as usize; + let heap_size = (*peb_ptr).guest_heap.size as usize; + #[cfg(not(feature = "mem_profile"))] + let heap_allocator = &HEAP_ALLOCATOR; + #[cfg(feature = "mem_profile")] + let heap_allocator = &HEAP_ALLOCATOR.0; + heap_allocator + .try_lock() + .expect("Failed to access HEAP_ALLOCATOR") + .init(heap_start, heap_size); + peb_ptr + }; -#[unsafe(no_mangle)] -pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_level: u64) { // Save the guest start TSC for tracing #[cfg(feature = "trace_guest")] let guest_start_tsc = hyperlight_guest_tracing::invariant_tsc::read_tsc(); - if peb_address == 0 { - panic!("PEB address is null"); - } + unsafe { + (*peb_ptr).guest_function_dispatch_ptr = dispatch_function as usize as u64; - INIT.call_once(|| { - unsafe { - GUEST_HANDLE = GuestHandle::init(peb_address as *mut HyperlightPEB); - #[allow(static_mut_refs)] - let peb_ptr = GUEST_HANDLE.peb().unwrap(); + let srand_seed = (((peb_address << 8) ^ (seed >> 4)) >> 32) as u32; + // Set the seed for the random number generator for C code using rand; + srand(srand_seed); - let srand_seed = (((peb_address << 8) ^ (seed >> 4)) >> 32) as u32; - - // Set the seed for the random number generator for C code using rand; - srand(srand_seed); - - // This static is to make it easier to implement the __chkstk function in assembly. - // It also means that should we change the layout of the struct in the future, we - // don't have to change the assembly code. - MIN_STACK_ADDRESS = (*peb_ptr).guest_stack.min_user_stack_address; - - #[cfg(target_arch = "x86_64")] - { - // Setup GDT and IDT - load_gdt(); - load_idt(); - } + OS_PAGE_SIZE = ops as u32; + } - let heap_start = (*peb_ptr).guest_heap.ptr as usize; - let heap_size = (*peb_ptr).guest_heap.size as usize; - #[cfg(not(feature = "mem_profile"))] - let heap_allocator = &HEAP_ALLOCATOR; - #[cfg(feature = "mem_profile")] - let heap_allocator = &HEAP_ALLOCATOR.0; - heap_allocator - .try_lock() - .expect("Failed to access HEAP_ALLOCATOR") - .init(heap_start, heap_size); - - OS_PAGE_SIZE = ops as u32; - - (*peb_ptr).guest_function_dispatch_ptr = dispatch_function as usize as u64; - - // set up the logger - let max_log_level = LevelFilter::iter() - .nth(max_log_level as usize) - .expect("Invalid log level"); - init_logger(max_log_level); - - // It is important that all the tracing events are produced after the tracing is initialized. - #[cfg(feature = "trace_guest")] - if max_log_level != LevelFilter::Off { - hyperlight_guest_tracing::init_guest_tracing(guest_start_tsc); - } + // set up the logger + let max_log_level = LevelFilter::iter() + .nth(max_log_level as usize) + .expect("Invalid log level"); + init_logger(max_log_level); - #[cfg(feature = "macros")] - for registration in __private::GUEST_FUNCTION_INIT { - registration(); - } + // It is important that all the tracing events are produced after the tracing is initialized. + #[cfg(feature = "trace_guest")] + if max_log_level != LevelFilter::Off { + hyperlight_guest_tracing::init_guest_tracing(guest_start_tsc); + } - hyperlight_main(); - } - }); + #[cfg(feature = "macros")] + for registration in __private::GUEST_FUNCTION_INIT { + registration(); + } - halt(); + unsafe { + hyperlight_main(); + } } #[cfg(feature = "macros")] diff --git a/src/hyperlight_guest_bin/src/paging.rs b/src/hyperlight_guest_bin/src/paging.rs index 2c5d62b23..15721a825 100644 --- a/src/hyperlight_guest_bin/src/paging.rs +++ b/src/hyperlight_guest_bin/src/paging.rs @@ -18,7 +18,6 @@ use core::arch::asm; use hyperlight_common::vmem; use hyperlight_guest::prim_alloc::alloc_phys_pages; -use tracing::{Span, instrument}; // TODO: This is not at all thread-safe atm // TODO: A lot of code in this file uses inline assembly to load and @@ -138,7 +137,6 @@ impl vmem::TableOps for GuestMappingOperations { /// as such do not use concurrently with any other page table operations /// - TLB invalidation is not performed, /// if previously-unmapped ranges are not being mapped, TLB invalidation may need to be performed afterwards. -#[instrument(skip_all, parent = Span::current(), level= "Trace")] pub unsafe fn map_region(phys_base: u64, virt_base: *mut u8, len: u64) { unsafe { vmem::map( diff --git a/src/hyperlight_host/src/error.rs b/src/hyperlight_host/src/error.rs index 837eb3b5a..4a3fc10ef 100644 --- a/src/hyperlight_host/src/error.rs +++ b/src/hyperlight_host/src/error.rs @@ -422,36 +422,6 @@ mod tests { }; use crate::sandbox::outb::HandleOutbError; - /// Test that StackOverflow from RunVmError promotes to HyperlightError::StackOverflow - #[test] - fn test_promote_stack_overflow_from_run_vm() { - let err = DispatchGuestCallError::Run(RunVmError::StackOverflow); - let (promoted, should_poison) = err.promote(); - - assert!(should_poison, "StackOverflow should poison the sandbox"); - assert!( - matches!(promoted, HyperlightError::StackOverflow()), - "Expected HyperlightError::StackOverflow, got {:?}", - promoted - ); - } - - /// Test that StackOverflow from HandleOutbError promotes to HyperlightError::StackOverflow - #[test] - fn test_promote_stack_overflow_from_outb() { - let err = DispatchGuestCallError::Run(RunVmError::HandleIo(HandleIoError::Outb( - HandleOutbError::StackOverflow, - ))); - let (promoted, should_poison) = err.promote(); - - assert!(should_poison, "StackOverflow should poison the sandbox"); - assert!( - matches!(promoted, HyperlightError::StackOverflow()), - "Expected HyperlightError::StackOverflow, got {:?}", - promoted - ); - } - /// Test that ExecutionCancelledByHost promotes to HyperlightError::ExecutionCanceledByHost #[test] fn test_promote_execution_cancelled_by_host() { @@ -517,26 +487,6 @@ mod tests { } } - /// Test that ConvertRspPointer does not poison the sandbox - #[test] - fn test_promote_convert_rsp_pointer_no_poison() { - let err = DispatchGuestCallError::ConvertRspPointer("test error".to_string()); - let (promoted, should_poison) = err.promote(); - - assert!( - !should_poison, - "ConvertRspPointer should not poison the sandbox" - ); - assert!( - matches!( - promoted, - HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(_)) - ), - "Expected HyperlightError::HyperlightVmError, got {:?}", - promoted - ); - } - /// Test that non-promoted Run errors are wrapped in HyperlightVmError #[test] fn test_promote_other_run_errors_wrapped() { diff --git a/src/hyperlight_host/src/hypervisor/gdb/mod.rs b/src/hyperlight_host/src/hypervisor/gdb/mod.rs index 82fe9503f..6ef5a3322 100644 --- a/src/hyperlight_host/src/hypervisor/gdb/mod.rs +++ b/src/hyperlight_host/src/hypervisor/gdb/mod.rs @@ -39,7 +39,7 @@ use crate::hypervisor::virtual_machine::{HypervisorError, RegisterError, Virtual use crate::mem::layout::SandboxMemoryLayout; use crate::mem::memory_region::MemoryRegion; use crate::mem::mgr::SandboxMemoryManager; -use crate::mem::shared_mem::HostSharedMemory; +use crate::mem::shared_mem::{HostSharedMemory, SharedMemory}; #[derive(Debug, Error)] pub enum GdbTargetError { @@ -98,6 +98,15 @@ pub enum DebugMemoryAccessError { } impl DebugMemoryAccess { + // TODO: There is a lot of common logic between both of these + // functions, as well as guest_page/access_gpa in snapshot.rs. It + // would be nice to factor that out at some point, but the + // snapshot versions deal with ExclusiveSharedMemory, since we + // never expect a guest to be running concurrent with a snapshot, + // and doesn't want to make unnecessary copies, since it runs over + // relatively large volumes of data, so it's not clear if it's + // terribly easy to combine them + /// Reads memory from the guest's address space with a maximum length of a PAGE_SIZE /// /// # Arguments @@ -153,16 +162,28 @@ impl DebugMemoryAccess { } if !region_found { + let mut mgr = self + .dbg_mem_access_fn + .try_lock() + .map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))?; + let scratch_base = + hyperlight_common::layout::scratch_base_gpa(mgr.scratch_mem.mem_size()); + let (mem, offset, name): (&mut HostSharedMemory, _, _) = if gpa >= scratch_base { + ( + &mut mgr.scratch_mem, + (gpa - scratch_base) as usize, + "scratch", + ) + } else { + (&mut mgr.shared_mem, mem_offset, "snapshot") + }; log::debug!( - "No mapped region found containing {:X}. Trying shared memory ...", - gpa + "No mapped region found containing {:X}. Trying {} memory at offset {:X} ...", + gpa, + name, + offset ); - - self.dbg_mem_access_fn - .try_lock() - .map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))? - .get_shared_mem_mut() - .copy_to_slice(&mut data[..read_len], mem_offset) + mem.copy_to_slice(&mut data[..read_len], offset) .map_err(|e| DebugMemoryAccessError::CopyFailed(Box::new(e)))?; } @@ -224,17 +245,28 @@ impl DebugMemoryAccess { } if !region_found { + let mut mgr = self + .dbg_mem_access_fn + .try_lock() + .map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))?; + let scratch_base = + hyperlight_common::layout::scratch_base_gpa(mgr.scratch_mem.mem_size()); + let (mem, offset, name): (&mut HostSharedMemory, _, _) = if gpa >= scratch_base { + ( + &mut mgr.scratch_mem, + (gpa - scratch_base) as usize, + "scratch", + ) + } else { + (&mut mgr.shared_mem, mem_offset, "snapshot") + }; log::debug!( - "No mapped region found containing {:X}. Trying shared memory at offset {:X} ...", + "No mapped region found containing {:X}. Trying {} memory at offset {:X} ...", gpa, - mem_offset + name, + offset ); - - self.dbg_mem_access_fn - .try_lock() - .map_err(|e| DebugMemoryAccessError::LockFailed(file!(), line!(), e.to_string()))? - .get_shared_mem_mut() - .copy_from_slice(&data[..write_len], mem_offset) + mem.copy_from_slice(&data[..write_len], offset) .map_err(|e| DebugMemoryAccessError::CopyFailed(Box::new(e)))?; } diff --git a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs index 65a4ee6f0..260aa8488 100644 --- a/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs +++ b/src/hyperlight_host/src/hypervisor/hyperlight_vm.rs @@ -62,7 +62,7 @@ use crate::hypervisor::virtual_machine::{ use crate::hypervisor::{InterruptHandle, InterruptHandleImpl, get_max_log_level}; use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags, MemoryRegionType}; use crate::mem::mgr::SandboxMemoryManager; -use crate::mem::ptr::{GuestPtr, RawPtr}; +use crate::mem::ptr::RawPtr; use crate::mem::shared_mem::{GuestSharedMemory, HostSharedMemory, SharedMemory}; use crate::metrics::{METRIC_ERRONEOUS_VCPU_KICKS, METRIC_GUEST_CANCELLATION}; use crate::sandbox::SandboxConfiguration; @@ -85,8 +85,8 @@ pub(crate) struct HyperlightVm { #[cfg(not(gdb))] vm: Box, page_size: usize, - entrypoint: u64, - orig_rsp: GuestPtr, + entrypoint: Option, // only present if this vm has not yet been initialised + rsp_gva: u64, interrupt_handle: Arc, next_slot: u32, // Monotonically increasing slot number @@ -116,8 +116,6 @@ pub(crate) struct HyperlightVm { /// DispatchGuestCall error #[derive(Debug, thiserror::Error)] pub enum DispatchGuestCallError { - #[error("Failed to convert RSP pointer: {0}")] - ConvertRspPointer(String), #[error("Failed to run vm: {0}")] Run(#[from] RunVmError), #[error("Failed to setup registers: {0}")] @@ -131,9 +129,7 @@ impl DispatchGuestCallError { // These errors poison the sandbox because they can leave it in an inconsistent state // by returning before the guest can unwind properly DispatchGuestCallError::Run(_) => true, - DispatchGuestCallError::ConvertRspPointer(_) | DispatchGuestCallError::SetupRegs(_) => { - false - } + DispatchGuestCallError::SetupRegs(_) => false, } } @@ -145,12 +141,6 @@ impl DispatchGuestCallError { pub(crate) fn promote(self) -> (HyperlightError, bool) { let should_poison = self.is_poison_error(); let promoted_error = match self { - // These errors poison the sandbox because the guest did not run to completion - DispatchGuestCallError::Run(RunVmError::StackOverflow) - | DispatchGuestCallError::Run(RunVmError::HandleIo(HandleIoError::Outb( - HandleOutbError::StackOverflow, - ))) => HyperlightError::StackOverflow(), - DispatchGuestCallError::Run(RunVmError::ExecutionCancelledByHost) => { HyperlightError::ExecutionCanceledByHost() } @@ -181,13 +171,13 @@ pub enum InitializeError { Run(#[from] RunVmError), #[error("Failed to setup registers: {0}")] SetupRegs(#[from] RegisterError), + #[error("Guest initialised stack pointer to architecturally invalid value: {0}")] + InvalidStackPointer(u64), } /// Errors that can occur during VM execution in the run loop #[derive(Debug, thiserror::Error)] pub enum RunVmError { - #[error("Error checking stack guard: {0}")] - CheckStackGuard(Box), #[cfg(crashdump)] #[error("Crashdump generation error: {0}")] CrashdumpGeneration(Box), @@ -215,8 +205,6 @@ pub enum RunVmError { MmioWriteUnmapped(u64), #[error("vCPU run failed: {0}")] RunVcpu(#[from] RunVcpuError), - #[error("Stack overflow detected")] - StackOverflow, #[error("Unexpected VM exit: {0}")] UnexpectedVmExit(String), #[cfg(gdb)] @@ -276,8 +264,6 @@ pub enum CreateHyperlightVmError { #[cfg(gdb)] #[error("Failed to add hardware breakpoint: {0}")] AddHwBreakpoint(DebugError), - #[error("Failed to convert RSP pointer: {0}")] - ConvertRspPointer(Box), #[error("No hypervisor was found")] NoHypervisorFound, #[cfg(gdb)] @@ -350,8 +336,8 @@ impl HyperlightVm { snapshot_mem: GuestSharedMemory, scratch_mem: GuestSharedMemory, _pml4_addr: u64, - entrypoint: u64, - rsp: u64, + entrypoint: Option, + rsp_gva: u64, #[cfg_attr(target_os = "windows", allow(unused_variables))] config: &SandboxConfiguration, #[cfg(gdb)] gdb_conn: Option>, #[cfg(crashdump)] rt_cfg: SandboxRuntimeConfig, @@ -378,8 +364,6 @@ impl HyperlightVm { #[cfg(not(feature = "init-paging"))] vm.set_sregs(&CommonSpecialRegisters::standard_real_mode_defaults()) .map_err(VmError::Register)?; - let rsp_gp = GuestPtr::try_from(RawPtr::from(rsp)) - .map_err(|e| CreateHyperlightVmError::ConvertRspPointer(Box::new(e)))?; #[cfg(any(kvm, mshv3))] let interrupt_handle: Arc = Arc::new(LinuxInterruptHandle { @@ -418,7 +402,7 @@ impl HyperlightVm { let mut ret = Self { vm, entrypoint, - orig_rsp: rsp_gp, + rsp_gva, interrupt_handle, page_size: 0, // Will be set in `initialise` @@ -450,11 +434,13 @@ impl HyperlightVm { #[cfg(gdb)] if ret.gdb_conn.is_some() { ret.send_dbg_msg(DebugResponse::InterruptHandle(ret.interrupt_handle.clone()))?; - // Add breakpoint to the entry point address + // Add breakpoint to the entry point address, if we are going to initialise ret.vm.set_debug(true).map_err(VmError::Debug)?; - ret.vm - .add_hw_breakpoint(entrypoint) - .map_err(CreateHyperlightVmError::AddHwBreakpoint)?; + if let Some(entrypoint) = entrypoint { + ret.vm + .add_hw_breakpoint(entrypoint) + .map_err(CreateHyperlightVmError::AddHwBreakpoint)?; + } } Ok(ret) @@ -474,6 +460,10 @@ impl HyperlightVm { guest_max_log_level: Option, #[cfg(gdb)] dbg_mem_access_fn: Arc>>, ) -> std::result::Result<(), InitializeError> { + let Some(entrypoint) = self.entrypoint else { + return Ok(()); + }; + self.page_size = page_size as usize; let guest_max_log_level: u64 = match guest_max_log_level { @@ -482,11 +472,14 @@ impl HyperlightVm { }; let regs = CommonRegisters { - rip: self.entrypoint, - rsp: self - .orig_rsp - .absolute() - .map_err(|e| InitializeError::ConvertPointer(e.to_string()))?, + rip: entrypoint, + // We usually keep the top of the stack 16-byte + // aligned. However, the ABI requirement is that the stack + // be aligned _before a call instruction_, which means + // that the stack needs to actually be ≡ 8 mod 16 at the + // first instruction (since, on x64, a call instruction + // automatically pushes a return address). + rsp: self.rsp_gva - 8, // function args rdi: peb_addr.into(), @@ -505,7 +498,16 @@ impl HyperlightVm { #[cfg(gdb)] dbg_mem_access_fn, ) - .map_err(InitializeError::Run) + .map_err(InitializeError::Run)?; + + let regs = self.vm.regs()?; + // todo(portability): this is architecture-specific + if !regs.rsp.is_multiple_of(16) { + return Err(InitializeError::InvalidStackPointer(regs.rsp)); + } + self.rsp_gva = regs.rsp; + + Ok(()) } /// Map a region of host memory into the sandbox. @@ -618,6 +620,16 @@ impl HyperlightVm { Ok(()) } + /// Get the current stack top virtual address + pub(crate) fn get_stack_top(&mut self) -> u64 { + self.rsp_gva + } + + /// Set the current stack top virtual address + pub(crate) fn set_stack_top(&mut self, gva: u64) { + self.rsp_gva = gva; + } + /// Dispatch a call from the host to the guest using the given pointer /// to the dispatch function _in the guest's address space_. /// @@ -636,10 +648,13 @@ impl HyperlightVm { // set RIP and RSP, reset others let regs = CommonRegisters { rip: dispatch_func_addr.into(), - rsp: self - .orig_rsp - .absolute() - .map_err(|e| DispatchGuestCallError::ConvertRspPointer(e.to_string()))?, + // We usually keep the top of the stack 16-byte + // aligned. However, the ABI requirement is that the stack + // be aligned _before a call instruction_, which means + // that the stack needs to actually be ≡ 8 mod 16 at the + // first instruction (since, on x64, a call instruction + // automatically pushes a return address). + rsp: self.rsp_gva - 8, rflags: 1 << 1, ..Default::default() }; @@ -740,8 +755,12 @@ impl HyperlightVm { #[cfg(gdb)] Ok(VmExit::Debug { dr6, exception }) => { // Handle debug event (breakpoints) - let stop_reason = - arch::vcpu_stop_reason(self.vm.as_mut(), dr6, self.entrypoint, exception)?; + let stop_reason = arch::vcpu_stop_reason( + self.vm.as_mut(), + dr6, + self.entrypoint.unwrap_or(0), + exception, + )?; if let Err(e) = self.handle_debug(dbg_mem_access_fn.clone(), stop_reason) { break Err(e.into()); } @@ -760,9 +779,6 @@ impl HyperlightVm { MemoryRegionFlags::WRITE, all_regions, ) { - Some(MemoryAccess::StackGuardPageViolation) => { - break Err(RunVmError::StackOverflow); - } Some(MemoryAccess::AccessViolation(region_flags)) => { break Err(RunVmError::MemoryAccessViolation { addr, @@ -771,13 +787,6 @@ impl HyperlightVm { }); } None => { - if !mem_mgr - .check_stack_guard() - .map_err(|e| RunVmError::CheckStackGuard(Box::new(e)))? - { - break Err(RunVmError::StackOverflow); - } - break Err(RunVmError::MmioReadUnmapped(addr)); } } @@ -789,9 +798,6 @@ impl HyperlightVm { MemoryRegionFlags::WRITE, all_regions, ) { - Some(MemoryAccess::StackGuardPageViolation) => { - break Err(RunVmError::StackOverflow); - } Some(MemoryAccess::AccessViolation(region_flags)) => { break Err(RunVmError::MemoryAccessViolation { addr, @@ -800,13 +806,6 @@ impl HyperlightVm { }); } None => { - if !mem_mgr - .check_stack_guard() - .map_err(|e| RunVmError::CheckStackGuard(Box::new(e)))? - { - break Err(RunVmError::StackOverflow); - } - break Err(RunVmError::MmioWriteUnmapped(addr)); } } @@ -920,6 +919,9 @@ impl HyperlightVm { } let mem_access = DebugMemoryAccess { + // TODO: dbg_mem_access_fn could be out of sync with the + // actual snapshot/scratch regions, if a snapshot restore + // has caused either of those to change. dbg_mem_access_fn, guest_mmap_regions: self.get_mapped_regions().cloned().collect(), }; @@ -1099,7 +1101,7 @@ impl HyperlightVm { regions, regs, xsave.to_vec(), - self.entrypoint, + self.entrypoint.unwrap_or(0), self.rt_cfg.binary_path.clone(), filename, ))) @@ -1119,8 +1121,6 @@ impl Drop for HyperlightVm { enum MemoryAccess { /// The accessed region has the given flags AccessViolation(MemoryRegionFlags), - /// The accessed region is a stack guard page - StackGuardPageViolation, } /// Determines if a known memory access violation occurred at the given address with the given action type. @@ -1131,9 +1131,6 @@ fn get_memory_access_violation<'a>( mut mem_regions: impl Iterator, ) -> Option { let region = mem_regions.find(|region| region.guest_region.contains(&gpa))?; - if region.region_type == MemoryRegionType::GuardPage { - return Some(MemoryAccess::StackGuardPageViolation); - } if !region.flags.contains(tried) { return Some(MemoryAccess::AccessViolation(region.flags)); } diff --git a/src/hyperlight_host/src/hypervisor/mod.rs b/src/hyperlight_host/src/hypervisor/mod.rs index 58410aa67..66a5a8cd5 100644 --- a/src/hyperlight_host/src/hypervisor/mod.rs +++ b/src/hyperlight_host/src/hypervisor/mod.rs @@ -526,9 +526,13 @@ pub(crate) mod tests { let sandbox = UninitializedSandbox::new(GuestBinary::FilePath(filename.clone()), Some(config))?; let (mut mem_mgr, gshm) = sandbox.mgr.build().unwrap(); + let exn_stack_top_gva = hyperlight_common::layout::MAX_GVA as u64 + - hyperlight_common::layout::SCRATCH_TOP_EXN_STACK_OFFSET + + 1; let mut vm = set_up_hypervisor_partition( gshm, &config, + exn_stack_top_gva, #[cfg(any(crashdump, gdb))] &rt_cfg, sandbox.load_info, diff --git a/src/hyperlight_host/src/mem/layout.rs b/src/hyperlight_host/src/mem/layout.rs index dfa161da4..925067c21 100644 --- a/src/hyperlight_host/src/mem/layout.rs +++ b/src/hyperlight_host/src/mem/layout.rs @@ -25,10 +25,6 @@ limitations under the License. //! +-------------------------------------------+ //! | Init Data | (GuestBlob size) //! +-------------------------------------------+ -//! | Guest (User) Stack | -//! +-------------------------------------------+ -//! | Guard Page (4KiB) | -//! +-------------------------------------------+ //! | Guest Heap | //! +-------------------------------------------+ //! | Output Data | @@ -45,7 +41,7 @@ limitations under the License. //! Everything except for the guest page tables is currently //! identity-mapped; the guest page tables themselves are mapped at //! [`hyperlight_common::layout::SNAPSHOT_PT_GVA`] = -//! 0xffff_0000_0000_0000. +//! 0xffff_8000_0000_0000. //! //! - `InitData` - some extra data that can be loaded onto the sandbox during //! initialization. @@ -61,25 +57,16 @@ limitations under the License. //! //! - `GuestHeap` - this is a buffer that is used for heap data in the guest. the length //! of this field is returned by the `heap_size()` method of this struct -//! -//! - `GuestStack` - this is a buffer that is used for stack data in the guest. the length -//! of this field is returned by the `stack_size()` method of this struct. in reality, -//! the stack might be slightly bigger or smaller than this value since total memory -//! size is rounded up to the nearest 4K, and there is a 16-byte stack guard written -//! to the top of the stack. (see below for more details use std::fmt::Debug; use std::mem::{offset_of, size_of}; -use hyperlight_common::mem::{GuestStack, HyperlightPEB, PAGE_SIZE_USIZE}; -use rand::{RngCore, rng}; +use hyperlight_common::mem::{GuestMemoryRegion, HyperlightPEB, PAGE_SIZE_USIZE}; use tracing::{Span, instrument}; #[cfg(feature = "init-paging")] use super::memory_region::MemoryRegionType::PageTables; -use super::memory_region::MemoryRegionType::{ - Code, GuardPage, Heap, InitData, InputData, OutputData, Peb, Stack, -}; +use super::memory_region::MemoryRegionType::{Code, Heap, InitData, InputData, OutputData, Peb}; use super::memory_region::{ DEFAULT_GUEST_BLOB_MEM_FLAGS, MemoryRegion_, MemoryRegionFlags, MemoryRegionKind, MemoryRegionVecBuilder, @@ -92,8 +79,6 @@ use crate::{Result, new_error}; #[derive(Copy, Clone)] pub(crate) struct SandboxMemoryLayout { pub(super) sandbox_memory_config: SandboxConfiguration, - /// The total stack size of this sandbox. - pub(super) stack_size: usize, /// The heap size of this sandbox. pub(super) heap_size: usize, init_data_size: usize, @@ -101,21 +86,17 @@ pub(crate) struct SandboxMemoryLayout { /// The following fields are offsets to the actual PEB struct fields. /// They are used when writing the PEB struct itself peb_offset: usize, - peb_security_cookie_seed_offset: usize, peb_guest_dispatch_function_ptr_offset: usize, // set by guest in guest entrypoint peb_input_data_offset: usize, peb_output_data_offset: usize, peb_init_data_offset: usize, peb_heap_data_offset: usize, - peb_guest_stack_data_offset: usize, // The following are the actual values // that are written to the PEB struct pub(super) input_data_buffer_offset: usize, pub(super) output_data_buffer_offset: usize, guest_heap_buffer_offset: usize, - guard_page_offset: usize, - guest_user_stack_buffer_offset: usize, // the lowest address of the user stack init_data_offset: usize, pt_offset: usize, pt_size: Option, @@ -140,7 +121,6 @@ impl Debug for SandboxMemoryLayout { "Total Memory Size", &format_args!("{:#x}", self.get_memory_size().unwrap_or(0)), ) - .field("Stack Size", &format_args!("{:#x}", self.stack_size)) .field("Heap Size", &format_args!("{:#x}", self.heap_size)) .field( "Init Data Size", @@ -149,10 +129,6 @@ impl Debug for SandboxMemoryLayout { .field("PEB Address", &format_args!("{:#x}", self.peb_address)) .field("PEB Offset", &format_args!("{:#x}", self.peb_offset)) .field("Code Size", &format_args!("{:#x}", self.code_size)) - .field( - "Security Cookie Seed Offset", - &format_args!("{:#x}", self.peb_security_cookie_seed_offset), - ) .field( "Guest Dispatch Function Pointer Offset", &format_args!("{:#x}", self.peb_guest_dispatch_function_ptr_offset), @@ -173,10 +149,6 @@ impl Debug for SandboxMemoryLayout { "Guest Heap Offset", &format_args!("{:#x}", self.peb_heap_data_offset), ) - .field( - "Guest Stack Offset", - &format_args!("{:#x}", self.peb_guest_stack_data_offset), - ) .field( "Input Data Buffer Offset", &format_args!("{:#x}", self.input_data_buffer_offset), @@ -189,14 +161,6 @@ impl Debug for SandboxMemoryLayout { "Guest Heap Buffer Offset", &format_args!("{:#x}", self.guest_heap_buffer_offset), ) - .field( - "Guard Page Offset", - &format_args!("{:#x}", self.guard_page_offset), - ) - .field( - "Guest User Stack Buffer Offset", - &format_args!("{:#x}", self.guest_user_stack_buffer_offset), - ) .field( "Init Data Offset", &format_args!("{:#x}", self.init_data_offset), @@ -236,7 +200,6 @@ impl SandboxMemoryLayout { pub(crate) fn new( cfg: SandboxConfiguration, code_size: usize, - stack_size: usize, heap_size: usize, scratch_size: usize, init_data_size: usize, @@ -244,61 +207,44 @@ impl SandboxMemoryLayout { ) -> Result { let guest_code_offset = 0; // The following offsets are to the fields of the PEB struct itself! - let peb_offset = round_up_to(code_size, PAGE_SIZE_USIZE); - let peb_security_cookie_seed_offset = - peb_offset + offset_of!(HyperlightPEB, security_cookie_seed); + let peb_offset = code_size.next_multiple_of(PAGE_SIZE_USIZE); let peb_guest_dispatch_function_ptr_offset = peb_offset + offset_of!(HyperlightPEB, guest_function_dispatch_ptr); let peb_input_data_offset = peb_offset + offset_of!(HyperlightPEB, input_stack); 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); - let peb_guest_stack_data_offset = peb_offset + offset_of!(HyperlightPEB, guest_stack); // 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 input data buffer starts at 4K boundary - let input_data_buffer_offset = round_up_to( - peb_guest_stack_data_offset + size_of::(), - PAGE_SIZE_USIZE, - ); - let output_data_buffer_offset = round_up_to( - input_data_buffer_offset + cfg.get_input_data_size(), - PAGE_SIZE_USIZE, - ); + let input_data_buffer_offset = (peb_heap_data_offset + size_of::()) + .next_multiple_of(PAGE_SIZE_USIZE); + let output_data_buffer_offset = (input_data_buffer_offset + cfg.get_input_data_size()) + .next_multiple_of(PAGE_SIZE_USIZE); // make sure heap buffer starts at 4K boundary - let guest_heap_buffer_offset = round_up_to( - output_data_buffer_offset + cfg.get_output_data_size(), - PAGE_SIZE_USIZE, - ); - // make sure guard page starts at 4K boundary - let guard_page_offset = round_up_to(guest_heap_buffer_offset + heap_size, PAGE_SIZE_USIZE); - let guest_user_stack_buffer_offset = guard_page_offset + PAGE_SIZE_USIZE; - // round up stack size to page size. This is needed for MemoryRegion - let stack_size_rounded = round_up_to(stack_size, PAGE_SIZE_USIZE); - let init_data_offset = guest_user_stack_buffer_offset + stack_size_rounded; - let pt_offset = round_up_to(init_data_offset + init_data_size, PAGE_SIZE_USIZE); + let guest_heap_buffer_offset = (output_data_buffer_offset + cfg.get_output_data_size()) + .next_multiple_of(PAGE_SIZE_USIZE); + // make sure init data starts at 4K boundary + let init_data_offset = + (guest_heap_buffer_offset + heap_size).next_multiple_of(PAGE_SIZE_USIZE); + let pt_offset = (init_data_offset + init_data_size).next_multiple_of(PAGE_SIZE_USIZE); Ok(Self { peb_offset, - stack_size: stack_size_rounded, heap_size, - peb_security_cookie_seed_offset, peb_guest_dispatch_function_ptr_offset, peb_input_data_offset, peb_output_data_offset, peb_init_data_offset, peb_heap_data_offset, - peb_guest_stack_data_offset, sandbox_memory_config: cfg, code_size, input_data_buffer_offset, output_data_buffer_offset, guest_heap_buffer_offset, - guest_user_stack_buffer_offset, peb_address, - guard_page_offset, guest_code_offset, init_data_offset, init_data_size, @@ -323,19 +269,6 @@ impl SandboxMemoryLayout { self.peb_init_data_offset } - /// Get the offset in guest memory to the minimum guest stack address. - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_min_guest_stack_address_offset(&self) -> usize { - // The minimum guest user stack address is the start of the guest stack - self.peb_guest_stack_data_offset - } - - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - #[cfg_attr(not(feature = "init-paging"), allow(unused))] - pub(super) fn get_guest_stack_size(&self) -> usize { - self.stack_size - } - #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn get_scratch_size(&self) -> usize { self.scratch_size @@ -400,21 +333,6 @@ impl SandboxMemoryLayout { self.get_heap_size_offset() + size_of::() } - /// Get the offset to the top of the stack in guest memory - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - #[cfg(feature = "init-paging")] - pub(crate) fn get_top_of_user_stack_offset(&self) -> usize { - self.guest_user_stack_buffer_offset - } - - /// Get the offset of the user stack pointer in guest memory, - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn get_user_stack_pointer_offset(&self) -> usize { - // The userStackAddress is immediately after the - // minUserStackAddress (top of user stack) field in the `GuestStackData` struct which is a `u64`. - self.get_min_guest_stack_address_offset() + size_of::() - } - /// Get the total size of guest memory in `self`'s memory /// layout. #[instrument(skip_all, parent = Span::current(), level= "Trace")] @@ -468,14 +386,6 @@ impl SandboxMemoryLayout { self.pt_size = Some(size); } - /// Get the offset into the snapshot region of the guest user stack - /// pointer, to be used when entering the guest - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - #[cfg(feature = "init-paging")] - pub(crate) fn get_rsp_offset(&self) -> usize { - self.get_top_of_user_stack_offset() + self.stack_size - 0x28 - } - /// Returns the memory regions associated with this memory layout, /// suitable for passing to a hypervisor for mapping into memory #[cfg_attr(not(feature = "init-paging"), allow(unused))] @@ -556,53 +466,18 @@ impl SandboxMemoryLayout { // heap #[cfg(feature = "executable_heap")] - let guard_page_offset = builder.push_page_aligned( + let init_data_offset = builder.push_page_aligned( self.heap_size, MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE, Heap, ); #[cfg(not(feature = "executable_heap"))] - let guard_page_offset = builder.push_page_aligned( + let init_data_offset = builder.push_page_aligned( self.heap_size, MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, Heap, ); - let expected_guard_page_offset = TryInto::::try_into(self.guard_page_offset)?; - - if guard_page_offset != expected_guard_page_offset { - return Err(new_error!( - "Guard Page offset does not match expected Guard Page offset expected: {}, actual: {}", - expected_guard_page_offset, - guard_page_offset - )); - } - - // guard page - let stack_offset = builder.push_page_aligned( - PAGE_SIZE_USIZE, - MemoryRegionFlags::READ | MemoryRegionFlags::STACK_GUARD, - GuardPage, - ); - - let expected_stack_offset = - TryInto::::try_into(self.guest_user_stack_buffer_offset)?; - - if stack_offset != expected_stack_offset { - return Err(new_error!( - "Stack offset does not match expected Stack offset expected: {}, actual: {}", - expected_stack_offset, - stack_offset - )); - } - - // stack - let init_data_offset = builder.push_page_aligned( - self.get_guest_stack_size(), - MemoryRegionFlags::READ | MemoryRegionFlags::WRITE, - Stack, - ); - let expected_init_data_offset = TryInto::::try_into(self.init_data_offset)?; if init_data_offset != expected_init_data_offset { @@ -613,6 +488,7 @@ impl SandboxMemoryLayout { )); } + // init data let after_init_offset = if self.init_data_size > 0 { let mem_flags = self .init_data_permissions @@ -623,6 +499,7 @@ impl SandboxMemoryLayout { }; #[cfg(feature = "init-paging")] + // page tables let final_offset = { let expected_pt_offset = TryInto::::try_into(self.pt_offset)?; @@ -695,11 +572,6 @@ impl SandboxMemoryLayout { // Start of setting up the PEB. The following are in the order of the PEB fields - // Set up the security cookie seed - let mut security_cookie_seed = [0u8; 8]; - rng().fill_bytes(&mut security_cookie_seed); - shared_mem.copy_from_slice(&security_cookie_seed, self.peb_security_cookie_seed_offset)?; - // Skip guest_dispatch_function_ptr_offset because it is set by the guest // Skip code, is set when loading binary @@ -738,22 +610,6 @@ impl SandboxMemoryLayout { shared_mem.write_u64(self.get_heap_size_offset(), self.heap_size.try_into()?)?; shared_mem.write_u64(self.get_heap_pointer_offset(), addr)?; - // Set up user stack pointers - - // Bottom of user stack - - shared_mem.write_u64( - self.get_min_guest_stack_address_offset(), - get_address!(guest_user_stack_buffer_offset), - )?; - - // Start of user stack - - let start_of_user_stack: u64 = - get_address!(guest_user_stack_buffer_offset) + self.stack_size as u64; - - shared_mem.write_u64(self.get_user_stack_pointer_offset(), start_of_user_stack)?; - // End of setting up the PEB // Initialize the stack pointers of input data and output data @@ -772,34 +628,12 @@ impl SandboxMemoryLayout { } } -fn round_up_to(value: usize, multiple: usize) -> usize { - (value + multiple - 1) & !(multiple - 1) -} - #[cfg(test)] mod tests { use hyperlight_common::mem::PAGE_SIZE_USIZE; use super::*; - #[test] - fn test_round_up() { - assert_eq!(0, round_up_to(0, 4)); - assert_eq!(4, round_up_to(1, 4)); - assert_eq!(4, round_up_to(2, 4)); - assert_eq!(4, round_up_to(3, 4)); - assert_eq!(4, round_up_to(4, 4)); - assert_eq!(8, round_up_to(5, 4)); - assert_eq!(8, round_up_to(6, 4)); - assert_eq!(8, round_up_to(7, 4)); - assert_eq!(8, round_up_to(8, 4)); - assert_eq!(PAGE_SIZE_USIZE, round_up_to(44, PAGE_SIZE_USIZE)); - assert_eq!(PAGE_SIZE_USIZE, round_up_to(4095, PAGE_SIZE_USIZE)); - assert_eq!(PAGE_SIZE_USIZE, round_up_to(4096, PAGE_SIZE_USIZE)); - assert_eq!(PAGE_SIZE_USIZE * 2, round_up_to(4097, PAGE_SIZE_USIZE)); - assert_eq!(PAGE_SIZE_USIZE * 2, round_up_to(8191, PAGE_SIZE_USIZE)); - } - // helper func for testing fn get_expected_memory_size(layout: &SandboxMemoryLayout) -> usize { let cfg = layout.sandbox_memory_config; @@ -807,17 +641,13 @@ mod tests { // in order of layout expected_size += layout.code_size; - expected_size += round_up_to(size_of::(), PAGE_SIZE_USIZE); - - expected_size += round_up_to(cfg.get_input_data_size(), PAGE_SIZE_USIZE); - - expected_size += round_up_to(cfg.get_output_data_size(), PAGE_SIZE_USIZE); + expected_size += size_of::().next_multiple_of(PAGE_SIZE_USIZE); - expected_size += round_up_to(layout.heap_size, PAGE_SIZE_USIZE); + expected_size += cfg.get_input_data_size().next_multiple_of(PAGE_SIZE_USIZE); - expected_size += PAGE_SIZE_USIZE; // guard page + expected_size += cfg.get_output_data_size().next_multiple_of(PAGE_SIZE_USIZE); - expected_size += round_up_to(layout.stack_size, PAGE_SIZE_USIZE); + expected_size += layout.heap_size.next_multiple_of(PAGE_SIZE_USIZE); expected_size } @@ -826,7 +656,7 @@ mod tests { fn test_get_memory_size() { let sbox_cfg = SandboxConfiguration::default(); let sbox_mem_layout = - SandboxMemoryLayout::new(sbox_cfg, 4096, 2048, 4096, 0x3000, 0, None).unwrap(); + SandboxMemoryLayout::new(sbox_cfg, 4096, 4096, 0x3000, 0, None).unwrap(); assert_eq!( sbox_mem_layout.get_memory_size().unwrap(), get_expected_memory_size(&sbox_mem_layout) diff --git a/src/hyperlight_host/src/mem/memory_region.rs b/src/hyperlight_host/src/mem/memory_region.rs index 36a5b3eb3..dbe83f1b0 100644 --- a/src/hyperlight_host/src/mem/memory_region.rs +++ b/src/hyperlight_host/src/mem/memory_region.rs @@ -135,10 +135,6 @@ pub enum MemoryRegionType { /// The region contains the Heap Heap, /// The region contains the Guard Page - GuardPage, - /// The region contains the Stack - Stack, - /// The scratch region Scratch, /// The snapshot region Snapshot, diff --git a/src/hyperlight_host/src/mem/mgr.rs b/src/hyperlight_host/src/mem/mgr.rs index 120589127..314098774 100644 --- a/src/hyperlight_host/src/mem/mgr.rs +++ b/src/hyperlight_host/src/mem/mgr.rs @@ -13,9 +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 = "init-paging")] -use std::cmp::Ordering; - use flatbuffers::FlatBufferBuilder; use hyperlight_common::flatbuffer_wrappers::function_call::{ FunctionCall, validate_guest_function_call_buffer, @@ -33,9 +30,6 @@ use super::shared_mem::{ExclusiveSharedMemory, GuestSharedMemory, HostSharedMemo use crate::sandbox::snapshot::Snapshot; use crate::{Result, new_error}; -/// The size of stack guard cookies -pub(crate) const STACK_COOKIE_LEN: usize = 16; - /// A struct that is responsible for laying out and managing the memory /// for a given `Sandbox`. #[derive(Clone)] @@ -52,8 +46,6 @@ pub(crate) struct SandboxMemoryManager { pub(crate) entrypoint_offset: Option, /// How many memory regions were mapped after sandbox creation pub(crate) mapped_rgns: u64, - /// Stack cookie for stack guard verification - pub(crate) stack_cookie: [u8; STACK_COOKIE_LEN], /// Buffer for accumulating guest abort messages pub(crate) abort_buffer: Vec, } @@ -160,7 +152,6 @@ where scratch_mem: S, load_addr: RawPtr, entrypoint_offset: Option, - stack_cookie: [u8; STACK_COOKIE_LEN], ) -> Self { Self { layout, @@ -169,24 +160,17 @@ where load_addr, entrypoint_offset, mapped_rgns: 0, - stack_cookie, abort_buffer: Vec::new(), } } - /// Get the stack cookie - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_stack_cookie(&self) -> &[u8; STACK_COOKIE_LEN] { - &self.stack_cookie - } - /// Get mutable access to the abort buffer pub(crate) fn get_abort_buffer_mut(&mut self) -> &mut Vec { &mut self.abort_buffer } /// Get `SharedMemory` in `self` as a mutable reference - #[cfg(any(gdb, test))] + #[cfg(test)] pub(crate) fn get_shared_mem_mut(&mut self) -> &mut S { &mut self.shared_mem } @@ -197,6 +181,7 @@ where sandbox_id: u64, mapped_regions: Vec, root_pt_gpa: u64, + rsp_gva: u64, ) -> Result { Snapshot::new( &mut self.shared_mem, @@ -206,6 +191,7 @@ where crate::mem::exe::LoadInfo::dummy(), mapped_regions, root_pt_gpa, + rsp_gva, ) } } @@ -217,7 +203,6 @@ impl SandboxMemoryManager { shared_mem.copy_from_slice(s.memory(), 0)?; let scratch_mem = ExclusiveSharedMemory::new(s.layout().get_scratch_size())?; let load_addr: RawPtr = RawPtr::try_from(layout.get_guest_code_address())?; - let stack_cookie = rand::random::<[u8; STACK_COOKIE_LEN]>(); let entrypoint_gva = s.preinitialise(); let entrypoint_offset = entrypoint_gva.map(|x| (x - u64::from(&load_addr)).into()); Ok(Self::new( @@ -226,7 +211,6 @@ impl SandboxMemoryManager { scratch_mem, load_addr, entrypoint_offset, - stack_cookie, )) } @@ -266,7 +250,6 @@ impl SandboxMemoryManager { load_addr: self.load_addr.clone(), entrypoint_offset: self.entrypoint_offset, mapped_rgns: self.mapped_rgns, - stack_cookie: self.stack_cookie, abort_buffer: self.abort_buffer, }; let guest_mgr = SandboxMemoryManager { @@ -276,7 +259,6 @@ impl SandboxMemoryManager { load_addr: self.load_addr.clone(), entrypoint_offset: self.entrypoint_offset, mapped_rgns: self.mapped_rgns, - stack_cookie: self.stack_cookie, abort_buffer: Vec::new(), // Guest doesn't need abort buffer }; host_mgr.update_scratch_bookkeeping( @@ -287,33 +269,6 @@ impl SandboxMemoryManager { } impl SandboxMemoryManager { - /// Check the stack guard of the memory in `shared_mem`, using - /// `layout` to calculate its location. - /// - /// Return `true` - /// if `shared_mem` could be accessed properly and the guard - /// matches `cookie`. If it could be accessed properly and the - /// guard doesn't match `cookie`, return `false`. Otherwise, return - /// a descriptive error. - /// - /// This method could be an associated function instead. See - /// documentation at the bottom `set_stack_guard` for description - /// of why it isn't. - #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] - #[cfg(feature = "init-paging")] - pub(crate) fn check_stack_guard(&self) -> Result { - let expected = self.stack_cookie; - let offset = self.layout.get_top_of_user_stack_offset(); - let actual: [u8; STACK_COOKIE_LEN] = self.shared_mem.read(offset)?; - let cmp_res = expected.iter().cmp(actual.iter()); - Ok(cmp_res == Ordering::Equal) - } - - #[cfg(not(feature = "init-paging"))] - pub(crate) fn check_stack_guard(&self) -> Result { - Ok(true) - } - /// Get the address of the dispatch function in memory #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn get_pointer_to_dispatch_function(&self) -> Result { diff --git a/src/hyperlight_host/src/sandbox/config.rs b/src/hyperlight_host/src/sandbox/config.rs index 75fc36f40..044c5f2fb 100644 --- a/src/hyperlight_host/src/sandbox/config.rs +++ b/src/hyperlight_host/src/sandbox/config.rs @@ -50,13 +50,6 @@ pub struct SandboxConfiguration { /// The size of the memory buffer that is made available for input to the /// Guest Binary output_data_size: usize, - /// The stack size to use in the guest sandbox. If set to 0, the stack - /// size will be determined from the PE file header. - /// - /// Note: this is a C-compatible struct, so even though this optional - /// field should be represented as an `Option`, that type is not - /// FFI-safe, so it cannot be. - stack_size_override: u64, /// The heap size to use in the guest sandbox. If set to 0, the heap /// size will be determined from the PE file header /// @@ -98,8 +91,6 @@ impl SandboxConfiguration { pub const INTERRUPT_VCPU_SIGRTMIN_OFFSET: u8 = 0; /// The default heap size of a hyperlight sandbox pub const DEFAULT_HEAP_SIZE: u64 = 131072; - /// The default stack size of a hyperlight sandbox - pub const DEFAULT_STACK_SIZE: u64 = 65536; /// The default size of the scratch region pub const DEFAULT_SCRATCH_SIZE: usize = 0x40000; @@ -109,7 +100,6 @@ impl SandboxConfiguration { fn new( input_data_size: usize, output_data_size: usize, - stack_size_override: Option, heap_size_override: Option, scratch_size: usize, interrupt_retry_delay: Duration, @@ -120,7 +110,6 @@ impl SandboxConfiguration { Self { input_data_size: max(input_data_size, Self::MIN_INPUT_SIZE), output_data_size: max(output_data_size, Self::MIN_OUTPUT_SIZE), - stack_size_override: stack_size_override.unwrap_or(0), heap_size_override: heap_size_override.unwrap_or(0), scratch_size, interrupt_retry_delay, @@ -146,12 +135,6 @@ impl SandboxConfiguration { self.output_data_size = max(output_data_size, Self::MIN_OUTPUT_SIZE); } - /// Set the stack size to use in the guest sandbox. If set to 0, the stack size will be determined from the PE file header - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub fn set_stack_size(&mut self, stack_size: u64) { - self.stack_size_override = stack_size; - } - /// Set the heap size to use in the guest sandbox. If set to 0, the heap size will be determined from the PE file header #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub fn set_heap_size(&mut self, heap_size: u64) { @@ -226,6 +209,12 @@ impl SandboxConfiguration { self.scratch_size } + /// Set the size of the scratch regiong + #[instrument(skip_all, parent = Span::current(), level= "Trace")] + pub fn set_scratch_size(&mut self, scratch_size: usize) { + self.scratch_size = scratch_size; + } + #[cfg(crashdump)] #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn get_guest_core_dump(&self) -> bool { @@ -238,24 +227,11 @@ impl SandboxConfiguration { self.guest_debug_info } - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - fn stack_size_override_opt(&self) -> Option { - (self.stack_size_override > 0).then_some(self.stack_size_override) - } - #[instrument(skip_all, parent = Span::current(), level= "Trace")] fn heap_size_override_opt(&self) -> Option { (self.heap_size_override > 0).then_some(self.heap_size_override) } - /// If self.stack_size is non-zero, return it. Otherwise, - /// return exe_info.stack_reserve() - #[instrument(skip_all, parent = Span::current(), level= "Trace")] - pub(crate) fn get_stack_size(&self) -> u64 { - self.stack_size_override_opt() - .unwrap_or(Self::DEFAULT_STACK_SIZE) - } - /// If self.heap_size_override is non-zero, return it. Otherwise, /// return exe_info.heap_reserve() #[instrument(skip_all, parent = Span::current(), level= "Trace")] @@ -272,7 +248,6 @@ impl Default for SandboxConfiguration { Self::DEFAULT_INPUT_SIZE, Self::DEFAULT_OUTPUT_SIZE, None, - None, Self::DEFAULT_SCRATCH_SIZE, Self::DEFAULT_INTERRUPT_RETRY_DELAY, Self::INTERRUPT_VCPU_SIGRTMIN_OFFSET, @@ -290,7 +265,6 @@ mod tests { #[test] fn overrides() { - const STACK_SIZE_OVERRIDE: u64 = 0x10000; const HEAP_SIZE_OVERRIDE: u64 = 0x50000; const INPUT_DATA_SIZE_OVERRIDE: usize = 0x4000; const OUTPUT_DATA_SIZE_OVERRIDE: usize = 0x4001; @@ -298,7 +272,6 @@ mod tests { let mut cfg = SandboxConfiguration::new( INPUT_DATA_SIZE_OVERRIDE, OUTPUT_DATA_SIZE_OVERRIDE, - Some(STACK_SIZE_OVERRIDE), Some(HEAP_SIZE_OVERRIDE), SCRATCH_SIZE_OVERRIDE, SandboxConfiguration::DEFAULT_INTERRUPT_RETRY_DELAY, @@ -309,17 +282,13 @@ mod tests { true, ); - let stack_size = cfg.get_stack_size(); let heap_size = cfg.get_heap_size(); let scratch_size = cfg.get_scratch_size(); - assert_eq!(STACK_SIZE_OVERRIDE, stack_size); assert_eq!(HEAP_SIZE_OVERRIDE, heap_size); assert_eq!(SCRATCH_SIZE_OVERRIDE, scratch_size); - cfg.stack_size_override = 1024; cfg.heap_size_override = 2048; cfg.scratch_size = 0x40000; - assert_eq!(1024, cfg.stack_size_override); assert_eq!(2048, cfg.heap_size_override); assert_eq!(0x40000, cfg.scratch_size); assert_eq!(INPUT_DATA_SIZE_OVERRIDE, cfg.input_data_size); @@ -332,7 +301,6 @@ mod tests { SandboxConfiguration::MIN_INPUT_SIZE - 1, SandboxConfiguration::MIN_OUTPUT_SIZE - 1, None, - None, SandboxConfiguration::DEFAULT_SCRATCH_SIZE, SandboxConfiguration::DEFAULT_INTERRUPT_RETRY_DELAY, SandboxConfiguration::INTERRUPT_VCPU_SIGRTMIN_OFFSET, @@ -343,7 +311,6 @@ mod tests { ); assert_eq!(SandboxConfiguration::MIN_INPUT_SIZE, cfg.input_data_size); assert_eq!(SandboxConfiguration::MIN_OUTPUT_SIZE, cfg.output_data_size); - assert_eq!(0, cfg.stack_size_override); assert_eq!(0, cfg.heap_size_override); cfg.set_input_data_size(SandboxConfiguration::MIN_INPUT_SIZE - 1); @@ -376,13 +343,6 @@ mod tests { } - #[test] - fn stack_size_override(size in 0x1000..=0x10000u64) { - let mut cfg = SandboxConfiguration::default(); - cfg.set_stack_size(size); - prop_assert_eq!(size, cfg.stack_size_override); - } - #[test] fn heap_size_override(size in 0x1000..=0x10000u64) { let mut cfg = SandboxConfiguration::default(); diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 9d82b22d7..08f3eef06 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -28,7 +28,6 @@ use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, Functi use hyperlight_common::flatbuffer_wrappers::function_types::{ ParameterValue, ReturnType, ReturnValue, }; -use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::flatbuffer_wrappers::util::estimate_flatbuffer_capacity; use tracing::{Span, instrument}; @@ -174,9 +173,10 @@ impl MultiUseSandbox { .vm .get_root_pt() .map_err(|e| HyperlightError::HyperlightVmError(e.into()))?; - let memory_snapshot = self - .mem_mgr - .snapshot(self.id, mapped_regions_vec, root_pt_gpa)?; + let stack_top_gpa = self.vm.get_stack_top(); + let memory_snapshot = + self.mem_mgr + .snapshot(self.id, mapped_regions_vec, root_pt_gpa, stack_top_gpa)?; let snapshot = Arc::new(memory_snapshot); self.snapshot = Some(snapshot.clone()); Ok(snapshot) @@ -300,6 +300,7 @@ impl MultiUseSandbox { self.vm .set_root_pt(snapshot.root_pt_gpa()) .map_err(|e| HyperlightError::HyperlightVmError(e.into()))?; + self.vm.set_stack_top(snapshot.stack_top_gva()); let current_regions: HashSet<_> = self.vm.get_mapped_regions().cloned().collect(); let snapshot_regions: HashSet<_> = snapshot.regions().iter().cloned().collect(); @@ -649,8 +650,6 @@ impl MultiUseSandbox { return Err(error); } - self.mem_mgr.check_stack_guard()?; - let guest_result = self.mem_mgr.get_guest_function_call_result()?.into_inner(); match guest_result { @@ -662,10 +661,10 @@ impl MultiUseSandbox { ) .increment(1); - Err(match guest_error.code { - ErrorCode::StackOverflow => HyperlightError::StackOverflow(), - _ => HyperlightError::GuestError(guest_error.code, guest_error.message), - }) + Err(HyperlightError::GuestError( + guest_error.code, + guest_error.message, + )) } } })(); @@ -817,9 +816,7 @@ impl Callable for MultiUseSandbox { impl std::fmt::Debug for MultiUseSandbox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MultiUseSandbox") - .field("stack_guard", &self.mem_mgr.get_stack_cookie()) - .finish() + f.debug_struct("MultiUseSandbox").finish() } } @@ -1021,7 +1018,10 @@ mod tests { fn test_with_small_stack_and_heap() { let mut cfg = SandboxConfiguration::default(); cfg.set_heap_size(20 * 1024); - cfg.set_stack_size(18 * 1024); + // min_scratch_size already includes 1 page (4k on most + // platforms) of guest stack, so add 20k more to get 24k total + let min_scratch = hyperlight_common::layout::min_scratch_size(); + cfg.set_scratch_size(min_scratch + 0x5000); let mut sbox1: MultiUseSandbox = { let path = simple_guest_as_string().unwrap(); diff --git a/src/hyperlight_host/src/sandbox/outb.rs b/src/hyperlight_host/src/sandbox/outb.rs index 87c40c744..688f37a8d 100644 --- a/src/hyperlight_host/src/sandbox/outb.rs +++ b/src/hyperlight_host/src/sandbox/outb.rs @@ -35,8 +35,6 @@ use crate::sandbox::trace::MemTraceInfo; /// Errors that can occur when handling an outb operation from the guest. #[derive(Debug, thiserror::Error)] pub enum HandleOutbError { - #[error("Stack overflow detected (signaled by guest)")] - StackOverflow, #[error("Guest aborted: error code {code}, message: {message}")] GuestAborted { /// The error code from the guest @@ -145,28 +143,24 @@ fn outb_abort( for &b in &bytes[1..=len as usize] { if b == ABORT_TERMINATOR { let guest_error_code = *buffer.first().unwrap_or(&0); - let guest_error = ErrorCode::from(guest_error_code as u64); - - let result = match guest_error { - ErrorCode::StackOverflow => Err(HandleOutbError::StackOverflow), - _ => { - let message = if let Some(&maybe_exception_code) = buffer.get(1) { - match Exception::try_from(maybe_exception_code) { - Ok(exception) => { - let extra_msg = String::from_utf8_lossy(&buffer[2..]); - format!("Exception: {:?} | {}", exception, extra_msg) - } - Err(_) => String::from_utf8_lossy(&buffer[1..]).into(), - } - } else { - String::new() - }; - Err(HandleOutbError::GuestAborted { - code: guest_error_code, - message, - }) - } + let result = { + let message = if let Some(&maybe_exception_code) = buffer.get(1) { + match Exception::try_from(maybe_exception_code) { + Ok(exception) => { + let extra_msg = String::from_utf8_lossy(&buffer[2..]); + format!("Exception: {:?} | {}", exception, extra_msg) + } + Err(_) => String::from_utf8_lossy(&buffer[1..]).into(), + } + } else { + String::new() + }; + + Err(HandleOutbError::GuestAborted { + code: guest_error_code, + message, + }) }; buffer.clear(); diff --git a/src/hyperlight_host/src/sandbox/snapshot.rs b/src/hyperlight_host/src/sandbox/snapshot.rs index 185e4274b..b1b252428 100644 --- a/src/hyperlight_host/src/sandbox/snapshot.rs +++ b/src/hyperlight_host/src/sandbox/snapshot.rs @@ -67,6 +67,8 @@ pub struct Snapshot { hash: [u8; 32], /// The address of the root page table root_pt_gpa: u64, + /// The address of the top of the guest stack + stack_top_gva: u64, /// Preinitialisation entry point for snapshots created directly from a /// guest binary. @@ -142,11 +144,11 @@ fn hash(memory: &[u8], regions: &[MemoryRegion]) -> Result<[u8; 32]> { format!("{:?}", rgn), )); } - hasher.update(&usize::to_le_bytes(guest_len)); - hasher.update(&u32::to_le_bytes(rgn.flags.bits())); // Ignore [`MemoryRegion::region_type`], since it is extra // information for debugging rather than a core part of the // identity of the snapshot/workload. + hasher.update(&usize::to_le_bytes(guest_len)); + hasher.update(&u32::to_le_bytes(rgn.flags.bits())); } // Ignore [`load_info`], since it is extra information for // debugging rather than a core part of the identity of the @@ -348,7 +350,6 @@ impl Snapshot { let mut layout = crate::mem::layout::SandboxMemoryLayout::new( cfg, exe_info.loaded_size(), - usize::try_from(cfg.get_stack_size())?, usize::try_from(cfg.get_heap_size())?, cfg.get_scratch_size(), guest_blob_size, @@ -413,6 +414,10 @@ impl Snapshot { #[cfg(not(feature = "init-paging"))] let pt_base_gpa = 0usize; + let exn_stack_top_gva = hyperlight_common::layout::MAX_GVA as u64 + - hyperlight_common::layout::SCRATCH_TOP_EXN_STACK_OFFSET + + 1; + let extra_regions = Vec::new(); let hash = hash(&memory, &extra_regions)?; @@ -424,10 +429,17 @@ impl Snapshot { load_info, hash, root_pt_gpa: pt_base_gpa as u64, + stack_top_gva: exn_stack_top_gva, preinitialise: Some(load_addr + entrypoint_offset), }) } + // It might be nice to consider moving at least stack_top_gva into + // layout, and sharing (via RwLock or similar) the layout between + // the (host-side) mem mgr (where it can be passed in here) and + // the sandbox vm itself (which modifies it as it receives + // requests from the sandbox). + #[allow(clippy::too_many_arguments)] /// Take a snapshot of the memory in `shared_mem`, then create a new /// instance of `Self` with the snapshot stored therein. #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")] @@ -439,6 +451,7 @@ impl Snapshot { load_info: LoadInfo, regions: Vec, root_pt_gpa: u64, + stack_top_gva: u64, ) -> Result { let (new_root_pt_gpa, memory) = shared_mem.with_exclusivity(|snap_e| { scratch_mem.with_exclusivity(|scratch_e| { @@ -488,6 +501,7 @@ impl Snapshot { load_info, hash, root_pt_gpa: new_root_pt_gpa as u64, + stack_top_gva, preinitialise: None, }) } @@ -527,6 +541,10 @@ impl Snapshot { self.root_pt_gpa } + pub(crate) fn stack_top_gva(&self) -> u64 { + self.stack_top_gva + } + pub(crate) fn preinitialise(&self) -> Option { self.preinitialise } @@ -570,12 +588,11 @@ mod tests { snapshot_mem.copy_from_slice(&pt_bytes, PAGE_SIZE).unwrap(); let cfg = crate::sandbox::SandboxConfiguration::default(); let mgr = SandboxMemoryManager::new( - SandboxMemoryLayout::new(cfg, 4096, 2048, 4096, 0x3000, 0, None).unwrap(), + SandboxMemoryLayout::new(cfg, 4096, 2048, 4096, 0x3000, None).unwrap(), snapshot_mem, scratch_mem, 0.into(), None, - [0u8; 16], ); let (mgr, _) = mgr.build().unwrap(); (mgr, pt_base as u64) @@ -599,6 +616,7 @@ mod tests { LoadInfo::dummy(), Vec::new(), pt_base, + 0, ) .unwrap(); @@ -628,6 +646,7 @@ mod tests { LoadInfo::dummy(), Vec::new(), pt_base, + 0, ) .unwrap(); assert_eq!(snapshot.mem_size(), size); @@ -648,6 +667,7 @@ mod tests { LoadInfo::dummy(), Vec::new(), pt_base, + 0, ) .unwrap(); @@ -662,6 +682,7 @@ mod tests { LoadInfo::dummy(), Vec::new(), pt_base, + 0, ) .unwrap(); diff --git a/src/hyperlight_host/src/sandbox/uninitialized.rs b/src/hyperlight_host/src/sandbox/uninitialized.rs index ea40a7f43..4e13fa0d6 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized.rs @@ -67,6 +67,9 @@ pub struct UninitializedSandbox { #[cfg(any(crashdump, gdb))] pub(crate) rt_cfg: SandboxRuntimeConfig, pub(crate) load_info: crate::mem::exe::LoadInfo, + // This is needed to convey the stack pointer between the snapshot + // and the HyperlightVm creation + pub(crate) stack_top_gva: u64, } impl Debug for UninitializedSandbox { @@ -211,6 +214,7 @@ impl UninitializedSandbox { #[cfg(any(crashdump, gdb))] rt_cfg, load_info: snapshot.load_info(), + stack_top_gva: snapshot.stack_top_gva(), }; // If we were passed a writer for host print register it otherwise use the default. @@ -368,7 +372,6 @@ mod tests { let mut cfg = SandboxConfiguration::default(); cfg.set_input_data_size(0x1000); cfg.set_output_data_size(0x1000); - cfg.set_stack_size(0x1000); cfg.set_heap_size(0x1000); Some(cfg) }; @@ -1088,10 +1091,10 @@ mod tests { let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); } - // Test 3: Create snapshot with custom stack size + // Test 3: Create snapshot with custom scratch size { let mut cfg = SandboxConfiguration::default(); - cfg.set_stack_size(128 * 1024); // 128KB stack + cfg.set_scratch_size(128 * 1024); // 128KB scratch let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None); @@ -1139,7 +1142,7 @@ mod tests { { let mut cfg = SandboxConfiguration::default(); cfg.set_heap_size(32 * 1024 * 1024); // 32MB heap - cfg.set_stack_size(256 * 1024); // 256KB stack + cfg.set_scratch_size(256 * 1024); // 256KB scratch cfg.set_input_data_size(128 * 1024); // 128KB input cfg.set_output_data_size(128 * 1024); // 128KB output @@ -1258,5 +1261,35 @@ mod tests { let _evolved: MultiUseSandbox = sandbox.evolve().expect("Failed to evolve sandbox"); } + + // Test 9: Create snapshot from existing sandbox + { + let env = GuestEnvironment::new(GuestBinary::FilePath(binary_path.clone()), None); + let orig_snapshot = Arc::new( + Snapshot::from_env(env, Default::default()) + .expect("Failed to create snapshot with default config"), + ); + let orig_sandbox = UninitializedSandbox::from_snapshot( + orig_snapshot, + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create orig_sandbox"); + let mut initialized_sandbox = orig_sandbox + .evolve() + .expect("Failed to evolve orig_sandbox"); + let new_snapshot = initialized_sandbox + .snapshot() + .expect("Failed to create new_snapshot"); + let new_sandbox = UninitializedSandbox::from_snapshot( + new_snapshot, + None, + #[cfg(crashdump)] + Some(binary_path.clone()), + ) + .expect("Failed to create new_sandbox"); + let _evolved = new_sandbox.evolve().expect("Failed to evolve new_sandbox"); + } } } diff --git a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs index 17395daa7..0dff0b060 100644 --- a/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs +++ b/src/hyperlight_host/src/sandbox/uninitialized_evolve.rs @@ -43,6 +43,7 @@ pub(super) fn evolve_impl_multi_use(u_sbox: UninitializedSandbox) -> Result Result, #[cfg_attr(target_os = "windows", allow(unused_variables))] config: &SandboxConfiguration, + stack_top_gva: u64, #[cfg(any(crashdump, gdb))] rt_cfg: &SandboxRuntimeConfig, _load_info: LoadInfo, ) -> Result { let base_ptr = GuestPtr::try_from(Offset::from(0))?; - #[cfg(feature = "init-paging")] - let rsp_ptr = { - let rsp_offset_u64 = mgr.layout.get_rsp_offset() as u64; - base_ptr + Offset::from(rsp_offset_u64) - }; - - #[cfg(not(feature = "init-paging"))] - let rsp_ptr = GuestPtr::try_from(Offset::from(0))?; let pml4_ptr = { let pml4_offset_u64 = mgr.layout.get_pt_offset() as u64; @@ -119,11 +113,11 @@ pub(crate) fn set_up_hypervisor_partition( }; let entrypoint_ptr = mgr .entrypoint_offset - .ok_or_else(|| new_error!("Entrypoint offset is None")) - .and_then(|x| { + .map(|x| { let entrypoint_total_offset = mgr.load_addr.clone() + x; - GuestPtr::try_from(entrypoint_total_offset) - })?; + GuestPtr::try_from(entrypoint_total_offset).and_then(|x| x.absolute()) + }) + .transpose()?; // Create gdb thread if gdb is enabled and the configuration is provided #[cfg(gdb)] @@ -153,8 +147,8 @@ pub(crate) fn set_up_hypervisor_partition( mgr.shared_mem, mgr.scratch_mem, pml4_ptr.absolute()?, - entrypoint_ptr.absolute()?, - rsp_ptr.absolute()?, + entrypoint_ptr, + stack_top_gva, config, #[cfg(gdb)] gdb_conn, diff --git a/src/hyperlight_host/tests/integration_test.rs b/src/hyperlight_host/tests/integration_test.rs index 713509a83..59fa7c3bb 100644 --- a/src/hyperlight_host/tests/integration_test.rs +++ b/src/hyperlight_host/tests/integration_test.rs @@ -20,7 +20,6 @@ use std::thread; use std::time::Duration; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use hyperlight_common::mem::PAGE_SIZE; use hyperlight_host::sandbox::SandboxConfiguration; use hyperlight_host::{GuestBinary, HyperlightError, MultiUseSandbox, UninitializedSandbox}; use hyperlight_testing::simplelogger::{LOGGER, SimpleLogger}; @@ -593,10 +592,6 @@ fn guest_panic_no_alloc() { ) .unwrap_err(); - if let HyperlightError::StackOverflow() = res { - panic!("panic on OOM caused stack overflow, this implies allocation in panic handler"); - } - assert!(matches!( res, HyperlightError::GuestAborted(code, msg) if code == ErrorCode::UnknownError as u8 && msg.contains("memory allocation of ") && msg.contains("bytes failed") @@ -605,7 +600,6 @@ fn guest_panic_no_alloc() { // Tests libc alloca #[test] -#[ignore] // this test will be re-enabled in the next PR fn dynamic_stack_allocate_c_guest() { let path = c_simple_guest_as_string().unwrap(); let guest_path = GuestBinary::FilePath(path); @@ -618,7 +612,9 @@ fn dynamic_stack_allocate_c_guest() { let res = sbox1 .call::("StackAllocate", 0x800_0000_i32) .unwrap_err(); - assert!(matches!(res, HyperlightError::StackOverflow())); + assert!( + matches!(res, HyperlightError::GuestAborted(code, _) if code == ErrorCode::MallocFailed as u8) + ); } // checks that a small buffer on stack works @@ -632,16 +628,16 @@ fn static_stack_allocate() { // checks that a huge buffer on stack fails with stackoverflow #[test] -#[ignore] // this test will be re-enabled in the next PR fn static_stack_allocate_overflow() { let mut sbox1 = new_uninit().unwrap().evolve().unwrap(); let res = sbox1.call::("LargeVar", ()).unwrap_err(); - assert!(matches!(res, HyperlightError::StackOverflow())); + assert!( + matches!(res, HyperlightError::GuestAborted(code, _) if code == ErrorCode::MallocFailed as u8) + ); } // checks that a recursive function with stack allocation works, (that chkstk can be called without overflowing) #[test] -#[ignore] // this test will be re-enabled in the next PR fn recursive_stack_allocate() { let mut sbox1 = new_uninit().unwrap().evolve().unwrap(); @@ -650,62 +646,14 @@ fn recursive_stack_allocate() { sbox1.call::("StackOverflow", iterations).unwrap(); } -// checks stack guard page (between guest stack and heap) -// is properly set up and cannot be written to -#[test] -#[ignore] // this test will be re-enabled in the next PR -fn guard_page_check() { - // this test is rust-guest only - let offsets_from_page_guard_start: Vec = vec![ - -1024, - -1, - 0, // should fail - 1, // should fail - 1024, // should fail - PAGE_SIZE as i64 - 1, // should fail - PAGE_SIZE as i64, - PAGE_SIZE as i64 + 1024, - ]; - - let guard_range = 0..PAGE_SIZE as i64; - - for offset in offsets_from_page_guard_start { - // we have to create a sandbox each iteration because can't reuse after MMIO error in release mode - - let mut sbox1 = new_uninit_rust().unwrap().evolve().unwrap(); - let result = sbox1.call::("test_write_raw_ptr", offset); - if guard_range.contains(&offset) { - // should have failed - assert!(matches!( - result.unwrap_err(), - HyperlightError::StackOverflow() - )); - } else { - assert!(result.is_ok(), "offset {} should pass", offset) - } - } -} - #[test] -#[ignore] // this test will be re-enabled in the next PR fn guard_page_check_2() { // this test is rust-guest only let mut sbox1 = new_uninit_rust().unwrap().evolve().unwrap(); - let result = sbox1.call::<()>("InfiniteRecursion", ()).unwrap_err(); - assert!(matches!(result, HyperlightError::StackOverflow())); -} - -#[test] -fn execute_on_stack() { - let mut sbox1 = new_uninit().unwrap().evolve().unwrap(); - - let result = sbox1.call::("ExecuteOnStack", ()).unwrap_err(); - - let err = result.to_string(); + let res = sbox1.call::<()>("InfiniteRecursion", ()).unwrap_err(); assert!( - // exception that indicates a page fault - err.contains("PageFault") + matches!(res, HyperlightError::GuestAborted(code, _) if code == ErrorCode::MallocFailed as u8) ); } @@ -730,15 +678,15 @@ fn execute_on_heap() { // checks that a recursive function with stack allocation eventually fails with stackoverflow #[test] -#[ignore] // this test will be re-enabled in the next PR fn recursive_stack_allocate_overflow() { let mut sbox1 = new_uninit().unwrap().evolve().unwrap(); - let iterations = 10_i32; + let iterations = 32_i32; let res = sbox1.call::<()>("StackOverflow", iterations).unwrap_err(); - println!("{:?}", res); - assert!(matches!(res, HyperlightError::StackOverflow())); + assert!( + matches!(res, HyperlightError::GuestAborted(code, _) if code == ErrorCode::MallocFailed as u8) + ); } // Check that log messages are emitted correctly from the guest @@ -753,14 +701,14 @@ fn log_message() { // follows: // - internal_dispatch_function does a log::trace! in debug mode // - logs from trace level tracing spans created as logs because of the tracing `log` feature - // - 6 from evolve call (hyperlight_main + halt) + // - 4 from evolve call (hyperlight_main + halt) // - 16 from guest call (internal_dispatch_function + others) // and are multiplied because we make 6 calls to `log_test_messages` // NOTE: These numbers need to be updated if log messages or spans are added/removed let num_fixed_trace_log = if cfg!(debug_assertions) { - (1 + 22) * 6 + (1 + 20) * 6 } else { - 22 * 6 + 20 * 6 }; let tests = vec![ diff --git a/src/schema/guest_error.fbs b/src/schema/guest_error.fbs index 14dfa85e4..551dea764 100644 --- a/src/schema/guest_error.fbs +++ b/src/schema/guest_error.fbs @@ -9,7 +9,6 @@ enum ErrorCode: ulong { GispatchFunctionPointerNotSet = 6, // Host Call Dispatch Function Pointer is not present. OutbError = 7, // Error in OutB Function UnknownError = 8, // The guest error is unknown. - StackOverflow = 9, // Guest stack allocations caused stack overflow GsCheckFailed = 10, // __security_check_cookie failed TooManyGuestFunctions = 11, // The guest tried to register too many guest functions FailureInDlmalloc = 12, // this error is set when dlmalloc calls ABORT (e.g. function defined in ABORT (dlmalloc_abort() calls setError with this errorcode) diff --git a/src/tests/c_guests/c_simpleguest/main.c b/src/tests/c_guests/c_simpleguest/main.c index 30caff590..773195b17 100644 --- a/src/tests/c_guests/c_simpleguest/main.c +++ b/src/tests/c_guests/c_simpleguest/main.c @@ -8,7 +8,7 @@ // Included from hyperlight_guest_bin/third_party/printf #include "printf.h" -#define GUEST_STACK_SIZE (65536) // default stack size +#define GUEST_SCRATCH_SIZE (0x40000) // default scratch size #define MAX_BUFFER_SIZE (1024) static char big_array[1024 * 1024] = {0}; @@ -74,10 +74,10 @@ int buffer_overrun(const char *String) { __attribute__((optnone)) int large_var(void) { - char buffer[GUEST_STACK_SIZE + 1] = {0}; + char buffer[GUEST_SCRATCH_SIZE + 1] = {0}; (void)buffer; - return GUEST_STACK_SIZE; + return GUEST_SCRATCH_SIZE; } int small_var(void) { @@ -416,4 +416,4 @@ hl_Vec *c_guest_dispatch_function(const hl_FunctionCall *function_call) { } return NULL; -} \ No newline at end of file +} diff --git a/src/tests/rust_guests/simpleguest/src/main.rs b/src/tests/rust_guests/simpleguest/src/main.rs index cb9e99848..2434836e4 100644 --- a/src/tests/rust_guests/simpleguest/src/main.rs +++ b/src/tests/rust_guests/simpleguest/src/main.rs @@ -16,7 +16,7 @@ limitations under the License. #![no_std] #![no_main] -const DEFAULT_GUEST_STACK_SIZE: i32 = 65536; // default stack size +const DEFAULT_GUEST_SCRATCH_SIZE: i32 = 0x40000; // default scratch size const MAX_BUFFER_SIZE: usize = 1024; // ^^^ arbitrary value for max buffer size // to support allocations when we'd get a @@ -32,7 +32,6 @@ use alloc::{format, vec}; use core::alloc::Layout; use core::ffi::c_char; use core::hint::black_box; -use core::ptr::write_volatile; use core::sync::atomic::{AtomicU64, Ordering}; use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType}; @@ -42,10 +41,9 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; use hyperlight_common::flatbuffer_wrappers::guest_log_level::LogLevel; use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result; -use hyperlight_common::mem::PAGE_SIZE; use hyperlight_guest::error::{HyperlightGuestError, Result}; use hyperlight_guest::exit::{abort_with_code, abort_with_code_and_message}; -use hyperlight_guest_bin::exceptions::handler::{Context, ExceptionInfo}; +use hyperlight_guest_bin::exception::arch::{Context, ExceptionInfo}; use hyperlight_guest_bin::guest_function::definition::GuestFunctionDefinition; use hyperlight_guest_bin::guest_function::register::register_function; use hyperlight_guest_bin::host_comm::{ @@ -53,7 +51,7 @@ use hyperlight_guest_bin::host_comm::{ print_output_with_host_print, read_n_bytes_from_user_memory, }; use hyperlight_guest_bin::memory::malloc; -use hyperlight_guest_bin::{MIN_STACK_ADDRESS, guest_function, guest_logger, host_function}; +use hyperlight_guest_bin::{guest_function, guest_logger, host_function}; use log::{LevelFilter, error}; use tracing::{Span, instrument}; @@ -132,7 +130,7 @@ fn test_exception_handler( /// Install handler for a specific vector #[guest_function("InstallHandler")] fn install_handler(vector: i32) { - hyperlight_guest_bin::exceptions::handler::HANDLERS[vector as usize] + hyperlight_guest_bin::exception::arch::HANDLERS[vector as usize] .store(test_exception_handler as usize as u64, Ordering::Release); } @@ -318,8 +316,8 @@ fn loop_stack_overflow(i: i32) { #[guest_function("LargeVar")] fn large_var() -> i32 { - let _buffer = black_box([0u8; (DEFAULT_GUEST_STACK_SIZE + 1) as usize]); - DEFAULT_GUEST_STACK_SIZE + 1 + let _buffer = black_box([0u8; DEFAULT_GUEST_SCRATCH_SIZE as usize]); + DEFAULT_GUEST_SCRATCH_SIZE } #[guest_function("SmallVar")] @@ -368,11 +366,7 @@ fn exhaust_heap() { #[guest_function("MallocAndFree")] fn malloc_and_free(size: i32) -> i32 { - let alloc_length = if size < DEFAULT_GUEST_STACK_SIZE { - size - } else { - size.min(MAX_BUFFER_SIZE as i32) - }; + let alloc_length = size.min(MAX_BUFFER_SIZE as i32); let allocated_buffer = vec![0; alloc_length as usize]; drop(allocated_buffer); @@ -447,38 +441,6 @@ fn test_guest_panic(message: String) { panic!("{}", message); } -#[guest_function] -fn test_write_raw_ptr(offset: i64) -> String { - let min_stack_addr = unsafe { MIN_STACK_ADDRESS }; - let page_guard_start = min_stack_addr - PAGE_SIZE; - let addr = { - let abs = u64::try_from(offset.abs()) - .map_err(|_| error!("Invalid offset")) - .unwrap(); - if offset.is_negative() { - page_guard_start - abs - } else { - page_guard_start + abs - } - }; - unsafe { - // host_print(format!("writing to {:#x}\n", addr).as_str()); - write_volatile(addr as *mut u8, 0u8); - } - String::from("success") -} - -#[guest_function("ExecuteOnStack")] -fn execute_on_stack() -> String { - unsafe { - let mut noop: u8 = 0x90; - let stack_fn: fn() = core::mem::transmute(&mut noop as *mut u8); - stack_fn(); - }; - // will only reach this point if stack is executable - String::from("fail") -} - #[guest_function("ExecuteOnHeap")] fn execute_on_heap() -> String { unsafe { diff --git a/typos.toml b/typos.toml index 62149dd4b..c04a7f2c8 100644 --- a/typos.toml +++ b/typos.toml @@ -11,3 +11,5 @@ mmaped="mmapped" fpr="fpr" # consts is a module name in stdlib consts="consts" +# ist is an acronym for Interrupt Stack Table, not a missspelling of its +ist="ist"