Skip to content

Fix two ice protocol connection bugs#4767

Merged
pepone merged 6 commits into
icerpc:mainfrom
pepone:fix-audit-19-32
Jun 17, 2026
Merged

Fix two ice protocol connection bugs#4767
pepone merged 6 commits into
icerpc:mainfrom
pepone:fix-audit-19-32

Conversation

@pepone

@pepone pepone commented Jun 12, 2026

Copy link
Copy Markdown
Member

Fixes icerpc/icerpc-csharp-audit#19.
Fixes icerpc/icerpc-csharp-audit#32.

Two small fixes in IceProtocolConnection:

  • Missing lower-bound check on the ice frame size (audit C# should prefer string interpolation to concatenation #19): ReadFramesAsync validated only FrameSize > _maxFrameSize. A Request/Reply prologue with FrameSize smaller than the prologue size (14) made frameSize - PrologueSize negative, which threw ArgumentOutOfRangeException from ReadOnlySequence.Slice and reached the Debug.Fail in the generic exception handler — an assertion failure on untrusted input in debug builds. The lower bound is now rejected as InvalidDataException (malformed peer input), like the upper bound.

  • Reply frame reader leak on a cancellation race (audit Remove Value factories, protected keyword and postMarshal/perUnmarshal callbacks #32): when the read frames loop completes responseCompletionSource with the reply frame reader (transferring ownership — the loop no longer completes it) and the invocation's WaitAsync(invocationCts.Token) observes cancellation in that same window, the reader was discarded without Complete(): the frame reader pipe's rented MemoryPool<byte> segments were never returned. The invocation's finally now completes the reader when the completion source holds a reader that was never retrieved (!responseCreated && frameReader is null). This also covers the read-loop-wins micro-race against the finally's own TrySetResult.

Tests

  • Receiving_a_frame_smaller_than_the_prologue_size_aborts_the_connection: a server-side duplex read decorator rewrites the incoming request's frame size to 0; the connection aborts with ConnectionAborted. Without the fix this test fails with the exact failure mode from the audit: ArgumentOutOfRangeException from Slice reaching Debug.Fail in ReadFramesAsync.
  • The Remove Value factories, protected keyword and postMarshal/perUnmarshal callbacks #32 race window (response landing in the completion source in the same instant the invocation observes cancellation) cannot be reached deterministically from a test — the fix is in the finally's already-completed branch and is covered by review plus the existing suite (all response/cancellation paths still pass).

Full IceRpc.Tests suite passes (988 passed / 0 failed), dotnet build and dotnet format --verify-no-changes clean.

Fixes icerpc/icerpc-csharp-audit#19.
Fixes icerpc/icerpc-csharp-audit#32.

- ReadFramesAsync validated only the upper bound of the peer-supplied
  frame size. A Request or Reply prologue with a frame size smaller than
  the prologue size (e.g. 0) made the frame body size negative, which
  threw ArgumentOutOfRangeException from ReadOnlySequence.Slice and
  reached the Debug.Fail in the generic exception handler: an assertion
  failure on untrusted input in debug builds, a less-clear connection
  abort in release builds. The frame size lower bound is now validated
  and rejected as invalid data.

- When a reply was received in the same window the invocation observed
  its cancellation, the reply frame reader set on the response
  completion source (whose ownership the read frames loop had already
  transferred) was discarded without Complete: the pipe's rented memory
  pool segments were never returned. The invocation's finally block now
  completes the reader when it was never retrieved.
Copilot AI review requested due to automatic review settings June 12, 2026 16:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses two robustness issues in IceProtocolConnection: validating malformed frame sizes early to avoid debug assertions/crashes on untrusted input, and ensuring reply frame readers are always completed to prevent pooled buffer leaks during a narrow cancellation race.

Changes:

  • Reject incoming Ice frames whose FrameSize is smaller than IceDefinitions.PrologueSize, treating it as malformed peer input.
  • Fix a cancellation race by completing a reply PipeReader when it was produced by the read loop but never retrieved by the canceled invocation.
  • Add a regression test that corrupts the incoming request prologue’s frame size to verify the connection aborts cleanly.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
tests/IceRpc.Tests/IceProtocolConnectionTests.cs Adds a regression test for receiving a frame smaller than the Ice prologue size.
src/IceRpc/Internal/IceProtocolConnection.cs Adds a lower-bound FrameSize check and completes leaked reply frame readers on a cancellation race.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/IceRpc/Internal/IceProtocolConnection.cs Outdated
Comment thread tests/IceRpc.Tests/IceProtocolConnectionTests.cs
Comment thread src/IceRpc/Internal/IceProtocolConnection.cs
@pepone pepone requested a review from bernardnormier June 17, 2026 11:02
Comment thread src/IceRpc/Internal/IceProtocolConnection.cs Outdated
@pepone pepone merged commit c4e3570 into icerpc:main Jun 17, 2026
12 checks passed
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.

3 participants