Skip to content

Commit f3efbb5

Browse files
committed
fix(guest-bin): align user memory allocations
The guest allocation wrapper aligned only the total memory block but did not guarantee that the pointer returned to the user was itself properly aligned. This patch attempts to fix that. It also adds alloc_aligned function which would be tricky to implement outside the guest but will make implementing other posix like allocation API much easier. Signed-off-by: Tomasz Andrzejak <[email protected]>
1 parent 45bb9fb commit f3efbb5

File tree

1 file changed

+70
-36
lines changed

1 file changed

+70
-36
lines changed

src/hyperlight_guest_bin/src/memory.rs

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,49 @@ use hyperlight_guest::exit::abort_with_code;
4040
*/
4141

4242
// We assume the maximum alignment for any value is the alignment of u128.
43-
const MAX_ALIGN: usize = align_of::<u128>();
43+
const DEFAULT_ALIGN: usize = align_of::<u128>();
44+
45+
#[repr(transparent)]
46+
// A header that stores the layout information for the allocated memory block.
47+
struct Header(Layout);
48+
49+
const HEADER_LEN: usize = size_of::<Header>();
4450

4551
/// Allocates a block of memory with the given size. The memory is only guaranteed to be initialized to 0s if `zero` is true, otherwise
4652
/// it may or may not be initialized.
4753
///
4854
/// # Safety
4955
/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
50-
unsafe fn alloc_helper(size: usize, zero: bool) -> *mut c_void {
56+
unsafe fn alloc_helper(size: usize, alignment: usize, zero: bool) -> *mut c_void {
5157
if size == 0 {
5258
return ptr::null_mut();
5359
}
5460

55-
// Allocate a block that includes space for both layout information and data
56-
let total_size = size
57-
.checked_add(size_of::<Layout>())
58-
.expect("data and layout size should not overflow in alloc");
59-
let layout = Layout::from_size_align(total_size, MAX_ALIGN).expect("Invalid layout");
61+
let actual_align = alignment.max(align_of::<Header>());
62+
let data_offset = HEADER_LEN.next_multiple_of(actual_align);
63+
64+
let Some(total_size) = data_offset.checked_add(size) else {
65+
abort_with_code(&[ErrorCode::MallocFailed as u8]);
66+
};
67+
68+
// Create layout for entire allocation
69+
let layout =
70+
Layout::from_size_align(total_size, actual_align).expect("Invalid layout parameters");
6071

6172
unsafe {
6273
let raw_ptr = match zero {
6374
true => alloc::alloc::alloc_zeroed(layout),
6475
false => alloc::alloc::alloc(layout),
6576
};
77+
6678
if raw_ptr.is_null() {
6779
abort_with_code(&[ErrorCode::MallocFailed as u8]);
68-
} else {
69-
let layout_ptr = raw_ptr as *mut Layout;
70-
layout_ptr.write(layout);
71-
layout_ptr.add(1) as *mut c_void
7280
}
81+
82+
// Place Header immediately before the user data region
83+
let header_ptr = raw_ptr.add(data_offset - HEADER_LEN).cast::<Header>();
84+
header_ptr.write(Header(layout));
85+
raw_ptr.add(data_offset) as *mut c_void
7386
}
7487
}
7588

@@ -80,7 +93,7 @@ unsafe fn alloc_helper(size: usize, zero: bool) -> *mut c_void {
8093
/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
8194
#[unsafe(no_mangle)]
8295
pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
83-
unsafe { alloc_helper(size, false) }
96+
unsafe { alloc_helper(size, DEFAULT_ALIGN, false) }
8497
}
8598

8699
/// Allocates a block of memory for an array of `nmemb` elements, each of `size` bytes.
@@ -95,22 +108,45 @@ pub unsafe extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {
95108
.checked_mul(size)
96109
.expect("nmemb * size should not overflow in calloc");
97110

98-
alloc_helper(total_size, true)
111+
alloc_helper(total_size, DEFAULT_ALIGN, true)
99112
}
100113
}
101114

115+
/// Allocates aligned memory.
116+
///
117+
/// # Safety
118+
/// The returned pointer must be freed with `free` when it is no longer needed.
119+
#[unsafe(no_mangle)]
120+
pub unsafe extern "C" fn aligned_alloc(alignment: usize, size: usize) -> *mut c_void {
121+
// Validate alignment
122+
if alignment == 0 || (alignment & (alignment - 1)) != 0 {
123+
return ptr::null_mut();
124+
}
125+
126+
unsafe { alloc_helper(size, alignment, false) }
127+
}
128+
102129
/// Frees the memory block pointed to by `ptr`.
103130
///
104131
/// # Safety
105132
/// `ptr` must be a pointer to a memory block previously allocated by `memory::malloc`, `memory::calloc`, or `memory::realloc`.
106133
#[unsafe(no_mangle)]
107134
pub unsafe extern "C" fn free(ptr: *mut c_void) {
108-
if !ptr.is_null() {
109-
unsafe {
110-
let block_start = (ptr as *const Layout).sub(1);
111-
let layout = block_start.read();
112-
alloc::alloc::dealloc(block_start as *mut u8, layout)
113-
}
135+
if ptr.is_null() {
136+
return;
137+
}
138+
139+
let user_ptr = ptr as *const u8;
140+
141+
unsafe {
142+
// Read the Header just before the user data
143+
let header_ptr = user_ptr.sub(HEADER_LEN).cast::<Header>();
144+
let layout = header_ptr.read().0;
145+
146+
// Deallocate from the original base pointer
147+
let offset = HEADER_LEN.next_multiple_of(layout.align());
148+
let raw_ptr = user_ptr.sub(offset) as *mut u8;
149+
alloc::alloc::dealloc(raw_ptr, layout);
114150
}
115151
}
116152

@@ -134,26 +170,24 @@ pub unsafe extern "C" fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
134170
return ptr::null_mut();
135171
}
136172

137-
let total_new_size = size
138-
.checked_add(size_of::<Layout>())
139-
.expect("data and layout size should not overflow in realloc");
173+
let user_ptr = ptr as *const u8;
140174

141-
let block_start = unsafe { (ptr as *const Layout).sub(1) };
142-
let old_layout = unsafe { block_start.read() };
143-
let new_layout = Layout::from_size_align(total_new_size, MAX_ALIGN).unwrap();
175+
unsafe {
176+
let header_ptr = user_ptr.sub(HEADER_LEN).cast::<Header>();
144177

145-
let new_block_start =
146-
unsafe { alloc::alloc::realloc(block_start as *mut u8, old_layout, total_new_size) }
147-
as *mut Layout;
178+
let old_layout = header_ptr.read().0;
179+
let old_offset = HEADER_LEN.next_multiple_of(old_layout.align());
180+
let old_user_size = old_layout.size() - old_offset;
148181

149-
if new_block_start.is_null() {
150-
// Realloc failed
151-
abort_with_code(&[ErrorCode::MallocFailed as u8]);
152-
} else {
153-
// Update the stored Layout, then return ptr to memory right after the Layout.
154-
unsafe {
155-
new_block_start.write(new_layout);
156-
new_block_start.add(1) as *mut c_void
182+
let new_ptr = alloc_helper(size, old_layout.align(), false);
183+
if new_ptr.is_null() {
184+
return ptr::null_mut();
157185
}
186+
187+
let copy_size = old_user_size.min(size);
188+
ptr::copy_nonoverlapping(user_ptr, new_ptr as *mut u8, copy_size);
189+
190+
free(ptr);
191+
new_ptr
158192
}
159193
}

0 commit comments

Comments
 (0)