Skip to content

Commit 03a6797

Browse files
committed
Richer error handling for entire HyperlightVm
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent 4caccbc commit 03a6797

File tree

15 files changed

+1263
-433
lines changed

15 files changed

+1263
-433
lines changed

src/hyperlight_host/src/error.rs

Lines changed: 159 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, Ret
3030
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
3131
use thiserror::Error;
3232

33+
use crate::hypervisor::hyperlight_vm::HyperlightVmError;
3334
#[cfg(target_os = "windows")]
3435
use crate::hypervisor::wrappers::HandleWrapper;
3536
use crate::mem::memory_region::MemoryRegionFlags;
@@ -111,6 +112,14 @@ pub enum HyperlightError {
111112
#[error("HostFunction {0} was not found")]
112113
HostFunctionNotFound(String),
113114

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+
114123
/// Reading Writing or Seeking data failed.
115124
#[error("Reading Writing or Seeking data failed {0:?}")]
116125
IOError(#[from] std::io::Error),
@@ -127,11 +136,6 @@ pub enum HyperlightError {
127136
#[error("Conversion of str data to json failed")]
128137
JsonConversionFailure(#[from] serde_json::Error),
129138

130-
/// KVM Error Occurred
131-
#[error("KVM Error {0:?}")]
132-
#[cfg(kvm)]
133-
KVMError(#[from] kvm_ioctls::Error),
134-
135139
/// An attempt to get a lock from a Mutex failed.
136140
#[error("Unable to lock resource")]
137141
LockAttemptFailed(String),
@@ -168,11 +172,6 @@ pub enum HyperlightError {
168172
#[error("mprotect failed with os error {0:?}")]
169173
MprotectFailed(Option<i32>),
170174

171-
/// mshv Error Occurred
172-
#[error("mshv Error {0:?}")]
173-
#[cfg(mshv3)]
174-
MSHVError(#[from] mshv_ioctls::MshvError),
175-
176175
/// No Hypervisor was found for Sandbox.
177176
#[error("No Hypervisor was found for Sandbox")]
178177
NoHypervisorFound(),
@@ -242,11 +241,6 @@ pub enum HyperlightError {
242241
#[error("SystemTimeError {0:?}")]
243242
SystemTimeError(#[from] SystemTimeError),
244243

245-
/// Error occurred when translating guest address
246-
#[error("An error occurred when translating guest address: {0:?}")]
247-
#[cfg(gdb)]
248-
TranslateGuestAddress(u64),
249-
250244
/// Error occurred converting a slice to an array
251245
#[error("TryFromSliceError {0:?}")]
252246
TryFromSliceError(#[from] TryFromSliceError),
@@ -334,6 +328,11 @@ impl HyperlightError {
334328
| HyperlightError::SnapshotSizeMismatch(_, _)
335329
| HyperlightError::MemoryRegionSizeMismatch(_, _, _) => true,
336330

331+
// HyperlightVmError::DispatchGuestCall may poison the sandbox
332+
HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(e)) => {
333+
e.is_poison_error()
334+
}
335+
337336
// All other errors do not poison the sandbox.
338337
HyperlightError::AnyhowError(_)
339338
| HyperlightError::BoundsCheckFailed(_, _)
@@ -348,6 +347,10 @@ impl HyperlightError {
348347
| HyperlightError::GuestInterfaceUnsupportedType(_)
349348
| HyperlightError::GuestOffsetIsInvalid(_)
350349
| HyperlightError::HostFunctionNotFound(_)
350+
| HyperlightError::HyperlightVmError(HyperlightVmError::Create(_))
351+
| HyperlightError::HyperlightVmError(HyperlightVmError::Initialize(_))
352+
| HyperlightError::HyperlightVmError(HyperlightVmError::MapRegion(_))
353+
| HyperlightError::HyperlightVmError(HyperlightVmError::UnmapRegion(_))
351354
| HyperlightError::IOError(_)
352355
| HyperlightError::IntConversionFailure(_)
353356
| HyperlightError::InvalidFlatBuffer(_)
@@ -384,12 +387,6 @@ impl HyperlightError {
384387
HyperlightError::WindowsAPIError(_) => false,
385388
#[cfg(target_os = "linux")]
386389
HyperlightError::VmmSysError(_) => false,
387-
#[cfg(kvm)]
388-
HyperlightError::KVMError(_) => false,
389-
#[cfg(mshv3)]
390-
HyperlightError::MSHVError(_) => false,
391-
#[cfg(gdb)]
392-
HyperlightError::TranslateGuestAddress(_) => false,
393390
}
394391
}
395392
}
@@ -410,3 +407,144 @@ macro_rules! new_error {
410407
$crate::error::HyperlightError::Error(__err_msg)
411408
}};
412409
}
410+
411+
#[cfg(test)]
412+
mod tests {
413+
use super::*;
414+
use crate::hypervisor::hyperlight_vm::{
415+
DispatchGuestCallError, HandleIoError, HyperlightVmError, RunVmError,
416+
};
417+
use crate::sandbox::outb::HandleOutbError;
418+
419+
/// Test that StackOverflow from RunVmError promotes to HyperlightError::StackOverflow
420+
#[test]
421+
fn test_promote_stack_overflow_from_run_vm() {
422+
let err = DispatchGuestCallError::Run(RunVmError::StackOverflow);
423+
let (promoted, should_poison) = err.promote();
424+
425+
assert!(should_poison, "StackOverflow should poison the sandbox");
426+
assert!(
427+
matches!(promoted, HyperlightError::StackOverflow()),
428+
"Expected HyperlightError::StackOverflow, got {:?}",
429+
promoted
430+
);
431+
}
432+
433+
/// Test that StackOverflow from HandleOutbError promotes to HyperlightError::StackOverflow
434+
#[test]
435+
fn test_promote_stack_overflow_from_outb() {
436+
let err = DispatchGuestCallError::Run(RunVmError::HandleIo(HandleIoError::Outb(
437+
HandleOutbError::StackOverflow,
438+
)));
439+
let (promoted, should_poison) = err.promote();
440+
441+
assert!(should_poison, "StackOverflow should poison the sandbox");
442+
assert!(
443+
matches!(promoted, HyperlightError::StackOverflow()),
444+
"Expected HyperlightError::StackOverflow, got {:?}",
445+
promoted
446+
);
447+
}
448+
449+
/// Test that ExecutionCancelledByHost promotes to HyperlightError::ExecutionCanceledByHost
450+
#[test]
451+
fn test_promote_execution_cancelled_by_host() {
452+
let err = DispatchGuestCallError::Run(RunVmError::ExecutionCancelledByHost);
453+
let (promoted, should_poison) = err.promote();
454+
455+
assert!(
456+
should_poison,
457+
"ExecutionCancelledByHost should poison the sandbox"
458+
);
459+
assert!(
460+
matches!(promoted, HyperlightError::ExecutionCanceledByHost()),
461+
"Expected HyperlightError::ExecutionCanceledByHost, got {:?}",
462+
promoted
463+
);
464+
}
465+
466+
/// Test that GuestAborted promotes to HyperlightError::GuestAborted with correct values
467+
#[test]
468+
fn test_promote_guest_aborted() {
469+
let err = DispatchGuestCallError::Run(RunVmError::HandleIo(HandleIoError::Outb(
470+
HandleOutbError::GuestAborted {
471+
code: 42,
472+
message: "test abort".to_string(),
473+
},
474+
)));
475+
let (promoted, should_poison) = err.promote();
476+
477+
assert!(should_poison, "GuestAborted should poison the sandbox");
478+
match promoted {
479+
HyperlightError::GuestAborted(code, msg) => {
480+
assert_eq!(code, 42);
481+
assert_eq!(msg, "test abort");
482+
}
483+
_ => panic!("Expected HyperlightError::GuestAborted, got {:?}", promoted),
484+
}
485+
}
486+
487+
/// Test that MemoryAccessViolation promotes to HyperlightError::MemoryAccessViolation
488+
#[test]
489+
fn test_promote_memory_access_violation() {
490+
let err = DispatchGuestCallError::Run(RunVmError::MemoryAccessViolation {
491+
addr: 0xDEADBEEF,
492+
access_type: MemoryRegionFlags::WRITE,
493+
region_flags: MemoryRegionFlags::READ,
494+
});
495+
let (promoted, should_poison) = err.promote();
496+
497+
assert!(
498+
should_poison,
499+
"MemoryAccessViolation should poison the sandbox"
500+
);
501+
match promoted {
502+
HyperlightError::MemoryAccessViolation(addr, access_type, region_flags) => {
503+
assert_eq!(addr, 0xDEADBEEF);
504+
assert_eq!(access_type, MemoryRegionFlags::WRITE);
505+
assert_eq!(region_flags, MemoryRegionFlags::READ);
506+
}
507+
_ => panic!(
508+
"Expected HyperlightError::MemoryAccessViolation, got {:?}",
509+
promoted
510+
),
511+
}
512+
}
513+
514+
/// Test that ConvertRspPointer does not poison the sandbox
515+
#[test]
516+
fn test_promote_convert_rsp_pointer_no_poison() {
517+
let err = DispatchGuestCallError::ConvertRspPointer("test error".to_string());
518+
let (promoted, should_poison) = err.promote();
519+
520+
assert!(
521+
!should_poison,
522+
"ConvertRspPointer should not poison the sandbox"
523+
);
524+
assert!(
525+
matches!(
526+
promoted,
527+
HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(_))
528+
),
529+
"Expected HyperlightError::HyperlightVmError, got {:?}",
530+
promoted
531+
);
532+
}
533+
534+
/// Test that non-promoted Run errors are wrapped in HyperlightVmError
535+
#[test]
536+
fn test_promote_other_run_errors_wrapped() {
537+
let err = DispatchGuestCallError::Run(RunVmError::MmioReadUnmapped(0x1000));
538+
let (promoted, should_poison) = err.promote();
539+
540+
assert!(should_poison, "Run errors should poison the sandbox");
541+
assert!(
542+
matches!(
543+
promoted,
544+
HyperlightError::HyperlightVmError(HyperlightVmError::DispatchGuestCall(_))
545+
),
546+
"Expected HyperlightError::HyperlightVmError, got {:?}",
547+
promoted
548+
);
549+
}
550+
}

src/hyperlight_host/src/hypervisor/gdb/arch.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,18 @@ limitations under the License.
1616

1717
//! This file contains architecture specific code for the x86_64
1818
19-
use super::{DebuggableVm, VcpuStopReason};
20-
use crate::Result;
19+
use super::{DebugError, DebuggableVm, VcpuStopReason};
2120
use crate::hypervisor::regs::CommonRegisters;
21+
use crate::hypervisor::virtual_machine::RegisterError;
22+
23+
/// Errors that can occur when determining the vCPU stop reason
24+
#[derive(Debug, thiserror::Error)]
25+
pub enum VcpuStopReasonError {
26+
#[error("Failed to get registers: {0}")]
27+
GetRegs(#[from] RegisterError),
28+
#[error("Failed to remove hardware breakpoint: {0}")]
29+
RemoveHwBreakpoint(#[from] DebugError),
30+
}
2231

2332
// Described in Table 6-1. Exceptions and Interrupts at Page 6-13 Vol. 1
2433
// of Intel 64 and IA-32 Architectures Software Developer's Manual
@@ -56,7 +65,7 @@ pub(crate) fn vcpu_stop_reason(
5665
dr6: u64,
5766
entrypoint: u64,
5867
exception: u32,
59-
) -> Result<VcpuStopReason> {
68+
) -> std::result::Result<VcpuStopReason, VcpuStopReasonError> {
6069
let CommonRegisters { rip, .. } = vm.regs()?;
6170
if DB_EX_ID == exception {
6271
// If the BS flag in DR6 register is set, it means a single step

0 commit comments

Comments
 (0)