ReadWritev.lean
Declarations on this page (8)
Linux Vectored Read and Write Syscalls
Models AArch64 Linux readv (__NR_readv = 65) and writev (__NR_writev = 66), following fs/ (do_readv, do_writev, vfs_readv, vfs_writev, do_loop_readv_writev) and lib/ (import_iovec, copy_iovec_from_user). Vectored transfers execute execRead or execWrite sequentially across the validated struct iovec segments.
@[expose] public sectionnamespace Arm64def SYS_READV
Linux AArch64 readv syscall number (__NR_readv = 65, include/, fs/).
def SYS_READV : BitVec 64 := 65#64def SYS_WRITEV
Linux AArch64 writev syscall number (__NR_writev = 66, include/, fs/).
def SYS_WRITEV : BitVec 64 := 66#64def UIO_MAXIOV
Maximum number of struct iovec segments per call (UIO_MAXIOV = 1024, include/).
def UIO_MAXIOV : Nat := 1024def readIovecs
Copy n 16-byte struct iovec { void *iov_base; size_t iov_len; } entries (include/) starting at user address vec, in order, as (base, len) pairs (lib/ copy_iovec_from_user). On failure returns the final state s with X0 set and PC advanced:
-
-EFAULTif an entry is not readable (unsafe_get_userfault); -
.
Exceptionif an entry is readable but lazily freed (seeMemory.);canRead -
-EINVALifiov_lenhas its sign bit set ((ssize_t)len < 0). Entries are checked one at a time, so an earlier bad length wins over a later fault.
def readIovecs (s : State) (vec : BitVec 64) : Nat → Except State (List (BitVec 64 × BitVec 64))
| 0 => .ok []
| n + 1 =>
if !s.mem.allPerm (·.r) vec 16 then .error (s.writeReg64 Reg.x0 EFAULT_RET).advancePC
else if !s.mem.canRead vec 16 then .error (s.setStatus (.Exception "iovec read from lazy-free memory"))
else
let base := s.mem.read64 vec
let len := s.mem.read64 (vec + 8#64)
if len.slt 0#64 then .error (s.writeReg64 Reg.x0 (-EINVAL)).advancePC
else (readIovecs s (vec + 16#64) n).map ((base, len) :: ·)def execIovLoop
Transfer segments in order with step (execRead or execWrite), matching fs/ do_loop_readv_writev: each segment is one step call on a state whose X1/X2 hold the segment's (base, len) and whose registers and PC are otherwise those of the syscall entry state s0. The loop stops after a short transfer (r < len, returning acc + r) or a negative result r in the first segment (returning r). A step that leaves .Running ends the syscall in that state. On return, the registers are those of s0 except X0 := result, and PC is advanced past the svc.
-
[INTENTIONAL_SCOPE]: A negative result after earlier segments transferredacc > 0bytes fails closed with .Exception(Linuxread_iter/write_itermay returnaccplus a partial count of the failing segment).
def execIovLoop (step : State → State) (s0 : State) :
State → Nat → List (BitVec 64 × BitVec 64) → State
| s, acc, [] => ({ s with regs := s0.regs, pc := s0.pc }.writeReg64 Reg.x0 (BitVec.ofNat 64 acc)).advancePC
| s, acc, (base, len) :: rest =>
let s' := step (({ s with regs := s0.regs, pc := s0.pc }.writeReg64 Reg.x1 base).writeReg64 Reg.x2 len)
match s'.status with
| .Running =>
let r := s'.readReg64 Reg.x0
if r.slt 0#64 then
if acc = 0 then ({ s' with regs := s0.regs, pc := s0.pc }.writeReg64 Reg.x0 r).advancePC
else s'.setStatus (.Exception "vectored transfer failed after partial progress")
else if r.toNat < len.toNat then
({ s' with regs := s0.regs, pc := s0.pc }.writeReg64 Reg.x0
(BitVec.ofNat 64 (acc + r.toNat))).advancePC
else execIovLoop step s0 s' (acc + r.toNat) rest
| _ => s'def execRwvCommon
Shared readv(fd, vec, vlen) (isRead = true, step = execRead) and writev(fd, vec, vlen) (isRead = false, step = execWrite) semantics (fs/ vfs_readv, fs/ vfs_writev). Checks, in Linux order:
-
Descriptor (
fdget_pos-EBADF,FMODE_READ/FMODE_WRITE-EBADF): runsstepwithX1 = X2 = 0as a zero-length probe and keeps only its verdict; a non-.Runningstatus fails closed with that status, and a negativeX0is returned except-EISDIRfrom a readable directory (generic_read_dirruns only in the transfer loop below). -
Segment count:
vlenis truncated tounsigned int(import_iovec'snr_segs); nr_segs >UIO_MAXIOVreturns-EINVAL;nr_segs = 0returns0. -
iovec array: vec + 16 * nr_segs >
USER_VA_LIMITreturns-EFAULT(user_access_beginaccess_ok), then entries are copied byreadIovecs. -
Segment buffers: any base + len >
USER_VA_LIMITreturns-EFAULT(access_ok). -
Zero total: returns
0without a transfer (if (!tot_len) goto out). -
File position: on a .
localFile, offset + total >MAX_LFS_FILESIZEreturns-EINVAL(rw_verify_area:(loff_t)(pos + count) < 0). -
Transfer:
execIovLoop.
-
[INTENTIONAL_SCOPE]: Fails closed with .Exceptionwhen the total length exceedsMAX_RW_COUNT(Linux silently truncates the iovecs toMAX_RW_COUNTbytes), and on any descriptorstepitself does not model.
def execRwvCommon (step : State → State) (isRead : Bool) (s : State) : State :=
let vec := s.readReg64 Reg.x1
let nrSegs := (s.readReg32 Reg.x2).toNat
let probe := step ((s.writeReg64 Reg.x1 0#64).writeReg64 Reg.x2 0#64)
match probe.status with
| .Running =>
let pr := probe.readReg64 Reg.x0
if pr.slt 0#64 && !(isRead && pr == -EISDIR) then (s.writeReg64 Reg.x0 pr).advancePC
else if nrSegs > UIO_MAXIOV then (s.writeReg64 Reg.x0 (-EINVAL)).advancePC
else if nrSegs = 0 then (s.writeReg64 Reg.x0 0#64).advancePC
else if vec.toNat + 16 * nrSegs > USER_VA_LIMIT then (s.writeReg64 Reg.x0 EFAULT_RET).advancePC
else
match readIovecs s vec nrSegs with
| .error s' => s'
| .ok segs =>
let total := (segs.map fun p => p.2.toNat).sum
if total > MAX_RW_COUNT then s.setStatus (.Exception "iovec total exceeds MAX_RW_COUNT")
else if segs.any (fun p => p.1.toNat + p.2.toNat > USER_VA_LIMIT) then
(s.writeReg64 Reg.x0 EFAULT_RET).advancePC
else if total = 0 then (s.writeReg64 Reg.x0 0#64).advancePC
else
let posOverflow : Bool := match s.fs.fds ((s.readReg32 Reg.x0).zeroExtend 64) with
| some desc => desc.kind == .localFile && decide (desc.offset + total > MAX_LFS_FILESIZE)
| none => false
if posOverflow then (s.writeReg64 Reg.x0 (-EINVAL)).advancePC
else execIovLoop step s s 0 segs
| _ => s.setStatus probe.statusdef execReadv
readv(fd, vec, vlen) (__NR_readv = 65, fs/ SYSCALL_DEFINE3(readv)): execRwvCommon over execRead.
def execReadv (s : State) : State :=
execRwvCommon execRead true sdef execWritev
writev(fd, vec, vlen) (__NR_writev = 66, fs/ SYSCALL_DEFINE3(writev)): execRwvCommon over execWrite.
def execWritev (s : State) : State :=
execRwvCommon execWrite false send Arm64