Skip to content

Commit 6432590

Browse files
adamperlinCopilotjsturtevantdanbugs
authored
Remove Allocations from Panic Handler (#818)
* Update cargo hash in flake.nix Signed-off-by: adamperlin <[email protected]> * Remove allocations in panic handler by formatting panic message using a new FixedStringBuf type backed by a static mut array. Adds a test to verify that StackOverflow no longer occurs on OOM panic Signed-off-by: adamperlin <[email protected]> * Clear buffer before recursive panics Add some docstrings Signed-off-by: adamperlin <[email protected]> * Update cargoHash in flake.nix Signed-off-by: adamperlin <[email protected]> * Remove unused imports Signed-off-by: adamperlin <[email protected]> * Add copyright header to fixed_buf.rrs Signed-off-by: adamperlin <[email protected]> * Call abort_with_code_and_message directly in case of format failure in panic handler to avoid any possible recursive panic Signed-off-by: adamperlin <[email protected]> * Update src/hyperlight_host/tests/integration_test.rs Co-authored-by: Copilot <[email protected]> Signed-off-by: Adam Perlin <[email protected]> * Update src/tests/rust_guests/simpleguest/src/main.rs Co-authored-by: Copilot <[email protected]> Signed-off-by: Adam Perlin <[email protected]> * Make FixedBuf take ownership of underlying byte slice Signed-off-by: adamperlin <[email protected]> * Apply cargo fmt Signed-off-by: adamperlin <[email protected]> * Remove unused import Signed-off-by: adamperlin <[email protected]> * 1. Add vec allocation after alloc::alloc failure to ensure panic in integration test 2. Resolve clippy errors Signed-off-by: adamperlin <[email protected]> * Use heapless crate instead of custom fixed string buffer for panic string formatting Signed-off-by: adamperlin <[email protected]> * Downgrade heapless to 0.8.0 to fix cargo crash Signed-off-by: adamperlin <[email protected]> * apply cargo fmt Signed-off-by: adamperlin <[email protected]> * Remove whitespace in panic handler and rename test function in simpleguest Signed-off-by: adamperlin <[email protected]> * Write null terminator as part of initial panic message format Signed-off-by: adamperlin <[email protected]> * Stack allocate panic message buffer to avoid use of mutex on static Signed-off-by: adamperlin <[email protected]> * Increase panic buffer size and add description to format error message Signed-off-by: adamperlin <[email protected]> * Create an fmt::Write implementation that writes formatted string to port-based io directly to stream error messages to the host and avoid needing a fixed-size buffer in the panic handler. Signed-off-by: adamperlin <[email protected]> * Remove heapless dependency Signed-off-by: adamperlin <[email protected]> * fmt-apply Signed-off-by: adamperlin <[email protected]> * Remove unnecessary unsafe in write_abort Signed-off-by: adamperlin <[email protected]> * Update src/hyperlight_guest/src/exit.rs Co-authored-by: Dan Chiarlone <[email protected]> Signed-off-by: James Sturtevant <[email protected]> --------- Signed-off-by: adamperlin <[email protected]> Signed-off-by: Adam Perlin <[email protected]> Signed-off-by: James Sturtevant <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: James Sturtevant <[email protected]> Co-authored-by: Dan Chiarlone <[email protected]>
1 parent f154d8e commit 6432590

File tree

5 files changed

+97
-7
lines changed

5 files changed

+97
-7
lines changed

flake.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
pname = "hyperlight";
108108
version = "0.0.0";
109109
src = lib.cleanSource ./.;
110-
cargoHash = "sha256-mNKnsaSKVz4khzWO7VhmN0cR+Ed5ML7fD1PJJCeQQ6E=";
110+
cargoHash = "sha256-hoeJEBdxaoyLlhQQ4X4Wk5X1QVtQ7RRQYaxkiGg8rWA=";
111111

112112
nativeBuildInputs = [
113113
azure-cli

src/hyperlight_guest/src/exit.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ pub unsafe fn abort_with_code_and_message(code: &[u8], message_ptr: *const c_cha
6767
}
6868
}
6969

70+
/// This function exists to give the guest more manual control
71+
/// over the abort sequence. For example, in `hyperlight_guest_bin`'s panic handler,
72+
/// we have a message of unknown length that we want to stream
73+
/// to the host, which requires sending the message in chunks
74+
pub fn write_abort(code: &[u8]) {
75+
outb(OutBAction::Abort as u16, code);
76+
}
77+
7078
/// OUT bytes to the host through multiple exits.
7179
#[hyperlight_guest_tracing::trace_function]
7280
pub(crate) fn outb(port: u16, data: &[u8]) {

src/hyperlight_guest_bin/src/lib.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ limitations under the License.
1818
// === Dependencies ===
1919
extern crate alloc;
2020

21-
use alloc::string::ToString;
21+
use core::fmt::Write;
2222

2323
use buddy_system_allocator::LockedHeap;
2424
#[cfg(target_arch = "x86_64")]
@@ -30,7 +30,7 @@ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
3030
use hyperlight_common::mem::HyperlightPEB;
3131
#[cfg(feature = "mem_profile")]
3232
use hyperlight_common::outb::OutBAction;
33-
use hyperlight_guest::exit::{abort_with_code_and_message, halt};
33+
use hyperlight_guest::exit::{halt, write_abort};
3434
use hyperlight_guest::guest_handle::handle::GuestHandle;
3535
use hyperlight_guest_tracing::{trace, trace_function};
3636
use log::LevelFilter;
@@ -139,11 +139,37 @@ pub static mut OS_PAGE_SIZE: u32 = 0;
139139
// to satisfy the clippy when cfg == test
140140
#[allow(dead_code)]
141141
fn panic(info: &core::panic::PanicInfo) -> ! {
142-
let msg = info.to_string();
143-
let c_string = alloc::ffi::CString::new(msg)
144-
.unwrap_or_else(|_| alloc::ffi::CString::new("panic (invalid utf8)").unwrap());
142+
_panic_handler(info)
143+
}
144+
145+
/// A writer that sends all output to the hyperlight host
146+
/// using output ports. This allows us to not impose a
147+
/// buffering limit on error message size on the guest end,
148+
/// though one exists for the host.
149+
struct HyperlightAbortWriter;
150+
impl core::fmt::Write for HyperlightAbortWriter {
151+
fn write_str(&mut self, s: &str) -> core::fmt::Result {
152+
write_abort(s.as_bytes());
153+
Ok(())
154+
}
155+
}
156+
157+
#[inline(always)]
158+
fn _panic_handler(info: &core::panic::PanicInfo) -> ! {
159+
let mut w = HyperlightAbortWriter;
160+
161+
// begin abort sequence by writing the error code
162+
write_abort(&[ErrorCode::UnknownError as u8]);
163+
164+
let write_res = write!(w, "{}", info);
165+
if write_res.is_err() {
166+
write_abort("panic: message format failed".as_bytes());
167+
}
145168

146-
unsafe { abort_with_code_and_message(&[ErrorCode::UnknownError as u8], c_string.as_ptr()) }
169+
// write abort terminator to finish the abort
170+
// and signal to the host that the message can now be read
171+
write_abort(&[0xFF]);
172+
unreachable!();
147173
}
148174

149175
// === Entrypoint ===

src/hyperlight_host/tests/integration_test.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,36 @@ fn guest_malloc_abort() {
531531
));
532532
}
533533

534+
#[test]
535+
fn guest_panic_no_alloc() {
536+
let heap_size = 0x4000;
537+
538+
let mut cfg = SandboxConfiguration::default();
539+
cfg.set_heap_size(heap_size);
540+
let uninit = UninitializedSandbox::new(
541+
GuestBinary::FilePath(simple_guest_as_string().unwrap()),
542+
Some(cfg),
543+
)
544+
.unwrap();
545+
let mut sbox: MultiUseSandbox = uninit.evolve().unwrap();
546+
547+
let res = sbox
548+
.call::<i32>(
549+
"ExhaustHeap", // uses the rust allocator to allocate small blocks on the heap until OOM
550+
(),
551+
)
552+
.unwrap_err();
553+
554+
if let HyperlightError::StackOverflow() = res {
555+
panic!("panic on OOM caused stack overflow, this implies allocation in panic handler");
556+
}
557+
558+
assert!(matches!(
559+
res,
560+
HyperlightError::GuestAborted(code, msg) if code == ErrorCode::UnknownError as u8 && msg.contains("memory allocation of ") && msg.contains("bytes failed")
561+
));
562+
}
563+
534564
// Tests libc alloca
535565
#[test]
536566
fn dynamic_stack_allocate_c_guest() {

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use alloc::boxed::Box;
2929
use alloc::string::ToString;
3030
use alloc::vec::Vec;
3131
use alloc::{format, vec};
32+
use core::alloc::Layout;
3233
use core::ffi::c_char;
3334
use core::hint::black_box;
3435
use core::ptr::write_volatile;
@@ -508,6 +509,23 @@ fn call_malloc(function_call: &FunctionCall) -> Result<Vec<u8>> {
508509
}
509510
}
510511

512+
#[hyperlight_guest_tracing::trace_function]
513+
unsafe fn exhaust_heap(_: &FunctionCall) -> ! {
514+
let layout: Layout = Layout::new::<u8>();
515+
let mut ptr = alloc::alloc::alloc_zeroed(layout);
516+
while !ptr.is_null() {
517+
black_box(ptr);
518+
ptr = alloc::alloc::alloc_zeroed(layout);
519+
}
520+
521+
// after alloc::alloc_zeroed failure (null return when called in loop above)
522+
// allocate a Vec to ensure OOM panic
523+
let vec = Vec::<i32>::with_capacity(1);
524+
black_box(vec);
525+
526+
panic!("function should have panicked before due to OOM")
527+
}
528+
511529
#[hyperlight_guest_tracing::trace_function]
512530
fn malloc_and_free(function_call: &FunctionCall) -> Result<Vec<u8>> {
513531
if let ParameterValue::Int(size) = function_call.parameters.clone().unwrap()[0].clone() {
@@ -1108,6 +1126,14 @@ pub extern "C" fn hyperlight_main() {
11081126
);
11091127
register_function(call_malloc_def);
11101128

1129+
let exhaust_heap_def = GuestFunctionDefinition::new(
1130+
"ExhaustHeap".to_string(),
1131+
Vec::new(),
1132+
ReturnType::Int,
1133+
exhaust_heap as usize,
1134+
);
1135+
register_function(exhaust_heap_def);
1136+
11111137
let malloc_and_free_def = GuestFunctionDefinition::new(
11121138
"MallocAndFree".to_string(),
11131139
Vec::from(&[ParameterType::Int]),

0 commit comments

Comments
 (0)