Skip to content

Commit 7b73a90

Browse files
committed
Enforce unsafe_op_in_unsafe_fn in more source files
1 parent d60dc53 commit 7b73a90

File tree

1 file changed

+83
-14
lines changed

1 file changed

+83
-14
lines changed

zlib-rs/src/allocate.rs

Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#![allow(unpredictable_function_pointer_comparisons)]
2-
#![allow(unsafe_op_in_unsafe_fn)]
32

43
#[cfg(unix)]
54
use core::ffi::c_int;
@@ -34,8 +33,14 @@ unsafe extern "C" fn zalloc_c(opaque: *mut c_void, items: c_uint, size: c_uint)
3433
}
3534

3635
let mut ptr = core::ptr::null_mut();
37-
// SAFETY: ALIGN is a power of 2 and multiple of sizeof(void*), as required by posix_memalign
38-
match unsafe { posix_memalign(&mut ptr, ALIGN.into(), items as size_t * size as size_t) } {
36+
let size = items as size_t * size as size_t;
37+
if size == 0 {
38+
return ptr;
39+
}
40+
// SAFETY: ALIGN is a power of 2 and multiple of sizeof(void*), as required by posix_memalign.
41+
// In addition, since posix_memalign is allowed to return a unique but non-null pointer when
42+
// called with a size of zero, we returned above if the size was zero.
43+
match unsafe { posix_memalign(&mut ptr, ALIGN.into(), size) } {
3944
0 => ptr,
4045
_ => core::ptr::null_mut(),
4146
}
@@ -48,11 +53,19 @@ unsafe extern "C" fn zalloc_c(opaque: *mut c_void, items: c_uint, size: c_uint)
4853
unsafe extern "C" fn zalloc_c(opaque: *mut c_void, items: c_uint, size: c_uint) -> *mut c_void {
4954
let _ = opaque;
5055

56+
let size = items as size_t * size as size_t;
57+
if size == 0 {
58+
return core::ptr::null_mut();
59+
}
60+
5161
extern "C" {
5262
fn malloc(size: size_t) -> *mut c_void;
5363
}
5464

55-
malloc(items as size_t * size as size_t)
65+
// SAFETY: malloc is allowed to return a unique but non-null pointer when given a size
66+
// of zero. To prevent potentially undefined behavior from such a pointer reaching Rust code
67+
// and being dereferenced, we handled the zero-size case separately above.
68+
unsafe { malloc(size) }
5669
}
5770

5871
/// # Safety
@@ -69,7 +82,15 @@ unsafe extern "C" fn zalloc_c_calloc(
6982
fn calloc(nitems: size_t, size: size_t) -> *mut c_void;
7083
}
7184

72-
calloc(items as size_t, size as size_t)
85+
if items as size_t * size as size_t == 0 {
86+
return core::ptr::null_mut();
87+
}
88+
89+
// SAFETY: When the item count or size is zero, calloc is allowed to return either
90+
// null or some non-null value that is safe to free but not safe to dereference.
91+
// To avoid the possibility of exposing such a pointer to Rust code, we check for
92+
// zero above and avoid calling calloc.
93+
unsafe { calloc(items as size_t, size as size_t) }
7394
}
7495

7596
/// # Safety
@@ -82,6 +103,8 @@ unsafe extern "C" fn zfree_c(opaque: *mut c_void, ptr: *mut c_void) {
82103
fn free(p: *mut c_void);
83104
}
84105

106+
// SAFETY: The caller ensured that ptr was obtained from the same allocator. Also,
107+
// free properly handles the case where ptr == NULL.
85108
unsafe { free(ptr) }
86109
}
87110

@@ -91,11 +114,16 @@ unsafe extern "C" fn zfree_c(opaque: *mut c_void, ptr: *mut c_void) {
91114
#[cfg(feature = "rust-allocator")]
92115
unsafe extern "C" fn zalloc_rust(_opaque: *mut c_void, count: c_uint, size: c_uint) -> *mut c_void {
93116
let size = count as usize * size as usize;
117+
if size == 0 {
118+
return core::ptr::null_mut();
119+
}
94120

95121
// internally, we want to align allocations to 64 bytes (in part for SIMD reasons)
96122
let layout = Layout::from_size_align(size, ALIGN.into()).unwrap();
97123

98-
let ptr = std::alloc::System.alloc(layout);
124+
// SAFETY: System.alloc requires that the layout have a nonzero size, so we return null
125+
// above (and never reach this call) if the requested count * size is zero.
126+
let ptr = unsafe { std::alloc::System.alloc(layout) };
99127

100128
ptr as *mut c_void
101129
}
@@ -110,11 +138,16 @@ unsafe extern "C" fn zalloc_rust_calloc(
110138
size: c_uint,
111139
) -> *mut c_void {
112140
let size = count as usize * size as usize;
141+
if size == 0 {
142+
return core::ptr::null_mut();
143+
}
113144

114145
// internally, we want to align allocations to 64 bytes (in part for SIMD reasons)
115146
let layout = Layout::from_size_align(size, ALIGN.into()).unwrap();
116147

117-
let ptr = std::alloc::System.alloc_zeroed(layout);
148+
// SAFETY: System.alloc_zeroed requires that the layout have a nonzero size, so we return
149+
// null above (and never reach this call) if the requested count * size is zero.
150+
let ptr = unsafe { std::alloc::System.alloc_zeroed(layout) };
118151

119152
ptr as *mut c_void
120153
}
@@ -135,12 +168,24 @@ unsafe extern "C" fn zfree_rust(opaque: *mut c_void, ptr: *mut c_void) {
135168
return;
136169
}
137170

138-
let size = *(opaque as *mut usize);
171+
// SAFETY: The caller ensured that *opaque is valid to dereference.
172+
let size = unsafe { *(opaque as *mut usize) };
173+
174+
// zalloc_rust and zalloc_rust_calloc bypass the Rust allocator and just return
175+
// null when asked to allocate something of zero size. So if a caller tries to
176+
// free something of zero size, we return here rather than trying to call the
177+
// Rust deallocator.
178+
if size == 0 {
179+
return;
180+
}
139181

140182
let layout = Layout::from_size_align(size, ALIGN.into());
141183
let layout = layout.unwrap();
142184

143-
std::alloc::System.dealloc(ptr.cast(), layout);
185+
// SAFETY: The caller ensured that ptr was allocated with the alloc::System allocator,
186+
// and the size check above ensures that we are not trying to use a zero-size layout
187+
// that would produce undefined behavior in the allocator.
188+
unsafe { std::alloc::System.dealloc(ptr.cast(), layout) };
144189
}
145190

146191
#[cfg(test)]
@@ -343,21 +388,30 @@ impl Allocator<'_> {
343388
if self.zfree == RUST.zfree {
344389
assert_ne!(len, 0, "invalid size for {ptr:?}");
345390
let mut size = core::mem::size_of::<T>() * len;
346-
return (RUST.zfree)(&mut size as *mut usize as *mut c_void, ptr.cast());
391+
// SAFETY: The caller ensured that ptr was allocated with this allocator, and
392+
// we initialized size above.
393+
return unsafe { (RUST.zfree)(&mut size as *mut usize as *mut c_void, ptr.cast()) };
347394
}
348395

349396
// General case for c-style allocation
350-
let original_ptr = (ptr as *mut u8).sub(core::mem::size_of::<*const c_void>());
351-
let free_ptr = core::ptr::read_unaligned(original_ptr as *mut *mut c_void);
352-
353-
(self.zfree)(self.opaque, free_ptr)
397+
// SAFETY: allocate_layout allocates extra space at the start so that *ptr is preceded
398+
// by a pointer holding the pointer to the actual allocation. Therefore, it is safe to
399+
// subtract size_of::<*const c_void> from pointer, dereference the resulting address,
400+
// and use that as the argument to the low-level free function.
401+
unsafe {
402+
let original_ptr = (ptr as *mut u8).sub(core::mem::size_of::<*const c_void>());
403+
let free_ptr = core::ptr::read_unaligned(original_ptr as *mut *mut c_void);
404+
405+
(self.zfree)(self.opaque, free_ptr)
406+
}
354407
}
355408
}
356409
}
357410

358411
#[cfg(test)]
359412
mod tests {
360413
use core::sync::atomic::{AtomicPtr, Ordering};
414+
use std::ptr;
361415
use std::sync::Mutex;
362416

363417
use super::*;
@@ -504,4 +558,19 @@ mod tests {
504558
(FAIL.zfree)(core::ptr::null_mut(), core::ptr::null_mut());
505559
}
506560
}
561+
562+
#[test]
563+
fn test_allocate_zero_size() {
564+
// Verify that zero-size allocation requests return a null pointer.
565+
unsafe {
566+
assert!(zalloc_c(ptr::null_mut(), 1, 0).is_null());
567+
assert!(zalloc_c(ptr::null_mut(), 0, 1).is_null());
568+
assert!(zalloc_c_calloc(ptr::null_mut(), 1, 0).is_null());
569+
assert!(zalloc_c_calloc(ptr::null_mut(), 0, 1).is_null());
570+
assert!(zalloc_rust(ptr::null_mut(), 1, 0).is_null());
571+
assert!(zalloc_rust(ptr::null_mut(), 0, 1).is_null());
572+
assert!(zalloc_rust_calloc(ptr::null_mut(), 1, 0).is_null());
573+
assert!(zalloc_rust_calloc(ptr::null_mut(), 0, 1).is_null());
574+
}
575+
}
507576
}

0 commit comments

Comments
 (0)