Skip to content

fix: validate envelope item payload lengths before allocating a read buffer - #5541

Merged
jamescrosswell merged 1 commit into
getsentry:mainfrom
thaildhe172591:fix/5536-envelope-payload-length-bound
Sep 8, 2026
Merged

fix: validate envelope item payload lengths before allocating a read buffer#5541
jamescrosswell merged 1 commit into
getsentry:mainfrom
thaildhe172591:fix/5536-envelope-payload-length-bound

Conversation

@thaildhe172591

@thaildhe172591 thaildhe172591 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Closes #5536, following on from #5507.

EnvelopeItem.DeserializePayloadAsync takes the payload length from the item header and rents a buffer of exactly that size, so a corrupt but parseable header can make a few-KB cache file allocate an arbitrary amount. The three shapes from the issue: "length": 2000000000 rents 2 GB, "length": 3000000000 overflows the unchecked (int) cast to -1294967296 and throws ArgumentOutOfRangeException out of ArrayPool.Rent, and an item with no length key at all takes (int)stream.Length, which overflows the same way once the file is larger than 2 GB.

The four buffering branches now derive their length from GetPayloadBufferLength, which rejects a declared length that is negative or longer than what remains in the stream, and a length that cannot fit in an int. Both throw InvalidDataException, which #5507 already routes into the caching transport's discard, so the user-visible outcome for a corrupt file is unchanged: it is discarded rather than retried on every launch. What changes is that the oversized allocation never happens. Neither bound encodes any Relay configuration, as you noted on the issue: remaining is measured from the file in front of us and int.MaxValue is the CLR array limit.

I kept this to the allocation bound. The attachment branch is untouched, so a file truncated mid-write still builds a short PartialStream and is sent as it is today. Discarding those is the behaviour change you flagged, and since it wants its own changelog line I left it out — happy to fold it in here instead if you would rather have both at once. I have left the issue open rather than closing it from this PR for the same reason.

Five tests in EnvelopeTests, covering each of the three failure modes above, the no-length path through a buffering branch, and the over-2 GB case. That last one uses a small OversizedStream : MemoryStream that reports a Length of 3 GB over a real buffer, since the int.MaxValue bound is otherwise only reachable with a 2 GB fixture.

Verified locally, as CI will not run on a first-time fork PR until it is approved. Ubuntu under WSL2, SDK 10.0.400 as pinned in global.json, with the .NET 8, 9 and 10 runtimes installed. dotnet build Sentry-CI-Build-Linux-NoMobile.slnf -c Release succeeds with no errors, and dotnet test on the same filter gives 10339 passed, 0 failed, 94 skipped across 53 project and framework combinations. Sentry.Tests on its own is 2548 passed and 0 failed on each of net8.0, net9.0 and net10.0, which includes ApiApprovalTests, so the public API snapshots are unchanged. dotnet format --verify-no-changes is clean on both files.

For the before and after: reverting only EnvelopeItem.cs and rerunning the same tests unchanged fails four of the five, each with the symptom the issue predicts — Rent(-1) for the negative length, Rent(-1294967296) for both overflow cases, and no exception at all for the length that simply exceeds the stream. The fifth passes in both states; it is there to catch a regression in the switch from stream.Length to the remaining bytes.

I could not run net48 or the macOS, iOS and Android targets here.

🤖 Generated with Claude Code

…buffer

EnvelopeItem.DeserializePayloadAsync took the payload length straight from the
item header and rented a buffer of exactly that size, so a corrupt but
parseable header could make a small cache file allocate an arbitrary amount.
"length": 2000000000 rents 2 GB; 3000000000 overflows the unchecked (int) cast
to -1294967296 and throws out of ArrayPool.Rent; and with no length key at all,
(int)stream.Length overflows the same way on a file larger than 2 GB.

The four buffering branches now take their length from GetPayloadBufferLength,
which rejects a declared length that is negative or longer than what remains in
the stream, and a length that cannot fit in an int. Both throw
InvalidDataException, which the caching transport already discards on.

The attachment branch is left alone, so a file truncated mid-write still
deserializes into a short PartialStream as it does today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 74.74%. Comparing base (be2a785) to head (1362295).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
src/Sentry/Protocol/Envelopes/EnvelopeItem.cs 91.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5541      +/-   ##
==========================================
+ Coverage   74.70%   74.74%   +0.03%     
==========================================
  Files         515      515              
  Lines       18948    18956       +8     
  Branches     3696     3694       -2     
==========================================
+ Hits        14155    14168      +13     
+ Misses       3909     3908       -1     
+ Partials      884      880       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jamescrosswell jamescrosswell left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Thanks for the contribution @thaildhe172591 !

@jamescrosswell
jamescrosswell merged commit d9da771 into getsentry:main Sep 8, 2026
46 checks passed
@jamescrosswell jamescrosswell changed the title fix: bound the envelope item payload length before allocating a read buffer fix: validate envelope item payloads length before allocating a read buffer Sep 10, 2026
@jamescrosswell jamescrosswell changed the title fix: validate envelope item payloads length before allocating a read buffer fix: validate envelope item payload lengths before allocating a read buffer Sep 10, 2026
This was referenced Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Envelope item payload length is trusted from the header, allowing unbounded allocation

2 participants