Skip to content

Potential soundnessissue in AllocRingBuffer::new for very large capacities #148

@cicilzx

Description

@cicilzx

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions