diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs index 654544594b..04ece0d46d 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/KestrelServer.cs @@ -247,6 +247,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel throw new InvalidOperationException( $"Maximum request buffer size ({Options.Limits.MaxRequestBufferSize.Value}) must be greater than or equal to maximum request line size ({Options.Limits.MaxRequestLineSize})."); } + + if (Options.Limits.MaxRequestBufferSize.HasValue && + Options.Limits.MaxRequestBufferSize < Options.Limits.MaxRequestHeadersTotalSize) + { + throw new InvalidOperationException( + $"Maximum request buffer size ({Options.Limits.MaxRequestBufferSize.Value}) must be greater than or equal to maximum request headers size ({Options.Limits.MaxRequestHeadersTotalSize})."); + } } private void StartLocalhost(KestrelEngine engine, ServerAddress parsedAddress) diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs index 3c09271a4f..d60ae18dc4 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/MaxRequestBufferSizeTests.cs @@ -6,9 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; -using System.Net.Security; using System.Net.Sockets; -using System.Security.Authentication; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -24,13 +22,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { private const int _dataLength = 20 * 1024 * 1024; + private static readonly string[] _requestLines = new[] + { + "POST / HTTP/1.0\r\n", + $"Content-Length: {_dataLength}\r\n", + "\r\n" + }; + public static IEnumerable LargeUploadData { get { var maxRequestBufferSizeValues = new Tuple[] { - // Smallest buffer that can hold a POST request line to the root. - Tuple.Create((long?)"POST / HTTP/1.1\r\n".Length, true), + // Smallest buffer that can hold a test request line without causing + // the server to hang waiting for the end of the request line or + // a header line. + Tuple.Create((long?)(_requestLines.Max(line => line.Length)), true), // Small buffer, but large enough to hold all request headers. Tuple.Create((long?)16 * 1024, true), @@ -186,6 +193,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { options.Limits.MaxRequestLineSize = (int)maxRequestBufferSize; } + + if (maxRequestBufferSize.HasValue && + maxRequestBufferSize.Value < options.Limits.MaxRequestHeadersTotalSize) + { + options.Limits.MaxRequestHeadersTotalSize = (int)maxRequestBufferSize; + } }) .UseContentRoot(Directory.GetCurrentDirectory()) .Configure(app => app.Run(async context => @@ -246,9 +259,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize: 1024, leaveOpen: true)) { - await writer.WriteAsync("POST / HTTP/1.0\r\n"); - await writer.WriteAsync($"Content-Length: {contentLength}\r\n"); - await writer.WriteAsync("\r\n"); + foreach (var line in _requestLines) + { + await writer.WriteAsync(line); + } } } } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/KestrelServerTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/KestrelServerTests.cs index 5225e5a16d..c22bdf6789 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/KestrelServerTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/KestrelServerTests.cs @@ -24,25 +24,30 @@ namespace Microsoft.AspNetCore.Server.KestrelTests public void StartWithNonPositiveThreadCountThrows(int threadCount) { var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false }; - var server = CreateServer(new KestrelServerOptions() { ThreadCount = threadCount }, testLogger); - var exception = Assert.Throws(() => StartDummyApplication(server)); + using (var server = CreateServer(new KestrelServerOptions() { ThreadCount = threadCount }, testLogger)) + { + var exception = Assert.Throws(() => StartDummyApplication(server)); - Assert.Equal("threadCount", exception.ParamName); - Assert.Equal(1, testLogger.CriticalErrorsLogged); + Assert.Equal("threadCount", exception.ParamName); + Assert.Equal(1, testLogger.CriticalErrorsLogged); + } } [Fact] public void StartWithInvalidAddressThrows() { var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false }; - var server = CreateServer(new KestrelServerOptions(), testLogger); - server.Features.Get().Addresses.Add("http:/asdf"); - var exception = Assert.Throws(() => StartDummyApplication(server)); + using (var server = CreateServer(new KestrelServerOptions(), testLogger)) + { + server.Features.Get().Addresses.Add("http:/asdf"); - Assert.Contains("Invalid URL", exception.Message); - Assert.Equal(1, testLogger.CriticalErrorsLogged); + var exception = Assert.Throws(() => StartDummyApplication(server)); + + Assert.Contains("Invalid URL", exception.Message); + Assert.Equal(1, testLogger.CriticalErrorsLogged); + } } [Theory] @@ -77,14 +82,37 @@ namespace Microsoft.AspNetCore.Server.KestrelTests options.Limits.MaxRequestBufferSize = maxRequestBufferSize; options.Limits.MaxRequestLineSize = maxRequestLineSize; - var server = CreateServer(options, testLogger); + using (var server = CreateServer(options, testLogger)) + { + var exception = Assert.Throws(() => StartDummyApplication(server)); - var exception = Assert.Throws(() => StartDummyApplication(server)); + Assert.Equal( + $"Maximum request buffer size ({maxRequestBufferSize}) must be greater than or equal to maximum request line size ({maxRequestLineSize}).", + exception.Message); + Assert.Equal(1, testLogger.CriticalErrorsLogged); + } + } - Assert.Equal( - $"Maximum request buffer size ({maxRequestBufferSize}) must be greater than or equal to maximum request line size ({maxRequestLineSize}).", - exception.Message); - Assert.Equal(1, testLogger.CriticalErrorsLogged); + [Theory] + [InlineData(1, 2)] + [InlineData(int.MaxValue - 1, int.MaxValue)] + public void StartWithMaxRequestBufferSizeLessThanMaxRequestHeadersTotalSizeThrows(long maxRequestBufferSize, int maxRequestHeadersTotalSize) + { + var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false }; + var options = new KestrelServerOptions(); + options.Limits.MaxRequestBufferSize = maxRequestBufferSize; + options.Limits.MaxRequestLineSize = (int)maxRequestBufferSize; + options.Limits.MaxRequestHeadersTotalSize = maxRequestHeadersTotalSize; + + using (var server = CreateServer(options, testLogger)) + { + var exception = Assert.Throws(() => StartDummyApplication(server)); + + Assert.Equal( + $"Maximum request buffer size ({maxRequestBufferSize}) must be greater than or equal to maximum request headers size ({maxRequestHeadersTotalSize}).", + exception.Message); + Assert.Equal(1, testLogger.CriticalErrorsLogged); + } } [Fact]