Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HTTP2: Guard against empty DATA frames (without end_of_stream flag) s…
…et (#9461) Motivation: It is possible for a remote peer to flood the server / client with empty DATA frames (without end_of_stream flag) set and so cause high CPU usage without the possibility to ever hit a limit. We need to guard against this. See CVE-2019-9518 Modifications: - Add a new config option to AbstractHttp2ConnectionBuilder and sub-classes which allows to set the max number of consecutive empty DATA frames (without end_of_stream flag). After this limit is hit we will close the connection. A limit of 10 is used by default. - Add unit tests Result: Guards against CVE-2019-9518
- Loading branch information
1 parent
c6c6795
commit 6283a78
Showing
7 changed files
with
365 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
...ttp2/src/main/java/io/netty/handler/codec/http2/Http2EmptyDataFrameConnectionDecoder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Copyright 2019 The Netty Project | ||
* | ||
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the | ||
* "License"); you may not use this file except in compliance with the License. You may obtain a | ||
* copy of the License at: | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
package io.netty.handler.codec.http2; | ||
|
||
import io.netty.util.internal.ObjectUtil; | ||
|
||
/** | ||
* Enforce a limit on the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed | ||
* before the connection will be closed. | ||
*/ | ||
final class Http2EmptyDataFrameConnectionDecoder extends DecoratingHttp2ConnectionDecoder { | ||
|
||
private final int maxConsecutiveEmptyFrames; | ||
|
||
Http2EmptyDataFrameConnectionDecoder(Http2ConnectionDecoder delegate, int maxConsecutiveEmptyFrames) { | ||
super(delegate); | ||
this.maxConsecutiveEmptyFrames = ObjectUtil.checkPositive( | ||
maxConsecutiveEmptyFrames, "maxConsecutiveEmptyFrames"); | ||
} | ||
|
||
@Override | ||
public void frameListener(Http2FrameListener listener) { | ||
if (listener != null) { | ||
super.frameListener(new Http2EmptyDataFrameListener(listener, maxConsecutiveEmptyFrames)); | ||
} else { | ||
super.frameListener(null); | ||
} | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
codec-http2/src/main/java/io/netty/handler/codec/http2/Http2EmptyDataFrameListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/* | ||
* Copyright 2019 The Netty Project | ||
* | ||
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the | ||
* "License"); you may not use this file except in compliance with the License. You may obtain a | ||
* copy of the License at: | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
package io.netty.handler.codec.http2; | ||
|
||
import io.netty.buffer.ByteBuf; | ||
import io.netty.channel.ChannelHandlerContext; | ||
import io.netty.util.internal.ObjectUtil; | ||
|
||
/** | ||
* Enforce a limit on the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed | ||
* before the connection will be closed. | ||
*/ | ||
final class Http2EmptyDataFrameListener extends Http2FrameListenerDecorator { | ||
private final int maxConsecutiveEmptyFrames; | ||
|
||
private boolean violationDetected; | ||
private int emptyDataFrames; | ||
|
||
Http2EmptyDataFrameListener(Http2FrameListener listener, int maxConsecutiveEmptyFrames) { | ||
super(listener); | ||
this.maxConsecutiveEmptyFrames = ObjectUtil.checkPositive( | ||
maxConsecutiveEmptyFrames, "maxConsecutiveEmptyFrames"); | ||
} | ||
|
||
@Override | ||
public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) | ||
throws Http2Exception { | ||
if (endOfStream || data.isReadable()) { | ||
emptyDataFrames = 0; | ||
} else if (emptyDataFrames++ == maxConsecutiveEmptyFrames && !violationDetected) { | ||
violationDetected = true; | ||
throw Http2Exception.connectionError(Http2Error.ENHANCE_YOUR_CALM, | ||
"Maximum number %d of empty data frames without end_of_stream flag received", | ||
maxConsecutiveEmptyFrames); | ||
} | ||
|
||
return super.onDataRead(ctx, streamId, data, padding, endOfStream); | ||
} | ||
|
||
@Override | ||
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, | ||
int padding, boolean endStream) throws Http2Exception { | ||
emptyDataFrames = 0; | ||
super.onHeadersRead(ctx, streamId, headers, padding, endStream); | ||
} | ||
|
||
@Override | ||
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, | ||
short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception { | ||
emptyDataFrames = 0; | ||
super.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
.../src/test/java/io/netty/handler/codec/http2/Http2EmptyDataFrameConnectionDecoderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright 2019 The Netty Project | ||
* | ||
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the | ||
* "License"); you may not use this file except in compliance with the License. You may obtain a | ||
* copy of the License at: | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
package io.netty.handler.codec.http2; | ||
|
||
import org.hamcrest.CoreMatchers; | ||
import org.junit.Test; | ||
import org.mockito.ArgumentCaptor; | ||
import org.mockito.invocation.InvocationOnMock; | ||
import org.mockito.stubbing.Answer; | ||
|
||
import static org.junit.Assert.assertNull; | ||
import static org.junit.Assert.assertThat; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.when; | ||
|
||
public class Http2EmptyDataFrameConnectionDecoderTest { | ||
|
||
@Test | ||
public void testDecoration() { | ||
Http2ConnectionDecoder delegate = mock(Http2ConnectionDecoder.class); | ||
final ArgumentCaptor<Http2FrameListener> listenerArgumentCaptor = | ||
ArgumentCaptor.forClass(Http2FrameListener.class); | ||
when(delegate.frameListener()).then(new Answer<Http2FrameListener>() { | ||
@Override | ||
public Http2FrameListener answer(InvocationOnMock invocationOnMock) { | ||
return listenerArgumentCaptor.getValue(); | ||
} | ||
}); | ||
Http2FrameListener listener = mock(Http2FrameListener.class); | ||
Http2EmptyDataFrameConnectionDecoder decoder = new Http2EmptyDataFrameConnectionDecoder(delegate, 2); | ||
decoder.frameListener(listener); | ||
verify(delegate).frameListener(listenerArgumentCaptor.capture()); | ||
|
||
assertThat(decoder.frameListener(), CoreMatchers.instanceOf(Http2EmptyDataFrameListener.class)); | ||
} | ||
|
||
@Test | ||
public void testDecorationWithNull() { | ||
Http2ConnectionDecoder delegate = mock(Http2ConnectionDecoder.class); | ||
|
||
Http2EmptyDataFrameConnectionDecoder decoder = new Http2EmptyDataFrameConnectionDecoder(delegate, 2); | ||
decoder.frameListener(null); | ||
assertNull(decoder.frameListener()); | ||
} | ||
} |
Oops, something went wrong.