Throw on invalid payload length in WebSockets (#57636)
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Wed, 18 Aug 2021 19:43:47 +0000 (12:43 -0700)
committerGitHub <noreply@github.com>
Wed, 18 Aug 2021 19:43:47 +0000 (12:43 -0700)
Co-authored-by: Natalia Kondratyeva <knatalia@microsoft.com>
src/libraries/System.Net.WebSockets/src/Resources/Strings.resx
src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs
src/libraries/System.Net.WebSockets/tests/WebSocketCreateTest.cs

index 693f8d3..e96ac19 100644 (file)
   <data name="net_WebSockets_Argument_MessageFlagsHasDifferentCompressionOptions" xml:space="preserve">
     <value>The compression options for a continuation cannot be different than the options used to send the first fragment of the message.</value>
   </data>
-</root>
\ No newline at end of file
+  <data name="net_Websockets_InvalidPayloadLength" xml:space="preserve">
+    <value>The WebSocket received a frame with an invalid payload length.</value>
+  </data>
+</root>
index 05b171c..ec62d92 100644 (file)
@@ -1116,6 +1116,14 @@ namespace System.Net.WebSockets
                 return SR.net_Websockets_ReservedBitsSet;
             }
 
+            if (header.PayloadLength < 0)
+            {
+                // as per RFC, if payload length is a 64-bit integer, the most significant bit MUST be 0
+                // frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF; 64 bits in length
+                resultHeader = default;
+                return SR.net_Websockets_InvalidPayloadLength;
+            }
+
             if (header.Compressed && _inflater is null)
             {
                 resultHeader = default;
index f940df0..f9fd4e3 100644 (file)
@@ -149,6 +149,37 @@ namespace System.Net.WebSockets.Tests
             }
         }
 
+        [Theory]
+        [InlineData(new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, false)] // max allowed value
+        [InlineData(new byte[] { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, true)]
+        public async Task ReceiveAsync_InvalidPayloadLength_AbortsAndThrowsException(byte[] lenBytes, bool shouldFail)
+        {
+            var frame = new byte[11];
+            frame[0] = 0b1_000_0010; // FIN, RSV, OPCODE
+            frame[1] = 0b0_1111111; // MASK, PAYLOAD_LEN
+            Assert.Equal(8, lenBytes.Length);
+            Array.Copy(lenBytes, 0, frame, 2, lenBytes.Length); // EXTENDED_PAYLOAD_LEN
+            frame[10] = (byte)'a';
+
+            using var stream = new MemoryStream(frame, writable: true);
+            using WebSocket websocket = CreateFromStream(stream, false, null, Timeout.InfiniteTimeSpan);
+
+            var buffer = new byte[1];
+            Task<WebSocketReceiveResult> t = websocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
+            if (shouldFail)
+            {
+                var exc = await Assert.ThrowsAsync<WebSocketException>(() => t);
+                Assert.Equal(WebSocketState.Aborted, websocket.State);
+            }
+            else
+            {
+                WebSocketReceiveResult result = await t;
+                Assert.False(result.EndOfMessage);
+                Assert.Equal(1, result.Count);
+                Assert.Equal('a', (char)buffer[0]);
+            }
+        }
+
         [Fact]
         [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform.")]
         [ActiveIssue("https://github.com/dotnet/runtime/issues/34690", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]