Skip to content

Commit 2e29377

Browse files
authored
fix(guest-bin): align user memory allocations (#753)
* 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]> * Add comment on alloc_helper invariants Signed-off-by: Tomasz Andrzejak <[email protected]> --------- Signed-off-by: Tomasz Andrzejak <[email protected]>
1 parent 85b4510 commit 2e29377

File tree

1 file changed

+72
-36
lines changed

1 file changed

+72
-36
lines changed

src/hyperlight_guest_bin/src/memory.rs

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -40,36 +40,51 @@ 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
///
53+
/// # Invariants
54+
/// `alignment` must be non-zero and a power of two
55+
///
4856
/// # Safety
4957
/// 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 {
58+
unsafe fn alloc_helper(size: usize, alignment: usize, zero: bool) -> *mut c_void {
5159
if size == 0 {
5260
return ptr::null_mut();
5361
}
5462

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

6174
unsafe {
6275
let raw_ptr = match zero {
6376
true => alloc::alloc::alloc_zeroed(layout),
6477
false => alloc::alloc::alloc(layout),
6578
};
79+
6680
if raw_ptr.is_null() {
6781
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
7282
}
83+
84+
// Place Header immediately before the user data region
85+
let header_ptr = raw_ptr.add(data_offset - HEADER_LEN).cast::<Header>();
86+
header_ptr.write(Header(layout));
87+
raw_ptr.add(data_offset) as *mut c_void
7388
}
7489
}
7590

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

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

98-
alloc_helper(total_size, true)
113+
alloc_helper(total_size, DEFAULT_ALIGN, true)
99114
}
100115
}
101116

117+
/// Allocates aligned memory.
118+
///
119+
/// # Safety
120+
/// The returned pointer must be freed with `free` when it is no longer needed.
121+
#[unsafe(no_mangle)]
122+
pub unsafe extern "C" fn aligned_alloc(alignment: usize, size: usize) -> *mut c_void {
123+
// Validate alignment
124+
if alignment == 0 || (alignment & (alignment - 1)) != 0 {
125+
return ptr::null_mut();
126+
}
127+
128+
unsafe { alloc_helper(size, alignment, false) }
129+
}
130+
102131
/// Frees the memory block pointed to by `ptr`.
103132
///
104133
/// # Safety
105134
/// `ptr` must be a pointer to a memory block previously allocated by `memory::malloc`, `memory::calloc`, or `memory::realloc`.
106135
#[unsafe(no_mangle)]
107136
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-
}
137+
if ptr.is_null() {
138+
return;
139+
}
140+
141+
let user_ptr = ptr as *const u8;
142+
143+
unsafe {
144+
// Read the Header just before the user data
145+
let header_ptr = user_ptr.sub(HEADER_LEN).cast::<Header>();
146+
let layout = header_ptr.read().0;
147+
148+
// Deallocate from the original base pointer
149+
let offset = HEADER_LEN.next_multiple_of(layout.align());
150+
let raw_ptr = user_ptr.sub(offset) as *mut u8;
151+
alloc::alloc::dealloc(raw_ptr, layout);
114152
}
115153
}
116154

@@ -134,26 +172,24 @@ pub unsafe extern "C" fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
134172
return ptr::null_mut();
135173
}
136174

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

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

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

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
184+
let new_ptr = alloc_helper(size, old_layout.align(), false);
185+
if new_ptr.is_null() {
186+
return ptr::null_mut();
157187
}
188+
189+
let copy_size = old_user_size.min(size);
190+
ptr::copy_nonoverlapping(user_ptr, new_ptr as *mut u8, copy_size);
191+
192+
free(ptr);
193+
new_ptr
158194
}
159195
}

0 commit comments

Comments
 (0)