1
1
#![ allow( unpredictable_function_pointer_comparisons) ]
2
- #![ allow( unsafe_op_in_unsafe_fn) ]
3
2
4
3
#[ cfg( unix) ]
5
4
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)
34
33
}
35
34
36
35
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) } {
39
44
0 => ptr,
40
45
_ => core:: ptr:: null_mut ( ) ,
41
46
}
@@ -48,11 +53,19 @@ unsafe extern "C" fn zalloc_c(opaque: *mut c_void, items: c_uint, size: c_uint)
48
53
unsafe extern "C" fn zalloc_c ( opaque : * mut c_void , items : c_uint , size : c_uint ) -> * mut c_void {
49
54
let _ = opaque;
50
55
56
+ let size = items as size_t * size as size_t ;
57
+ if size == 0 {
58
+ return core:: ptr:: null_mut ( ) ;
59
+ }
60
+
51
61
extern "C" {
52
62
fn malloc ( size : size_t ) -> * mut c_void ;
53
63
}
54
64
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) }
56
69
}
57
70
58
71
/// # Safety
@@ -69,7 +82,15 @@ unsafe extern "C" fn zalloc_c_calloc(
69
82
fn calloc ( nitems : size_t , size : size_t ) -> * mut c_void ;
70
83
}
71
84
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 ) }
73
94
}
74
95
75
96
/// # Safety
@@ -82,6 +103,8 @@ unsafe extern "C" fn zfree_c(opaque: *mut c_void, ptr: *mut c_void) {
82
103
fn free ( p : * mut c_void ) ;
83
104
}
84
105
106
+ // SAFETY: The caller ensured that ptr was obtained from the same allocator. Also,
107
+ // free properly handles the case where ptr == NULL.
85
108
unsafe { free ( ptr) }
86
109
}
87
110
@@ -91,11 +114,16 @@ unsafe extern "C" fn zfree_c(opaque: *mut c_void, ptr: *mut c_void) {
91
114
#[ cfg( feature = "rust-allocator" ) ]
92
115
unsafe extern "C" fn zalloc_rust ( _opaque : * mut c_void , count : c_uint , size : c_uint ) -> * mut c_void {
93
116
let size = count as usize * size as usize ;
117
+ if size == 0 {
118
+ return core:: ptr:: null_mut ( ) ;
119
+ }
94
120
95
121
// internally, we want to align allocations to 64 bytes (in part for SIMD reasons)
96
122
let layout = Layout :: from_size_align ( size, ALIGN . into ( ) ) . unwrap ( ) ;
97
123
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) } ;
99
127
100
128
ptr as * mut c_void
101
129
}
@@ -110,11 +138,16 @@ unsafe extern "C" fn zalloc_rust_calloc(
110
138
size : c_uint ,
111
139
) -> * mut c_void {
112
140
let size = count as usize * size as usize ;
141
+ if size == 0 {
142
+ return core:: ptr:: null_mut ( ) ;
143
+ }
113
144
114
145
// internally, we want to align allocations to 64 bytes (in part for SIMD reasons)
115
146
let layout = Layout :: from_size_align ( size, ALIGN . into ( ) ) . unwrap ( ) ;
116
147
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) } ;
118
151
119
152
ptr as * mut c_void
120
153
}
@@ -135,12 +168,24 @@ unsafe extern "C" fn zfree_rust(opaque: *mut c_void, ptr: *mut c_void) {
135
168
return ;
136
169
}
137
170
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
+ }
139
181
140
182
let layout = Layout :: from_size_align ( size, ALIGN . into ( ) ) ;
141
183
let layout = layout. unwrap ( ) ;
142
184
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) } ;
144
189
}
145
190
146
191
#[ cfg( test) ]
@@ -343,21 +388,30 @@ impl Allocator<'_> {
343
388
if self . zfree == RUST . zfree {
344
389
assert_ne ! ( len, 0 , "invalid size for {ptr:?}" ) ;
345
390
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 ( ) ) } ;
347
394
}
348
395
349
396
// 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
+ }
354
407
}
355
408
}
356
409
}
357
410
358
411
#[ cfg( test) ]
359
412
mod tests {
360
413
use core:: sync:: atomic:: { AtomicPtr , Ordering } ;
414
+ use std:: ptr;
361
415
use std:: sync:: Mutex ;
362
416
363
417
use super :: * ;
@@ -504,4 +558,19 @@ mod tests {
504
558
( FAIL . zfree ) ( core:: ptr:: null_mut ( ) , core:: ptr:: null_mut ( ) ) ;
505
559
}
506
560
}
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
+ }
507
576
}
0 commit comments