Skip to content

arrow-buffer: Potential Undefined Behavior Reported by Miri #9289

Closed as not planned

Description

@yilin0518

Hi!

We are a team of researchers studying the memory safety problem in Rust. As part of our ongoing research, we performed random testing on arrow-buffer(version: 57.2.0) and found that the following code snippet is reported as undefined behavior by Miri:

Describe the bug

#![feature(allocator_api)]
use arrow_buffer::*;
fn main() {
    let v11 = 18446744073709551508;
    let mut v12 = builder::NullBufferBuilder::new_with_len(v11);
    let v19 = [false, true, false];
    builder::NullBufferBuilder::append_slice(&mut v12, &v19);
}

The error message miri report is as follows:

error: resource exhaustion: tried to allocate more memory than available to compiler
   --> /home/chenyl/projects/check_UB/arrow-buffer-57.2.0/src/buffer/mutable.rs:135:40
    |
135 |                 let raw_ptr = unsafe { std::alloc::alloc(layout) };
    |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^ resource exhaustion occurred here
    |
    = note: BACKTRACE:
    = note: inside `arrow_buffer::MutableBuffer::with_capacity` at /home/chenyl/projects/check_UB/arrow-buffer-57.2.0/src/buffer/mutable.rs:135:40: 135:65
    = note: inside `arrow_buffer::MutableBuffer::new` at /home/chenyl/projects/check_UB/arrow-buffer-57.2.0/src/buffer/mutable.rs:117:9: 117:38
    = note: inside `arrow_buffer::BooleanBufferBuilder::new` at /home/chenyl/projects/check_UB/arrow-buffer-57.2.0/src/builder/boolean.rs:44:22: 44:55
    = note: inside `arrow_buffer::NullBufferBuilder::materialize` at /home/chenyl/projects/check_UB/arrow-buffer-57.2.0/src/builder/null.rs:223:25: 223:79
    = note: inside `arrow_buffer::NullBufferBuilder::materialize_if_needed` at /home/chenyl/projects/check_UB/arrow-buffer-57.2.0/src/builder/null.rs:216:13: 216:31
    = note: inside `arrow_buffer::NullBufferBuilder::append_slice` at /home/chenyl/projects/check_UB/arrow-buffer-57.2.0/src/builder/null.rs:173:13: 173:41
note: inside `main`
   --> src/main.rs:7:5
    |
  7 |     builder::NullBufferBuilder::append_slice(&mut v12, &v19);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

It seems that the NullBufferBuilder::materialize will create a BooleanBufferBuilder with the given length and capacity, but without a capacity limitation, thus user can specify a vey large capacity and exceed the size that compiler can allocate.

To Reproduce

  1. Copy this code snippet.
  2. Select the rustc version: nightly-2025-12-06-x86_64-unknown-linux-gnu.
  3. Install the miri, run cargo miri run.

Expected behavior

There should not be any undefined behavior.

Additional context

The OS I use is Linux Ubuntu.

We’d appreciate it if you could take a look and confirm whether this behavior indicates a real issue, or if it’s a false positive or an expected limitation of Miri.

Thank you very much for your time and for maintaining this great project!

Activity

  1. Jefffrey commented on Jan 28, 2026

    @Jefffrey
    Contributor

    I think this might be a false positive. Looking at alloc:

    let data = match layout.size() {
    0 => dangling_ptr(),
    _ => {
    // Safety: Verified size != 0
    let raw_ptr = unsafe { std::alloc::alloc(layout) };
    NonNull::new(raw_ptr).unwrap_or_else(|| handle_alloc_error(layout))
    }
    };

    We have code to call handle_alloc_error in case the returned pointer is null, which is returned if memory is exhausted:

    Returning a null pointer indicates that either memory is exhausted or layout does not meet this allocator’s size or alignment constraints.

    For reference, the other calls to alloc have the same handling:

    _ => {
    // Safety: Verified size != 0
    let raw_ptr = unsafe { std::alloc::alloc_zeroed(layout) };
    NonNull::new(raw_ptr).unwrap_or_else(|| handle_alloc_error(layout))
    }

    let data = match self.layout.size() {
    // Safety: new_layout is not empty
    0 => unsafe { std::alloc::alloc(new_layout) },
    // Safety: verified new layout is valid and not empty
    _ => unsafe { std::alloc::realloc(self.as_mut_ptr(), self.layout, capacity) },
    };
    self.data = NonNull::new(data).unwrap_or_else(|| handle_alloc_error(new_layout));

  2. yilin0518 commented on Jan 28, 2026

    @yilin0518
    Author

    I think this might be a false positive. Looking at alloc:

    arrow-rs/arrow-buffer/src/buffer/mutable.rs

    Lines 131 to 138 in a49af1d

    let data = match layout.size() {
    0 => dangling_ptr(),
    _ => {
    // Safety: Verified size != 0
    let raw_ptr = unsafe { std::alloc::alloc(layout) };
    NonNull::new(raw_ptr).unwrap_or_else(|| handle_alloc_error(layout))
    }
    };
    We have code to call handle_alloc_error in case the returned pointer is null, which is returned if memory is exhausted:

    Returning a null pointer indicates that either memory is exhausted or layout does not meet this allocator’s size or alignment constraints.

    For reference, the other calls to alloc have the same handling:

    arrow-rs/arrow-buffer/src/buffer/mutable.rs

    Lines 163 to 167 in a49af1d

    _ => {
    // Safety: Verified size != 0
    let raw_ptr = unsafe { std::alloc::alloc_zeroed(layout) };
    NonNull::new(raw_ptr).unwrap_or_else(|| handle_alloc_error(layout))
    }
    arrow-rs/arrow-buffer/src/buffer/mutable.rs

    Lines 346 to 352 in a49af1d

    let data = match self.layout.size() {
    // Safety: new_layout is not empty
    0 => unsafe { std::alloc::alloc(new_layout) },
    // Safety: verified new layout is valid and not empty
    _ => unsafe { std::alloc::realloc(self.as_mut_ptr(), self.layout, capacity) },
    };
    self.data = NonNull::new(data).unwrap_or_else(|| handle_alloc_error(new_layout));

    So the expected behaviour of allocation failure is program abort? Could you consider that adding a safety comment about the new_with_len to remind user that create a new NullBufferBuilder with a very large capacity maybe result in allocation fail?

  3. Jefffrey commented on Jan 28, 2026

    @Jefffrey
    Contributor

    So the expected behaviour of allocation failure is program abort? Could you consider that adding a safety comment about the new_with_len to remind user that create a new NullBufferBuilder with a very large capacity maybe result in allocation fail?

    Personally, I feel this goes without saying? There isn't much else to do otherwise 🤔

  4. yilin0518 commented on Jan 28, 2026

    @yilin0518
    Author

    So the expected behaviour of allocation failure is program abort? Could you consider that adding a safety comment about the new_with_len to remind user that create a new NullBufferBuilder with a very large capacity maybe result in allocation fail?

    Personally, I feel this goes without saying? There isn't much else to do otherwise 🤔

    You are right, thank you for your reply and I close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions