-
Notifications
You must be signed in to change notification settings - Fork 27
Description
Hi, thanks for maintaining this crate.
While fuzzing the safe public API AllocRingBuffer::new, I found that it currently rejects capacity == 0, but it does not guard against excessively large non-zero capacities.
Because the constructor computes:
let size = capacity.next_power_of_two();very large capacity values can lead to different failures depending on the build/runtime configuration.
Affected code
#[inline]
#[must_use]
pub fn new(capacity: usize) -> Self {
assert_ne!(capacity, 0, "Capacity must be greater than 0");
let size = capacity.next_power_of_two();
let layout = alloc::alloc::Layout::array::<T>(size).unwrap();
let buf = unsafe { alloc::alloc::alloc(layout).cast() };
Self {
buf,
size,
capacity,
readptr: 0,
writeptr: 0,
}
}Reproducer 1: overflow in debug, zero-sized allocation UB under Miri release
fn main() {
let capacity_0: usize = 16864431761580506389usize;
let _ = ringbuffer::AllocRingBuffer::<char>::new(capacity_0);
}cargo run
thread 'main' (1000) panicked at .../lib/rustlib/src/rust/library/core/src/num/mod.rs:1248:5:
attempt to add with overflow
cargo run --release
This does not panic for me.
cargo +nightly-2025-08-20 miri run --release
error: Undefined Behavior: creating allocation with size 0
--> .../ringbuffer/src/with_alloc/alloc_ringbuffer.rs:326:28
|
326 | let buf = unsafe { alloc::alloc::alloc(layout).cast() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE:
= note: inside `ringbuffer::AllocRingBuffer::<char>::new` at .../ringbuffer/src/with_alloc/alloc_ringbuffer.rs:326:28: 326:55
Reproducer 2: resource exhaustion under Miri release
fn main() {
let capacity_0: usize = 239078165072318867usize;
let _ = ringbuffer::AllocRingBuffer::<char>::new(capacity_0);
}cargo run
This does not panic for me.
cargo +nightly-2025-08-20 miri run --release
error: resource exhaustion: tried to allocate more memory than available to compiler
--> .../ringbuffer/src/with_alloc/alloc_ringbuffer.rs:326:28
|
326 | let buf = unsafe { alloc::alloc::alloc(layout).cast() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ resource exhaustion occurred here
|
= note: BACKTRACE:
= note: inside `ringbuffer::AllocRingBuffer::<char>::new` at .../ringbuffer/src/with_alloc/alloc_ringbuffer.rs:326:28: 326:55
What seems to be happening
The constructor rejects capacity == 0, but it does not reject excessively large non-zero capacities.
For very large inputs, capacity.next_power_of_two() can overflow. When overflow checks are enabled, this results in an overflow panic. When overflow checks are disabled, the overflow can instead produce 0, so the code proceeds with a zero-sized layout and then calls alloc with size 0, which Miri reports as UB. Other large inputs may not hit that exact case, but can still lead to an extremely large allocation attempt and fail due to resource exhaustion.
So the issue does not seem to be limited to zero capacity. Large non-zero capacities also appear to be unsupported, but they are not rejected up front.