[Bug 298159] read(2)/write(2) of >= 2 GiB return more bytes than requested: vn_io_fault1() truncates the EFAULT-redo advance to int
- Reply: bugzilla-noreply_a_freebsd.org: "[Bug 298159] read(2)/write(2) of >= 2 GiB return more bytes than requested: vn_io_fault1() truncates the EFAULT-redo advance to int"
- Reply: bugzilla-noreply_a_freebsd.org: "[Bug 298159] read(2)/write(2) of >= 2 GiB return more bytes than requested: vn_io_fault1() truncates the EFAULT-redo advance to int"
- Reply: bugzilla-noreply_a_freebsd.org: "[Bug 298159] read(2)/write(2) of >= 2 GiB return more bytes than requested: vn_io_fault1() truncates the EFAULT-redo advance to int"
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 04 Sep 2026 07:43:31 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=298159
Bug ID: 298159
Summary: read(2)/write(2) of >= 2 GiB return more bytes than
requested: vn_io_fault1() truncates the EFAULT-redo
advance to int
Product: Base System
Version: 15.1-RELEASE
Hardware: Any
OS: Any
Status: New
Severity: Affects Some People
Priority: ---
Component: kern
Assignee: bugs@FreeBSD.org
Reporter: tw@waldmann-edv.de
Attachment #274396 text/plain
mime type:
Created attachment 274396
--> https://bugs.freebsd.org/bugzilla/attachment.cgi?id=274396&action=edit
reproducer: single 2 GiB read() into a partially resident buffer
Affects: releng/10.0 through releng/15.1 and main (sys/kern/vfs_vnops.c,
vn_io_fault1()).
Filesystems: any with MNTK_NO_IOPF that does not rewind the uio on EFAULT: ZFS,
UFS reads, NFS client, msdosfs, nullfs over those.
Verified on 15.1-RELEASE amd64 with the stock GENERIC kernel
(releng/15.1-n283562-96841ea08dcf), on ZFS (OpenZFS 2.4.2) and on UFS.
Description
vn_io_fault1() first calls the VOP with page faults disabled. If the copy hits
a non-resident user page it gets EFAULT back with the uio already advanced by
the bytes transferred so far, then advances its clone to that point with
uiomove(NULL, resid - uio->uio_resid, uio_clone);
uiomove(9) takes an int count; resid - uio->uio_resid is a size_t. When the
first attempt transferred 2 GiB or more before faulting, (int) of that is
negative, uiomove() is a no-op, the clone stays at the original position, and
the retry loop re-does the whole request from the beginning while still
subtracting every chunk from uio->uio_resid. The syscall ends with a negative
uio_resid and dofileread()/dofilewrite() return nbyte + (bytes done before the
fault). Data ends up correct; the count and the file offset are wrong. Nothing
asserts uio_resid >= 0 on this path, even with INVARIANTS.
Reproduction (attached faultread2.c / faultwrite.c / gen.c)
./gen /pool/counters.bin 3221225472 ./faultread2 /pool/counters.bin 2415919104
18
allocates a fresh anonymous mapping, touches the first 2.25 GiB, then does a
single read() of 3221225454 bytes at offset 18. Result on 15.1:
read() returned 5637144540; debug.vn_io_faults delta=1; fd position
after=5637144558 (expected 3221225472) *** BUG: returned 2415919086 bytes MORE
than requested (= prefix - 18, ZFS record aligned) content check over
3221225454 bytes: 0 bad words
With a 1 GiB or 2 GiB - 128 KiB touched prefix the count is correct. write()
shows the same (3221225472 requested, 5637144576 returned, file size correct).
With debug.vn_io_fault_enable=0 the count is correct.
Real-world impact: borgbackup 1.x reads its repository index with one read() of
the whole file; users on FreeBSD/ZFS hosting (Hetzner StorageBox, rsync.net)
with indexes above 2 GiB have been getting "raw readinto() returned invalid
length N (should have been between 0 and M)" from CPython since 2022
(https://github.com/borgbackup/borg/issues/6140,
https://github.com/python/cpython/issues/93287). All reported N/M pairs satisfy
N = M + P - 18 with P a 128 KiB boundary in (2^31, 2^32), i.e. exactly this
bug.
Fix
See https://github.com/freebsd/freebsd-src/pull/2407 .
A KASSERT(uio->uio_resid >= 0) at the end of vn_io_fault1() would have caught
this.
--
You are receiving this mail because:
You are the assignee for the bug.