Skip to main content

hyperlight_host/
error.rs

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