Skip to content

Commit 7ab4735

Browse files
committed
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]>
1 parent 3ebb69a commit 7ab4735

File tree

5 files changed

+149
-4
lines changed

5 files changed

+149
-4
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use core::fmt;
2+
use core::result::Result;
3+
use core::ffi::{CStr, FromBytesUntilNulError};
4+
5+
pub struct FixedStringBuf<'a> {
6+
pub buf: &'a mut [u8],
7+
pub pos: usize,
8+
}
9+
10+
impl<'a> fmt::Write for FixedStringBuf<'a> {
11+
fn write_str(&mut self, s: &str) -> fmt::Result {
12+
// we always reserve 1 byte for the null terminator,
13+
// as the buffer must be convertible to a CStr.
14+
let buf_end = self.buf.len() - 1;
15+
if self.pos + s.len() > buf_end {
16+
return Err(fmt::Error)
17+
}
18+
19+
self.buf[self.pos..self.pos + s.len()].copy_from_slice(s.as_bytes());
20+
self.pos += s.len();
21+
Ok(())
22+
}
23+
}
24+
25+
impl <'a> FixedStringBuf<'a> {
26+
pub fn as_str(&self) -> Result<&str, core::str::Utf8Error> {
27+
core::str::from_utf8(&self.buf[..self.pos])
28+
}
29+
30+
pub fn new(buf: &'a mut [u8]) -> FixedStringBuf<'a> {
31+
assert!(buf.len() > 0);
32+
FixedStringBuf {
33+
buf,
34+
pos: 0,
35+
}
36+
}
37+
38+
pub fn as_c_str(&mut self) -> Result<&CStr, FromBytesUntilNulError> {
39+
// null terminate buffer.
40+
// we are guaranteed to have enough space since we always reserve one extra
41+
// byte for null in write_str
42+
self.buf[self.pos] = 0;
43+
CStr::from_bytes_until_nul(&self.buf[..self.pos + 1])
44+
}
45+
}
46+
47+
mod test {
48+
use super::{FixedStringBuf};
49+
use core::fmt::Write;
50+
use core::fmt;
51+
#[test]
52+
fn test_fixed_buf() {
53+
let mut bs = [0; 21];
54+
let mut buf = FixedStringBuf::new(&mut bs);
55+
write!(&mut buf, "{}", "0123456789").expect("Failed to write to FixedBuf");
56+
write!(&mut buf, "{}", "0123456789").expect("Failed to write to FixedBuf");
57+
assert_eq!(buf.as_str().unwrap(), "01234567890123456789");
58+
assert_eq!(buf.pos, 20);
59+
60+
let res = write!(&mut buf, "10");
61+
assert_eq!(res, Err(fmt::Error));
62+
}
63+
}

src/hyperlight_common/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ pub mod outb;
4242

4343
/// cbindgen:ignore
4444
pub mod resource;
45+
46+
pub mod fixed_buf;

src/hyperlight_guest_bin/src/lib.rs

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

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

23+
use alloc::string::ToString;
2324
use buddy_system_allocator::LockedHeap;
2425
#[cfg(target_arch = "x86_64")]
2526
use exceptions::{gdt::load_gdt, idtr::load_idt};
@@ -30,11 +31,12 @@ use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
3031
use hyperlight_common::mem::HyperlightPEB;
3132
#[cfg(feature = "mem_profile")]
3233
use hyperlight_common::outb::OutBAction;
34+
use hyperlight_common::fixed_buf::{FixedStringBuf};
3335
use hyperlight_guest::exit::{abort_with_code_and_message, halt};
3436
use hyperlight_guest::guest_handle::handle::GuestHandle;
3537
use hyperlight_guest_tracing::{trace, trace_function};
3638
use log::LevelFilter;
37-
use spin::Once;
39+
use spin::{Once};
3840

3941
// === Modules ===
4042
#[cfg(target_arch = "x86_64")]
@@ -139,7 +141,33 @@ pub static mut OS_PAGE_SIZE: u32 = 0;
139141
// to satisfy the clippy when cfg == test
140142
#[allow(dead_code)]
141143
fn panic(info: &core::panic::PanicInfo) -> ! {
142-
let msg = info.to_string();
144+
_panic_handler(info)
145+
}
146+
147+
148+
static mut PANIC_MSG: [u8; 512] = [0u8; 512];
149+
150+
#[allow(static_mut_refs)]
151+
static mut PANIC_BUF: FixedStringBuf = FixedStringBuf{
152+
buf: unsafe { &mut PANIC_MSG },
153+
pos: 0,
154+
};
155+
156+
157+
#[inline(always)]
158+
#[allow(static_mut_refs)]
159+
fn _panic_handler(info: &core::panic::PanicInfo) -> ! {
160+
let panic_buf: &mut FixedStringBuf = unsafe { &mut PANIC_BUF };
161+
write!(panic_buf, "{}", info).expect("panic: message format failed");
162+
163+
// create a CStr from the underlying array in panic_buf.
164+
// Note that we do NOT use CString here to avoid allocating
165+
let c_string = panic_buf.as_c_str().expect("panic: failed to convert to CStr");
166+
unsafe { abort_with_code_and_message(&[ErrorCode::UnknownError as u8], c_string.as_ptr()) }
167+
}
168+
169+
fn _panic_handler_old(info: &core::panic::PanicInfo) {
170+
let msg = info.to_string();
143171
let c_string = alloc::ffi::CString::new(msg)
144172
.unwrap_or_else(|_| alloc::ffi::CString::new("panic (invalid utf8)").unwrap());
145173

@@ -213,4 +241,4 @@ pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_leve
213241
});
214242

215243
halt();
216-
}
244+
}

src/hyperlight_host/tests/integration_test.rs

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

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

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ 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;
36+
use core::sync::atomic::AtomicPtr;
3537

3638
use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType};
3739
use hyperlight_common::flatbuffer_wrappers::function_types::{
@@ -507,6 +509,18 @@ fn call_malloc(function_call: &FunctionCall) -> Result<Vec<u8>> {
507509
}
508510
}
509511

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+
panic!("function should have panicked before due to OOM")
522+
}
523+
510524
#[hyperlight_guest_tracing::trace_function]
511525
fn malloc_and_free(function_call: &FunctionCall) -> Result<Vec<u8>> {
512526
if let ParameterValue::Int(size) = function_call.parameters.clone().unwrap()[0].clone() {
@@ -1023,6 +1037,14 @@ pub extern "C" fn hyperlight_main() {
10231037
);
10241038
register_function(call_malloc_def);
10251039

1040+
let call_malloc_and_panic_def = GuestFunctionDefinition::new(
1041+
"ExhaustHeap".to_string(),
1042+
Vec::new(),
1043+
ReturnType::Int,
1044+
exhaust_heap as usize,
1045+
);
1046+
register_function(call_malloc_and_panic_def);
1047+
10261048
let malloc_and_free_def = GuestFunctionDefinition::new(
10271049
"MallocAndFree".to_string(),
10281050
Vec::from(&[ParameterType::Int]),

0 commit comments

Comments
 (0)