From 92f21dbda0d9ef4780385d408a4e5d0c92e06029 Mon Sep 17 00:00:00 2001 From: John Luo <johluo@microsoft.com> Date: Thu, 7 Feb 2019 12:50:53 -0800 Subject: [PATCH] Unset reserved bit when setting 31 bit uint values (#7339) * Unset reserved bit when setting 31 bit uint values * Add FrameWriter tests * Add overload --- .../Core/src/Internal/Http2/Bitshifter.cs | 17 ++- .../src/Internal/Http2/Http2FrameWriter.cs | 6 +- .../Kestrel/Core/test/BitShifterTests.cs | 48 ++++++++ .../Core/test/Http2FrameWriterTests.cs | 108 ++++++++++++++++++ .../Http2/Http2TestBase.cs | 2 +- 5 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 src/Servers/Kestrel/Core/test/BitShifterTests.cs create mode 100644 src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Bitshifter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Bitshifter.cs index 6dea8682911..8aa5f59e352 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Bitshifter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Bitshifter.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -36,11 +36,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 // Does not overwrite the highest order bit [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUInt31BigEndian(Span<byte> destination, uint value) + => WriteUInt31BigEndian(destination, value, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUInt31BigEndian(Span<byte> destination, uint value, bool preserveHighestBit) { Debug.Assert(value <= 0x7F_FF_FF_FF, value.ToString()); - // Keep the highest bit - var reserved = (destination[0] & 0x80u) << 24; - BinaryPrimitives.WriteUInt32BigEndian(destination, value | reserved); + + if (preserveHighestBit) + { + // Keep the highest bit + value |= (destination[0] & 0x80u) << 24; + } + + BinaryPrimitives.WriteUInt32BigEndian(destination, value); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index 2f67445b272..73be2ed64ad 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -402,7 +402,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 _outgoingFrame.PrepareWindowUpdate(streamId, sizeIncrement); WriteHeaderUnsynchronized(); var buffer = _outputWriter.GetSpan(4); - Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement); + Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement, preserveHighestBit: false); _outputWriter.Advance(4); return TimeFlushUnsynchronizedAsync(); } @@ -538,7 +538,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 WriteHeaderUnsynchronized(); var buffer = _outputWriter.GetSpan(8); - Bitshifter.WriteUInt31BigEndian(buffer, (uint)lastStreamId); + Bitshifter.WriteUInt31BigEndian(buffer, (uint)lastStreamId, preserveHighestBit: false); buffer = buffer.Slice(4); BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)errorCode); _outputWriter.Advance(8); @@ -578,7 +578,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 buffer[1] = frame.Flags; buffer = buffer.Slice(2); - Bitshifter.WriteUInt31BigEndian(buffer, (uint)frame.StreamId); + Bitshifter.WriteUInt31BigEndian(buffer, (uint)frame.StreamId, preserveHighestBit: false); output.Advance(Http2FrameReader.HeaderLength); } diff --git a/src/Servers/Kestrel/Core/test/BitShifterTests.cs b/src/Servers/Kestrel/Core/test/BitShifterTests.cs new file mode 100644 index 00000000000..5ba9b1747a9 --- /dev/null +++ b/src/Servers/Kestrel/Core/test/BitShifterTests.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class BitShifterTests + { + [Fact] + public void WriteUInt31BigEndian_PreservesHighestBit() + { + // Arrange + Span<byte> dirtySpan = new byte[] { 0xff, 0xff, 0xff, 0xff }; + + // Act + Bitshifter.WriteUInt31BigEndian(dirtySpan, 1); + + Assert.Equal(new byte[] { 0x80, 0x00, 0x00, 0x01 }, dirtySpan.ToArray()); + } + + [Fact] + public void WriteUInt31BigEndian_True_PreservesHighestBit() + { + // Arrange + Span<byte> dirtySpan = new byte[] { 0xff, 0xff, 0xff, 0xff }; + + // Act + Bitshifter.WriteUInt31BigEndian(dirtySpan, 1, true); + + Assert.Equal(new byte[] { 0x80, 0x00, 0x00, 0x01 }, dirtySpan.ToArray()); + } + + [Fact] + public void WriteUInt31BigEndian_False_OverwritesHighestBit() + { + // Arrange + Span<byte> dirtySpan = new byte[] { 0xff, 0xff, 0xff, 0xff }; + + // Act + Bitshifter.WriteUInt31BigEndian(dirtySpan, 1, false); + + Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x01 }, dirtySpan.ToArray()); + } + } +} diff --git a/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs b/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs new file mode 100644 index 00000000000..dd5f993ea25 --- /dev/null +++ b/src/Servers/Kestrel/Core/test/Http2FrameWriterTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Buffers; +using System.IO.Pipelines; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests +{ + public class Http2FrameWriterTests + { + private MemoryPool<byte> _dirtyMemoryPool; + + public Http2FrameWriterTests() + { + var memoryBlock = new Mock<IMemoryOwner<byte>>(); + memoryBlock.Setup(block => block.Memory).Returns(() => + { + var blockArray = new byte[4096]; + for (int i = 0; i < 4096; i++) + { + blockArray[i] = 0xff; + } + return new Memory<byte>(blockArray); + }); + + var dirtyMemoryPool = new Mock<MemoryPool<byte>>(); + dirtyMemoryPool.Setup(pool => pool.Rent(It.IsAny<int>())).Returns(memoryBlock.Object); + _dirtyMemoryPool = dirtyMemoryPool.Object; + } + + [Fact] + public async Task WriteWindowUpdate_UnsetsReservedBit() + { + // Arrange + var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline)); + var frameWriter = new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, new Mock<IKestrelTrace>().Object); + + // Act + await frameWriter.WriteWindowUpdateAsync(1, 1); + + // Assert + var payload = await pipe.Reader.ReadForLengthAsync(Http2FrameReader.HeaderLength + 4); + + Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x01 }, payload.Skip(Http2FrameReader.HeaderLength).Take(4).ToArray()); + } + + [Fact] + public async Task WriteGoAway_UnsetsReservedBit() + { + // Arrange + var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline)); + var frameWriter = new Http2FrameWriter(pipe.Writer, null, null, null, null, null, null, new Mock<IKestrelTrace>().Object); + + // Act + await frameWriter.WriteGoAwayAsync(1, Http2ErrorCode.NO_ERROR); + + // Assert + var payload = await pipe.Reader.ReadForLengthAsync(Http2FrameReader.HeaderLength + 4); + + Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x01 }, payload.Skip(Http2FrameReader.HeaderLength).Take(4).ToArray()); + } + + [Fact] + public async Task WriteHeader_UnsetsReservedBit() + { + // Arrange + var pipe = new Pipe(new PipeOptions(_dirtyMemoryPool, PipeScheduler.Inline, PipeScheduler.Inline)); + var frame = new Http2Frame(); + frame.PreparePing(Http2PingFrameFlags.NONE); + + // Act + Http2FrameWriter.WriteHeader(frame, pipe.Writer); + await pipe.Writer.FlushAsync(); + + // Assert + var payload = await pipe.Reader.ReadForLengthAsync(Http2FrameReader.HeaderLength); + + Assert.Equal(new byte[] { 0x00, 0x00, 0x00, 0x00 }, payload.Skip(5).Take(4).ToArray()); + } + } + + public static class PipeReaderExtensions + { + public static async Task<byte[]> ReadForLengthAsync(this PipeReader pipeReader, int length) + { + while (true) + { + var result = await pipeReader.ReadAsync(); + var buffer = result.Buffer; + + if (!buffer.IsEmpty && buffer.Length >= length) + { + return buffer.Slice(0, length).ToArray(); + } + + pipeReader.AdvanceTo(buffer.Start, buffer.End); + } + } + } +} diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs index 41a451eb765..05b54a8d12f 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http2/Http2TestBase.cs @@ -1055,7 +1055,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests frame.PrepareWindowUpdate(streamId, sizeIncrement); Http2FrameWriter.WriteHeader(frame, outputWriter); var buffer = outputWriter.GetSpan(4); - Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement); + BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)sizeIncrement); outputWriter.Advance(4); return FlushAsync(outputWriter); } -- GitLab