@@ -5,6 +5,7 @@ use std::io;
55use std:: io:: ErrorKind ;
66
77use rustc_abi:: Size ;
8+ use rustc_middle:: ty:: layout:: TyAndLayout ;
89
910use crate :: helpers:: check_min_arg_count;
1011use crate :: shims:: files:: FileDescription ;
@@ -257,6 +258,250 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
257258 interp_ok ( ( ) )
258259 }
259260
261+ /// Reads data from a file descriptor into multiple buffers atomically (vectored I/O).
262+ ///
263+ /// This implementation follows POSIX readv() semantics, reading data from the file descriptor
264+ /// specified by `fd_num` into the buffers described by the array of iovec structures pointed
265+ /// to by `iov_ptr`. The `iovcnt` argument specifies the number of iovec structures in the array.
266+ ///
267+ /// # Arguments
268+ /// * `fd_num` - The file descriptor to read from
269+ /// * `iov_ptr` - Pointer to an array of iovec structures
270+ /// * `iovcnt` - Number of iovec structures in the array
271+ /// * `dest` - Destination for storing the number of bytes read
272+ ///
273+ /// # Returns
274+ /// * `Ok(())` - Operation completed successfully, with total bytes read stored in `dest`
275+ /// * `Err(_)` - Operation failed with appropriate errno set
276+ ///
277+ /// # Errors
278+ /// * `EBADF` - `fd_num` is not a valid file descriptor
279+ /// * `EFAULT` - Part of iovec array or buffers point outside accessible address space
280+ /// * `EINVAL` - `iovcnt` is negative or exceeds system limit
281+ /// * `EIO` - I/O error occurred while reading from the file descriptor
282+ ///
283+ /// # POSIX Compliance
284+ /// Implements standard POSIX readv() behavior:
285+ /// * Performs reads atomically with respect to other threads
286+ /// * Returns exact number of bytes read or -1 on error
287+ /// * Handles partial reads and end-of-file conditions
288+ /// * Respects system-imposed limits on total transfer size
289+ fn readv (
290+ & mut self ,
291+ fd_num : i32 ,
292+ iov_ptr : & OpTy < ' tcx > ,
293+ iovcnt : i32 ,
294+ dest : & MPlaceTy < ' tcx > ,
295+ ) -> InterpResult < ' tcx > {
296+ let this = self . eval_context_mut ( ) ;
297+
298+ // POSIX Compliance: Must handle negative values (EINVAL).
299+ if iovcnt < 0 {
300+ return this. set_last_error_and_return ( LibcError ( "EINVAL" ) , dest) ;
301+ }
302+
303+ // POSIX Compliance: Must handle zero properly.
304+ if iovcnt == 0 {
305+ return this. write_scalar ( Scalar :: from_i32 ( 0 ) , dest) ;
306+ }
307+
308+ // POSIX Compliance: Check if iovcnt exceeds system limits.
309+ // Most implementations limit this to IOV_MAX
310+ // Common system default
311+ // https://github.com/turbolent/w2c2/blob/d94227c22f8d78a04fbad70fa744481ca4a1912e/examples/clang/sys/include/limits.h#L50
312+ const IOV_MAX : i32 = 1024 ;
313+ if iovcnt > IOV_MAX {
314+ trace ! ( "readv: iovcnt exceeds IOV_MAX" ) ;
315+ return this. set_last_error_and_return ( LibcError ( "EINVAL" ) , dest) ;
316+ }
317+
318+ // POSIX Compliance: Validate file descriptor.
319+ let Some ( fd) = this. machine . fds . get ( fd_num) else {
320+ return this. set_last_error_and_return ( LibcError ( "EBADF" ) , dest) ;
321+ } ;
322+
323+ // Convert iovcnt to usize for array indexing.
324+ let iovcnt = usize:: try_from ( iovcnt) . expect ( "iovcnt exceeds platform size" ) ;
325+ let iovec_layout = this. libc_ty_layout ( "iovec" ) ;
326+
327+ // Gather iovec information.
328+ // Pre-allocate vectors for iovec information
329+ let mut iov_info = Vec :: with_capacity ( iovcnt) ;
330+ let mut total_size: u64 = 0 ;
331+
332+ // POSIX Compliance: Validate each iovec structure.
333+ // Must check for EFAULT (invalid buffer addresses) and EINVAL (invalid length).
334+ for i in 0 ..iovcnt {
335+ // Calculate offset to current iovec structure.
336+ let offset = iovec_layout
337+ . size
338+ . bytes ( )
339+ . checked_mul ( i as u64 )
340+ . expect ( "iovec array index overflow" ) ;
341+
342+ // Access current iovec structure.
343+ let current_iov = match this
344+ . deref_pointer_and_offset_vectored (
345+ iov_ptr,
346+ offset,
347+ iovec_layout,
348+ iovcnt,
349+ iovec_layout,
350+ )
351+ . report_err ( )
352+ {
353+ Ok ( iov) => iov,
354+ Err ( _) => {
355+ return this. set_last_error_and_return ( LibcError ( "EFAULT" ) , dest) ;
356+ }
357+ } ;
358+
359+ // Extract and validate buffer pointer and length.
360+ let base_field = this. project_field_named ( & current_iov, "iov_base" ) ?;
361+ let base = this. read_pointer ( & base_field) ?;
362+
363+ let len_field = this. project_field_named ( & current_iov, "iov_len" ) ?;
364+ let len = this. read_target_usize ( & len_field) ?;
365+
366+ // Validate buffer alignment and accessibility.
367+ if this
368+ . check_ptr_access ( base, Size :: from_bytes ( len) , CheckInAllocMsg :: MemoryAccessTest )
369+ . report_err ( )
370+ . is_err ( )
371+ {
372+ return this. set_last_error_and_return ( LibcError ( "EFAULT" ) , dest) ;
373+ }
374+
375+ // Update total size safely.
376+ total_size = match total_size. checked_add ( len) {
377+ Some ( new_size) => new_size,
378+ None => {
379+ return this. set_last_error_and_return ( LibcError ( "EINVAL" ) , dest) ;
380+ }
381+ } ;
382+
383+ iov_info. push ( ( base, len) ) ;
384+ }
385+
386+ // Cap total size to platform limits.
387+ let total_size =
388+ total_size. min ( u64:: try_from ( this. target_isize_max ( ) ) . unwrap ( ) ) . min ( isize:: MAX as u64 ) ;
389+
390+ // Early return for zero total size
391+ if total_size == 0 {
392+ return this. write_scalar ( Scalar :: from_i32 ( 0 ) , dest) ;
393+ }
394+
395+ let mut temp_buf: Vec < u8 > = vec ! [ 0 ; total_size. try_into( ) . unwrap( ) ] ;
396+
397+ // Perform single atomic read operation
398+ let read_result =
399+ fd. read_buffer ( & fd, this. machine . communicate ( ) , & mut temp_buf[ ..] , dest, this) ;
400+
401+ // Handle read result
402+ if read_result. report_err ( ) . is_err ( ) {
403+ return this. set_last_error_and_return ( LibcError ( "EIO" ) , dest) ;
404+ }
405+
406+ // Get bytes read from dest and convert to usize for slice operations.
407+ let read_bytes: usize = this
408+ . read_target_usize ( dest) ?
409+ . try_into ( )
410+ . expect ( "read bytes count exceeds platform usize" ) ;
411+
412+ if read_bytes > 0 {
413+ // Copy data to individual iovec buffers.
414+ let mut current_pos = 0usize ;
415+
416+ for ( base, len) in iov_info {
417+ // Early exit if no more bytes to copy.
418+ if current_pos >= read_bytes {
419+ break ;
420+ }
421+
422+ // Convert len to usize safely and handle potential overflow.
423+ let buffer_len = usize:: try_from ( len) . expect ( "buffer length exceeds platform size" ) ;
424+
425+ // Calculate remaining bytes safely.
426+ let bytes_remaining =
427+ read_bytes. checked_sub ( current_pos) . expect ( "position calculation underflow" ) ;
428+
429+ // Calculate copy size as minimum of buffer length and remaining bytes.
430+ let copy_size = buffer_len. min ( bytes_remaining) ;
431+
432+ // Calculate end position safely.
433+ let slice_end =
434+ current_pos. checked_add ( copy_size) . expect ( "slice end calculation overflow" ) ;
435+
436+ // Verify slice bounds.
437+ if slice_end > temp_buf. len ( ) {
438+ return this. set_last_error_and_return ( LibcError ( "EFAULT" ) , dest) ;
439+ }
440+
441+ // Write the slice to the destination buffer.
442+ if this
443+ . write_bytes_ptr ( base, temp_buf[ current_pos..slice_end] . iter ( ) . copied ( ) )
444+ . report_err ( )
445+ . is_err ( )
446+ {
447+ return this. set_last_error_and_return ( LibcError ( "EIO" ) , dest) ;
448+ }
449+
450+ // Update position safely.
451+ current_pos = slice_end;
452+ }
453+ }
454+
455+ interp_ok ( ( ) )
456+ }
457+
458+ /// Dereferences a pointer to access an element within a source array, with specialized bounds checking
459+ /// for vectored I/O operations like readv().
460+ ///
461+ /// This function provides array-aware bounds checking that is specifically designed for situations
462+ /// where we need to access multiple independent memory regions, such as when processing an array
463+ /// of iovec structures. Unlike simple pointer arithmetic bounds checking, this implementation
464+ /// understands and validates array-based access patterns.
465+ fn deref_pointer_and_offset_vectored (
466+ & self ,
467+ op : & impl Projectable < ' tcx , Provenance > ,
468+ offset_bytes : u64 ,
469+ base_layout : TyAndLayout < ' tcx > ,
470+ count : usize ,
471+ value_layout : TyAndLayout < ' tcx > ,
472+ ) -> InterpResult < ' tcx , MPlaceTy < ' tcx > > {
473+ // 1. Validate the iovec array bounds.
474+ let array_size = base_layout
475+ . size
476+ . bytes ( )
477+ . checked_mul ( count as u64 )
478+ . ok_or_else ( || err_ub_format ! ( "iovec array size overflow" ) ) ?;
479+
480+ // 2. Check if our offset is within the array.
481+ if offset_bytes >= array_size {
482+ throw_ub_format ! (
483+ "{}" ,
484+ format!(
485+ "iovec array access out of bounds: offset {} in array of size {}" ,
486+ offset_bytes, array_size
487+ )
488+ ) ;
489+ }
490+
491+ // 3. Ensure the iovec structure we're accessing is fully contained.
492+ if offset_bytes. checked_add ( base_layout. size . bytes ( ) ) . is_none_or ( |end| end > array_size) {
493+ throw_ub_format ! ( "iovec structure would extend past array bounds" ) ;
494+ }
495+
496+ // 4. Proceed with the dereferencing.
497+ let this = self . eval_context_ref ( ) ;
498+ let op_place = this. deref_pointer_as ( op, base_layout) ?;
499+ let offset = Size :: from_bytes ( offset_bytes) ;
500+
501+ let value_place = op_place. offset ( offset, value_layout, this) ?;
502+ interp_ok ( value_place)
503+ }
504+
260505 fn write (
261506 & mut self ,
262507 fd_num : i32 ,
0 commit comments