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 a snapshot into a sandbox whose memory
244    /// layout is not compatible with the snapshot's.
245    #[error("Snapshot memory layout is not compatible with this sandbox")]
246    SnapshotLayoutMismatch,
247
248    /// Tried to restore a snapshot into a sandbox whose registered
249    /// host functions do not satisfy the snapshot's required set.
250    #[error(
251        "Snapshot host function mismatch: missing=[{}], signature mismatches=[{}]",
252        missing.join(", "),
253        signature_mismatches.join("; ")
254    )]
255    SnapshotHostFunctionMismatch {
256        /// Functions that are required by the snapshot but not present in the target sandbox.
257        missing: Vec<String>,
258        /// Human-readable descriptions of functions whose signatures
259        /// disagree between the snapshot and the target sandbox.
260        signature_mismatches: Vec<String>,
261    },
262
263    /// SystemTimeError
264    #[error("SystemTimeError {0:?}")]
265    SystemTimeError(#[from] SystemTimeError),
266
267    /// Error occurred converting a slice to an array
268    #[error("TryFromSliceError {0:?}")]
269    TryFromSliceError(#[from] TryFromSliceError),
270
271    /// A function was called with an incorrect number of arguments
272    #[error("The number of arguments to the function is wrong: got {0:?} expected {1:?}")]
273    UnexpectedNoOfArguments(usize, usize),
274
275    /// The parameter value type is unexpected
276    #[error("The parameter value type is unexpected got {0:?} expected {1:?}")]
277    UnexpectedParameterValueType(ParameterValue, String),
278
279    /// The return value type is unexpected
280    #[error("The return value type is unexpected got {0:?} expected {1:?}")]
281    UnexpectedReturnValueType(ReturnValue, String),
282
283    /// Slice conversion to UTF8 failed
284    #[error("String Conversion of UTF8 data to str failed")]
285    UTF8StringConversionFailure(#[from] FromUtf8Error),
286
287    /// The capacity of the vector is incorrect
288    #[error(
289        "The capacity of the vector is incorrect. Capacity: {0}, Length: {1}, FlatBuffer Size: {2}"
290    )]
291    VectorCapacityIncorrect(usize, usize, i32),
292
293    /// vmm sys Error Occurred
294    #[error("vmm sys Error {0:?}")]
295    #[cfg(target_os = "linux")]
296    VmmSysError(vmm_sys_util::errno::Error),
297
298    /// Windows Error
299    #[cfg(target_os = "windows")]
300    #[error("Windows API Error Result {0:?}")]
301    WindowsAPIError(#[from] windows_result::Error),
302}
303
304impl From<Infallible> for HyperlightError {
305    fn from(_: Infallible) -> Self {
306        "Impossible as this is an infallible error".into()
307    }
308}
309
310impl From<&str> for HyperlightError {
311    fn from(s: &str) -> Self {
312        HyperlightError::Error(s.to_string())
313    }
314}
315
316impl<T> From<PoisonError<MutexGuard<'_, T>>> for HyperlightError {
317    // Implemented this way rather than passing the error as a source to LockAttemptFailed as that would require
318    // Box<dyn Error + Send + Sync> which is not easy to implement for PoisonError<MutexGuard<'_, T>>
319    // This is a good enough solution and allows use to use the ? operator on lock() calls
320    fn from(e: PoisonError<MutexGuard<'_, T>>) -> Self {
321        let source = match e.source() {
322            Some(s) => s.to_string(),
323            None => String::from(""),
324        };
325        HyperlightError::LockAttemptFailed(source)
326    }
327}
328
329impl HyperlightError {
330    /// Internal helper to determines if the given error has potential to poison the sandbox.
331    ///
332    /// Errors that poison the sandbox are those that can leave the sandbox in an inconsistent
333    /// state where memory, resources, or data structures may be corrupted or leaked. Usually
334    /// due to the guest not running to completion.
335    ///
336    /// If this method returns `true`, the sandbox will be poisoned and all further operations
337    /// will fail until the sandbox is restored from a non-poisoned snapshot using
338    /// [`crate::MultiUseSandbox::restore()`].
339    pub(crate) fn is_poison_error(&self) -> bool {
340        // wildcard _ or matches! not used here purposefully to ensure that new error variants
341        // are explicitly considered for poisoning behavior.
342        match self {
343            // These errors poison the sandbox because they can leave it in an inconsistent state due
344            // to the guest not running to completion.
345            HyperlightError::GuestAborted(_, _)
346            | HyperlightError::ExecutionCanceledByHost()
347            | HyperlightError::PoisonedSandbox
348            | HyperlightError::ExecutionAccessViolation(_)
349            | HyperlightError::MemoryAccessViolation(_, _, _)
350            | HyperlightError::MemoryRegionSizeMismatch(_, _, _)
351            // HyperlightVmError::Restore is already handled manually in restore(), but we mark it
352            // as poisoning here too for defense in depth.
353            | HyperlightError::HyperlightVmError(HyperlightVmError::Restore(_)) => true,
354
355            // These errors poison the sandbox because they can leave
356            // it in an inconsistent state due to snapshot restore
357            // failing partway through
358            HyperlightError::HyperlightVmError(HyperlightVmError::UpdateRegion(_))
359            | HyperlightError::HyperlightVmError(HyperlightVmError::AccessPageTable(_)) => true,
360
361            // HyperlightVmError::DispatchGuestCall may poison the sandbox
362            HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(e)) => {
363                e.is_poison_error()
364            }
365
366            // All other errors do not poison the sandbox.
367            HyperlightError::AnyhowError(_)
368            | HyperlightError::BoundsCheckFailed(_, _)
369            | HyperlightError::CheckedAddOverflow(_, _)
370            | HyperlightError::CStringConversionError(_)
371            | HyperlightError::Error(_)
372            | HyperlightError::FailedToGetValueFromParameter()
373            | HyperlightError::FieldIsMissingInGuestLogData(_)
374            | HyperlightError::GuestBinVersionMismatch { .. }
375            | HyperlightError::GuestError(_, _)
376            | HyperlightError::GuestExecutionHungOnHostFunctionCall()
377            | HyperlightError::GuestFunctionCallAlreadyInProgress()
378            | HyperlightError::GuestInterfaceUnsupportedType(_)
379            | HyperlightError::HostFunctionNotFound(_)
380            | HyperlightError::HyperlightVmError(HyperlightVmError::Create(_))
381            | HyperlightError::HyperlightVmError(HyperlightVmError::Initialize(_))
382            | HyperlightError::HyperlightVmError(HyperlightVmError::MapRegion(_))
383            | HyperlightError::HyperlightVmError(HyperlightVmError::UnmapRegion(_))
384            | HyperlightError::IOError(_)
385            | HyperlightError::IntConversionFailure(_)
386            | HyperlightError::InvalidFlatBuffer(_)
387            | HyperlightError::JsonConversionFailure(_)
388            | HyperlightError::LockAttemptFailed(_)
389            | HyperlightError::MemoryAllocationFailed(_)
390            | HyperlightError::MemoryProtectionFailed(_)
391            | HyperlightError::MemoryRequestTooBig(_, _)
392            | HyperlightError::MemoryRequestTooSmall(_, _)
393            | HyperlightError::MetricNotFound(_)
394            | HyperlightError::MmapFailed(_)
395            | HyperlightError::MprotectFailed(_)
396            | HyperlightError::NoHypervisorFound()
397            | HyperlightError::NoMemorySnapshot
398            | HyperlightError::ParameterValueConversionFailure(_, _)
399            | HyperlightError::PEFileProcessingFailure(_)
400            | HyperlightError::RawPointerLessThanBaseAddress(_, _)
401            | HyperlightError::RefCellBorrowFailed(_)
402            | HyperlightError::RefCellMutBorrowFailed(_)
403            | HyperlightError::ReturnValueConversionFailure(_, _)
404            | HyperlightError::SnapshotLayoutMismatch
405            | HyperlightError::SnapshotHostFunctionMismatch { .. }
406            | HyperlightError::SystemTimeError(_)
407            | HyperlightError::TryFromSliceError(_)
408            | HyperlightError::UnexpectedNoOfArguments(_, _)
409            | HyperlightError::UnexpectedParameterValueType(_, _)
410            | HyperlightError::UnexpectedReturnValueType(_, _)
411            | HyperlightError::UTF8StringConversionFailure(_)
412            | HyperlightError::VectorCapacityIncorrect(_, _, _) => false,
413
414            #[cfg(target_os = "windows")]
415            HyperlightError::CrossBeamReceiveError(_) => false,
416            #[cfg(target_os = "windows")]
417            HyperlightError::CrossBeamSendError(_) => false,
418            #[cfg(target_os = "windows")]
419            HyperlightError::WindowsAPIError(_) => false,
420            #[cfg(target_os = "linux")]
421            HyperlightError::VmmSysError(_) => false,
422        }
423    }
424}
425
426/// Creates a `HyperlightError::Error` from a string literal or format string
427#[macro_export]
428macro_rules! new_error {
429    ($msg:literal $(,)?) => {{
430        let __args = std::format_args!($msg);
431        let __err_msg = match __args.as_str() {
432            Some(msg) => String::from(msg),
433            None => std::format!($msg),
434        };
435        $crate::HyperlightError::Error(__err_msg)
436    }};
437    ($fmtstr:expr, $($arg:tt)*) => {{
438           let __err_msg = std::format!($fmtstr, $($arg)*);
439           $crate::error::HyperlightError::Error(__err_msg)
440    }};
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446    use crate::hypervisor::hyperlight_vm::{
447        DispatchGuestCallError, HandleIoError, HyperlightVmError, RunVmError,
448    };
449    use crate::sandbox::outb::HandleOutbError;
450
451    /// Test that ExecutionCancelledByHost promotes to HyperlightError::ExecutionCanceledByHost
452    #[test]
453    fn test_promote_execution_cancelled_by_host() {
454        let err = DispatchGuestCallError::Run(RunVmError::ExecutionCancelledByHost);
455        let (promoted, should_poison) = err.promote();
456
457        assert!(
458            should_poison,
459            "ExecutionCancelledByHost should poison the sandbox"
460        );
461        assert!(
462            matches!(promoted, HyperlightError::ExecutionCanceledByHost()),
463            "Expected HyperlightError::ExecutionCanceledByHost, got {:?}",
464            promoted
465        );
466    }
467
468    /// Test that GuestAborted promotes to HyperlightError::GuestAborted with correct values
469    #[test]
470    fn test_promote_guest_aborted() {
471        let err = DispatchGuestCallError::Run(RunVmError::HandleIo(HandleIoError::Outb(
472            HandleOutbError::GuestAborted {
473                code: 42,
474                message: "test abort".to_string(),
475            },
476        )));
477        let (promoted, should_poison) = err.promote();
478
479        assert!(should_poison, "GuestAborted should poison the sandbox");
480        match promoted {
481            HyperlightError::GuestAborted(code, msg) => {
482                assert_eq!(code, 42);
483                assert_eq!(msg, "test abort");
484            }
485            _ => panic!("Expected HyperlightError::GuestAborted, got {:?}", promoted),
486        }
487    }
488
489    /// Test that MemoryAccessViolation promotes to HyperlightError::MemoryAccessViolation
490    #[test]
491    fn test_promote_memory_access_violation() {
492        let err = DispatchGuestCallError::Run(RunVmError::MemoryAccessViolation {
493            addr: 0xDEADBEEF,
494            access_type: MemoryRegionFlags::WRITE,
495            region_flags: MemoryRegionFlags::READ,
496        });
497        let (promoted, should_poison) = err.promote();
498
499        assert!(
500            should_poison,
501            "MemoryAccessViolation should poison the sandbox"
502        );
503        match promoted {
504            HyperlightError::MemoryAccessViolation(addr, access_type, region_flags) => {
505                assert_eq!(addr, 0xDEADBEEF);
506                assert_eq!(access_type, MemoryRegionFlags::WRITE);
507                assert_eq!(region_flags, MemoryRegionFlags::READ);
508            }
509            _ => panic!(
510                "Expected HyperlightError::MemoryAccessViolation, got {:?}",
511                promoted
512            ),
513        }
514    }
515
516    /// Test that non-promoted Run errors are wrapped in HyperlightVmError
517    #[test]
518    fn test_promote_other_run_errors_wrapped() {
519        let err = DispatchGuestCallError::Run(RunVmError::MmioReadUnmapped(0x1000));
520        let (promoted, should_poison) = err.promote();
521
522        assert!(should_poison, "Run errors should poison the sandbox");
523        assert!(
524            matches!(
525                promoted,
526                HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(_))
527            ),
528            "Expected HyperlightError::HyperlightVmError, got {:?}",
529            promoted
530        );
531    }
532}