Skip to content

Commit

Permalink
[UNDERTOW-2323] At Http2Channel, prevent DDoS attack where a series o…
Browse files Browse the repository at this point in the history
…f requests followed by rst frames canceling the requests can cause a denial of service

Signed-off-by: Flavia Rainone <frainone@redhat.com>
  • Loading branch information
fl4via committed Oct 16, 2023
1 parent a6861ee commit 6f0ca47
Show file tree
Hide file tree
Showing 4 changed files with 899 additions and 4 deletions.
23 changes: 23 additions & 0 deletions core/src/main/java/io/undertow/UndertowOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,29 @@ public class UndertowOptions {
*/
public static final Option<Boolean> TRACK_ACTIVE_REQUESTS = Option.simple(UndertowOptions.class, "TRACK_ACTIVE_REQUESTS", Boolean.class);

/**
* Default value of {@link #RST_FRAMES_TIME_WINDOW} option.
*/
public static final int DEFAULT_RST_FRAMES_TIME_WINDOW = 30000;
/**
* Default value of {@link #MAX_RST_FRAMES_PER_WINDOW} option.
*/
public static final int DEFAULT_MAX_RST_FRAMES_PER_WINDOW = 200;

/**
* Window of time per which the number of HTTP2 RST received frames is measured, in milliseconds.
* If a number of RST frames bigger than {@link #MAX_RST_FRAMES_PER_WINDOW} is received during this time window,
* the server will send a GO_AWAY frame with error code 11 ({@code ENHANCE_YOUR_CALM}) and it will close the connection.
*/
public static final Option<Integer> RST_FRAMES_TIME_WINDOW = Option.simple(UndertowOptions.class, "MAX_RST_STREAM_TIME_WINDOW", Integer.class);

/**
* Maximum number of HTTP2 RST frames received allowed during a time window.
* If a number of RST frames bigger than this limit is received during {@link #RST_FRAMES_TIME_WINDOW} milliseconds,
* the server will send a GO_AWAY frame with error code 11 ({@code ENHANCE_YOUR_CALM}) and it will close the connection.
*/
public static final Option<Integer> MAX_RST_FRAMES_PER_WINDOW = Option.simple(UndertowOptions.class, "MAX_RST_STREAMS_PER_TIME_WINDOW", Integer.class);

private UndertowOptions() {

}
Expand Down
35 changes: 31 additions & 4 deletions core/src/main/java/io/undertow/protocols/http2/Http2Channel.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ public class Http2Channel extends AbstractFramedChannel<Http2Channel, AbstractHt
private final int maxHeaders;
private final int maxHeaderListSize;

// the max number of rst frames received per window
private final int maxRstFramesPerWindow;
// the time window for counting rst frames received
private final long rstFramesTimeWindow;
// the time in milliseconds the last rst frame was received
private long lastRstFrameMillis = System.currentTimeMillis();
// the total number of received rst frames during current time windows
private int receivedRstFramesPerWindow;


private static final AtomicIntegerFieldUpdater<Http2Channel> sendConcurrentStreamsAtomicUpdater = AtomicIntegerFieldUpdater.newUpdater(
Http2Channel.class, "sendConcurrentStreams");

Expand Down Expand Up @@ -236,6 +246,8 @@ public Http2Channel(StreamConnection connectedStreamChannel, String protocol, By
} else {
paddingRandom = null;
}
maxRstFramesPerWindow = settings.get(UndertowOptions.MAX_RST_FRAMES_PER_WINDOW, settings.get(UndertowOptions.MAX_RST_FRAMES_PER_WINDOW, UndertowOptions.DEFAULT_MAX_RST_FRAMES_PER_WINDOW));
rstFramesTimeWindow = settings.get(UndertowOptions.RST_FRAMES_TIME_WINDOW, settings.get(UndertowOptions.RST_FRAMES_TIME_WINDOW, UndertowOptions.DEFAULT_RST_FRAMES_TIME_WINDOW));

this.decoder = new HpackDecoder(encoderHeaderTableSize);
this.encoder = new HpackEncoder(encoderHeaderTableSize);
Expand Down Expand Up @@ -479,7 +491,7 @@ protected AbstractHttp2StreamSourceChannel createChannelImpl(FrameHeaderData fra
throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(FRAME_TYPE_RST_STREAM));
}
channel = new Http2RstStreamStreamSourceChannel(this, frameData, parser.getErrorCode(), frameParser.streamId);
handleRstStream(frameParser.streamId);
handleRstStream(frameParser.streamId, true);
if(isIdle(frameParser.streamId)) {
sendGoAway(ERROR_PROTOCOL_ERROR);
}
Expand Down Expand Up @@ -1137,16 +1149,16 @@ public void sendRstStream(int streamId, int statusCode) {
//no point sending if the channel is closed
return;
}
sentRstStreams.store(streamId, handleRstStream(streamId));
sentRstStreams.store(streamId, handleRstStream(streamId, false));
if(UndertowLogger.REQUEST_IO_LOGGER.isDebugEnabled()) {
UndertowLogger.REQUEST_IO_LOGGER.debugf(new ClosedChannelException(), "Sending rststream on channel %s stream %s", this, streamId);
}
Http2RstStreamSinkChannel channel = new Http2RstStreamSinkChannel(this, streamId, statusCode);
flushChannelIgnoreFailure(channel);
}

private StreamHolder handleRstStream(int streamId) {
StreamHolder holder = currentStreams.remove(streamId);
private StreamHolder handleRstStream(int streamId, boolean receivedRst) {
final StreamHolder holder = currentStreams.remove(streamId);
if(holder != null) {
if(streamId % 2 == (isClient() ? 1 : 0)) {
sendConcurrentStreamsAtomicUpdater.getAndDecrement(this);
Expand All @@ -1159,6 +1171,21 @@ private StreamHolder handleRstStream(int streamId) {
if (holder.sourceChannel != null) {
holder.sourceChannel.rstStream();
}
if (receivedRst) {
long currentTimeMillis = System.currentTimeMillis();
// reset the window tracking
if (currentTimeMillis - lastRstFrameMillis >= rstFramesTimeWindow) {
lastRstFrameMillis = currentTimeMillis;
receivedRstFramesPerWindow = 1;
} else {
//
receivedRstFramesPerWindow ++;
if (receivedRstFramesPerWindow > maxRstFramesPerWindow) {
sendGoAway(Http2Channel.ERROR_ENHANCE_YOUR_CALM);
UndertowLogger.REQUEST_IO_LOGGER.debugf("Reached maximum number of rst frames %s during %s ms, sending GO_AWAY 11", maxRstFramesPerWindow, rstFramesTimeWindow);
}
}
}
}
return holder;
}
Expand Down

0 comments on commit 6f0ca47

Please sign in to comment.