vfs: fix vn_io_fault1() resume point past 2 GiB - #2407
Open
ThomasWaldmann wants to merge 1 commit into
Open
Conversation
|
Thank you for taking the time to contribute to FreeBSD! All issues resolved. |
ThomasWaldmann
force-pushed
the
vn_io_fault1-int-truncation
branch
from
September 3, 2026 21:41
57fa568 to
1ab76bd
Compare
vn_io_fault1() first runs the VOP with page faults disabled. When that attempt returns EFAULT after transferring part of the request, the number of bytes already done (resid - uio->uio_resid, a size_t) is used to advance uio_clone to the resume point with uiomove(9), whose count argument is an int. For 2 GiB and more the count is truncated to a negative int, uiomove() does nothing, the clone stays at the start of the request, and the retry loop re-does the whole transfer while still subtracting every chunk from uio->uio_resid. The syscall ends with a negative uio_resid and read(2)/write(2) return more bytes than were requested; the data itself is correct. This needs a single request of 2 GiB or more into a user buffer that is resident for its first 2 GiB and then has a non-resident page, on a filesystem that sets MNTK_NO_IOPF and does not rewind the uio on EFAULT (ZFS, UFS reads, NFS client, msdosfs, nullfs over those). borgbackup 1.x reads its repository index with one read() of the whole file and has been hitting this on FreeBSD/ZFS hosts since 2022: borgbackup/borg#6140 Split the advance into INT_MAX-sized uiomove() calls. Verified with a reproducer on 15.1-RELEASE (ZFS and UFS, read and write): the retry path is still taken and the returned count is exact. PR: 298159 Fixes: 41014d9 ("vn_io_fault() is a facility to prevent page faults while filesystems") MFC after: 1 week Signed-off-by: Thomas Waldmann <tw@waldmann-edv.de> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ThomasWaldmann
force-pushed
the
vn_io_fault1-int-truncation
branch
from
September 4, 2026 07:48
1ab76bd to
b88623b
Compare
kostikbel
reviewed
Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
vn_io_fault1()advances its clone to the EFAULT resume point withuiomove(NULL, resid - uio->uio_resid, uio_clone). The count is asize_t;uiomove(9)takes anint. When the first (faults-disabled) attempt transferred 2 GiB or more before hitting a non-resident user page, the truncated count is negative, the clone is not advanced, and the retry loop re-does the whole request while still subtracting fromuio->uio_resid.read(2)/write(2)then return more bytes than requested; the data is correct. Present since the originalvn_io_fault()commit (41014d9, 2012), shipped in 9.2 through 15.1.Real-world impact. borgbackup 1.x reads its repository index with a single
read()of the whole file. Users on FreeBSD/ZFS hosting (Hetzner StorageBox, rsync.net) with indexes above 2 GiB have been gettingraw readinto() returned invalid length N (should have been between 0 and M)from CPython since 2022 (borgbackup/borg#6140, python/cpython#93287). Every reported N/M pair isN = M + P - 18withPa 128 KiB ZFS record boundary in (2^31, 2^32), i.e. exactly this truncation.Reproduction (15.1-RELEASE amd64, stock GENERIC kernel; sources: https://gist.github.com/ThomasWaldmann/8b5c0e6f0c376b7a3da2de9fd29e3639):
genwrites a 3 GiB file of counters,faultread2maps a fresh anonymous buffer, touches its first N bytes and issues oneread()of the rest of the file from offset 18.The excess is always
prefix − 18and record aligned; the file offset advances by the same excess; the buffer content is correct.write()of 3221225472 bytes with a 2.25 GiB touched prefix returns 5637144576. Withdebug.vn_io_fault_enable=0the counts are exact.Validation of the fix. Kernel built from the stock GENERIC config on 15.1-RELEASE, installed and booted: every cell above returns the exact count,
write()returns 3221225472, anddebug.vn_io_faultsstill increments, so the retry path is exercised rather than bypassed.Checklist.
tools/build/checkstyle9.plon the commit: 0 errors, 0 warnings; no trailing whitespace. No new Kyua test is included because a test needs a ≥ 3 GiB file and ≥ 2.25 GiB of RAM for the user buffer; I can add one undertests/sys/kernwithrequire.memory/require.diskspacemetadata if that is wanted. Bugzilla: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=298159 (reproducers attached there; the patch is submitted only via this pull request).🤖 Generated with Claude Code