Skip to content

Commit

Permalink
Add support for libc::readv
Browse files Browse the repository at this point in the history
- This enables vectorized reads.

Signed-off-by: shamb0 <[email protected]>
  • Loading branch information
shamb0 committed Dec 17, 2024
1 parent b9a24fc commit 279090c
Show file tree
Hide file tree
Showing 5 changed files with 656 additions and 0 deletions.
47 changes: 47 additions & 0 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,53 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(value_place)
}

/// Dereferences a pointer to access an element within a source array, with specialized bounds checking
/// for vectored I/O operations like readv().
///
/// This function provides array-aware bounds checking that is specifically designed for situations
/// where we need to access multiple independent memory regions, such as when processing an array
/// of iovec structures. Unlike simple pointer arithmetic bounds checking, this implementation
/// understands and validates array-based access patterns.
fn deref_pointer_and_offset_vectored(
&self,
op: &impl Projectable<'tcx, Provenance>,
offset_bytes: u64,
base_layout: TyAndLayout<'tcx>,
count: usize,
value_layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
// 1. Validate the iovec array bounds.
let array_size = base_layout
.size
.bytes()
.checked_mul(count as u64)
.ok_or_else(|| err_ub_format!("iovec array size overflow"))?;

// 2. Check if our offset is within the array.
if offset_bytes >= array_size {
throw_ub_format!(
"{}",
format!(
"iovec array access out of bounds: offset {} in array of size {}",
offset_bytes, array_size
)
);
}

// 3. Ensure the iovec structure we're accessing is fully contained.
if offset_bytes.checked_add(base_layout.size.bytes()).is_none_or(|end| end > array_size) {
throw_ub_format!("iovec structure would extend past array bounds");
}

// 4. Proceed with the dereferencing.
let this = self.eval_context_ref();
let op_place = this.deref_pointer_as(op, base_layout)?;
let offset = Size::from_bytes(offset_bytes);

let value_place = op_place.offset(offset, value_layout, this)?;
interp_ok(value_place)
}

fn deref_pointer_and_read(
&self,
op: &impl Projectable<'tcx, Provenance>,
Expand Down
129 changes: 129 additions & 0 deletions src/shims/unix/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,135 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
interp_ok(())
}

fn readv(
&mut self,
fd_num: i32,
iov_ptr: &OpTy<'tcx>,
iovcnt: i32,
offset: Option<i128>,
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();

// Early returns for empty or invalid cases
if iovcnt == 0 {
trace!("readv: iovcnt is 0, returning 0 bytes read.");
return this.write_scalar(Scalar::from_i32(0), dest);
}

let Some(fd) = this.machine.fds.get(fd_num) else {
trace!("readv: FD not found");
return this.set_last_error_and_return(LibcError("EBADF"), dest);
};
trace!("readv: FD mapped to {fd:?}");

// Convert count only once at the start
let iovcnt = usize::try_from(iovcnt).expect("iovcnt conversion to usize failed");

// Get iovec layout information
let iovec_layout = this.libc_ty_layout("iovec");

// Create temporary storage for read results
// We need temporary storage for each individual read operation's result
// Using an intermediate buffer helps handle error conditions cleanly
// We use i128 to safely handle both success (positive) and error (-1) cases
let read_dest = this.allocate(this.machine.layouts.i128, MiriMemoryKind::Machine.into())?;

// Use usize to match ssize_t semantics while staying platform-independent
let mut total_bytes_read: usize = 0;

let mut current_offset = offset;

// Process each iovec structure
for i in 0..iovcnt {
// Access the current iovec structure
let offset_bytes = iovec_layout
.size
.bytes()
.checked_mul(i as u64)
.expect("iovec array index calculation overflow");

let current_iov = this.deref_pointer_and_offset_vectored(
iov_ptr,
offset_bytes,
iovec_layout,
iovcnt,
iovec_layout,
)?;

// Extract buffer information
let iov_base = this.project_field_named(&current_iov, "iov_base")?;
let iov_base_ptr = this.read_pointer(&iov_base)?;

let iov_len = this.project_field_named(&current_iov, "iov_len")?;
let iov_len = usize::try_from(this.read_target_usize(&iov_len)?)
.expect("iovec length exceeds platform size");

if iov_len == 0 {
continue;
}

// Validate buffer access
let buffer_size = Size::from_bytes(iov_len);
this.check_ptr_access(iov_base_ptr, buffer_size, CheckInAllocMsg::MemoryAccessTest)?;

// Perform the read operation
let read_result = if let Some(off) = current_offset {
// Handle pread case
let Ok(off) = u64::try_from(off) else {
return this.set_last_error_and_return(LibcError("EINVAL"), dest);
};

fd.as_unix().pread(
this.machine.communicate(),
off,
iov_base_ptr,
iov_len,
&read_dest,
this,
)?;
this.read_scalar(&read_dest)?.to_i128()?
} else {
// Handle regular read case
fd.read(&fd, this.machine.communicate(), iov_base_ptr, iov_len, &read_dest, this)?;
this.read_scalar(&read_dest)?.to_i128()?
};

// Handle read result
if read_result < 0 {
this.write_int(-1, dest)?;
return interp_ok(());
}

// Update offset for next read if preadv
if let Some(off) = current_offset.as_mut() {
// Safe addition with overflow check for offset
*off = off.checked_add(read_result).expect("file offset calculation overflow");
}

let read_result = usize::try_from(read_result).unwrap();

// Safe addition with overflow check
total_bytes_read = total_bytes_read
.checked_add(read_result)
.expect("total bytes read calculation overflow");

// Break if we hit EOF (partial read)
// Convert read_result to unsigned safely for comparison
if read_result < iov_len {
break;
}
}

trace!("readv: Total bytes read: {}", total_bytes_read);
this.write_int(
u64::try_from(total_bytes_read).expect("total bytes read exceeds u64 capacity"),
dest,
)?;

interp_ok(())
}

fn write(
&mut self,
fd_num: i32,
Expand Down
6 changes: 6 additions & 0 deletions src/shims/unix/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let count = this.read_target_usize(count)?;
this.read(fd, buf, count, None, dest)?;
}
"readv" => {
let [fd, iov, iovcnt] = this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
let fd = this.read_scalar(fd)?.to_i32()?;
let iovcnt = this.read_scalar(iovcnt)?.to_i32()?;
this.readv(fd, iov, iovcnt, None, dest)?;
}
"write" => {
let [fd, buf, n] = this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
let fd = this.read_scalar(fd)?.to_i32()?;
Expand Down
Loading

0 comments on commit 279090c

Please sign in to comment.