Skip to content

Commit

Permalink
GL64 CVE-2023-44487 Rapid Reset protection
Browse files Browse the repository at this point in the history
  • Loading branch information
danielkec committed Oct 17, 2023
1 parent 3b30d14 commit c9f6b95
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ public interface Http2Stream {
* Close the stream.
*
* @param rstStream rst stream frame
* @return true if rapid reset(rst received before any data are sent)
*/
void rstStream(Http2RstStream rstStream);
boolean rstStream(Http2RstStream rstStream);

/**
* Flow control window update.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public void headers(Http2Headers headers, boolean endOfStream) {
this.hasEntity = !endOfStream;
}
@Override
public void rstStream(Http2RstStream rstStream) {
public boolean rstStream(Http2RstStream rstStream) {
if (state == Http2StreamState.IDLE) {
throw new Http2Exception(Http2ErrorCode.PROTOCOL,
"Received RST_STREAM for stream "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ interface Http2ConfigBlueprint extends ProtocolConfig {
@ConfiguredOption("false")
boolean sendErrorDetails();

/**
* Period for counting rapid resets(stream RST sent by client before any data have been sent by server).
* Default value is {@code PT10S}.
*
* @return duration
* @see <a href="https://nvd.nist.gov/vuln/detail/CVE-2023-44487">CVE-2023-44487</a>
* @see <a href="https://en.wikipedia.org/wiki/ISO_8601#Durations">ISO_8601 Durations</a>
*/
@ConfiguredOption("PT10S")
Duration rapidResetCheckPeriod();

/**
* Maximum number of rapid resets(stream RST sent by client before any data have been sent by server).
* When reached within {@link #rapidResetCheckPeriod()}, GOAWAY is sent to client and connection is closed.
* Default value is {@code 100}.
*
* @return maximum number of rapid resets
* @see <a href="https://nvd.nist.gov/vuln/detail/CVE-2023-44487">CVE-2023-44487</a>
*/
@ConfiguredOption("100")
int maxRapidResets();

/**
* If set to false, any path is accepted (even containing illegal characters).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,11 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
private final boolean sendErrorDetails;
private final ConnectionFlowControl flowControl;
private final WritableHeaders<?> connectionHeaders;

private final long rapidResetCheckPeriod;
private final int maxRapidResets;
private final long maxClientConcurrentStreams;
private int rapidResetCnt = 0;
private long rapidResetPeriodStart = 0;
// initial client settings, until we receive real ones
private Http2Settings clientSettings = Http2Settings.builder()
.build();
Expand All @@ -120,6 +123,8 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
Http2Connection(ConnectionContext ctx, Http2Config http2Config, List<Http2SubProtocolSelector> subProviders) {
this.ctx = ctx;
this.http2Config = http2Config;
this.rapidResetCheckPeriod = http2Config.rapidResetCheckPeriod().toNanos();
this.maxRapidResets = http2Config.maxRapidResets();
this.serverSettings = Http2Settings.builder()
.update(builder -> settingsUpdate(http2Config, builder))
.add(Http2Setting.ENABLE_PUSH, false)
Expand Down Expand Up @@ -465,7 +470,6 @@ private void readWindowUpdateFrame() {

state = State.READ_FRAME;

boolean overflow;
int increment = windowUpdate.windowSizeIncrement();


Expand All @@ -475,9 +479,10 @@ private void readWindowUpdateFrame() {
Http2GoAway frame = new Http2GoAway(0, Http2ErrorCode.PROTOCOL, "Window size 0");
connectionWriter.write(frame.toFrameData(clientSettings, 0, Http2Flag.NoFlags.create()));
}
overflow = flowControl.incrementOutboundConnectionWindowSize(increment) > WindowSize.MAX_WIN_SIZE;
if (overflow) {
Http2GoAway frame = new Http2GoAway(0, Http2ErrorCode.FLOW_CONTROL, "Window size too big. Max: ");

long size = flowControl.incrementOutboundConnectionWindowSize(increment);
if (size > WindowSize.MAX_WIN_SIZE || size < 0) {
Http2GoAway frame = new Http2GoAway(0, Http2ErrorCode.FLOW_CONTROL, "Window size too big.");
connectionWriter.write(frame.toFrameData(clientSettings, 0, Http2Flag.NoFlags.create()));
}
} else {
Expand Down Expand Up @@ -731,7 +736,18 @@ private void rstStream() {

try {
StreamContext streamContext = stream(streamId);
streamContext.stream().rstStream(rstStream);
boolean rapidReset = streamContext.stream().rstStream(rstStream);
if (rapidReset && maxRapidResets != -1) {
long currentTime = System.nanoTime();
if (rapidResetCheckPeriod >= currentTime - rapidResetPeriodStart) {
rapidResetCnt = 1;
rapidResetPeriodStart = currentTime;
} else if (maxRapidResets < rapidResetCnt) {
throw new Http2Exception(Http2ErrorCode.PROTOCOL, "Rapid reset attack detected!");
} else {
rapidResetCnt++;
}
}

state = State.READ_FRAME;
} catch (Http2Exception e) {
Expand Down Expand Up @@ -778,7 +794,7 @@ private StreamContext stream(int streamId) {
}

// 5.1.2 MAX_CONCURRENT_STREAMS limit check - stream error of type PROTOCOL_ERROR or REFUSED_STREAM
if (streams.size() > maxClientConcurrentStreams) {
if (streams.size() + 1 > maxClientConcurrentStreams) {
throw new Http2Exception(Http2ErrorCode.REFUSED_STREAM,
"Maximum concurrent streams limit " + maxClientConcurrentStreams + " exceeded");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import io.helidon.http.http2.Http2FrameData;
import io.helidon.http.http2.Http2FrameHeader;
import io.helidon.http.http2.Http2FrameTypes;
import io.helidon.http.http2.Http2GoAway;
import io.helidon.http.http2.Http2Headers;
import io.helidon.http.http2.Http2Priority;
import io.helidon.http.http2.Http2RstStream;
Expand Down Expand Up @@ -180,29 +181,35 @@ public void checkHeadersReceivable() throws Http2Exception {
}

@Override
public void rstStream(Http2RstStream rstStream) {
public boolean rstStream(Http2RstStream rstStream) {
if (state == Http2StreamState.IDLE) {
throw new Http2Exception(Http2ErrorCode.PROTOCOL,
"Received RST_STREAM for stream "
+ streamId + " in IDLE state");
}
// TODO interrupt
boolean rapidReset = writeState == WriteState.INIT;
this.state = Http2StreamState.CLOSED;
return rapidReset;
}

@Override
public void windowUpdate(Http2WindowUpdate windowUpdate) {
//5.1/3
if (state == Http2StreamState.IDLE) {
throw new Http2Exception(Http2ErrorCode.PROTOCOL, "Received WINDOW_UPDATE for stream "
+ streamId + " in state IDLE");
String msg = "Received WINDOW_UPDATE for stream " + streamId + " in state IDLE";
Http2GoAway frame = new Http2GoAway(0, Http2ErrorCode.PROTOCOL, msg);
writer.write(frame.toFrameData(clientSettings, 0, Http2Flag.NoFlags.create()));
throw new Http2Exception(Http2ErrorCode.PROTOCOL, msg);
}
//6.9/2
if (windowUpdate.windowSizeIncrement() == 0) {
Http2RstStream frame = new Http2RstStream(Http2ErrorCode.PROTOCOL);
writer.write(frame.toFrameData(clientSettings, streamId, Http2Flag.NoFlags.create()));
}
//6.9.1/3
if (flowControl.outbound().incrementStreamWindowSize(windowUpdate.windowSizeIncrement()) > WindowSize.MAX_WIN_SIZE) {
long size = flowControl.outbound().incrementStreamWindowSize(windowUpdate.windowSizeIncrement());
if (size > WindowSize.MAX_WIN_SIZE || size < 0L) {
Http2RstStream frame = new Http2RstStream(Http2ErrorCode.FLOW_CONTROL);
writer.write(frame.toFrameData(clientSettings, streamId, Http2Flag.NoFlags.create()));
}
Expand Down

0 comments on commit c9f6b95

Please sign in to comment.