Fix bytes_to_read() SystemError on Python 3.14 (FIONREAD undersized buffer) - #1077
Open
tagilman wants to merge 1 commit into
Open
Fix bytes_to_read() SystemError on Python 3.14 (FIONREAD undersized buffer)#1077tagilman wants to merge 1 commit into
tagilman wants to merge 1 commit into
Conversation
…uffer) FIONREAD writes back a C int (4 bytes) but bytes_to_read() handed fcntl.ioctl a 2-byte buffer and unpacked a short. Prior to Python 3.14 this was silently tolerated (ioctl copies into a 1024-byte static buffer and returns the first N bytes); 3.14 tightened fcntl.ioctl to raise SystemError on the buffer mismatch. Size the buffer to a full int and unpack accordingly. Same root cause as the TIOCGWINSZ fix in pyinvoke#1038 / pyinvoke#1040, which did not touch this code path. Fixes pyinvoke#1070.
Mukller
approved these changes
Aug 23, 2026
Mukller
left a comment
There was a problem hiding this comment.
Verified as far as Windows allows (real CPython 3.14.5 via uv, editable install of the branch):
bytes_to_readguard paths work on 3.14/Windows: non-ttyStringIOand fileno-less objects return1without touching the ioctl branch.- The size claim is arithmetically exact: the old buffer
b" "is 2 bytes, while FIONREAD writes back a Cint(4 bytes) — so on 3.14's stricterfcntl.ioctlthis is a genuine buffer mismatch, not a style issue. The fix'sstruct.pack("i", 0)/unpack("i", ...)pair is symmetric at exactly 4 bytes. - The change mirrors the maintainer-accepted TIOCGWINSZ fixes (#1038/#1040) — same root cause class, same remedy, and per the PR description deliberately scoped away from that earlier patch.
Honest limits: the failing path requires a POSIX tty + fcntl, which I cannot exercise on Windows; their CI (Linux, including 3.14 jobs) is green, and the updated fake_ioctl in tests/runners.py now packs "i" consistently with the production code, so the mocked round-trip validates the new sizing.
Approving — this unblocks every POSIX user on 3.14, where the current release raises SystemError on any tty-driven read.
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.
Fixes #1070.
Problem
On Python 3.14,
invoke.terminals.bytes_to_read()raisesSystemError: buffer overflow:FIONREADwrites back a Cint(4 bytes), but the buffer passed is only2 bytes. Before 3.14, CPython's
fcntl.ioctlcopied an immutable bufferinto a 1024-byte static scratch buffer and returned only the first
len(buf)bytes, so the undersized buffer was silently tolerated(and the 4-byte result truncated to 2). Python 3.14 tightened
fcntl.ioctlto raiseSystemErrorwhen the ioctl would write morebytes than the passed buffer holds (cpython#144206).
This is the same root cause as #1038, whose fix (#1040) only touched
the
TIOCGWINSZpath in_pty_size()and did not address this secondoccurrence in
bytes_to_read(). It bites anyRunner.run(pty=True)froma TTY-attached process on 3.14.
Fix
Size the buffer to a full native
intand unpack it as one:Tests
skip()pedterminals.bytes_to_read_.returns_FIONREAD_result_when_stream_is_a_ttyas a real regression test that drives the actual
ioctlagainst apty (a mock can't reproduce 3.14's buffer-size check). It asserts the
call does not raise, returns a non-negative int, and — to guard against
reintroducing the undersized buffer — that the buffer handed to
FIONREADisint-sized. Verified it fails withSystemError: buffer overflowon 3.14 without the fix and passes with it.FIONREADmock intests/runners.py(
reads_FIONREAD_bytes_from_stdin_when_fileno) tostruct.pack("i", …)to match.
Verified on Python 3.14.6 and 3.12.13 (
tests/terminals.py: 7 passed,1 skipped on both).
flake8clean.Note: the broader test suite still has unrelated Python 3.14 failures
(e.g.
tests/_util.py'sfakereadharness hardcodes fd numbers) that areindependent of this change and part of the wider 3.14-support effort
(#1015 / #1052); this PR is scoped to #1070 and introduces no new
failures (it turns 7 previously-failing FIONREAD-dependent tests green).
Thanks to @RemiDesgrange for the report and root-cause analysis in #1070.