Close the out-of-bounds hole in ReadBytes/WriteBytes#74
Merged
Conversation
bbReadBytes/bbWriteBytes passed a caller-supplied count straight to the stream
layer after only a single composite debugBank(b, offset+count-1) check, with no
count<=0 guard and no independent start-offset check. Two arbitrary OOB memory
primitives reachable from pure Blitz code:
- ReadBytes(bank, s, 10, -5): composite index 4 is in range -> passes -> a negative
count widens to a huge std::streamsize in the stream read -> massive OOB write.
- ReadBytes(bank, s, -100, 200): composite index 99 is in range -> passes -> reads
into data-100 -> OOB underflow.
This is the same audit class bbCopyBank already fixed (count<=0 + independent start
and end checks); ReadBytes/WriteBytes were the unfinished tail and still carried the
old dead //if(debug){ scaffolding. Mirror CopyBank's guards in both functions and
drop the dead comments. Valid (positive, in-range) calls are byte-identical.
Advances INTENT's "no silent corruption / bounds checks always worth doing" goal.
tests/BankBoundsTest.bb: add a WriteBytes->file->ReadBytes round-trip plus
negative-count no-op cases for both (WriteBytes(-1) leaves FileSize 0; ReadBytes(-1)
leaves a sentinel-filled bank unchanged). The blocks completing is the regression
guard, since pre-fix the negative count drove an out-of-bounds transfer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause (two OOB primitives), the CopyBank idiom mirrored, the pile-on tradeoff (impact over the anti-pile-on heuristic; steer to diagnostics/DevEx next), and deferred follow-ups (seTranslator, compiler diagnostics, qstreambuf new[]/delete). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced May 30, 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.
Non-technical summary
ReadBytes/WriteBytes(which transfer raw bytes between a memory bank and a file/socket stream) trusted the bytecountyou handed them. A negative count — or a negative offset with a compensating count — slipped past the single bounds check and drove an out-of-bounds memory read/write from ordinary Blitz code: an arbitrary read/write primitive. This PR closes that hole using the same guard pattern the project already applied toCopyBank. Advances INTENT's "no silent corruption — bounds checks always worth doing."Technical summary
bbReadBytes/bbWriteBytesinsrc/blitzrc/bbruntime/bbbank.cppvalidated only the compositedebugBank(b, offset+count-1)index — nocount<=0guard, no independent start-offset check:ReadBytes(bank, s, 10, -5)→ composite index4is in range → passes →s->read(data+10, -5); the negative count widens to a hugestd::streamsize→ massive OOB write into the bank.ReadBytes(bank, s, -100, 200)→ composite index99is in range → passes → reads intodata-100→ OOB underflow.bbCopyBank(Round-4 audit) already fixed this exact class withif(count<=0) return;+ independent start and enddebugBankchecks. ReadBytes/WriteBytes were the unfinished tail (they still carried the old dead//if(debug){scaffolding). This PR mirrors CopyBank's guards in both functions and removes the dead comments. In debug builds a bad index raises a clean BlitzRuntimeError; otherwise the call no-ops and returns 0. Valid positive, in-range calls are byte-identical.Acceptance criteria + results
WriteBytes/ReadByteswithcount <= 0return 0 without touching the stream — verified:WriteBytes(..., -1)leaves the outputFileSize = 0;ReadBytes(..., -1)leaves a sentinel-filled bank unchanged; both blocks complete (no crash) — the regression guard, since pre-fix the negative count drove an OOB transfer.WriteBytes, file → bank viaReadBytes) reproduces the bytes exactly.BankBoundsTest.bb(CopyBank) + fulltest.batgreen; local build 0 errors; corpus sweep unaffected.Trade-offs, deferred follow-ups
bbStream::read/write; CopyBank/Peek/Poke (already correct).seTranslatormissing-break(mislabels native crashes); human-readable compiler diagnostics (source line + caret,blitzide-gated);qstreambufnew[]/deletemismatch (stdutil.cpp).🤖 Generated with Claude Code