Skip to content

Check the inflate ceiling before the write, not after it - #102

Closed
JohnCampionJr wants to merge 1 commit into
mainfrom
inflate-overshoot
Closed

JohnCampionJr wants to merge 1 commit into
mainfrom
inflate-overshoot

Conversation

@JohnCampionJr

Copy link
Copy Markdown
Collaborator

Follow-up to #89, from a Copilot comment left on #92 that pointed at code which had already merged. It is a real hole in the bound that PR shipped.

Inflate creates its MemoryStream with capacity expected, then writes a read into it and afterwards asks whether the total exceeded the ceiling. A write that crosses the capacity makes MemoryStream grow its backing array — it doubles — so on a large declared image the check fires only once roughly a hundred megabytes has already been allocated in order to refuse the payload.

That is precisely the "bounded after the fact" shape #89 existed to eliminate, reintroduced by the guard itself. output.Length + read > expected is asked before the write now, so the refusal costs nothing.

KittyTransmission.TryInflate had the same ordering and gets the same fix.

Suite green: 1899. No perf run — this is an image-decode path, not the print path, and the change removes an allocation rather than adding work.

🤖 Generated with Claude Code

From Copilot's review on #92, against code that merged in #89 -- and
it is right. The stream is created with capacity , so a
write crossing that grows the backing array (MemoryStream doubles) and
the check fired only once the allocation had happened. On a large
declared image that is a hundred megabytes handed out in order to
refuse a payload: the exact 'bounded after the fact' shape the guard
exists to prevent, reintroduced by the guard itself.

KittyTransmission.TryInflate had the same ordering and the same fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JohnCampionJr

Copy link
Copy Markdown
Collaborator Author

Folded into #92 instead — the finding came from a review comment there, it is two lines, and a separate PR is more review and merge overhead than the change is worth. The branch is deleted; the commit is on parser-conformance unchanged.

@JohnCampionJr
JohnCampionJr deleted the inflate-overshoot branch August 29, 2026 17:02
@github-actions

Copy link
Copy Markdown
Contributor

Perf comparison — this change, against its base

3 run(s) of each side, alternating on one machine. Allocation is a count and is gated exactly. Time is a measurement, so its gate is derived from the spread this job just observed in itself rather than fixed in advance.

corpus bytes/char gen0/Mchar ns/char Δ time noise gate
scroll-ascii 0.00 → 0.00 0.00 → 0.00 3.36 → 3.35 -0.2% ±1% 4%
sgr-churn 0.00 → 0.00 0.00 → 0.00 8.88 → 8.97 +1.0% ±1% 4%
truecolor 0.00 → 0.00 0.00 → 0.00 9.99 → 9.95 -0.4% ±2% 5%
alt-redraw 0.00 → 0.00 0.00 → 0.00 13.62 → 13.77 +1.1% ±3% 9%
unicode 7.66 → 7.66 0.45 → 0.45 33.64 → 33.73 +0.3% ±8% 25%
flood 0.00 → 0.00 0.00 → 0.00 94.07 → 94.31 +0.2% ±1% 4%

Each corpus is gated at max(4%, 3 × its own noise). A wide noise column means this runner was busy and the timing half of the table should be read as advisory; the allocation half is exact either way.

assemblies measured
  • base: XTerm.NET 2.0.0.0 mvid:91683bf8-0b08-46ef-9d8a-e8eeefb93cea
  • head: XTerm.NET 2.0.0.0 mvid:5a78121e-9f8d-431d-9b32-e55b3558bbbb

Perf comparison — cumulative, everything since 2.0.0-rc002

3 run(s) of each side, alternating on one machine. Allocation is a count and is gated exactly. Time is a measurement, so its gate is derived from the spread this job just observed in itself rather than fixed in advance.

corpus bytes/char gen0/Mchar ns/char Δ time noise gate
scroll-ascii 0.00 → 0.00 0.00 → 0.00 3.38 → 3.35 -0.7% ±1% 5%
sgr-churn 0.00 → 0.00 0.00 → 0.00 8.82 → 8.97 +1.8% ±1% 5%
truecolor 0.00 → 0.00 0.00 → 0.00 9.74 → 9.95 +2.2% ±2% 5%
alt-redraw 0.00 → 0.00 0.00 → 0.00 13.65 → 13.77 +0.9% ±3% 9%
unicode 7.66 → 7.66 0.45 → 0.45 33.12 → 33.73 +1.9% ±6% 17%
flood 0.00 → 0.00 0.00 → 0.00 94.30 → 94.31 +0.0% ±2% 5%

Each corpus is gated at max(5%, 3 × its own noise). A wide noise column means this runner was busy and the timing half of the table should be read as advisory; the allocation half is exact either way.

assemblies measured
  • base: XTerm.NET 2.0.0.0 mvid:1b573f7c-2562-4a81-9dc9-0cdd9bd9fb1d
  • head: XTerm.NET 2.0.0.0 mvid:5a78121e-9f8d-431d-9b32-e55b3558bbbb

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.

1 participant