Skip to content

AtlasEngine: Fix out-of-bounds row access in PaintCursor (#20269)#20363

Closed
yuu61 wants to merge 1 commit into
microsoft:mainfrom
yuu61:fix-20269-paintcursor-oob
Closed

AtlasEngine: Fix out-of-bounds row access in PaintCursor (#20269)#20363
yuu61 wants to merge 1 commit into
microsoft:mainfrom
yuu61:fix-20269-paintcursor-oob

Conversation

@yuu61

@yuu61 yuu61 commented Jun 26, 2026

Copy link
Copy Markdown

Summary of the Pull Request

AtlasEngine::PaintCursor indexes _p.rows[options.coordCursor.y] without clamping the row index. coordCursor.y is relative to the renderer's viewport, which can momentarily disagree with AtlasEngine's own viewport (_p.s->viewportCellCount.y, the size of _p.rows) when the viewport dimensions change. When the renderer's viewport is taller than AtlasEngine's current one, Renderer::_PaintCursor still treats the cursor as in-viewport and calls PaintCursor, but coordCursor.y can exceed _p.rows.size(), causing an out-of-bounds read and an access violation.

Every other method in this file that indexes _p.rows from an external coordinate already clamps it to [0, viewportCellCount.y - 1] first (PrepareLineTransform, PaintBufferLine, PaintBufferGridLines, PaintImageSlice). This makes PaintCursor consistent with them.

References and Relevant Issues

Fixes #20269

Detailed Description of the Pull Request / Additional comments

  • The _p.rows slots always point into _p.unorderedRows (set up in _recreateCellCountDependentResources), so any in-bounds index yields a valid ShapedRow*. Clamping the index therefore fully eliminates the out-of-bounds access — there is no remaining in-bounds-dangling case to guard against.
  • Only the index used for the _p.rows lookup is clamped. top/bottom keep their raw values, so when the cursor is genuinely outside AtlasEngine's current viewport, cursorRect still collapses to an empty rect (bottom <= toptil::rect::operator bool() is false) and nothing is drawn — no behavioral change for the in-viewport case.

Validation Steps Performed

  • Root-caused from user crash minidumps (Windows Terminal 1.24.11321.0, Stable): the fault is AtlasEngine::PaintCursor reading _p.rows[<index>] with an out-of-range index (index = 77 in one dump), matching the disassembly already posted in AtlasEngine::PaintCursor crashes #20269.
  • Built src/renderer/atlas/atlas.vcxproj (Debug | x64) locally with the change: compiles cleanly, 0 warnings / 0 errors.

…0269)

PaintCursor indexed _p.rows[options.coordCursor.y] without clamping the
row index. coordCursor.y is relative to the renderer's viewport, which
can momentarily disagree with AtlasEngine's own viewport
(_p.s->viewportCellCount.y, the size of _p.rows) when the viewport
dimensions change. When the renderer's viewport is taller than
AtlasEngine's current one, Renderer::_PaintCursor still treats the cursor
as in-viewport and calls PaintCursor, but coordCursor.y can exceed
_p.rows.size(), causing an out-of-bounds read and an access violation.

Clamp the row index to [0, viewportCellCount.y - 1] before indexing
_p.rows, matching every other Paint*() method in this file. The _p.rows
slots always point into _p.unorderedRows, so any in-bounds index yields
a valid ShapedRow*. Only the lookup index is clamped; top/bottom keep
their raw values so cursorRect still collapses to empty when the cursor
is genuinely outside the viewport.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@yuu61 yuu61 force-pushed the fix-20269-paintcursor-oob branch from 1507a6d to 2ee40c8 Compare June 26, 2026 01:43
@DHowett

DHowett commented Jun 26, 2026

Copy link
Copy Markdown
Member

I would rather understand why the backing buffer is not being resized under the same lock as the cursor move. Fixing a symptom is not fixing the problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AtlasEngine::PaintCursor crashes

2 participants