Skip to content

Commit 6e4f43f

Browse files
andreiltdjsturtevant
authored andcommitted
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 00d620b commit 6e4f43f

File tree

1 file changed

+69
-36
lines changed

1 file changed

+69
-36
lines changed

src/hyperlight_guest_bin/src/memory.rs

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,48 @@ 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+
const HEADER_LEN: usize = size_of::<Header>();
45+
46+
#[repr(transparent)]
47+
// A header that stores the layout information for the allocated memory block.
48+
struct Header(Layout);
4449

4550
/// Allocates a block of memory with the given size. The memory is only guaranteed to be initialized to 0s if `zero` is true, otherwise
4651
/// it may or may not be initialized.
4752
///
4853
/// # Safety
4954
/// 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 {
55+
unsafe fn alloc_helper(size: usize, alignment: usize, zero: bool) -> *mut c_void {
5156
if size == 0 {
5257
return ptr::null_mut();
5358
}
5459

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");
60+
let actual_align = alignment.max(align_of::<Header>());
61+
let data_offset = HEADER_LEN.next_multiple_of(actual_align);
62+
63+
let Some(total_size) = data_offset.checked_add(size) else {
64+
abort_with_code(&[ErrorCode::MallocFailed as u8]);
65+
};
66+
67+
// Create layout for entire allocation
68+
let layout =
69+
Layout::from_size_align(total_size, actual_align).expect("Invalid layout parameters");
6070

6171
unsafe {
6272
let raw_ptr = match zero {
6373
true => alloc::alloc::alloc_zeroed(layout),
6474
false => alloc::alloc::alloc(layout),
6575
};
76+
6677
if raw_ptr.is_null() {
6778
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
7279
}
80+
81+
// Place Header immediately before the user data region
82+
let header_ptr = raw_ptr.add(data_offset - HEADER_LEN).cast::<Header>();
83+
header_ptr.write(Header(layout));
84+
raw_ptr.add(data_offset) as *mut c_void
7385
}
7486
}
7587

@@ -80,7 +92,7 @@ unsafe fn alloc_helper(size: usize, zero: bool) -> *mut c_void {
8092
/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
8193
#[unsafe(no_mangle)]
8294
pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
83-
unsafe { alloc_helper(size, false) }
95+
unsafe { alloc_helper(size, DEFAULT_ALIGN, false) }
8496
}
8597

8698
/// Allocates a block of memory for an array of `nmemb` elements, each of `size` bytes.
@@ -95,8 +107,22 @@ pub unsafe extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {
95107
.checked_mul(size)
96108
.expect("nmemb * size should not overflow in calloc");
97109

98-
alloc_helper(total_size, true)
110+
alloc_helper(total_size, DEFAULT_ALIGN, true)
111+
}
112+
}
113+
114+
/// Allocates aligned memory.
115+
///
116+
/// # Safety
117+
/// The returned pointer must be freed with `free` when it is no longer needed.
118+
#[unsafe(no_mangle)]
119+
pub unsafe extern "C" fn aligned_alloc(alignment: usize, size: usize) -> *mut c_void {
120+
// Validate alignment
121+
if alignment == 0 || (alignment & (alignment - 1)) != 0 {
122+
return ptr::null_mut();
99123
}
124+
125+
unsafe { alloc_helper(size, alignment, false) }
100126
}
101127

102128
/// Frees the memory block pointed to by `ptr`.
@@ -105,12 +131,21 @@ pub unsafe extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {
105131
/// `ptr` must be a pointer to a memory block previously allocated by `memory::malloc`, `memory::calloc`, or `memory::realloc`.
106132
#[unsafe(no_mangle)]
107133
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-
}
134+
if ptr.is_null() {
135+
return;
136+
}
137+
138+
let user_ptr = ptr as *const u8;
139+
140+
unsafe {
141+
// Read the Header just before the user data
142+
let header_ptr = user_ptr.sub(HEADER_LEN).cast::<Header>();
143+
let layout = header_ptr.read().0;
144+
145+
// Deallocate from the original base pointer
146+
let offset = HEADER_LEN.next_multiple_of(layout.align());
147+
let raw_ptr = user_ptr.sub(offset) as *mut u8;
148+
alloc::alloc::dealloc(raw_ptr, layout);
114149
}
115150
}
116151

@@ -134,26 +169,24 @@ pub unsafe extern "C" fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
134169
return ptr::null_mut();
135170
}
136171

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

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();
174+
unsafe {
175+
let header_ptr = user_ptr.sub(HEADER_LEN).cast::<Header>();
144176

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

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
181+
let new_ptr = alloc_helper(size, old_layout.align(), false);
182+
if new_ptr.is_null() {
183+
return ptr::null_mut();
157184
}
185+
186+
let copy_size = old_user_size.min(size);
187+
ptr::copy_nonoverlapping(user_ptr, new_ptr as *mut u8, copy_size);
188+
189+
free(ptr);
190+
new_ptr
158191
}
159192
}

0 commit comments

Comments
 (0)