Skip to content

Commit 69de57b

Browse files
authored
Throw on invalid payload length in WebSockets (#57598)
Port of 9eb5680 **Description:** Avoid integer overflow to prevent infinite loop in reading from WebSocket. (also complies better with WebSocket RFC) MSRC 65273 - Prevents DoS attack by sending frames with invalid payload length. **Risk:** Low **Impacted assemblies:** System.Net.WebSockets.dll
1 parent ab0f54a commit 69de57b

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

src/libraries/System.Net.WebSockets/src/Resources/Strings.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,7 @@
165165
<data name="net_WebSockets_Argument_MessageFlagsHasDifferentCompressionOptions" xml:space="preserve">
166166
<value>The compression options for a continuation cannot be different than the options used to send the first fragment of the message.</value>
167167
</data>
168-
</root>
168+
<data name="net_Websockets_InvalidPayloadLength" xml:space="preserve">
169+
<value>The WebSocket received a frame with an invalid payload length.</value>
170+
</data>
171+
</root>

src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,14 @@ private async ValueTask CloseWithReceiveErrorAndThrowAsync(
11161116
return SR.net_Websockets_ReservedBitsSet;
11171117
}
11181118

1119+
if (header.PayloadLength < 0)
1120+
{
1121+
// as per RFC, if payload length is a 64-bit integer, the most significant bit MUST be 0
1122+
// frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF; 64 bits in length
1123+
resultHeader = default;
1124+
return SR.net_Websockets_InvalidPayloadLength;
1125+
}
1126+
11191127
if (header.Compressed && _inflater is null)
11201128
{
11211129
resultHeader = default;

src/libraries/System.Net.WebSockets/tests/WebSocketCreateTest.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,37 @@ public async Task ReceiveAsync_InvalidFrameHeader_AbortsAndThrowsException(byte
149149
}
150150
}
151151

152+
[Theory]
153+
[InlineData(new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, false)] // max allowed value
154+
[InlineData(new byte[] { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, true)]
155+
public async Task ReceiveAsync_InvalidPayloadLength_AbortsAndThrowsException(byte[] lenBytes, bool shouldFail)
156+
{
157+
var frame = new byte[11];
158+
frame[0] = 0b1_000_0010; // FIN, RSV, OPCODE
159+
frame[1] = 0b0_1111111; // MASK, PAYLOAD_LEN
160+
Assert.Equal(8, lenBytes.Length);
161+
Array.Copy(lenBytes, 0, frame, 2, lenBytes.Length); // EXTENDED_PAYLOAD_LEN
162+
frame[10] = (byte)'a';
163+
164+
using var stream = new MemoryStream(frame, writable: true);
165+
using WebSocket websocket = CreateFromStream(stream, false, null, Timeout.InfiniteTimeSpan);
166+
167+
var buffer = new byte[1];
168+
Task<WebSocketReceiveResult> t = websocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
169+
if (shouldFail)
170+
{
171+
var exc = await Assert.ThrowsAsync<WebSocketException>(() => t);
172+
Assert.Equal(WebSocketState.Aborted, websocket.State);
173+
}
174+
else
175+
{
176+
WebSocketReceiveResult result = await t;
177+
Assert.False(result.EndOfMessage);
178+
Assert.Equal(1, result.Count);
179+
Assert.Equal('a', (char)buffer[0]);
180+
}
181+
}
182+
152183
[Fact]
153184
[SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform.")]
154185
[ActiveIssue("https://github.com/dotnet/runtime/issues/34690", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]

0 commit comments

Comments
 (0)