-
-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Summary
The with_size_unzeroed
function creates a ByteView
with uninitialized memory, but accessing this memory through indexing (bytes[0]
) causes undefined behavior as detected by Miri.
Reproduction
#[test]
fn test_with_size_unzeroed() {
let bytes = ByteView::with_size_unzeroed(INLINE_SIZE + 1);
let b = bytes[0]; // UB: reading uninitialized memory
std::hint::black_box(b);
}
Miri Output
error: Undefined Behavior: reading memory at alloc106017[0x8..0x9], but memory is uninitialized at [0x8..0x9], and this operation requires initialized memory
--> src/byteview.rs:1299:17
|
1299 | let b = bytes[0];
| ^^^^^^^^ Undefined Behavior occurred here
Command used: cargo +nightly miri test -- test_with_size_unzeroed
Root Cause
Reading from uninitialized memory is undefined behavior in Rust, even if the memory was properly allocated. While creating a slice from uninitialized memory is technically safe, any operation that reads the actual bytes (indexing, iteration, copying, etc.) triggers UB.
Impact
- Any code that reads from a
ByteView
created withwith_size_unzeroed
before writing to it is unsound - This makes the current API footgun-prone and potentially unsafe
- Miri will catch this in tests, but release builds may exhibit unpredictable behavior
Proposed Solutions
Option 1: Remove from Public API (Breaking Change)
Remove with_size_unzeroed
entirely from the public API if uninitialized memory access is not a core use case.
// Remove or make private
// pub fn with_size_unzeroed(size: usize) -> Self
Option 2: Add Initialization Flag to Header (Non-breaking)
Extend the internal header/metadata to track whether memory is initialized, preventing accidental reads from uninitialized regions.
struct ByteViewHeader {
// existing fields...
is_initialized: bool,
}
impl ByteView {
pub fn with_size_unzeroed(size: usize) -> Self {
// Create with is_initialized = false
}
// Add bounds checking in Index impl
fn index(&self, index: usize) -> &u8 {
if !self.header().is_initialized {
panic!("Cannot read from uninitialized ByteView");
}
// existing indexing logic...
}
}
Option 3: Use MaybeUninit-based API (Breaking Change)
Replace with_size_unzeroed
with a MaybeUninit<u8>
-based API:
impl ByteView {
pub fn with_size_uninit(size: usize) -> ByteViewUninit {
// Return a wrapper that only allows safe initialization
}
}
pub struct ByteViewUninit {
// Internal implementation
}
impl ByteViewUninit {
pub fn write(&mut self, index: usize, value: u8) { /* ... */ }
pub fn assume_init(self) -> ByteView { /* ... */ }
}