Skip to content

fix: UB in aligned buffer, rename CachedDevice, dedup zero-fill logic#5

Open
SF-Zhou wants to merge 3 commits intomainfrom
dev
Open

fix: UB in aligned buffer, rename CachedDevice, dedup zero-fill logic#5
SF-Zhou wants to merge 3 commits intomainfrom
dev

Conversation

@SF-Zhou
Copy link
Copy Markdown
Owner

@SF-Zhou SF-Zhou commented Apr 20, 2026

Summary

  • Fix undefined behavior in aligned buffer allocation
  • Rename CachedDevice to BlockDevice for clarity
  • Extract zero_fill_range helper to deduplicate hole-filling logic

Changes

  1. src/bin/blkreader.rs: Replace unsafe alloc_aligned_buffer (which had UB on drop) with AlignedBuffer struct that tracks the allocation Layout for correct deallocation
  2. src/cache.rs + src/reader.rs: Rename CachedDevice to BlockDevice — eliminates semantic confusion where uncached handles also used the "Cached" type name
  3. src/reader.rs: Extract zero_fill_range helper to eliminate repeated zero-fill + state-update code in read_from_device (4 call sites unified)

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the block-reading path by fixing aligned-buffer allocation safety, clarifying block-device handle naming, and deduplicating repeated zero-fill logic used when reading holes/unwritten extents.

Changes:

  • Replace the CLI’s unsafe aligned buffer allocation with an AlignedBuffer wrapper that deallocates using the original Layout.
  • Rename CachedDevice to BlockDevice across the cache + reader code to better reflect semantics.
  • Extract a zero_fill_range helper in ReadContext to consolidate repeated zero-fill + state update logic.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/reader.rs Introduces zero_fill_range and applies it to hole/unwritten/trailing-hole filling; updates device handle types to BlockDevice.
src/cache.rs Renames cached device entry type to BlockDevice and updates cache and open helpers accordingly.
src/bin/blkreader.rs Adds AlignedBuffer for aligned direct-I/O buffers and removes the prior Vec::from_raw_parts allocation helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/reader.rs
Comment on lines +217 to +219
let len = (fill_end - *current_offset) as usize;
buf[*bytes_read..*bytes_read + len].fill(0);
*bytes_read += len;
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zero_fill_range assumes fill_end >= *current_offset and that *bytes_read + len <= buf.len(). If either invariant is violated (e.g., unexpected/unsorted FIEMAP extents or logic changes), this will underflow and/or panic due to an out-of-bounds slice. Consider using checked_sub for len, validating the target range against buf.len(), and returning an io::Error (or at least early-return) instead of panicking via slice indexing.

Suggested change
let len = (fill_end - *current_offset) as usize;
buf[*bytes_read..*bytes_read + len].fill(0);
*bytes_read += len;
let len = match fill_end
.checked_sub(*current_offset)
.and_then(|delta| usize::try_from(delta).ok())
{
Some(len) => len,
None => return,
};
let end = match bytes_read.checked_add(len) {
Some(end) => end,
None => return,
};
let Some(fill_range) = buf.get_mut(*bytes_read..end) else {
return;
};
fill_range.fill(0);
*bytes_read = end;

Copilot uses AI. Check for mistakes.
Comment thread src/reader.rs
Comment on lines +209 to +221
/// Zero-fill a range in the buffer from current_offset to fill_end,
/// updating bytes_read and current_offset.
fn zero_fill_range(
buf: &mut [u8],
bytes_read: &mut usize,
current_offset: &mut u64,
fill_end: u64,
) {
let len = (fill_end - *current_offset) as usize;
buf[*bytes_read..*bytes_read + len].fill(0);
*bytes_read += len;
*current_offset = fill_end;
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new hole-filling helper changes behavior but there are no tests covering hole/unwritten/trailing-hole zero-fill paths in read_from_device. Adding unit tests for zero_fill_range (including boundary conditions like filling exactly to the end of buf) and at least one integration-style test that exercises read_from_device zero-fill behavior would help prevent regressions.

Copilot uses AI. Check for mistakes.
Comment thread src/bin/blkreader.rs
Comment on lines +30 to +36
fn new(size: usize, align: usize) -> Self {
let layout = Layout::from_size_align(size, align).expect("invalid alignment");
let ptr = unsafe { alloc_zeroed(layout) };
if ptr.is_null() {
handle_alloc_error(layout);
}
Self { ptr, size, layout }
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AlignedBuffer::new will panic on invalid align (0 or non-power-of-two) due to Layout::from_size_align(...).expect(...), and later code also assumes power-of-two alignment in align_down/align_up. Since alignment is user-provided CLI input, validate it up-front (non-zero, power-of-two, and not larger than the allocated buffer/chunk size) and return a structured io::Error/clap validation error instead of panicking.

Suggested change
fn new(size: usize, align: usize) -> Self {
let layout = Layout::from_size_align(size, align).expect("invalid alignment");
let ptr = unsafe { alloc_zeroed(layout) };
if ptr.is_null() {
handle_alloc_error(layout);
}
Self { ptr, size, layout }
fn validate_alignment(size: usize, align: usize) -> io::Result<()> {
if align == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"alignment must be non-zero",
));
}
if !align.is_power_of_two() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"alignment must be a power of two",
));
}
if align > size {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"alignment must not be larger than the buffer size",
));
}
Ok(())
}
fn new(size: usize, align: usize) -> io::Result<Self> {
Self::validate_alignment(size, align)?;
let layout = Layout::from_size_align(size, align).map_err(|_| {
io::Error::new(io::ErrorKind::InvalidInput, "invalid size/alignment layout")
})?;
let ptr = unsafe { alloc_zeroed(layout) };
if ptr.is_null() {
handle_alloc_error(layout);
}
Ok(Self { ptr, size, layout })

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants