Skip to content

Commit 58f4367

Browse files
authored
GL64 CVE-2023-44487 Rapid Reset protection (#7822)
1 parent c462d22 commit 58f4367

File tree

5 files changed

+59
-13
lines changed

5 files changed

+59
-13
lines changed

http/http2/src/main/java/io/helidon/http/http2/Http2Stream.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ public interface Http2Stream {
2727
* Close the stream.
2828
*
2929
* @param rstStream rst stream frame
30+
* @return true if rapid reset(rst received before any data are sent)
3031
*/
31-
void rstStream(Http2RstStream rstStream);
32+
boolean rstStream(Http2RstStream rstStream);
3233

3334
/**
3435
* Flow control window update.

webclient/http2/src/main/java/io/helidon/webclient/http2/Http2ClientStream.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public void headers(Http2Headers headers, boolean endOfStream) {
112112
this.hasEntity = !endOfStream;
113113
}
114114
@Override
115-
public void rstStream(Http2RstStream rstStream) {
115+
public boolean rstStream(Http2RstStream rstStream) {
116116
if (state == Http2StreamState.IDLE) {
117117
throw new Http2Exception(Http2ErrorCode.PROTOCOL,
118118
"Received RST_STREAM for stream "

webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ConfigBlueprint.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,28 @@ interface Http2ConfigBlueprint extends ProtocolConfig {
105105
@ConfiguredOption("false")
106106
boolean sendErrorDetails();
107107

108+
/**
109+
* Period for counting rapid resets(stream RST sent by client before any data have been sent by server).
110+
* Default value is {@code PT10S}.
111+
*
112+
* @return duration
113+
* @see <a href="https://nvd.nist.gov/vuln/detail/CVE-2023-44487">CVE-2023-44487</a>
114+
* @see <a href="https://en.wikipedia.org/wiki/ISO_8601#Durations">ISO_8601 Durations</a>
115+
*/
116+
@ConfiguredOption("PT10S")
117+
Duration rapidResetCheckPeriod();
118+
119+
/**
120+
* Maximum number of rapid resets(stream RST sent by client before any data have been sent by server).
121+
* When reached within {@link #rapidResetCheckPeriod()}, GOAWAY is sent to client and connection is closed.
122+
* Default value is {@code 100}.
123+
*
124+
* @return maximum number of rapid resets
125+
* @see <a href="https://nvd.nist.gov/vuln/detail/CVE-2023-44487">CVE-2023-44487</a>
126+
*/
127+
@ConfiguredOption("100")
128+
int maxRapidResets();
129+
108130
/**
109131
* If set to false, any path is accepted (even containing illegal characters).
110132
*

webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Connection.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,11 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
9898
private final boolean sendErrorDetails;
9999
private final ConnectionFlowControl flowControl;
100100
private final WritableHeaders<?> connectionHeaders;
101-
101+
private final long rapidResetCheckPeriod;
102+
private final int maxRapidResets;
102103
private final long maxClientConcurrentStreams;
104+
private int rapidResetCnt = 0;
105+
private long rapidResetPeriodStart = 0;
103106
// initial client settings, until we receive real ones
104107
private Http2Settings clientSettings = Http2Settings.builder()
105108
.build();
@@ -120,6 +123,8 @@ public class Http2Connection implements ServerConnection, InterruptableTask<Void
120123
Http2Connection(ConnectionContext ctx, Http2Config http2Config, List<Http2SubProtocolSelector> subProviders) {
121124
this.ctx = ctx;
122125
this.http2Config = http2Config;
126+
this.rapidResetCheckPeriod = http2Config.rapidResetCheckPeriod().toNanos();
127+
this.maxRapidResets = http2Config.maxRapidResets();
123128
this.serverSettings = Http2Settings.builder()
124129
.update(builder -> settingsUpdate(http2Config, builder))
125130
.add(Http2Setting.ENABLE_PUSH, false)
@@ -465,7 +470,6 @@ private void readWindowUpdateFrame() {
465470

466471
state = State.READ_FRAME;
467472

468-
boolean overflow;
469473
int increment = windowUpdate.windowSizeIncrement();
470474

471475

@@ -475,9 +479,10 @@ private void readWindowUpdateFrame() {
475479
Http2GoAway frame = new Http2GoAway(0, Http2ErrorCode.PROTOCOL, "Window size 0");
476480
connectionWriter.write(frame.toFrameData(clientSettings, 0, Http2Flag.NoFlags.create()));
477481
}
478-
overflow = flowControl.incrementOutboundConnectionWindowSize(increment) > WindowSize.MAX_WIN_SIZE;
479-
if (overflow) {
480-
Http2GoAway frame = new Http2GoAway(0, Http2ErrorCode.FLOW_CONTROL, "Window size too big. Max: ");
482+
483+
long size = flowControl.incrementOutboundConnectionWindowSize(increment);
484+
if (size > WindowSize.MAX_WIN_SIZE || size < 0) {
485+
Http2GoAway frame = new Http2GoAway(0, Http2ErrorCode.FLOW_CONTROL, "Window size too big.");
481486
connectionWriter.write(frame.toFrameData(clientSettings, 0, Http2Flag.NoFlags.create()));
482487
}
483488
} else {
@@ -731,7 +736,18 @@ private void rstStream() {
731736

732737
try {
733738
StreamContext streamContext = stream(streamId);
734-
streamContext.stream().rstStream(rstStream);
739+
boolean rapidReset = streamContext.stream().rstStream(rstStream);
740+
if (rapidReset && maxRapidResets != -1) {
741+
long currentTime = System.nanoTime();
742+
if (rapidResetCheckPeriod >= currentTime - rapidResetPeriodStart) {
743+
rapidResetCnt = 1;
744+
rapidResetPeriodStart = currentTime;
745+
} else if (maxRapidResets < rapidResetCnt) {
746+
throw new Http2Exception(Http2ErrorCode.PROTOCOL, "Rapid reset attack detected!");
747+
} else {
748+
rapidResetCnt++;
749+
}
750+
}
735751

736752
state = State.READ_FRAME;
737753
} catch (Http2Exception e) {
@@ -778,7 +794,7 @@ private StreamContext stream(int streamId) {
778794
}
779795

780796
// 5.1.2 MAX_CONCURRENT_STREAMS limit check - stream error of type PROTOCOL_ERROR or REFUSED_STREAM
781-
if (streams.size() > maxClientConcurrentStreams) {
797+
if (streams.size() + 1 > maxClientConcurrentStreams) {
782798
throw new Http2Exception(Http2ErrorCode.REFUSED_STREAM,
783799
"Maximum concurrent streams limit " + maxClientConcurrentStreams + " exceeded");
784800
}

webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ServerStream.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import io.helidon.http.http2.Http2FrameData;
4444
import io.helidon.http.http2.Http2FrameHeader;
4545
import io.helidon.http.http2.Http2FrameTypes;
46+
import io.helidon.http.http2.Http2GoAway;
4647
import io.helidon.http.http2.Http2Headers;
4748
import io.helidon.http.http2.Http2Priority;
4849
import io.helidon.http.http2.Http2RstStream;
@@ -180,29 +181,35 @@ public void checkHeadersReceivable() throws Http2Exception {
180181
}
181182

182183
@Override
183-
public void rstStream(Http2RstStream rstStream) {
184+
public boolean rstStream(Http2RstStream rstStream) {
184185
if (state == Http2StreamState.IDLE) {
185186
throw new Http2Exception(Http2ErrorCode.PROTOCOL,
186187
"Received RST_STREAM for stream "
187188
+ streamId + " in IDLE state");
188189
}
189190
// TODO interrupt
191+
boolean rapidReset = writeState == WriteState.INIT;
190192
this.state = Http2StreamState.CLOSED;
193+
return rapidReset;
191194
}
192195

193196
@Override
194197
public void windowUpdate(Http2WindowUpdate windowUpdate) {
198+
//5.1/3
195199
if (state == Http2StreamState.IDLE) {
196-
throw new Http2Exception(Http2ErrorCode.PROTOCOL, "Received WINDOW_UPDATE for stream "
197-
+ streamId + " in state IDLE");
200+
String msg = "Received WINDOW_UPDATE for stream " + streamId + " in state IDLE";
201+
Http2GoAway frame = new Http2GoAway(0, Http2ErrorCode.PROTOCOL, msg);
202+
writer.write(frame.toFrameData(clientSettings, 0, Http2Flag.NoFlags.create()));
203+
throw new Http2Exception(Http2ErrorCode.PROTOCOL, msg);
198204
}
199205
//6.9/2
200206
if (windowUpdate.windowSizeIncrement() == 0) {
201207
Http2RstStream frame = new Http2RstStream(Http2ErrorCode.PROTOCOL);
202208
writer.write(frame.toFrameData(clientSettings, streamId, Http2Flag.NoFlags.create()));
203209
}
204210
//6.9.1/3
205-
if (flowControl.outbound().incrementStreamWindowSize(windowUpdate.windowSizeIncrement()) > WindowSize.MAX_WIN_SIZE) {
211+
long size = flowControl.outbound().incrementStreamWindowSize(windowUpdate.windowSizeIncrement());
212+
if (size > WindowSize.MAX_WIN_SIZE || size < 0L) {
206213
Http2RstStream frame = new Http2RstStream(Http2ErrorCode.FLOW_CONTROL);
207214
writer.write(frame.toFrameData(clientSettings, streamId, Http2Flag.NoFlags.create()));
208215
}

0 commit comments

Comments
 (0)