Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 3cb0f12

Browse files
committed
Limit size of memory buffer when reading request (#304)
1 parent 528832f commit 3cb0f12

File tree

4 files changed

+138
-1
lines changed

4 files changed

+138
-1
lines changed

src/Microsoft.AspNetCore.Server.Kestrel/Http/Connection.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,26 @@ public Connection(ListenerContext context, UvStreamHandle socket) : base(context
4646

4747
ConnectionId = GenerateConnectionId(Interlocked.Increment(ref _lastConnectionId));
4848

49-
_rawSocketInput = new SocketInput(Memory, ThreadPool);
49+
var maxInputBufferLength = context.ServerInformation.MaxInputBufferLength;
50+
if (maxInputBufferLength < -1 || maxInputBufferLength == 0)
51+
{
52+
var paramName = String.Join(".",
53+
nameof(context),
54+
nameof(context.ServerInformation),
55+
nameof(context.ServerInformation.MaxInputBufferLength));
56+
57+
throw new ArgumentOutOfRangeException(paramName, maxInputBufferLength, $"{paramName} must be positive or -1.");
58+
}
59+
60+
if (maxInputBufferLength == -1)
61+
{
62+
_rawSocketInput = new SocketInput(Memory, ThreadPool);
63+
}
64+
else
65+
{
66+
_rawSocketInput = new SocketInput(Memory, ThreadPool, maxInputBufferLength, this, Thread);
67+
}
68+
5069
_rawSocketOutput = new SocketOutput(Thread, _socket, Memory, this, ConnectionId, Log, ThreadPool, WriteReqPool);
5170
}
5271

src/Microsoft.AspNetCore.Server.Kestrel/Http/SocketInput.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,21 @@ public class SocketInput : ICriticalNotifyCompletion, IDisposable
3030
private int _consumingState;
3131
private object _sync = new object();
3232

33+
private readonly BufferLengthConnectionController _bufferLengthConnectionController;
34+
3335
public SocketInput(MemoryPool memory, IThreadPool threadPool)
3436
{
3537
_memory = memory;
3638
_threadPool = threadPool;
3739
_awaitableState = _awaitableIsNotCompleted;
3840
}
3941

42+
public SocketInput(MemoryPool memory, IThreadPool threadPool, long maxBufferLength, IConnectionControl connectionControl,
43+
KestrelThread connectionThread) : this(memory, threadPool)
44+
{
45+
_bufferLengthConnectionController = new BufferLengthConnectionController(maxBufferLength, connectionControl, connectionThread);
46+
}
47+
4048
public bool RemoteIntakeFin { get; set; }
4149

4250
public bool IsCompleted => ReferenceEquals(_awaitableState, _awaitableIsCompleted);
@@ -61,6 +69,9 @@ public void IncomingData(byte[] buffer, int offset, int count)
6169
{
6270
lock (_sync)
6371
{
72+
// Must call Add() before bytes are available to consumer, to ensure that Length is >= 0
73+
_bufferLengthConnectionController?.Add(count);
74+
6475
if (count > 0)
6576
{
6677
if (_tail == null)
@@ -91,6 +102,9 @@ public void IncomingComplete(int count, Exception error)
91102
{
92103
lock (_sync)
93104
{
105+
// Must call Add() before bytes are available to consumer, to ensure that Length is >= 0
106+
_bufferLengthConnectionController?.Add(count);
107+
94108
if (_pinned != null)
95109
{
96110
_pinned.End += count;
@@ -176,10 +190,16 @@ public void ConsumingComplete(
176190

177191
if (!consumed.IsDefault)
178192
{
193+
var lengthConsumed = new MemoryPoolIterator(_head).GetLength(consumed);
194+
179195
returnStart = _head;
180196
returnEnd = consumed.Block;
181197
_head = consumed.Block;
182198
_head.Start = consumed.Index;
199+
200+
// Must call Subtract() after bytes have been freed, to avoid producer starting too early and growing
201+
// buffer beyond max length.
202+
_bufferLengthConnectionController?.Subtract(lengthConsumed);
183203
}
184204

185205
if (!examined.IsDefault &&
@@ -295,5 +315,72 @@ public void Dispose()
295315
_head = null;
296316
_tail = null;
297317
}
318+
319+
private class BufferLengthConnectionController
320+
{
321+
private readonly long _maxLength;
322+
private readonly IConnectionControl _connectionControl;
323+
private readonly KestrelThread _connectionThread;
324+
325+
private readonly object _lock = new object();
326+
327+
private long _length;
328+
private bool _connectionPaused;
329+
330+
public BufferLengthConnectionController(long maxLength, IConnectionControl connectionControl, KestrelThread connectionThread)
331+
{
332+
_maxLength = maxLength;
333+
_connectionControl = connectionControl;
334+
_connectionThread = connectionThread;
335+
}
336+
337+
public long Length
338+
{
339+
get
340+
{
341+
return _length;
342+
}
343+
set
344+
{
345+
// Caller should ensure that bytes are never consumed before the producer has called Add()
346+
Debug.Assert(value >= 0);
347+
348+
_length = value;
349+
}
350+
}
351+
352+
public void Add(int count)
353+
{
354+
// Add() should never be called while connection is paused, since ConnectionControl.Pause() runs on a libuv thread
355+
// and should take effect immediately.
356+
Debug.Assert(!_connectionPaused);
357+
358+
lock (_lock)
359+
{
360+
Length += count;
361+
if (Length >= _maxLength)
362+
{
363+
_connectionPaused = true;
364+
_connectionControl.Pause();
365+
}
366+
}
367+
}
368+
369+
public void Subtract(int count)
370+
{
371+
lock (_lock)
372+
{
373+
Length -= count;
374+
375+
if (_connectionPaused && Length < _maxLength)
376+
{
377+
_connectionPaused = false;
378+
_connectionThread.Post(
379+
(connectionControl) => ((IConnectionControl)connectionControl).Resume(),
380+
_connectionControl);
381+
}
382+
}
383+
}
384+
}
298385
}
299386
}

src/Microsoft.AspNetCore.Server.Kestrel/IKestrelServerInformation.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ public interface IKestrelServerInformation
1010
{
1111
int ThreadCount { get; set; }
1212

13+
/// <summary>
14+
/// The maximum number of bytes that will be buffered in memory when reading a request.
15+
/// Default value is 1048576 (1 MB).
16+
/// A custom value can be configured using the "kestrel.maxInputBufferLength" key in
17+
/// <seealso cref="Microsoft.Extensions.Configuration.IConfiguration"/>.
18+
/// </summary>
19+
long MaxInputBufferLength { get; set; }
20+
1321
/// <summary>
1422
/// The amount of time after the server begins shutting down before connections will be forcefully closed.
1523
/// By default, Kestrel will wait 5 seconds for any ongoing requests to complete before terminating

src/Microsoft.AspNetCore.Server.Kestrel/KestrelServerInformation.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public KestrelServerInformation(IConfiguration configuration)
2121

2222
Addresses = GetAddresses(configuration);
2323
ThreadCount = GetThreadCount(configuration);
24+
MaxInputBufferLength = GetMaxInputBufferLength(configuration);
2425
ShutdownTimeout = GetShutdownTimeout(configuration);
2526
NoDelay = GetNoDelay(configuration);
2627
PoolingParameters = new KestrelServerPoolingParameters(configuration);
@@ -30,6 +31,8 @@ public KestrelServerInformation(IConfiguration configuration)
3031

3132
public int ThreadCount { get; set; }
3233

34+
public long MaxInputBufferLength { get; set; }
35+
3336
public TimeSpan ShutdownTimeout { get; set; }
3437

3538
public bool NoDelay { get; set; }
@@ -96,6 +99,26 @@ private static int GetThreadCount(IConfiguration configuration)
9699
return ProcessorThreadCount;
97100
}
98101

102+
private static long GetMaxInputBufferLength(IConfiguration configuration)
103+
{
104+
const long defaultMaxInputBufferLength = 1024 * 1024;
105+
106+
var maxInputBufferLengthString = configuration["kestrel.maxInputBufferLength"];
107+
108+
if (string.IsNullOrEmpty(maxInputBufferLengthString))
109+
{
110+
return defaultMaxInputBufferLength;
111+
}
112+
113+
long maxInputBufferLength;
114+
if (long.TryParse(maxInputBufferLengthString, NumberStyles.Integer, CultureInfo.InvariantCulture, out maxInputBufferLength))
115+
{
116+
return maxInputBufferLength;
117+
}
118+
119+
return defaultMaxInputBufferLength;
120+
}
121+
99122
private TimeSpan GetShutdownTimeout(IConfiguration configuration)
100123
{
101124
var shutdownTimeoutString = configuration["kestrel.shutdownTimout"];

0 commit comments

Comments
 (0)