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