Repository navigation
fix(interpreter): bound script text echoed into arithmetic diagnostics - #2665
Merged
Merged
Conversation
An arithmetic error names the whole expression and then the whole unparsed rest as the "error token". Both come from the script, so an expression whose first rejected character sits near the front was echoed back twice over: one input well under MAX_ARITHMETIC_EXPANSION_BYTES rendered a diagnostic about twice its size, past the 1 KiB stderr budget TM-INF-022 holds every builtin to. Nightly fuzz run 270 (arithmetic_fuzz) tripped assert_no_leak on exactly that: 507 bytes of input, 1,076 bytes of stderr. Real bash emits 1,065 bytes for the same input, so the ceiling is bashkit's own contract rather than a parity bug, and the arithmetic path had never been held to it. diag_echo bounds each echoed fragment at MAX_ARITHMETIC_DIAG_ECHO (256 bytes) on a UTF-8 boundary, applied to the expression echo, the tokenizer and parser error tokens, bad-number and division-by-zero tokens, the [[ ]] operand, and the substring-expression messages. The fixed text that says what went wrong always survives. Expressions at or under the cap are unchanged, byte for byte. A new arithmetic_fuzz scaffold suite carries the crash input into cargo test, so the class is caught per-PR rather than only by the nightly fuzz job. Recorded as L-ARITH-002; TM-INF-022 now states that interpreter-written diagnostics quoting script text must bound the quoted run, not the finished line. Claude-Session: https://claude.ai/code/session_01BegucbcUgCEsPZiAzt6GRx
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
bashkit | 987ff45 | Commit Preview URL Branch Preview URL |
Oct 10 2026, 09:32 AM |
The rustdoc TM-INF-022 row described the ceiling as a builtin concern enforced by the Debug-format scan. Arithmetic diagnostics quote the script back and had their own way past it, so name that path and the cap that now bounds it. Claude-Session: https://claude.ai/code/session_01BegucbcUgCEsPZiAzt6GRx
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.
What changed
An arithmetic error no longer echoes unbounded script text back into stderr.
$(( )),(( )),let,[[ a -eq b ]]and${v:off:len}name at most 256bytes each of the expression and of the "error token" (the unparsed rest), with
a
...marker where either was cut. Expressions at or under the cap areunchanged, byte for byte.
A new
arithmetic_fuzzscaffold suite carries the crash input intocargo test,so this class is caught on every PR instead of only by the nightly fuzz job.
Why
An arithmetic diagnostic names the whole expression and then the whole
remaining source. Both come from the script, so an expression whose first
rejected character sits near the front was echoed back twice over. One input
well under
MAX_ARITHMETIC_EXPANSION_BYTES(64 KiB) therefore rendered adiagnostic about twice its size, past the 1 KiB stderr budget every builtin is
held to under TM-INF-022.
Nightly fuzz run 270 (
arithmetic_fuzz) trippedassert_no_leakon exactlythat: 507 bytes of input, 1,076 bytes of stderr.
Real bash emits 1,065 bytes for the same input, so bashkit was bash-faithful
here — the ceiling is bashkit's own contract, and the arithmetic path had simply
never been held to it. The divergence is recorded as L-ARITH-002.
Before / After
The fuzz crash input, 507 bytes of
~with a rejected#near the front:(Measured through the CLI as 691 bytes with a 99-character script path as
$0;596 is the same line with
$0=bash, as the fuzz target and tests run it.)The reason for the failure survives truncation. Both fragments stop at 256
bytes with a
...marker, and the text between and after them is intact(real output, elided in the middle here only for width):
Short diagnostics are untouched and still match bash 5.2 exactly:
Negative control: with
MAX_ARITHMETIC_DIAG_ECHOneutralised, all six newbounded-diagnostic tests fail, at 1,081 / 1,085 / 1,085 / 1,086 / 1,265 / 1,658
bytes of stderr; with the cap in place all six pass.
Risk
caller that string-matched on the tail of a very long echoed expression would
see the
...marker instead. Expressions at or under the cap are unchanged.of the diagnostic.
Checklist
https://claude.ai/code/session_01BegucbcUgCEsPZiAzt6GRx
Generated by Claude Code