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    /// A Hyperlight sandbox was already created in this process. The
220    /// KVM substrate supports at most ONE live sandbox per process:
221    /// the guest-VA window is a single process-wide reservation
222    /// ([`nub_host_common::layout::reserve_guest_va_range`], a
223    /// `OnceLock` that silently no-ops on a second call) and every
224    /// sandbox `MAP_FIXED`-overlays its kernel-shadow at the one fixed
225    /// VA inside it (`FixedVaMapping`, whose `Drop` munmaps it). A
226    /// second sandbox — concurrent or sequential — would silently
227    /// clobber the first one's live guest memory rather than fail.
228    #[error(
229        "a Hyperlight sandbox was already created in this process; the KVM substrate supports \
230         at most one live sandbox per process (the kernel-shadow is a MAP_FIXED overlay at a \
231         single fixed VA inside the one process-wide guest-VA reservation — a second sandbox \
232         would silently corrupt the first one's guest memory)"
233    )]
234    SandboxAlreadyCreated(),
235
236    /// SystemTimeError
237    #[error("SystemTimeError {0:?}")]
238    SystemTimeError(#[from] SystemTimeError),
239
240    /// Error occurred converting a slice to an array
241    #[error("TryFromSliceError {0:?}")]
242    TryFromSliceError(#[from] TryFromSliceError),
243
244    /// A function was called with an incorrect number of arguments
245    #[error("The number of arguments to the function is wrong: got {0:?} expected {1:?}")]
246    UnexpectedNoOfArguments(usize, usize),
247
248    /// The parameter value type is unexpected
249    #[error("The parameter value type is unexpected got {0:?} expected {1:?}")]
250    UnexpectedParameterValueType(ParameterValue, String),
251
252    /// The return value type is unexpected
253    #[error("The return value type is unexpected got {0:?} expected {1:?}")]
254    UnexpectedReturnValueType(ReturnValue, String),
255
256    /// Slice conversion to UTF8 failed
257    #[error("String Conversion of UTF8 data to str failed")]
258    UTF8StringConversionFailure(#[from] FromUtf8Error),
259
260    /// The capacity of the vector is incorrect
261    #[error(
262        "The capacity of the vector is incorrect. Capacity: {0}, Length: {1}, FlatBuffer Size: {2}"
263    )]
264    VectorCapacityIncorrect(usize, usize, i32),
265
266    /// vmm sys Error Occurred
267    #[error("vmm sys Error {0:?}")]
268    #[cfg(target_os = "linux")]
269    VmmSysError(vmm_sys_util::errno::Error),
270}
271
272impl From<Infallible> for HyperlightError {
273    fn from(_: Infallible) -> Self {
274        "Impossible as this is an infallible error".into()
275    }
276}
277
278impl From<&str> for HyperlightError {
279    fn from(s: &str) -> Self {
280        HyperlightError::Error(s.to_string())
281    }
282}
283
284impl<T> From<PoisonError<MutexGuard<'_, T>>> for HyperlightError {
285    // Implemented this way rather than passing the error as a source to LockAttemptFailed as that would require
286    // Box<dyn Error + Send + Sync> which is not easy to implement for PoisonError<MutexGuard<'_, T>>
287    // This is a good enough solution and allows use to use the ? operator on lock() calls
288    fn from(e: PoisonError<MutexGuard<'_, T>>) -> Self {
289        let source = match e.source() {
290            Some(s) => s.to_string(),
291            None => String::from(""),
292        };
293        HyperlightError::LockAttemptFailed(source)
294    }
295}
296
297/// Creates a `HyperlightError::Error` from a string literal or format string
298#[macro_export]
299macro_rules! new_error {
300    ($msg:literal $(,)?) => {{
301        let __args = std::format_args!($msg);
302        let __err_msg = match __args.as_str() {
303            Some(msg) => String::from(msg),
304            None => std::format!($msg),
305        };
306        $crate::HyperlightError::Error(__err_msg)
307    }};
308    ($fmtstr:expr, $($arg:tt)*) => {{
309           let __err_msg = std::format!($fmtstr, $($arg)*);
310           $crate::error::HyperlightError::Error(__err_msg)
311    }};
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317    use crate::hypervisor::hyperlight_vm::{
318        DispatchGuestCallError, HandleIoError, HyperlightVmError, RunVmError,
319    };
320    use crate::sandbox::outb::HandleOutbError;
321
322    /// Test that ExecutionCancelledByHost promotes to HyperlightError::ExecutionCanceledByHost
323    #[test]
324    fn test_promote_execution_cancelled_by_host() {
325        let err = DispatchGuestCallError::Run(RunVmError::ExecutionCancelledByHost);
326        let (promoted, should_poison) = err.promote();
327
328        assert!(
329            should_poison,
330            "ExecutionCancelledByHost should poison the sandbox"
331        );
332        assert!(
333            matches!(promoted, HyperlightError::ExecutionCanceledByHost()),
334            "Expected HyperlightError::ExecutionCanceledByHost, got {:?}",
335            promoted
336        );
337    }
338
339    /// Test that GuestAborted promotes to HyperlightError::GuestAborted with correct values
340    #[test]
341    fn test_promote_guest_aborted() {
342        let err = DispatchGuestCallError::Run(RunVmError::HandleIo(HandleIoError::Outb(
343            HandleOutbError::GuestAborted {
344                code: 42,
345                message: "test abort".to_string(),
346            },
347        )));
348        let (promoted, should_poison) = err.promote();
349
350        assert!(should_poison, "GuestAborted should poison the sandbox");
351        match promoted {
352            HyperlightError::GuestAborted(code, msg) => {
353                assert_eq!(code, 42);
354                assert_eq!(msg, "test abort");
355            }
356            _ => panic!("Expected HyperlightError::GuestAborted, got {:?}", promoted),
357        }
358    }
359
360    /// Test that MemoryAccessViolation promotes to HyperlightError::MemoryAccessViolation
361    #[test]
362    fn test_promote_memory_access_violation() {
363        let err = DispatchGuestCallError::Run(RunVmError::MemoryAccessViolation {
364            addr: 0xDEADBEEF,
365            access_type: MemoryRegionFlags::WRITE,
366            region_flags: MemoryRegionFlags::READ,
367        });
368        let (promoted, should_poison) = err.promote();
369
370        assert!(
371            should_poison,
372            "MemoryAccessViolation should poison the sandbox"
373        );
374        match promoted {
375            HyperlightError::MemoryAccessViolation(addr, access_type, region_flags) => {
376                assert_eq!(addr, 0xDEADBEEF);
377                assert_eq!(access_type, MemoryRegionFlags::WRITE);
378                assert_eq!(region_flags, MemoryRegionFlags::READ);
379            }
380            _ => panic!(
381                "Expected HyperlightError::MemoryAccessViolation, got {:?}",
382                promoted
383            ),
384        }
385    }
386
387    /// Test that non-promoted Run errors are wrapped in HyperlightVmError
388    #[test]
389    fn test_promote_other_run_errors_wrapped() {
390        let err = DispatchGuestCallError::Run(RunVmError::MmioReadUnmapped(0x1000));
391        let (promoted, should_poison) = err.promote();
392
393        assert!(should_poison, "Run errors should poison the sandbox");
394        assert!(
395            matches!(
396                promoted,
397                HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(_))
398            ),
399            "Expected HyperlightError::HyperlightVmError, got {:?}",
400            promoted
401        );
402    }
403}