Skip to content

Commit b81669d

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 b81669d

File tree

1 file changed

+87
-46
lines changed

1 file changed

+87
-46
lines changed

src/hyperlight_guest_bin/src/memory.rs

Lines changed: 87 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -25,51 +25,71 @@ use hyperlight_guest::exit::abort_with_code;
2525
/*
2626
C-wrappers for Rust's registered global allocator.
2727
28-
Each memory allocation via `malloc/calloc/realloc` is stored together with a `alloc::Layout` describing
29-
the size and alignment of the allocation. This layout is stored just before the actual raw memory returned to the caller.
28+
Each memory allocation via `malloc/calloc/realloc` is stored together with a `Header` containing
29+
an `alloc::Layout` and offset information. The header is stored just before the actual raw memory
30+
returned to the caller.
3031
31-
Example: A call to malloc(64) will allocate space for both an `alloc::Layout` and 64 bytes of memory:
32+
Example: A call to malloc(64) with 16-byte alignment will allocate space for the header and 64 bytes:
3233
3334
----------------------------------------------------------------------------------------
34-
| Layout { size: 64 + size_of::<Layout>(), ... } | 64 bytes of memory | ...
35+
| allocated memory... | Header { layout, offset } | 64 bytes of memory | ...
3536
----------------------------------------------------------------------------------------
36-
^
37-
|
38-
|
39-
ptr returned to caller
37+
^ ^ ^
38+
| | |
39+
raw_ptr header_ptr ptr returned to caller
40+
(from allocator) (at data_offset - header_size) (at data_offset)
41+
42+
The header contains:
43+
- layout: The Layout used for the entire allocation (total_size, actual_align)
44+
- offset: The offset from raw_ptr to the user data (data_offset)
4045
*/
4146

4247
// We assume the maximum alignment for any value is the alignment of u128.
43-
const MAX_ALIGN: usize = align_of::<u128>();
48+
const DEFAULT_ALIGN: usize = align_of::<u128>();
49+
50+
#[repr(C)]
51+
struct Header {
52+
layout: Layout,
53+
offset: usize,
54+
}
4455

4556
/// Allocates a block of memory with the given size. The memory is only guaranteed to be initialized to 0s if `zero` is true, otherwise
4657
/// it may or may not be initialized.
4758
///
4859
/// # Safety
4960
/// 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 {
61+
unsafe fn alloc_helper(size: usize, alignment: usize, zero: bool) -> *mut c_void {
5162
if size == 0 {
5263
return ptr::null_mut();
5364
}
5465

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");
66+
let actual_align = alignment.max(align_of::<Header>());
67+
let header_size = size_of::<Header>();
68+
let data_offset = header_size.next_multiple_of(actual_align);
69+
let total_size = data_offset + size;
70+
71+
// Create layout for entire allocation
72+
let layout =
73+
Layout::from_size_align(total_size, actual_align).expect("Invalid layout parameters");
6074

6175
unsafe {
6276
let raw_ptr = match zero {
6377
true => alloc::alloc::alloc_zeroed(layout),
6478
false => alloc::alloc::alloc(layout),
6579
};
80+
6681
if raw_ptr.is_null() {
6782
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
7283
}
84+
85+
// Place Header immediately before the user data region
86+
let header_ptr = raw_ptr.add(data_offset - header_size).cast::<Header>();
87+
88+
header_ptr.write(Header {
89+
layout,
90+
offset: data_offset,
91+
});
92+
raw_ptr.add(data_offset) as *mut c_void
7393
}
7494
}
7595

@@ -80,7 +100,7 @@ unsafe fn alloc_helper(size: usize, zero: bool) -> *mut c_void {
80100
/// The returned pointer must be freed with `memory::free` when it is no longer needed, otherwise memory will leak.
81101
#[unsafe(no_mangle)]
82102
pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
83-
unsafe { alloc_helper(size, false) }
103+
unsafe { alloc_helper(size, DEFAULT_ALIGN, false) }
84104
}
85105

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

98-
alloc_helper(total_size, true)
118+
alloc_helper(total_size, DEFAULT_ALIGN, true)
119+
}
120+
}
121+
122+
/// Allocates aligned memory.
123+
///
124+
/// # Safety
125+
/// The returned pointer must be freed with `free` when it is no longer needed.
126+
#[unsafe(no_mangle)]
127+
pub unsafe extern "C" fn aligned_alloc(alignment: usize, size: usize) -> *mut c_void {
128+
// Validate alignment
129+
if alignment == 0 || (alignment & (alignment - 1)) != 0 {
130+
return ptr::null_mut();
99131
}
132+
133+
unsafe { alloc_helper(size, alignment, false) }
100134
}
101135

102136
/// Frees the memory block pointed to by `ptr`.
@@ -105,12 +139,20 @@ pub unsafe extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {
105139
/// `ptr` must be a pointer to a memory block previously allocated by `memory::malloc`, `memory::calloc`, or `memory::realloc`.
106140
#[unsafe(no_mangle)]
107141
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-
}
142+
if ptr.is_null() {
143+
return;
144+
}
145+
146+
let user_ptr = ptr as *const u8;
147+
148+
unsafe {
149+
// Read the Header just before the user data
150+
let header_ptr = user_ptr.sub(size_of::<Header>()).cast::<Header>();
151+
let Header { layout, offset } = header_ptr.read();
152+
153+
// Deallocate from the original base pointer
154+
let raw_ptr = user_ptr.sub(offset) as *mut u8;
155+
alloc::alloc::dealloc(raw_ptr, layout);
114156
}
115157
}
116158

@@ -134,26 +176,25 @@ pub unsafe extern "C" fn realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
134176
return ptr::null_mut();
135177
}
136178

137-
let total_new_size = size
138-
.checked_add(size_of::<Layout>())
139-
.expect("data and layout size should not overflow in realloc");
140-
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();
179+
unsafe {
180+
let user_ptr = ptr as *const u8;
181+
let header_ptr = user_ptr.sub(size_of::<Header>()).cast::<Header>();
182+
let Header {
183+
layout: old_layout,
184+
offset,
185+
} = header_ptr.read();
186+
let old_user_size = old_layout.size() - offset;
187+
188+
let new_ptr = alloc_helper(size, old_layout.align(), false);
189+
if new_ptr.is_null() {
190+
return ptr::null_mut();
191+
}
144192

145-
let new_block_start =
146-
unsafe { alloc::alloc::realloc(block_start as *mut u8, old_layout, total_new_size) }
147-
as *mut Layout;
193+
// Copy the lesser of old and new sizes
194+
let copy_size = old_user_size.min(size);
195+
ptr::copy_nonoverlapping(user_ptr, new_ptr as *mut u8, copy_size);
148196

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
157-
}
197+
free(ptr);
198+
new_ptr
158199
}
159200
}

0 commit comments

Comments
 (0)