Skip to content

Commit 8fa2009

Browse files
committed
Fix realloc by updating the stored Layout. Previously the old Layout was copied over but not updated to account for the new size, resulting in all kinds of UB and memory corruption
Signed-off-by: Ludvig Liljenberg <[email protected]>
1 parent 1181e50 commit 8fa2009

File tree

7 files changed

+97
-42
lines changed

7 files changed

+97
-42
lines changed

src/hyperlight_guest/build.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,6 @@ fn cargo_main() {
133133
cfg.define("__x86_64__", None);
134134
cfg.define("__LITTLE_ENDIAN__", None);
135135

136-
cfg.define("malloc", "hlmalloc");
137-
cfg.define("calloc", "hlcalloc");
138-
cfg.define("free", "hlfree");
139-
cfg.define("realloc", "hlrealloc");
140-
141136
// silence compiler warnings
142137
cfg.flag("-Wno-sign-compare");
143138
cfg.flag("-Wno-bitwise-op-parentheses");

src/hyperlight_guest/src/memory.rs

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,42 @@ use crate::entrypoint::abort_with_code;
2525

2626
extern crate alloc;
2727

28-
#[no_mangle]
29-
pub extern "C" fn hlmalloc(size: usize) -> *mut c_void {
30-
alloc_helper(size, false)
31-
}
28+
/*
29+
C-wrappers for Rust's registered global allocator.
3230
33-
pub fn alloc_helper(size: usize, zero: bool) -> *mut c_void {
34-
// Allocate a block that includes space for both layout information and data
31+
Each memory allocation via `malloc/calloc/realloc` is stored together with a `alloc::Layout` describing
32+
the size and alignment of the allocation. This layout is stored just before the actual raw memory returned to the caller.
33+
34+
Example: A call to malloc(64) will allocate space for both an `alloc::Layout` and 64 bytes of memory:
35+
36+
----------------------------------------------------------------------------------------
37+
| Layout { size: 64 + size_of::<Layout>(), ... } | 64 bytes of memory | ...
38+
----------------------------------------------------------------------------------------
39+
^
40+
|
41+
|
42+
ptr returned to caller
43+
*/
44+
45+
// We assume the maximum alignment for any value is the alignment of u128.
46+
const MAX_ALIGN: usize = align_of::<u128>();
47+
48+
/// Allocates a block of memory with the given size. The memory is only guaranteed to be initialized to 0s if `zero` is true, otherwise
49+
/// it may or may not be initialized.
50+
///
51+
/// # Safety
52+
/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
53+
unsafe fn alloc_helper(size: usize, zero: bool) -> *mut c_void {
3554
if size == 0 {
3655
return ptr::null_mut();
3756
}
3857

39-
let total_size = size + size_of::<Layout>();
40-
let layout = Layout::from_size_align(total_size, align_of::<usize>()).unwrap();
58+
// Allocate a block that includes space for both layout information and data
59+
let total_size = size
60+
.checked_add(size_of::<Layout>())
61+
.expect("data and layout size should not overflow in alloc");
62+
let layout = Layout::from_size_align(total_size, MAX_ALIGN).expect("Invalid layout");
63+
4164
unsafe {
4265
let raw_ptr = match zero {
4366
true => alloc::alloc::alloc_zeroed(layout),
@@ -53,14 +76,36 @@ pub fn alloc_helper(size: usize, zero: bool) -> *mut c_void {
5376
}
5477
}
5578

79+
/// Allocates a block of memory with the given size.
80+
/// The memory is not guaranteed to be initialized to 0s.
81+
///
82+
/// # Safety
83+
/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
84+
#[no_mangle]
85+
pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
86+
alloc_helper(size, false)
87+
}
88+
89+
/// Allocates a block of memory for an array of `nmemb` elements, each of `size` bytes.
90+
/// The memory is initialized to 0s.
91+
///
92+
/// # Safety
93+
/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
5694
#[no_mangle]
57-
pub extern "C" fn hlcalloc(n: usize, size: usize) -> *mut c_void {
58-
let total_size = n * size;
95+
pub unsafe extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {
96+
let total_size = nmemb
97+
.checked_mul(size)
98+
.expect("nmemb * size should not overflow in calloc");
99+
59100
alloc_helper(total_size, true)
60101
}
61102

103+
/// Frees the memory block pointed to by `ptr`.
104+
///
105+
/// # Safety
106+
/// `ptr` must be a pointer to a memory block previously allocated by `memory::malloc`, `memory::calloc`, or `memory::realloc`.
62107
#[no_mangle]
63-
pub extern "C" fn hlfree(ptr: *mut c_void) {
108+
pub unsafe extern "C" fn free(ptr: *mut c_void) {
64109
if !ptr.is_null() {
65110
unsafe {
66111
let block_start = (ptr as *const Layout).sub(1);
@@ -70,26 +115,43 @@ pub extern "C" fn hlfree(ptr: *mut c_void) {
70115
}
71116
}
72117

118+
/// Changes the size of the memory block pointed to by `ptr` to `size` bytes. If the returned ptr is non-null,
119+
/// any usage of the old memory block is immediately undefined behavior.
120+
///
121+
/// # Safety
122+
/// `ptr` must be a pointer to a memory block previously allocated by `memory::malloc`, `memory::calloc`, or `memory::realloc`.
73123
#[no_mangle]
74-
pub extern "C" fn hlrealloc(ptr: *mut c_void, size: usize) -> *mut c_void {
124+
pub unsafe extern "C" fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
75125
if ptr.is_null() {
76126
// If the pointer is null, treat as a malloc
77-
return hlmalloc(size);
127+
return malloc(size);
128+
}
129+
130+
if size == 0 {
131+
// If the size is 0, treat as a free and return null
132+
free(ptr);
133+
return ptr::null_mut();
78134
}
79135

80136
unsafe {
137+
let total_new_size = size
138+
.checked_add(size_of::<Layout>())
139+
.expect("data and layout size should not overflow in realloc");
140+
81141
let block_start = (ptr as *const Layout).sub(1);
82-
let layout = block_start.read();
83-
let total_new_size = size + size_of::<Layout>();
142+
let old_layout = block_start.read();
143+
let new_layout = Layout::from_size_align(total_new_size, align_of::<usize>()).unwrap();
144+
84145
let new_block_start =
85-
alloc::alloc::realloc(block_start as *mut u8, layout, total_new_size) as *mut Layout;
146+
alloc::alloc::realloc(block_start as *mut u8, old_layout, total_new_size)
147+
as *mut Layout;
86148

87149
if new_block_start.is_null() {
88150
// Realloc failed
89151
abort_with_code(ErrorCode::MallocFailed as i32);
90152
} else {
91-
// Return the pointer just after the layout information
92-
// since old layout should still as it would have been copied
153+
// Update the stored Layout, then return ptr to memory right after the Layout.
154+
new_block_start.write(new_layout);
93155
new_block_start.add(1) as *mut c_void
94156
}
95157
}

src/hyperlight_host/tests/integration_test.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,14 @@ fn guest_panic() {
186186
}
187187

188188
#[test]
189-
fn guest_hlmalloc() {
189+
fn guest_malloc() {
190190
// this test is rust-only
191191
let sbox1: SingleUseSandbox = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap();
192192

193193
let size_to_allocate = 2000;
194194
let res = sbox1
195195
.call_guest_function_by_name(
196-
"TestHlMalloc", // uses hlmalloc
196+
"TestMalloc",
197197
ReturnType::Int,
198198
Some(vec![ParameterValue::Int(size_to_allocate)]),
199199
)
@@ -202,7 +202,7 @@ fn guest_hlmalloc() {
202202
}
203203

204204
#[test]
205-
fn guest_malloc() {
205+
fn guest_allocate_vec() {
206206
let sbox1: SingleUseSandbox = new_uninit().unwrap().evolve(Noop::default()).unwrap();
207207

208208
let size_to_allocate = 2000;
@@ -220,14 +220,14 @@ fn guest_malloc() {
220220

221221
// checks that malloc failures are captured correctly
222222
#[test]
223-
fn guest_hlmalloc_abort() {
223+
fn guest_malloc_abort() {
224224
let sbox1: SingleUseSandbox = new_uninit_rust().unwrap().evolve(Noop::default()).unwrap();
225225

226226
let size = 20000000; // some big number that should fail when allocated
227227

228228
let res = sbox1
229229
.call_guest_function_by_name(
230-
"TestHlMalloc",
230+
"TestMalloc",
231231
ReturnType::Int,
232232
Some(vec![ParameterValue::Int(size)]),
233233
)

src/tests/c_guests/c_simpleguest/main.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ double echo_double(double d) { return d; }
1919

2020
hl_Vec *set_byte_array_to_zero(const hl_FunctionCall* params) {
2121
hl_Vec input = params->parameters[0].value.VecBytes;
22-
uint8_t *x = hlmalloc(input.len);
22+
uint8_t *x = malloc(input.len);
2323
for (uintptr_t i = 0; i < input.len; i++) {
2424
x[i] = 0;
2525
}
@@ -90,7 +90,7 @@ int small_var(void) {
9090
}
9191

9292
int call_malloc(int32_t size) {
93-
void *heap_memory = hlmalloc(size);
93+
void *heap_memory = malloc(size);
9494
if (NULL == heap_memory) {
9595
hl_set_error(hl_ErrorCode_GuestError, "Malloc Failed");
9696
}
@@ -99,12 +99,12 @@ int call_malloc(int32_t size) {
9999
}
100100

101101
int malloc_and_free(int32_t size) {
102-
void *heap_memory = hlmalloc(size);
102+
void *heap_memory = malloc(size);
103103
if (NULL == heap_memory) {
104104
hl_set_error(hl_ErrorCode_GuestError, "Malloc Failed");
105105
}
106106

107-
hlfree(heap_memory);
107+
free(heap_memory);
108108

109109
return size;
110110
}

src/tests/rust_guests/callbackguest/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tests/rust_guests/simpleguest/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use hyperlight_guest::guest_function_register::register_function;
5454
use hyperlight_guest::host_function_call::{
5555
call_host_function, get_host_value_return_as_int, get_host_value_return_as_ulong,
5656
};
57-
use hyperlight_guest::memory::hlmalloc;
57+
use hyperlight_guest::memory::malloc;
5858
use hyperlight_guest::{logging, MIN_STACK_ADDRESS};
5959
use log::{error, LevelFilter};
6060

@@ -623,11 +623,9 @@ fn execute_on_heap(_function_call: &FunctionCall) -> Result<Vec<u8>> {
623623
Ok(get_flatbuffer_result_from_string("fail"))
624624
}
625625

626-
#[no_mangle]
627-
#[allow(improper_ctypes_definitions)]
628-
pub extern "C" fn test_rust_malloc(function_call: &FunctionCall) -> Result<Vec<u8>> {
626+
fn test_rust_malloc(function_call: &FunctionCall) -> Result<Vec<u8>> {
629627
if let ParameterValue::Int(code) = function_call.parameters.clone().unwrap()[0].clone() {
630-
let ptr = hlmalloc(code as usize);
628+
let ptr = unsafe { malloc(code as usize) };
631629
Ok(get_flatbuffer_result_from_int(ptr as i32))
632630
} else {
633631
Err(HyperlightGuestError::new(
@@ -1012,7 +1010,7 @@ pub extern "C" fn hyperlight_main() {
10121010
register_function(guest_panic_def);
10131011

10141012
let rust_malloc_def = GuestFunctionDefinition::new(
1015-
"TestHlMalloc".to_string(),
1013+
"TestMalloc".to_string(),
10161014
Vec::from(&[ParameterType::Int]),
10171015
ReturnType::Int,
10181016
test_rust_malloc as i64,

0 commit comments

Comments
 (0)