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    /// A Host function was called by the guest but it was not registered.
112    #[error("HostFunction {0} was not found")]
113    HostFunctionNotFound(String),
114
115    /// Hyperlight VM error.
116    ///
117    /// **Note:** This error variant is considered internal and its structure is not stable.
118    /// It may change between versions without notice. Users should not rely on this.
119    #[doc(hidden)]
120    #[error("Internal Hyperlight VM error: {0}")]
121    HyperlightVmError(#[from] HyperlightVmError),
122
123    /// Reading Writing or Seeking data failed.
124    #[error("Reading Writing or Seeking data failed {0:?}")]
125    IOError(#[from] std::io::Error),
126
127    /// Failed to convert to Integer
128    #[error("Failed To Convert Size to usize")]
129    IntConversionFailure(#[from] TryFromIntError),
130
131    /// The flatbuffer is invalid
132    #[error("The flatbuffer is invalid")]
133    InvalidFlatBuffer(#[from] InvalidFlatbuffer),
134
135    /// Conversion of str to Json failed
136    #[error("Conversion of str data to json failed")]
137    JsonConversionFailure(#[from] serde_json::Error),
138
139    /// An attempt to get a lock from a Mutex failed.
140    #[error("Unable to lock resource")]
141    LockAttemptFailed(String),
142
143    /// Memory Access Violation at the given address. The access type and memory region flags are provided.
144    #[error("Memory Access Violation at address {0:#x} of type {1}, but memory is marked as {2}")]
145    MemoryAccessViolation(u64, MemoryRegionFlags, MemoryRegionFlags),
146
147    /// Memory Allocation Failed.
148    #[error("Memory Allocation Failed with OS Error {0:?}.")]
149    MemoryAllocationFailed(Option<i32>),
150
151    /// Memory Protection Failed
152    #[error("Memory Protection Failed with OS Error {0:?}.")]
153    MemoryProtectionFailed(Option<i32>),
154
155    /// Memory region size mismatch
156    #[error("Memory region size mismatch: host size {0:?}, guest size {1:?} region {2:?}")]
157    MemoryRegionSizeMismatch(usize, usize, String),
158
159    /// The memory request exceeds the maximum size allowed
160    #[error("Memory requested {0} exceeds maximum size allowed {1}")]
161    MemoryRequestTooBig(usize, usize),
162
163    /// The memory request is too small to contain everything that is
164    /// required
165    #[error("Memory requested {0} is less than the minimum size allowed {1}")]
166    MemoryRequestTooSmall(usize, usize),
167
168    /// Metric Not Found.
169    #[error("Metric Not Found {0:?}.")]
170    MetricNotFound(&'static str),
171
172    /// mmap Failed.
173    #[error("mmap failed with os error {0:?}")]
174    MmapFailed(Option<i32>),
175
176    /// mprotect Failed.
177    #[error("mprotect failed with os error {0:?}")]
178    MprotectFailed(Option<i32>),
179
180    /// No Hypervisor was found for Sandbox.
181    #[error("No Hypervisor was found for Sandbox")]
182    NoHypervisorFound(),
183
184    /// Restore_state called with no valid snapshot
185    #[error("Restore_state called with no valid snapshot")]
186    NoMemorySnapshot,
187
188    /// Failed to get value from parameter value
189    #[error("Failed To Convert Parameter Value {0:?} to {1:?}")]
190    ParameterValueConversionFailure(ParameterValue, &'static str),
191
192    /// a failure occurred processing a PE file
193    #[error("Failure processing PE File {0:?}")]
194    PEFileProcessingFailure(#[from] goblin::error::Error),
195
196    /// The sandbox becomes **poisoned** when the guest is not run to completion, leaving it in
197    /// an inconsistent state that could compromise memory safety, data integrity, or security.
198    ///
199    /// ### When Does Poisoning Occur?
200    ///
201    /// Poisoning happens when guest execution is interrupted before normal completion:
202    ///
203    /// - **Guest panics or aborts** - When a guest function panics, crashes, or calls `abort()`,
204    ///   the normal cleanup and unwinding process is interrupted
205    /// - **Invalid memory access** - Attempts to read/write/execute memory outside allowed regions
206    /// - **Stack overflow** - Guest exhausts its stack space during execution
207    /// - **Heap exhaustion** - Guest runs out of heap memory
208    /// - **Host-initiated cancellation** - Calling [`InterruptHandle::kill()`] to forcefully
209    ///   terminate an in-progress guest function
210    ///
211    /// ## Recovery
212    ///
213    /// Use [`crate::MultiUseSandbox::restore()`] to recover from a poisoned sandbox.
214    #[error("The sandbox was poisoned")]
215    PoisonedSandbox,
216
217    /// Raw pointer is less than base address
218    #[error("Raw pointer ({0:?}) was less than the base address ({1})")]
219    RawPointerLessThanBaseAddress(RawPtr, u64),
220
221    /// RefCell borrow failed
222    #[error("RefCell borrow failed")]
223    RefCellBorrowFailed(#[from] BorrowError),
224
225    /// RefCell mut borrow failed
226    #[error("RefCell mut borrow failed")]
227    RefCellMutBorrowFailed(#[from] BorrowMutError),
228
229    /// Failed to get value from return value
230    #[error("Failed To Convert Return Value {0:?} to {1:?}")]
231    ReturnValueConversionFailure(ReturnValue, &'static str),
232
233    /// Attempted to process a snapshot but the snapshot size does not match the current memory size
234    #[error("Snapshot Size Mismatch: Memory Size {0:?} Snapshot Size {1:?}")]
235    SnapshotSizeMismatch(usize, usize),
236
237    /// Tried to restore snapshot to a sandbox that is not the same as the one the snapshot was taken from
238    #[error("Snapshot was taken from a different sandbox")]
239    SnapshotSandboxMismatch,
240
241    /// SystemTimeError
242    #[error("SystemTimeError {0:?}")]
243    SystemTimeError(#[from] SystemTimeError),
244
245    /// Error occurred converting a slice to an array
246    #[error("TryFromSliceError {0:?}")]
247    TryFromSliceError(#[from] TryFromSliceError),
248
249    /// A function was called with an incorrect number of arguments
250    #[error("The number of arguments to the function is wrong: got {0:?} expected {1:?}")]
251    UnexpectedNoOfArguments(usize, usize),
252
253    /// The parameter value type is unexpected
254    #[error("The parameter value type is unexpected got {0:?} expected {1:?}")]
255    UnexpectedParameterValueType(ParameterValue, String),
256
257    /// The return value type is unexpected
258    #[error("The return value type is unexpected got {0:?} expected {1:?}")]
259    UnexpectedReturnValueType(ReturnValue, String),
260
261    /// Slice conversion to UTF8 failed
262    #[error("String Conversion of UTF8 data to str failed")]
263    UTF8StringConversionFailure(#[from] FromUtf8Error),
264
265    /// The capacity of the vector is incorrect
266    #[error(
267        "The capacity of the vector is incorrect. Capacity: {0}, Length: {1}, FlatBuffer Size: {2}"
268    )]
269    VectorCapacityIncorrect(usize, usize, i32),
270
271    /// vmm sys Error Occurred
272    #[error("vmm sys Error {0:?}")]
273    #[cfg(target_os = "linux")]
274    VmmSysError(vmm_sys_util::errno::Error),
275
276    /// Windows Error
277    #[cfg(target_os = "windows")]
278    #[error("Windows API Error Result {0:?}")]
279    WindowsAPIError(#[from] windows_result::Error),
280}
281
282impl From<Infallible> for HyperlightError {
283    fn from(_: Infallible) -> Self {
284        "Impossible as this is an infallible error".into()
285    }
286}
287
288impl From<&str> for HyperlightError {
289    fn from(s: &str) -> Self {
290        HyperlightError::Error(s.to_string())
291    }
292}
293
294impl<T> From<PoisonError<MutexGuard<'_, T>>> for HyperlightError {
295    // Implemented this way rather than passing the error as a source to LockAttemptFailed as that would require
296    // Box<dyn Error + Send + Sync> which is not easy to implement for PoisonError<MutexGuard<'_, T>>
297    // This is a good enough solution and allows use to use the ? operator on lock() calls
298    fn from(e: PoisonError<MutexGuard<'_, T>>) -> Self {
299        let source = match e.source() {
300            Some(s) => s.to_string(),
301            None => String::from(""),
302        };
303        HyperlightError::LockAttemptFailed(source)
304    }
305}
306
307impl HyperlightError {
308    /// Internal helper to determines if the given error has potential to poison the sandbox.
309    ///
310    /// Errors that poison the sandbox are those that can leave the sandbox in an inconsistent
311    /// state where memory, resources, or data structures may be corrupted or leaked. Usually
312    /// due to the guest not running to completion.
313    ///
314    /// If this method returns `true`, the sandbox will be poisoned and all further operations
315    /// will fail until the sandbox is restored from a non-poisoned snapshot using
316    /// [`crate::MultiUseSandbox::restore()`].
317    pub(crate) fn is_poison_error(&self) -> bool {
318        // wildcard _ or matches! not used here purposefully to ensure that new error variants
319        // are explicitly considered for poisoning behavior.
320        match self {
321            // These errors poison the sandbox because they can leave it in an inconsistent state due
322            // to the guest not running to completion.
323            HyperlightError::GuestAborted(_, _)
324            | HyperlightError::ExecutionCanceledByHost()
325            | HyperlightError::PoisonedSandbox
326            | HyperlightError::ExecutionAccessViolation(_)
327            | HyperlightError::MemoryAccessViolation(_, _, _)
328            | HyperlightError::SnapshotSizeMismatch(_, _)
329            | HyperlightError::MemoryRegionSizeMismatch(_, _, _)
330            // HyperlightVmError::Restore is already handled manually in restore(), but we mark it
331            // as poisoning here too for defense in depth.
332            | HyperlightError::HyperlightVmError(HyperlightVmError::Restore(_)) => true,
333
334            // These errors poison the sandbox because they can leave
335            // it in an inconsistent state due to snapshot restore
336            // failing partway through
337            HyperlightError::HyperlightVmError(HyperlightVmError::UpdateRegion(_))
338            | HyperlightError::HyperlightVmError(HyperlightVmError::AccessPageTable(_)) => true,
339
340            // HyperlightVmError::DispatchGuestCall may poison the sandbox
341            HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(e)) => {
342                e.is_poison_error()
343            }
344
345            // All other errors do not poison the sandbox.
346            HyperlightError::AnyhowError(_)
347            | HyperlightError::BoundsCheckFailed(_, _)
348            | HyperlightError::CheckedAddOverflow(_, _)
349            | HyperlightError::CStringConversionError(_)
350            | HyperlightError::Error(_)
351            | HyperlightError::FailedToGetValueFromParameter()
352            | HyperlightError::FieldIsMissingInGuestLogData(_)
353            | HyperlightError::GuestError(_, _)
354            | HyperlightError::GuestExecutionHungOnHostFunctionCall()
355            | HyperlightError::GuestFunctionCallAlreadyInProgress()
356            | HyperlightError::GuestInterfaceUnsupportedType(_)
357            | HyperlightError::GuestOffsetIsInvalid(_)
358            | HyperlightError::HostFunctionNotFound(_)
359            | HyperlightError::HyperlightVmError(HyperlightVmError::Create(_))
360            | HyperlightError::HyperlightVmError(HyperlightVmError::Initialize(_))
361            | HyperlightError::HyperlightVmError(HyperlightVmError::MapRegion(_))
362            | HyperlightError::HyperlightVmError(HyperlightVmError::UnmapRegion(_))
363            | HyperlightError::IOError(_)
364            | HyperlightError::IntConversionFailure(_)
365            | HyperlightError::InvalidFlatBuffer(_)
366            | HyperlightError::JsonConversionFailure(_)
367            | HyperlightError::LockAttemptFailed(_)
368            | HyperlightError::MemoryAllocationFailed(_)
369            | HyperlightError::MemoryProtectionFailed(_)
370            | HyperlightError::MemoryRequestTooBig(_, _)
371            | HyperlightError::MemoryRequestTooSmall(_, _)
372            | HyperlightError::MetricNotFound(_)
373            | HyperlightError::MmapFailed(_)
374            | HyperlightError::MprotectFailed(_)
375            | HyperlightError::NoHypervisorFound()
376            | HyperlightError::NoMemorySnapshot
377            | HyperlightError::ParameterValueConversionFailure(_, _)
378            | HyperlightError::PEFileProcessingFailure(_)
379            | HyperlightError::RawPointerLessThanBaseAddress(_, _)
380            | HyperlightError::RefCellBorrowFailed(_)
381            | HyperlightError::RefCellMutBorrowFailed(_)
382            | HyperlightError::ReturnValueConversionFailure(_, _)
383            | HyperlightError::SnapshotSandboxMismatch
384            | HyperlightError::SystemTimeError(_)
385            | HyperlightError::TryFromSliceError(_)
386            | HyperlightError::UnexpectedNoOfArguments(_, _)
387            | HyperlightError::UnexpectedParameterValueType(_, _)
388            | HyperlightError::UnexpectedReturnValueType(_, _)
389            | HyperlightError::UTF8StringConversionFailure(_)
390            | HyperlightError::VectorCapacityIncorrect(_, _, _) => false,
391
392            #[cfg(target_os = "windows")]
393            HyperlightError::CrossBeamReceiveError(_) => false,
394            #[cfg(target_os = "windows")]
395            HyperlightError::CrossBeamSendError(_) => false,
396            #[cfg(target_os = "windows")]
397            HyperlightError::WindowsAPIError(_) => false,
398            #[cfg(target_os = "linux")]
399            HyperlightError::VmmSysError(_) => false,
400        }
401    }
402}
403
404/// Creates a `HyperlightError::Error` from a string literal or format string
405#[macro_export]
406macro_rules! new_error {
407    ($msg:literal $(,)?) => {{
408        let __args = std::format_args!($msg);
409        let __err_msg = match __args.as_str() {
410            Some(msg) => String::from(msg),
411            None => std::format!($msg),
412        };
413        $crate::HyperlightError::Error(__err_msg)
414    }};
415    ($fmtstr:expr, $($arg:tt)*) => {{
416           let __err_msg = std::format!($fmtstr, $($arg)*);
417           $crate::error::HyperlightError::Error(__err_msg)
418    }};
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424    use crate::hypervisor::hyperlight_vm::{
425        DispatchGuestCallError, HandleIoError, HyperlightVmError, RunVmError,
426    };
427    use crate::sandbox::outb::HandleOutbError;
428
429    /// Test that ExecutionCancelledByHost promotes to HyperlightError::ExecutionCanceledByHost
430    #[test]
431    fn test_promote_execution_cancelled_by_host() {
432        let err = DispatchGuestCallError::Run(RunVmError::ExecutionCancelledByHost);
433        let (promoted, should_poison) = err.promote();
434
435        assert!(
436            should_poison,
437            "ExecutionCancelledByHost should poison the sandbox"
438        );
439        assert!(
440            matches!(promoted, HyperlightError::ExecutionCanceledByHost()),
441            "Expected HyperlightError::ExecutionCanceledByHost, got {:?}",
442            promoted
443        );
444    }
445
446    /// Test that GuestAborted promotes to HyperlightError::GuestAborted with correct values
447    #[test]
448    fn test_promote_guest_aborted() {
449        let err = DispatchGuestCallError::Run(RunVmError::HandleIo(HandleIoError::Outb(
450            HandleOutbError::GuestAborted {
451                code: 42,
452                message: "test abort".to_string(),
453            },
454        )));
455        let (promoted, should_poison) = err.promote();
456
457        assert!(should_poison, "GuestAborted should poison the sandbox");
458        match promoted {
459            HyperlightError::GuestAborted(code, msg) => {
460                assert_eq!(code, 42);
461                assert_eq!(msg, "test abort");
462            }
463            _ => panic!("Expected HyperlightError::GuestAborted, got {:?}", promoted),
464        }
465    }
466
467    /// Test that MemoryAccessViolation promotes to HyperlightError::MemoryAccessViolation
468    #[test]
469    fn test_promote_memory_access_violation() {
470        let err = DispatchGuestCallError::Run(RunVmError::MemoryAccessViolation {
471            addr: 0xDEADBEEF,
472            access_type: MemoryRegionFlags::WRITE,
473            region_flags: MemoryRegionFlags::READ,
474        });
475        let (promoted, should_poison) = err.promote();
476
477        assert!(
478            should_poison,
479            "MemoryAccessViolation should poison the sandbox"
480        );
481        match promoted {
482            HyperlightError::MemoryAccessViolation(addr, access_type, region_flags) => {
483                assert_eq!(addr, 0xDEADBEEF);
484                assert_eq!(access_type, MemoryRegionFlags::WRITE);
485                assert_eq!(region_flags, MemoryRegionFlags::READ);
486            }
487            _ => panic!(
488                "Expected HyperlightError::MemoryAccessViolation, got {:?}",
489                promoted
490            ),
491        }
492    }
493
494    /// Test that non-promoted Run errors are wrapped in HyperlightVmError
495    #[test]
496    fn test_promote_other_run_errors_wrapped() {
497        let err = DispatchGuestCallError::Run(RunVmError::MmioReadUnmapped(0x1000));
498        let (promoted, should_poison) = err.promote();
499
500        assert!(should_poison, "Run errors should poison the sandbox");
501        assert!(
502            matches!(
503                promoted,
504                HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(_))
505            ),
506            "Expected HyperlightError::HyperlightVmError, got {:?}",
507            promoted
508        );
509    }
510}