fix: clamp saturated CSI cursor-move counts before the add (int overflow) - #27
Merged
pocketprobe merged 3 commits intoAug 26, 2026
Merged
Conversation
pocketprobe
added a commit
to pocketprobe/OC2R
that referenced
this pull request
Aug 26, 2026
) Loki's review: the y=8 -> scrollLast case traced in the PR body wasn't covered by the 8 overflow tests. Add moveCursorBySaturatedDownInScroll- RegionClampsToScrollLast: from inside a region [5..10] (0-indexed 4..9) at y=6, a saturated CUD (down) must land on scrollLast (9) — exercising the two-stage clamp (delta bound in moveCursorBy, region clamp in setClampedCursorPos). Revert-and-fail spot-checked: against the pre-fix raw-add CUD the test fails with "expected: 9 but was: 4" (the int sum overflows negative and the region clamp lands on scrollFirst). 100/0. Assisted by: GLM (syn:large:text on synthetic.new) — code generation and review.
…low) EscapeUtilities.parseArgument saturates at Integer.MAX_VALUE, and the relative cursor-move handlers added the saturated arg to terminal.x/y BEFORE setClampedCursorPos clamped the result. The int addition overflowed to a negative value, which setClampedCursorPos then faithfully clamped to 0 (the near edge) instead of the far edge — so a guest sending CSI 2147483647C from column 5 moved the cursor to column 0, not the right margin. Same bug class as the SU/SD freeze (§36 B2, fixed by clamping in CH8/CH9), but a correctness misposition rather than a DoS. setClampedCursorPos can't be the clamp site: it takes an absolute target, not a delta, so the direction information (and the overflow) has already happened before it runs. Add Terminal.moveCursorBy(dx, dy), which bounds the delta to +/- screen extent before the add; setClampedCursorPos still applies the screen and scroll-region clamp to the result, so scroll-region behavior is unchanged (traced scrollFirst=5/scrollLast=10: from y=8 a saturated down-move now hits scrollLast=10, where it hit scrollFirst=5 before). Affected handlers: - CUD (B), CUF (C), VPR (e), HPR (a): the additive movers, now via moveCursorBy. VPR/HPR are new in PR DivByDiamond#24 and had inherited the bug. - CUU (A), CUB (D): routed through moveCursorBy for uniformity. They subtract, so they never overflowed — verified bit-identical for in-range args and equivalent for huge args (both clamp to the near edge). - CNL (E): stays per-handler — it resets the column to 0, which moveCursorBy (delta-preserving) can't express; inline Math.clamp on the row delta. CPL (F) is untouched (subtraction + column reset; safe). Separate overflow found in the same hunt: setRelativeCursorPos under DECOM did scrollFirst + y with the raw arg. CUP/HVP/VPA pass the row here, so a saturated row overflowed scrollFirst + MAX_VALUE to a negative int and clamped to scrollFirst (top) instead of scrollLast (bottom). Bounds y to [0, scrollLast - scrollFirst] before the add, preserving origin-relative semantics (row 1 = scrollFirst). Distinct from §36 m1 / §37 B6 (the saved-cursor-after-width-change AIOOBE PR DivByDiamond#24 fixed via SavedCursor). Tests: 8 in TerminalBufferTest. Six revert-and-fail (CUD/CUF/VPR/HPR/CNL/ DECOM) assert a saturated count from a non-zero start lands on the far edge, not 0; CUU/CUB are behavior-preservation for the routing. 99/0. QA: compileJava + full test suite green; Checkstyle 0, PMD 0 in touched files; SpotBugs delta 0 (no BugInstance at moveCursorBy/setRelativeCursorPos; no new EI_EXPOSE_REP2/MS_* — the primitive takes primitives and calls existing methods). The 5 checkstyle-main / 41 pmd-main warnings are pre-existing in unrelated files (inet session imports, TunnelManager). Assisted by: GLM (syn:large:text on synthetic.new) — code generation and review.
These four pre-existing handlers predate PR DivByDiamond#24 and were missing the final newline that VPR/HPR/CNL (and the rest of the csi/ package) have. Pure EOF hygiene; no content change. Assisted by: GLM (syn:large:text on synthetic.new) — code generation and review.
) Loki's review: the y=8 -> scrollLast case traced in the PR body wasn't covered by the 8 overflow tests. Add moveCursorBySaturatedDownInScroll- RegionClampsToScrollLast: from inside a region [5..10] (0-indexed 4..9) at y=6, a saturated CUD (down) must land on scrollLast (9) — exercising the two-stage clamp (delta bound in moveCursorBy, region clamp in setClampedCursorPos). Revert-and-fail spot-checked: against the pre-fix raw-add CUD the test fails with "expected: 9 but was: 4" (the int sum overflows negative and the region clamp lands on scrollFirst). 100/0. Assisted by: GLM (syn:large:text on synthetic.new) — code generation and review.
pocketprobe
force-pushed
the
fix/work/cursor-arg-overflow
branch
from
August 26, 2026 22:03
7c75838 to
fba18be
Compare
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 an int-overflow class in the relative CSI cursor-move handlers: a saturated CSI count (
parseArgumentcaps atInteger.MAX_VALUE) was added toterminal.x/terminal.ybeforesetClampedCursorPosclamped the result, so theintaddition wrapped negative and the clamp landed the cursor on 0 (the near edge) instead of the far edge. Same root cause as the SU/SD freeze (§36 Б2, fixed by clamping in CH8/CH9), but a correctness misposition rather than a DoS.What this does
Terminal.moveCursorBy(dx, dy)bounds the delta to ±screen extent before the add;setClampedCursorPosstill applies the screen + scroll-region clamp to the result, so scroll-region behavior is unchanged (tracedscrollFirst=5/scrollLast=10: from y=8 a saturated down-move now hitsscrollLast=10, where it hitscrollFirst=5before).setClampedCursorPoscan't be the clamp site — it takes an absolute target, not a delta, so the overflow has already happened before it runs.moveCursorBy. VPR/HPR are new in PR fix: missing CSI cursor escapes + unify cursor save/restore (SavedCursor) #24 and had inherited the bug.moveCursorByfor uniformity. They subtract, so they never overflowed; verified bit-identical for in-range args and equivalent for huge args (both clamp to the near edge). This is the only non-buggy code touched, kept so all four cardinal directions share the one primitive.Math.clampon the row delta. CPL (F) is untouched (subtraction + column reset; safe).setRelativeCursorPosunder DECOM — a separate overflow the hunt surfaced in the same pass: it didscrollFirst + ywith the raw arg, and CUP/HVP/VPA feed the row here, so a saturated row overflowedscrollFirst + MAX_VALUEnegative and clamped toscrollFirst(top) instead ofscrollLast(bottom). Now boundsyto[0, scrollLast - scrollFirst]before the add, preserving origin-relative semantics (row 1 =scrollFirst). Distinct from §36 m1 / §37 Б6 — the saved-cursor-after-width-change AIOOBE that PR fix: missing CSI cursor escapes + unify cursor save/restore (SavedCursor) #24'sSavedCursorrefactor already resolved.Verification
TerminalBufferTest, 100/0. Six revert-and-fail (CUD/CUF/VPR/HPR/CNL/DECOM) feedCSI 2147483647from a non-zero start and assert the far edge, not 0; CUU/CUB are behavior-preservation for the routing;moveCursorBySaturatedDownInScrollRegionClampsToScrollLastcovers the scroll-region case (saturated down-move from inside a region lands onscrollLast, exercising the two-stage delta-then-region clamp). Each pairs a normal small move with the saturated one so the pre-fix path is exercised before the overflow path../gradlew test).upstream/work(760bc44): the three+ args[0]sites PR fix: missing CSI cursor escapes + unify cursor save/restore (SavedCursor) #24 added (VPR/HPR/CNL) were the inherited regressions; the two pre-existing ones (CUD/CUF) predate fix: missing CSI cursor escapes + unify cursor save/restore (SavedCursor) #24.QA gate
TunnelManager).BugInstanceatmoveCursorBy/setRelativeCursorPos; the primitive takes primitives and calls existing methods, adding noEI_EXPOSE_REP2/MS_*/PA_*findings.Notes for review
moveCursorBywhen they don't strictly need the fix — it's a uniformity win (all four cardinal directions share one bounded primitive, so a future handler can't forget the clamp) at the cost of touching two files that weren't broken. Happy to pull them back if you'd rather keep this to the five broken handlers only.Next
Utf8Decoder.hasActiveSequence0 refs,TerminalIO.putOutput(byte),Terminal.getTerminalWidth()tests-only, the never-calledincrementLastLineToDisplay(true)branch,TerminalRenderer.findLineIndex/isPrintableCharacter→ private,ImplementedPrivateModes.modeStatuspublic-mutable → private) plus §37'sColorUtils,RunnableUtils.doNothing,SessionOperator. Separate PR.Assisted by: GLM (syn:large:text on synthetic.new) — code generation and review.