Skip to main content

hyperlight_host/
error.rs

1/*
2Copyright 2025  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use std::array::TryFromSliceError;
18use std::cell::{BorrowError, BorrowMutError};
19use std::convert::Infallible;
20use std::error::Error;
21use std::num::TryFromIntError;
22use std::string::FromUtf8Error;
23use std::sync::{MutexGuard, PoisonError};
24use std::time::SystemTimeError;
25
26#[cfg(target_os = "windows")]
27use crossbeam_channel::{RecvError, SendError};
28use flatbuffers::InvalidFlatbuffer;
29use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue};
30use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
31use thiserror::Error;
32
33use crate::hypervisor::hyperlight_vm::HyperlightVmError;
34#[cfg(target_os = "windows")]
35use crate::hypervisor::wrappers::HandleWrapper;
36use crate::mem::memory_region::MemoryRegionFlags;
37use crate::mem::ptr::RawPtr;
38
39/// The error type for Hyperlight operations
40#[derive(Error, Debug)]
41pub enum HyperlightError {
42    /// Anyhow error
43    #[error("Anyhow Error was returned: {0}")]
44    AnyhowError(#[from] anyhow::Error),
45    /// Memory access out of bounds
46    #[error("Offset: {0} out of bounds, Max is: {1}")]
47    BoundsCheckFailed(u64, usize),
48
49    /// Checked Add Overflow
50    #[error("Couldn't add offset to base address. Offset: {0}, Base Address: {1}")]
51    CheckedAddOverflow(u64, u64),
52
53    /// Cross beam channel receive error
54    #[error("{0:?}")]
55    #[cfg(target_os = "windows")]
56    CrossBeamReceiveError(#[from] RecvError),
57
58    /// Cross beam channel send error
59    #[error("{0:?}")]
60    #[cfg(target_os = "windows")]
61    CrossBeamSendError(#[from] SendError<HandleWrapper>),
62
63    /// CString conversion error
64    #[error("Error converting CString {0:?}")]
65    CStringConversionError(#[from] std::ffi::NulError),
66
67    /// A generic error with a message
68    #[error("{0}")]
69    Error(String),
70
71    /// Execution violation
72    #[error("Non-executable address {0:#x} tried to be executed")]
73    ExecutionAccessViolation(u64),
74
75    /// Guest execution was cancelled by the host
76    #[error("Execution was cancelled by the host.")]
77    ExecutionCanceledByHost(),
78
79    /// Accessing the value of a flatbuffer parameter failed
80    #[error("Failed to get a value from flat buffer parameter")]
81    FailedToGetValueFromParameter(),
82
83    ///Field Name not found in decoded GuestLogData
84    #[error("Field Name {0} not found in decoded GuestLogData")]
85    FieldIsMissingInGuestLogData(String),
86
87    /// Guest aborted during outb
88    #[error("Guest aborted: {0} {1}")]
89    GuestAborted(u8, String),
90
91    /// Guest call resulted in error in guest
92    #[error("Guest error occurred {0:?}: {1}")]
93    GuestError(ErrorCode, String),
94
95    /// An attempt to cancel guest execution failed because it is hanging on a host function call
96    #[error("Guest execution hung on the execution of a host function call")]
97    GuestExecutionHungOnHostFunctionCall(),
98
99    /// Guest call already in progress
100    #[error("Guest call is already in progress")]
101    GuestFunctionCallAlreadyInProgress(),
102
103    /// The given type is not supported by the guest interface.
104    #[error("Unsupported type: {0}")]
105    GuestInterfaceUnsupportedType(String),
106
107    /// The guest offset is invalid.
108    #[error("The guest offset {0} is invalid.")]
109    GuestOffsetIsInvalid(usize),
110
111    /// The guest binary was built with a different hyperlight-guest-bin version than the host expects.
112    /// Hyperlight currently provides no backwards compatibility guarantees for guest binaries,
113    /// so the guest and host versions must match exactly. This might change in the future.
114    #[error(
115        "Guest binary was built with hyperlight-guest-bin {guest_bin_version}, \
116         but the host is running hyperlight {host_version}"
117    )]
118    GuestBinVersionMismatch {
119        /// Version of hyperlight-guest-bin the guest was compiled against.
120        guest_bin_version: String,
121        /// Version of hyperlight-host.
122        host_version: String,
123    },
124
125    /// A Host function was called by the guest but it was not registered.
126    #[error("HostFunction {0} was not found")]
127    HostFunctionNotFound(String),
128
129    /// Hyperlight VM error.
130    ///
131    /// **Note:** This error variant is considered internal and its structure is not stable.
132    /// It may change between versions without notice. Users should not rely on this.
133    #[doc(hidden)]
134    #[error("Internal Hyperlight VM error: {0}")]
135    HyperlightVmError(#[from] HyperlightVmError),
136
137    /// Reading Writing or Seeking data failed.
138    #[error("Reading Writing or Seeking data failed {0:?}")]
139    IOError(#[from] std::io::Error),
140
141    /// Failed to convert to Integer
142    #[error("Failed To Convert Size to usize")]
143    IntConversionFailure(#[from] TryFromIntError),
144
145    /// The flatbuffer is invalid
146    #[error("The flatbuffer is invalid")]
147    InvalidFlatBuffer(#[from] InvalidFlatbuffer),
148
149    /// Conversion of str to Json failed
150    #[error("Conversion of str data to json failed")]
151    JsonConversionFailure(#[from] serde_json::Error),
152
153    /// An attempt to get a lock from a Mutex failed.
154    #[error("Unable to lock resource")]
155    LockAttemptFailed(String),
156
157    /// Memory Access Violation at the given address. The access type and memory region flags are provided.
158    #[error("Memory Access Violation at address {0:#x} of type {1}, but memory is marked as {2}")]
159    MemoryAccessViolation(u64, MemoryRegionFlags, MemoryRegionFlags),
160
161    /// Memory Allocation Failed.
162    #[error("Memory Allocation Failed with OS Error {0:?}.")]
163    MemoryAllocationFailed(Option<i32>),
164
165    /// Memory Protection Failed
166    #[error("Memory Protection Failed with OS Error {0:?}.")]
167    MemoryProtectionFailed(Option<i32>),
168
169    /// Memory region size mismatch
170    #[error("Memory region size mismatch: host size {0:?}, guest size {1:?} region {2:?}")]
171    MemoryRegionSizeMismatch(usize, usize, String),
172
173    /// The memory request exceeds the maximum size allowed
174    #[error("Memory requested {0} exceeds maximum size allowed {1}")]
175    MemoryRequestTooBig(usize, usize),
176
177    /// The memory request is too small to contain everything that is
178    /// required
179    #[error("Memory requested {0} is less than the minimum size allowed {1}")]
180    MemoryRequestTooSmall(usize, usize),
181
182    /// Metric Not Found.
183    #[error("Metric Not Found {0:?}.")]
184    MetricNotFound(&'static str),
185
186    /// mmap Failed.
187    #[error("mmap failed with os error {0:?}")]
188    MmapFailed(Option<i32>),
189
190    /// mprotect Failed.
191    #[error("mprotect failed with os error {0:?}")]
192    MprotectFailed(Option<i32>),
193
194    /// No Hypervisor was found for Sandbox.
195    #[error("No Hypervisor was found for Sandbox")]
196    NoHypervisorFound(),
197
198    /// Restore_state called with no valid snapshot
199    #[error("Restore_state called with no valid snapshot")]
200    NoMemorySnapshot,
201
202    /// Failed to get value from parameter value
203    #[error("Failed To Convert Parameter Value {0:?} to {1:?}")]
204    ParameterValueConversionFailure(ParameterValue, &'static str),
205
206    /// a failure occurred processing a PE file
207    #[error("Failure processing PE File {0:?}")]
208    PEFileProcessingFailure(#[from] goblin::error::Error),
209
210    /// The sandbox becomes **poisoned** when the guest is not run to completion, leaving it in
211    /// an inconsistent state that could compromise memory safety, data integrity, or security.
212    ///
213    /// ### When Does Poisoning Occur?
214    ///
215    /// Poisoning happens when guest execution is interrupted before normal completion:
216    ///
217    /// - **Guest panics or aborts** - When a guest function panics, crashes, or calls `abort()`,
218    ///   the normal cleanup and unwinding process is interrupted
219    /// - **Invalid memory access** - Attempts to read/write/execute memory outside allowed regions
220    /// - **Stack overflow** - Guest exhausts its stack space during execution
221    /// - **Heap exhaustion** - Guest runs out of heap memory
222    /// - **Host-initiated cancellation** - Calling [`InterruptHandle::kill()`] to forcefully
223    ///   terminate an in-progress guest function
224    ///
225    /// ## Recovery
226    ///
227    /// Use [`crate::MultiUseSandbox::restore()`] to recover from a poisoned sandbox.
228    #[error("The sandbox was poisoned")]
229    PoisonedSandbox,
230
231    /// Raw pointer is less than base address
232    #[error("Raw pointer ({0:?}) was less than the base address ({1})")]
233    RawPointerLessThanBaseAddress(RawPtr, u64),
234
235    /// RefCell borrow failed
236    #[error("RefCell borrow failed")]
237    RefCellBorrowFailed(#[from] BorrowError),
238
239    /// RefCell mut borrow failed
240    #[error("RefCell mut borrow failed")]
241    RefCellMutBorrowFailed(#[from] BorrowMutError),
242
243    /// Failed to get value from return value
244    #[error("Failed To Convert Return Value {0:?} to {1:?}")]
245    ReturnValueConversionFailure(ReturnValue, &'static str),
246
247    /// Attempted to process a snapshot but the snapshot size does not match the current memory size
248    #[error("Snapshot Size Mismatch: Memory Size {0:?} Snapshot Size {1:?}")]
249    SnapshotSizeMismatch(usize, usize),
250
251    /// Tried to restore snapshot to a sandbox that is not the same as the one the snapshot was taken from
252    #[error("Snapshot was taken from a different sandbox")]
253    SnapshotSandboxMismatch,
254
255    /// SystemTimeError
256    #[error("SystemTimeError {0:?}")]
257    SystemTimeError(#[from] SystemTimeError),
258
259    /// Error occurred converting a slice to an array
260    #[error("TryFromSliceError {0:?}")]
261    TryFromSliceError(#[from] TryFromSliceError),
262
263    /// A function was called with an incorrect number of arguments
264    #[error("The number of arguments to the function is wrong: got {0:?} expected {1:?}")]
265    UnexpectedNoOfArguments(usize, usize),
266
267    /// The parameter value type is unexpected
268    #[error("The parameter value type is unexpected got {0:?} expected {1:?}")]
269    UnexpectedParameterValueType(ParameterValue, String),
270
271    /// The return value type is unexpected
272    #[error("The return value type is unexpected got {0:?} expected {1:?}")]
273    UnexpectedReturnValueType(ReturnValue, String),
274
275    /// Slice conversion to UTF8 failed
276    #[error("String Conversion of UTF8 data to str failed")]
277    UTF8StringConversionFailure(#[from] FromUtf8Error),
278
279    /// The capacity of the vector is incorrect
280    #[error(
281        "The capacity of the vector is incorrect. Capacity: {0}, Length: {1}, FlatBuffer Size: {2}"
282    )]
283    VectorCapacityIncorrect(usize, usize, i32),
284
285    /// vmm sys Error Occurred
286    #[error("vmm sys Error {0:?}")]
287    #[cfg(target_os = "linux")]
288    VmmSysError(vmm_sys_util::errno::Error),
289
290    /// Windows Error
291    #[cfg(target_os = "windows")]
292    #[error("Windows API Error Result {0:?}")]
293    WindowsAPIError(#[from] windows_result::Error),
294}
295
296impl From<Infallible> for HyperlightError {
297    fn from(_: Infallible) -> Self {
298        "Impossible as this is an infallible error".into()
299    }
300}
301
302impl From<&str> for HyperlightError {
303    fn from(s: &str) -> Self {
304        HyperlightError::Error(s.to_string())
305    }
306}
307
308impl<T> From<PoisonError<MutexGuard<'_, T>>> for HyperlightError {
309    // Implemented this way rather than passing the error as a source to LockAttemptFailed as that would require
310    // Box<dyn Error + Send + Sync> which is not easy to implement for PoisonError<MutexGuard<'_, T>>
311    // This is a good enough solution and allows use to use the ? operator on lock() calls
312    fn from(e: PoisonError<MutexGuard<'_, T>>) -> Self {
313        let source = match e.source() {
314            Some(s) => s.to_string(),
315            None => String::from(""),
316        };
317        HyperlightError::LockAttemptFailed(source)
318    }
319}
320
321impl HyperlightError {
322    /// Internal helper to determines if the given error has potential to poison the sandbox.
323    ///
324    /// Errors that poison the sandbox are those that can leave the sandbox in an inconsistent
325    /// state where memory, resources, or data structures may be corrupted or leaked. Usually
326    /// due to the guest not running to completion.
327    ///
328    /// If this method returns `true`, the sandbox will be poisoned and all further operations
329    /// will fail until the sandbox is restored from a non-poisoned snapshot using
330    /// [`crate::MultiUseSandbox::restore()`].
331    pub(crate) fn is_poison_error(&self) -> bool {
332        // wildcard _ or matches! not used here purposefully to ensure that new error variants
333        // are explicitly considered for poisoning behavior.
334        match self {
335            // These errors poison the sandbox because they can leave it in an inconsistent state due
336            // to the guest not running to completion.
337            HyperlightError::GuestAborted(_, _)
338            | HyperlightError::ExecutionCanceledByHost()
339            | HyperlightError::PoisonedSandbox
340            | HyperlightError::ExecutionAccessViolation(_)
341            | HyperlightError::MemoryAccessViolation(_, _, _)
342            | HyperlightError::SnapshotSizeMismatch(_, _)
343            | HyperlightError::MemoryRegionSizeMismatch(_, _, _)
344            // HyperlightVmError::Restore is already handled manually in restore(), but we mark it
345            // as poisoning here too for defense in depth.
346            | HyperlightError::HyperlightVmError(HyperlightVmError::Restore(_)) => true,
347
348            // These errors poison the sandbox because they can leave
349            // it in an inconsistent state due to snapshot restore
350            // failing partway through
351            HyperlightError::HyperlightVmError(HyperlightVmError::UpdateRegion(_))
352            | HyperlightError::HyperlightVmError(HyperlightVmError::AccessPageTable(_)) => true,
353
354            // HyperlightVmError::DispatchGuestCall may poison the sandbox
355            HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(e)) => {
356                e.is_poison_error()
357            }
358
359            // All other errors do not poison the sandbox.
360            HyperlightError::AnyhowError(_)
361            | HyperlightError::BoundsCheckFailed(_, _)
362            | HyperlightError::CheckedAddOverflow(_, _)
363            | HyperlightError::CStringConversionError(_)
364            | HyperlightError::Error(_)
365            | HyperlightError::FailedToGetValueFromParameter()
366            | HyperlightError::FieldIsMissingInGuestLogData(_)
367            | HyperlightError::GuestBinVersionMismatch { .. }
368            | HyperlightError::GuestError(_, _)
369            | HyperlightError::GuestExecutionHungOnHostFunctionCall()
370            | HyperlightError::GuestFunctionCallAlreadyInProgress()
371            | HyperlightError::GuestInterfaceUnsupportedType(_)
372            | HyperlightError::GuestOffsetIsInvalid(_)
373            | HyperlightError::HostFunctionNotFound(_)
374            | HyperlightError::HyperlightVmError(HyperlightVmError::Create(_))
375            | HyperlightError::HyperlightVmError(HyperlightVmError::Initialize(_))
376            | HyperlightError::HyperlightVmError(HyperlightVmError::MapRegion(_))
377            | HyperlightError::HyperlightVmError(HyperlightVmError::UnmapRegion(_))
378            | HyperlightError::IOError(_)
379            | HyperlightError::IntConversionFailure(_)
380            | HyperlightError::InvalidFlatBuffer(_)
381            | HyperlightError::JsonConversionFailure(_)
382            | HyperlightError::LockAttemptFailed(_)
383            | HyperlightError::MemoryAllocationFailed(_)
384            | HyperlightError::MemoryProtectionFailed(_)
385            | HyperlightError::MemoryRequestTooBig(_, _)
386            | HyperlightError::MemoryRequestTooSmall(_, _)
387            | HyperlightError::MetricNotFound(_)
388            | HyperlightError::MmapFailed(_)
389            | HyperlightError::MprotectFailed(_)
390            | HyperlightError::NoHypervisorFound()
391            | HyperlightError::NoMemorySnapshot
392            | HyperlightError::ParameterValueConversionFailure(_, _)
393            | HyperlightError::PEFileProcessingFailure(_)
394            | HyperlightError::RawPointerLessThanBaseAddress(_, _)
395            | HyperlightError::RefCellBorrowFailed(_)
396            | HyperlightError::RefCellMutBorrowFailed(_)
397            | HyperlightError::ReturnValueConversionFailure(_, _)
398            | HyperlightError::SnapshotSandboxMismatch
399            | HyperlightError::SystemTimeError(_)
400            | HyperlightError::TryFromSliceError(_)
401            | HyperlightError::UnexpectedNoOfArguments(_, _)
402            | HyperlightError::UnexpectedParameterValueType(_, _)
403            | HyperlightError::UnexpectedReturnValueType(_, _)
404            | HyperlightError::UTF8StringConversionFailure(_)
405            | HyperlightError::VectorCapacityIncorrect(_, _, _) => false,
406
407            #[cfg(target_os = "windows")]
408            HyperlightError::CrossBeamReceiveError(_) => false,
409            #[cfg(target_os = "windows")]
410            HyperlightError::CrossBeamSendError(_) => false,
411            #[cfg(target_os = "windows")]
412            HyperlightError::WindowsAPIError(_) => false,
413            #[cfg(target_os = "linux")]
414            HyperlightError::VmmSysError(_) => false,
415        }
416    }
417}
418
419/// Creates a `HyperlightError::Error` from a string literal or format string
420#[macro_export]
421macro_rules! new_error {
422    ($msg:literal $(,)?) => {{
423        let __args = std::format_args!($msg);
424        let __err_msg = match __args.as_str() {
425            Some(msg) => String::from(msg),
426            None => std::format!($msg),
427        };
428        $crate::HyperlightError::Error(__err_msg)
429    }};
430    ($fmtstr:expr, $($arg:tt)*) => {{
431           let __err_msg = std::format!($fmtstr, $($arg)*);
432           $crate::error::HyperlightError::Error(__err_msg)
433    }};
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439    use crate::hypervisor::hyperlight_vm::{
440        DispatchGuestCallError, HandleIoError, HyperlightVmError, RunVmError,
441    };
442    use crate::sandbox::outb::HandleOutbError;
443
444    /// Test that ExecutionCancelledByHost promotes to HyperlightError::ExecutionCanceledByHost
445    #[test]
446    fn test_promote_execution_cancelled_by_host() {
447        let err = DispatchGuestCallError::Run(RunVmError::ExecutionCancelledByHost);
448        let (promoted, should_poison) = err.promote();
449
450        assert!(
451            should_poison,
452            "ExecutionCancelledByHost should poison the sandbox"
453        );
454        assert!(
455            matches!(promoted, HyperlightError::ExecutionCanceledByHost()),
456            "Expected HyperlightError::ExecutionCanceledByHost, got {:?}",
457            promoted
458        );
459    }
460
461    /// Test that GuestAborted promotes to HyperlightError::GuestAborted with correct values
462    #[test]
463    fn test_promote_guest_aborted() {
464        let err = DispatchGuestCallError::Run(RunVmError::HandleIo(HandleIoError::Outb(
465            HandleOutbError::GuestAborted {
466                code: 42,
467                message: "test abort".to_string(),
468            },
469        )));
470        let (promoted, should_poison) = err.promote();
471
472        assert!(should_poison, "GuestAborted should poison the sandbox");
473        match promoted {
474            HyperlightError::GuestAborted(code, msg) => {
475                assert_eq!(code, 42);
476                assert_eq!(msg, "test abort");
477            }
478            _ => panic!("Expected HyperlightError::GuestAborted, got {:?}", promoted),
479        }
480    }
481
482    /// Test that MemoryAccessViolation promotes to HyperlightError::MemoryAccessViolation
483    #[test]
484    fn test_promote_memory_access_violation() {
485        let err = DispatchGuestCallError::Run(RunVmError::MemoryAccessViolation {
486            addr: 0xDEADBEEF,
487            access_type: MemoryRegionFlags::WRITE,
488            region_flags: MemoryRegionFlags::READ,
489        });
490        let (promoted, should_poison) = err.promote();
491
492        assert!(
493            should_poison,
494            "MemoryAccessViolation should poison the sandbox"
495        );
496        match promoted {
497            HyperlightError::MemoryAccessViolation(addr, access_type, region_flags) => {
498                assert_eq!(addr, 0xDEADBEEF);
499                assert_eq!(access_type, MemoryRegionFlags::WRITE);
500                assert_eq!(region_flags, MemoryRegionFlags::READ);
501            }
502            _ => panic!(
503                "Expected HyperlightError::MemoryAccessViolation, got {:?}",
504                promoted
505            ),
506        }
507    }
508
509    /// Test that non-promoted Run errors are wrapped in HyperlightVmError
510    #[test]
511    fn test_promote_other_run_errors_wrapped() {
512        let err = DispatchGuestCallError::Run(RunVmError::MmioReadUnmapped(0x1000));
513        let (promoted, should_poison) = err.promote();
514
515        assert!(should_poison, "Run errors should poison the sandbox");
516        assert!(
517            matches!(
518                promoted,
519                HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(_))
520            ),
521            "Expected HyperlightError::HyperlightVmError, got {:?}",
522            promoted
523        );
524    }
525}