Skip to main content

nub_host_kvm/
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
26use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue};
27use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
28use thiserror::Error;
29
30use crate::hypervisor::hyperlight_vm::HyperlightVmError;
31use crate::mem::memory_region::MemoryRegionFlags;
32use crate::mem::ptr::RawPtr;
33
34/// The error type for Hyperlight operations
35#[derive(Error, Debug)]
36pub enum HyperlightError {
37    /// Anyhow error
38    #[error("Anyhow Error was returned: {0}")]
39    AnyhowError(#[from] anyhow::Error),
40    /// Memory access out of bounds
41    #[error("Offset: {0} out of bounds, Max is: {1}")]
42    BoundsCheckFailed(u64, usize),
43
44    /// Checked Add Overflow
45    #[error("Couldn't add offset to base address. Offset: {0}, Base Address: {1}")]
46    CheckedAddOverflow(u64, u64),
47
48    /// CString conversion error
49    #[error("Error converting CString {0:?}")]
50    CStringConversionError(#[from] std::ffi::NulError),
51
52    /// A generic error with a message
53    #[error("{0}")]
54    Error(String),
55
56    /// Execution violation
57    #[error("Non-executable address {0:#x} tried to be executed")]
58    ExecutionAccessViolation(u64),
59
60    /// Guest execution was cancelled by the host
61    #[error("Execution was cancelled by the host.")]
62    ExecutionCanceledByHost(),
63
64    /// Accessing the value of a flatbuffer parameter failed
65    #[error("Failed to get a value from flat buffer parameter")]
66    FailedToGetValueFromParameter(),
67
68    ///Field Name not found in decoded GuestLogData
69    #[error("Field Name {0} not found in decoded GuestLogData")]
70    FieldIsMissingInGuestLogData(String),
71
72    /// Guest aborted during outb
73    #[error("Guest aborted: {0} {1}")]
74    GuestAborted(u8, String),
75
76    /// Guest call resulted in error in guest
77    #[error("Guest error occurred {0:?}: {1}")]
78    GuestError(ErrorCode, String),
79
80    /// An attempt to cancel guest execution failed because it is hanging on a host function call
81    #[error("Guest execution hung on the execution of a host function call")]
82    GuestExecutionHungOnHostFunctionCall(),
83
84    /// Guest call already in progress
85    #[error("Guest call is already in progress")]
86    GuestFunctionCallAlreadyInProgress(),
87
88    /// The given type is not supported by the guest interface.
89    #[error("Unsupported type: {0}")]
90    GuestInterfaceUnsupportedType(String),
91
92    /// The guest binary was built against a different guest-bin version than the host expects.
93    /// Guest and host versions must match exactly.
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    /// Conversion of str to Json failed
126    #[error("Conversion of str data to json failed")]
127    JsonConversionFailure(#[from] serde_json::Error),
128
129    /// An attempt to get a lock from a Mutex failed.
130    #[error("Unable to lock resource")]
131    LockAttemptFailed(String),
132
133    /// Memory Access Violation at the given address. The access type and memory region flags are provided.
134    #[error("Memory Access Violation at address {0:#x} of type {1}, but memory is marked as {2}")]
135    MemoryAccessViolation(u64, MemoryRegionFlags, MemoryRegionFlags),
136
137    /// Memory Allocation Failed.
138    #[error("Memory Allocation Failed with OS Error {0:?}.")]
139    MemoryAllocationFailed(Option<i32>),
140
141    /// Memory Protection Failed
142    #[error("Memory Protection Failed with OS Error {0:?}.")]
143    MemoryProtectionFailed(Option<i32>),
144
145    /// Memory region size mismatch
146    #[error("Memory region size mismatch: host size {0:?}, guest size {1:?} region {2:?}")]
147    MemoryRegionSizeMismatch(usize, usize, String),
148
149    /// The memory request exceeds the maximum size allowed
150    #[error("Memory requested {0} exceeds maximum size allowed {1}")]
151    MemoryRequestTooBig(usize, usize),
152
153    /// The memory request is too small to contain everything that is
154    /// required
155    #[error("Memory requested {0} is less than the minimum size allowed {1}")]
156    MemoryRequestTooSmall(usize, usize),
157
158    /// Metric Not Found.
159    #[error("Metric Not Found {0:?}.")]
160    MetricNotFound(&'static str),
161
162    /// mmap Failed.
163    #[error("mmap failed with os error {0:?}")]
164    MmapFailed(Option<i32>),
165
166    /// mprotect Failed.
167    #[error("mprotect failed with os error {0:?}")]
168    MprotectFailed(Option<i32>),
169
170    /// No Hypervisor was found for Sandbox.
171    #[error("No Hypervisor was found for Sandbox")]
172    NoHypervisorFound(),
173
174    /// Failed to get value from parameter value
175    #[error("Failed To Convert Parameter Value {0:?} to {1:?}")]
176    ParameterValueConversionFailure(ParameterValue, &'static str),
177
178    /// a failure occurred processing a PE file
179    #[error("Failure processing PE File {0:?}")]
180    PEFileProcessingFailure(#[from] goblin::error::Error),
181
182    /// The sandbox becomes **poisoned** when the guest is not run to completion, leaving it in
183    /// an inconsistent state that could compromise memory safety, data integrity, or security.
184    ///
185    /// ### When Does Poisoning Occur?
186    ///
187    /// Poisoning happens when guest execution is interrupted before normal completion:
188    ///
189    /// - **Guest panics or aborts** - When a guest function panics, crashes, or calls `abort()`,
190    ///   the normal cleanup and unwinding process is interrupted
191    /// - **Invalid memory access** - Attempts to read/write/execute memory outside allowed regions
192    /// - **Stack overflow** - Guest exhausts its stack space during execution
193    /// - **Heap exhaustion** - Guest runs out of heap memory
194    /// - **Host-initiated cancellation** - Calling `InterruptHandle::kill()` to forcefully
195    ///   terminate an in-progress guest function
196    ///
197    /// ## Recovery
198    ///
199    /// Use `MultiUseSandbox::restore()` to recover from a poisoned sandbox.
200    #[error("The sandbox was poisoned")]
201    PoisonedSandbox,
202
203    /// Raw pointer is less than base address
204    #[error("Raw pointer ({0:?}) was less than the base address ({1})")]
205    RawPointerLessThanBaseAddress(RawPtr, u64),
206
207    /// RefCell borrow failed
208    #[error("RefCell borrow failed")]
209    RefCellBorrowFailed(#[from] BorrowError),
210
211    /// RefCell mut borrow failed
212    #[error("RefCell mut borrow failed")]
213    RefCellMutBorrowFailed(#[from] BorrowMutError),
214
215    /// Failed to get value from return value
216    #[error("Failed To Convert Return Value {0:?} to {1:?}")]
217    ReturnValueConversionFailure(ReturnValue, &'static str),
218
219    /// SystemTimeError
220    #[error("SystemTimeError {0:?}")]
221    SystemTimeError(#[from] SystemTimeError),
222
223    /// Error occurred converting a slice to an array
224    #[error("TryFromSliceError {0:?}")]
225    TryFromSliceError(#[from] TryFromSliceError),
226
227    /// A function was called with an incorrect number of arguments
228    #[error("The number of arguments to the function is wrong: got {0:?} expected {1:?}")]
229    UnexpectedNoOfArguments(usize, usize),
230
231    /// The parameter value type is unexpected
232    #[error("The parameter value type is unexpected got {0:?} expected {1:?}")]
233    UnexpectedParameterValueType(ParameterValue, String),
234
235    /// The return value type is unexpected
236    #[error("The return value type is unexpected got {0:?} expected {1:?}")]
237    UnexpectedReturnValueType(ReturnValue, String),
238
239    /// Slice conversion to UTF8 failed
240    #[error("String Conversion of UTF8 data to str failed")]
241    UTF8StringConversionFailure(#[from] FromUtf8Error),
242
243    /// The capacity of the vector is incorrect
244    #[error(
245        "The capacity of the vector is incorrect. Capacity: {0}, Length: {1}, FlatBuffer Size: {2}"
246    )]
247    VectorCapacityIncorrect(usize, usize, i32),
248
249    /// vmm sys Error Occurred
250    #[error("vmm sys Error {0:?}")]
251    #[cfg(target_os = "linux")]
252    VmmSysError(vmm_sys_util::errno::Error),
253}
254
255impl From<Infallible> for HyperlightError {
256    fn from(_: Infallible) -> Self {
257        "Impossible as this is an infallible error".into()
258    }
259}
260
261impl From<&str> for HyperlightError {
262    fn from(s: &str) -> Self {
263        HyperlightError::Error(s.to_string())
264    }
265}
266
267impl<T> From<PoisonError<MutexGuard<'_, T>>> for HyperlightError {
268    // Implemented this way rather than passing the error as a source to LockAttemptFailed as that would require
269    // Box<dyn Error + Send + Sync> which is not easy to implement for PoisonError<MutexGuard<'_, T>>
270    // This is a good enough solution and allows use to use the ? operator on lock() calls
271    fn from(e: PoisonError<MutexGuard<'_, T>>) -> Self {
272        let source = match e.source() {
273            Some(s) => s.to_string(),
274            None => String::from(""),
275        };
276        HyperlightError::LockAttemptFailed(source)
277    }
278}
279
280/// Creates a `HyperlightError::Error` from a string literal or format string
281#[macro_export]
282macro_rules! new_error {
283    ($msg:literal $(,)?) => {{
284        let __args = std::format_args!($msg);
285        let __err_msg = match __args.as_str() {
286            Some(msg) => String::from(msg),
287            None => std::format!($msg),
288        };
289        $crate::HyperlightError::Error(__err_msg)
290    }};
291    ($fmtstr:expr, $($arg:tt)*) => {{
292           let __err_msg = std::format!($fmtstr, $($arg)*);
293           $crate::error::HyperlightError::Error(__err_msg)
294    }};
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300    use crate::hypervisor::hyperlight_vm::{
301        DispatchGuestCallError, HandleIoError, HyperlightVmError, RunVmError,
302    };
303    use crate::sandbox::outb::HandleOutbError;
304
305    /// Test that ExecutionCancelledByHost promotes to HyperlightError::ExecutionCanceledByHost
306    #[test]
307    fn test_promote_execution_cancelled_by_host() {
308        let err = DispatchGuestCallError::Run(RunVmError::ExecutionCancelledByHost);
309        let (promoted, should_poison) = err.promote();
310
311        assert!(
312            should_poison,
313            "ExecutionCancelledByHost should poison the sandbox"
314        );
315        assert!(
316            matches!(promoted, HyperlightError::ExecutionCanceledByHost()),
317            "Expected HyperlightError::ExecutionCanceledByHost, got {:?}",
318            promoted
319        );
320    }
321
322    /// Test that GuestAborted promotes to HyperlightError::GuestAborted with correct values
323    #[test]
324    fn test_promote_guest_aborted() {
325        let err = DispatchGuestCallError::Run(RunVmError::HandleIo(HandleIoError::Outb(
326            HandleOutbError::GuestAborted {
327                code: 42,
328                message: "test abort".to_string(),
329            },
330        )));
331        let (promoted, should_poison) = err.promote();
332
333        assert!(should_poison, "GuestAborted should poison the sandbox");
334        match promoted {
335            HyperlightError::GuestAborted(code, msg) => {
336                assert_eq!(code, 42);
337                assert_eq!(msg, "test abort");
338            }
339            _ => panic!("Expected HyperlightError::GuestAborted, got {:?}", promoted),
340        }
341    }
342
343    /// Test that MemoryAccessViolation promotes to HyperlightError::MemoryAccessViolation
344    #[test]
345    fn test_promote_memory_access_violation() {
346        let err = DispatchGuestCallError::Run(RunVmError::MemoryAccessViolation {
347            addr: 0xDEADBEEF,
348            access_type: MemoryRegionFlags::WRITE,
349            region_flags: MemoryRegionFlags::READ,
350        });
351        let (promoted, should_poison) = err.promote();
352
353        assert!(
354            should_poison,
355            "MemoryAccessViolation should poison the sandbox"
356        );
357        match promoted {
358            HyperlightError::MemoryAccessViolation(addr, access_type, region_flags) => {
359                assert_eq!(addr, 0xDEADBEEF);
360                assert_eq!(access_type, MemoryRegionFlags::WRITE);
361                assert_eq!(region_flags, MemoryRegionFlags::READ);
362            }
363            _ => panic!(
364                "Expected HyperlightError::MemoryAccessViolation, got {:?}",
365                promoted
366            ),
367        }
368    }
369
370    /// Test that non-promoted Run errors are wrapped in HyperlightVmError
371    #[test]
372    fn test_promote_other_run_errors_wrapped() {
373        let err = DispatchGuestCallError::Run(RunVmError::MmioReadUnmapped(0x1000));
374        let (promoted, should_poison) = err.promote();
375
376        assert!(should_poison, "Run errors should poison the sandbox");
377        assert!(
378            matches!(
379                promoted,
380                HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(_))
381            ),
382            "Expected HyperlightError::HyperlightVmError, got {:?}",
383            promoted
384        );
385    }
386}