Skip to content

Commit f3acc2e

Browse files
authored
Add poison state to sandbox to prevent misuse (#931)
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent 8c30579 commit f3acc2e

File tree

7 files changed

+479
-14
lines changed

7 files changed

+479
-14
lines changed

src/hyperlight_host/src/error.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,27 @@ pub enum HyperlightError {
191191
#[error("Failure processing PE File {0:?}")]
192192
PEFileProcessingFailure(#[from] goblin::error::Error),
193193

194+
/// The sandbox becomes **poisoned** when the guest is not run to completion, leaving it in
195+
/// an inconsistent state that could compromise memory safety, data integrity, or security.
196+
///
197+
/// ### When Does Poisoning Occur?
198+
///
199+
/// Poisoning happens when guest execution is interrupted before normal completion:
200+
///
201+
/// - **Guest panics or aborts** - When a guest function panics, crashes, or calls `abort()`,
202+
/// the normal cleanup and unwinding process is interrupted
203+
/// - **Invalid memory access** - Attempts to read/write/execute memory outside allowed regions
204+
/// - **Stack overflow** - Guest exhausts its stack space during execution
205+
/// - **Heap exhaustion** - Guest runs out of heap memory
206+
/// - **Host-initiated cancellation** - Calling [`InterruptHandle::kill()`] to forcefully
207+
/// terminate an in-progress guest function
208+
///
209+
/// ## Recovery
210+
///
211+
/// Use [`crate::MultiUseSandbox::restore()`] to recover from a poisoned sandbox.
212+
#[error("The sandbox was poisoned")]
213+
PoisonedSandbox,
214+
194215
/// Raw pointer is less than base address
195216
#[error("Raw pointer ({0:?}) was less than the base address ({1})")]
196217
RawPointerLessThanBaseAddress(RawPtr, u64),
@@ -286,6 +307,89 @@ impl<T> From<PoisonError<MutexGuard<'_, T>>> for HyperlightError {
286307
}
287308
}
288309

310+
impl HyperlightError {
311+
/// Internal helper to determines if the given error has potential to poison the sandbox.
312+
///
313+
/// Errors that poison the sandbox are those that can leave the sandbox in an inconsistent
314+
/// state where memory, resources, or data structures may be corrupted or leaked. Usually
315+
/// due to the guest not running to completion.
316+
///
317+
/// If this method returns `true`, the sandbox will be poisoned and all further operations
318+
/// will fail until the sandbox is restored from a non-poisoned snapshot using
319+
/// [`crate::MultiUseSandbox::restore()`].
320+
pub(crate) fn is_poison_error(&self) -> bool {
321+
// wildcard _ or matches! not used here purposefully to ensure that new error variants
322+
// are explicitly considered for poisoning behavior.
323+
match self {
324+
// These errors poison the sandbox because they can leave it in an inconsistent state due
325+
// to the guest not running to completion.
326+
HyperlightError::GuestAborted(_, _)
327+
| HyperlightError::ExecutionCanceledByHost()
328+
| HyperlightError::PoisonedSandbox
329+
| HyperlightError::ExecutionAccessViolation(_)
330+
| HyperlightError::StackOverflow()
331+
| HyperlightError::MemoryAccessViolation(_, _, _) => true,
332+
333+
// All other errors do not poison the sandbox.
334+
HyperlightError::AnyhowError(_)
335+
| HyperlightError::BoundsCheckFailed(_, _)
336+
| HyperlightError::CheckedAddOverflow(_, _)
337+
| HyperlightError::CStringConversionError(_)
338+
| HyperlightError::Error(_)
339+
| HyperlightError::FailedToGetValueFromParameter()
340+
| HyperlightError::FieldIsMissingInGuestLogData(_)
341+
| HyperlightError::GuestError(_, _)
342+
| HyperlightError::GuestExecutionHungOnHostFunctionCall()
343+
| HyperlightError::GuestFunctionCallAlreadyInProgress()
344+
| HyperlightError::GuestInterfaceUnsupportedType(_)
345+
| HyperlightError::GuestOffsetIsInvalid(_)
346+
| HyperlightError::HostFunctionNotFound(_)
347+
| HyperlightError::IOError(_)
348+
| HyperlightError::IntConversionFailure(_)
349+
| HyperlightError::InvalidFlatBuffer(_)
350+
| HyperlightError::JsonConversionFailure(_)
351+
| HyperlightError::LockAttemptFailed(_)
352+
| HyperlightError::MemoryAllocationFailed(_)
353+
| HyperlightError::MemoryProtectionFailed(_)
354+
| HyperlightError::MemoryRequestTooBig(_, _)
355+
| HyperlightError::MetricNotFound(_)
356+
| HyperlightError::MmapFailed(_)
357+
| HyperlightError::MprotectFailed(_)
358+
| HyperlightError::NoHypervisorFound()
359+
| HyperlightError::NoMemorySnapshot
360+
| HyperlightError::ParameterValueConversionFailure(_, _)
361+
| HyperlightError::PEFileProcessingFailure(_)
362+
| HyperlightError::RawPointerLessThanBaseAddress(_, _)
363+
| HyperlightError::RefCellBorrowFailed(_)
364+
| HyperlightError::RefCellMutBorrowFailed(_)
365+
| HyperlightError::ReturnValueConversionFailure(_, _)
366+
| HyperlightError::SnapshotSandboxMismatch
367+
| HyperlightError::SystemTimeError(_)
368+
| HyperlightError::TryFromSliceError(_)
369+
| HyperlightError::UnexpectedNoOfArguments(_, _)
370+
| HyperlightError::UnexpectedParameterValueType(_, _)
371+
| HyperlightError::UnexpectedReturnValueType(_, _)
372+
| HyperlightError::UTF8StringConversionFailure(_)
373+
| HyperlightError::VectorCapacityIncorrect(_, _, _) => false,
374+
375+
#[cfg(target_os = "windows")]
376+
HyperlightError::CrossBeamReceiveError(_) => false,
377+
#[cfg(target_os = "windows")]
378+
HyperlightError::CrossBeamSendError(_) => false,
379+
#[cfg(target_os = "windows")]
380+
HyperlightError::WindowsAPIError(_) => false,
381+
#[cfg(target_os = "linux")]
382+
HyperlightError::VmmSysError(_) => false,
383+
#[cfg(kvm)]
384+
HyperlightError::KVMError(_) => false,
385+
#[cfg(mshv)]
386+
HyperlightError::MSHVError(_) => false,
387+
#[cfg(gdb)]
388+
HyperlightError::TranslateGuestAddress(_) => false,
389+
}
390+
}
391+
}
392+
289393
/// Creates a `HyperlightError::Error` from a string literal or format string
290394
#[macro_export]
291395
macro_rules! new_error {

0 commit comments

Comments
 (0)