fix(python): report an oversize file as EFBIG and a read-only mount as EROFS - #424
mutewinter wants to merge 3 commits into
Conversation
…as EROFS The worker's errno table had EFBIG as 27, which is EINTR under Emscripten's numbering. CPython retries an open() that fails with EINTR, and each retry leaked the stream Emscripten allocated for the failed open, so reading a file over maxStringLength looped until the descriptor table ran out and the script died of EMFILE, taking any later import with it. A read the bridge refused as too large for its buffer surfaced as ENOENT, and a write into a read-only mount as EIO. EFBIG is 22, ENODATA 116, and the two failures carry their own errno.
|
@mutewinter is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
🤖 auto-maintain reviewAutomated, advisory triage for
Review panel: 🔴 high highest severity just-bash maintainer code review: 🔴 high
General code review: 🟡 medium
Adversarial security: 🟢 low
Adversarial security (second opinion): 🟡 medium
Standard Bash and host portability: 🟡 medium
Posted by auto-maintain. This automated code review is advisory; a human maintainer makes the call. |
An append opens with O_CREAT, so a read the bridge refused as too large fell into the create branch, was treated as an empty file, and close() wrote the appended bytes back over the whole file. The size failure is now recognized before the fallback and raises EFBIG, leaving the file as it was.
|
Good catch on the append path, and it was real: with the first commit, |
…ews asked for An append opens with O_CREAT, so a read the bridge refused as too large fell into the create fallback and close() wrote the appended bytes back over the whole file; the size failure is classified first now and raises EFBIG (vercel-labs/just-bash#424 review). The program runs in a types.ModuleType('__main__') registered in sys.modules rather than a bare dict, so pickle, unittest.main() and doctest find what it defines; a compile-time SyntaxError prints from the exception alone the way CPython prints it rather than naming the wrapper; and every non-None, non-integer sys.exit value prints and exits 1 (#425 review). Guarded by two more cases in create-bash-env-python.test.ts.
Problem
Any file over
maxStringLength(8 MB by default through the bridge buffer) does this, after a noticeable pause, and the second line is the stdlib zip: the interpreter cannot importtracebackto print the first error. Two smaller ones: a file the bridge refuses as too large for its buffer is reported asFileNotFoundErrorfor a file that exists, and a write into a read-only mount isOSError: [Errno 29] I/O errorrather than theEROFSthe mount actually raised.Cause
The errno table in
createHOSTFShasUnder Emscripten's numbering 27 is
EINTR;EFBIGis 22 (import errno; errno.EFBIGin the vendored build says so, andos.strerror(22)isFile too large). CPython retries anopen()that fails withEINTR(PEP 475), and Emscripten'sFS.openhas already allocated the stream whenstream_ops.openthrows, so every retry leaks a descriptor until the table is exhausted and the retry fails with the realEMFILE, 33.ENODATA: 42is wrong the same way (116).The two mappings:
stream_ops.opencatches everybackend.readFilefailure asENOENT, so the bridge'sResult too large: N > 8388608reads as a missing file; andtryFSOperationhas no branch forread-only/erofs, so the mount'sEROFS: read-only file system, ...falls through toEIO.python3.security.test.tsasserted[Errno 27]for the truncate case, which is how the wrong number stayed in place: the test was checking the message the bug produced.Fix
EFBIGis 22 andENODATA116;tryFSOperationmapsread-only/erofstoEROFSandtoo largetoEFBIG;stream_ops.openthrowsEFBIGrather thanENOENTwhen the read failed as too large.Scope
Unchanged:
maxFileSizeand where it is enforced; a missing file is stillENOENT.Not addressed, deliberately: the 8 MB per-operation bridge buffer (
Size.DATA_BUFFER), which is what makes a 9 MB file unreadable at anymaxStringLength. Chunked reads across the bridge would lift it; that is a protocol change and a separate PR if you want it.Tests
python3.files.test.ts: a 256-byte file undermaxStringLength: 128raisesOSError: [Errno 22] File too largeonce, with noNo file descriptors available; a write into anOverlayFs({ readOnly: true })mount raises[Errno 69] Read-only file systemand creates nothing, while a read of the same mount works.python3.security.test.ts: the truncate assertion now names[Errno 22] File too large. Both new tests fail before the change with the output quoted above.Suite: 240 passed, 2 skipped across the 16 files.
Authored with Claude Opus 5