Skip to content

Commit a43f30a

Browse files
authored
Don't allocate in exception handler (#1125)
Signed-off-by: James Sturtevant <jsturtevant@gmail.com>
1 parent a73c804 commit a43f30a

File tree

3 files changed

+74
-17
lines changed

3 files changed

+74
-17
lines changed

src/hyperlight_guest_bin/src/exceptions/handler.rs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
use alloc::format;
18-
use core::ffi::c_char;
17+
use core::fmt::Write;
1918

2019
use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
2120
use hyperlight_common::outb::Exception;
22-
use hyperlight_guest::exit::abort_with_code_and_message;
21+
use hyperlight_guest::exit::write_abort;
22+
23+
use crate::HyperlightAbortWriter;
2324

2425
/// Exception information pushed onto the stack by the CPU during an excpection.
2526
///
@@ -125,15 +126,6 @@ pub(crate) extern "C" fn hl_exception_handler(
125126
let saved_rip = unsafe { (&raw const (*exn_info).rip).read_volatile() };
126127
let error_code = unsafe { (&raw const (*exn_info).error_code).read_volatile() };
127128

128-
let msg = format!(
129-
"Exception vector: {:#}\n\
130-
Faulting Instruction: {:#x}\n\
131-
Page Fault Address: {:#x}\n\
132-
Error code: {:#x}\n\
133-
Stack Pointer: {:#x}",
134-
exception_number, saved_rip, page_fault_address, error_code, stack_pointer
135-
);
136-
137129
// Check for registered user handlers (only for architecture-defined vectors 0-30)
138130
if exception_number < 31 {
139131
let handler =
@@ -149,10 +141,24 @@ pub(crate) extern "C" fn hl_exception_handler(
149141
}
150142
}
151143

152-
unsafe {
153-
abort_with_code_and_message(
154-
&[ErrorCode::GuestError as u8, exception as u8],
155-
msg.as_ptr() as *const c_char,
156-
);
144+
// begin abort sequence by writing the error code
145+
let mut w = HyperlightAbortWriter;
146+
write_abort(&[ErrorCode::GuestError as u8, exception as u8]);
147+
let write_res = write!(
148+
w,
149+
"Exception vector: {}\n\
150+
Faulting Instruction: {:#x}\n\
151+
Page Fault Address: {:#x}\n\
152+
Error code: {:#x}\n\
153+
Stack Pointer: {:#x}",
154+
exception_number, saved_rip, page_fault_address, error_code, stack_pointer
155+
);
156+
if write_res.is_err() {
157+
write_abort("exception message format failed".as_bytes());
157158
}
159+
160+
write_abort(&[0xFF]);
161+
// At this point, write_abort with the 0xFF terminator is expected to terminate guest execution,
162+
// so control should never reach beyond this call.
163+
unreachable!();
158164
}

src/hyperlight_host/tests/integration_test.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,6 +1668,44 @@ fn exception_handler_installation_and_validation() {
16681668
assert_eq!(count, 2, "Handler should have been called twice");
16691669
}
16701670

1671+
/// Tests that an exception can be properly handled even when the heap is exhausted.
1672+
/// The guest function fills the heap completely, then triggers a ud2 exception.
1673+
/// This validates that the exception handling path does not require heap allocations.
1674+
#[test]
1675+
fn fill_heap_and_cause_exception() {
1676+
let mut sandbox: MultiUseSandbox = new_uninit_rust().unwrap().evolve().unwrap();
1677+
let result = sandbox.call::<()>("FillHeapAndCauseException", ());
1678+
1679+
// The call should fail with an exception error since there's no handler installed
1680+
assert!(result.is_err(), "Expected an error from ud2 exception");
1681+
1682+
let err = result.unwrap_err();
1683+
match &err {
1684+
HyperlightError::GuestAborted(code, message) => {
1685+
assert_eq!(*code, ErrorCode::GuestError as u8, "Full error: {:?}", err);
1686+
1687+
// Verify the message was properly formatted (proves no-allocation path worked)
1688+
// Exception vector 6 is #UD (Invalid Opcode from ud2 instruction)
1689+
assert!(
1690+
message.contains("Exception vector: 6"),
1691+
"Message should contain 'Exception vector: 6'\nFull error: {:?}",
1692+
err
1693+
);
1694+
assert!(
1695+
message.contains("Faulting Instruction:"),
1696+
"Message should contain 'Faulting Instruction:'\nFull error: {:?}",
1697+
err
1698+
);
1699+
assert!(
1700+
message.contains("Stack Pointer:"),
1701+
"Message should contain 'Stack Pointer:'\nFull error: {:?}",
1702+
err
1703+
);
1704+
}
1705+
_ => panic!("Expected GuestAborted error, got: {:?}", err),
1706+
}
1707+
}
1708+
16711709
/// This test is "likely" to catch a race condition where WHvCancelRunVirtualProcessor runs halfway, then the partition is deleted (by drop calling WHvDeletePartition),
16721710
/// and WHvCancelRunVirtualProcessor continues, and tries to access freed memory.
16731711
///

src/tests/rust_guests/simpleguest/src/main.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,19 @@ fn call_malloc(size: i32) -> i32 {
336336
size
337337
}
338338

339+
#[guest_function("FillHeapAndCauseException")]
340+
fn fill_heap_and_cause_exception() {
341+
let layout: Layout = Layout::new::<u8>();
342+
let mut ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
343+
while !ptr.is_null() {
344+
black_box(ptr);
345+
ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
346+
}
347+
348+
// trigger an undefined instruction exception
349+
unsafe { core::arch::asm!("ud2") };
350+
}
351+
339352
#[guest_function("ExhaustHeap")]
340353
fn exhaust_heap() {
341354
let layout: Layout = Layout::new::<u8>();

0 commit comments

Comments
 (0)