1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
From: Norman Maurer <norman_maurer@apple.com>
Date: Tue, 30 Mar 2021 09:40:47 +0200
Subject: CVE-2021-21409 (was: [PATCH] Merge pull request from GHSA-f256-j965-7f32)
Origin: https://github.com/netty/netty/commit/b0fa4d5aab4215f3c22ce6123dd8dd5f38dc0432
Motivation:
We also need to ensure that all the header validation is done when a single header with the endStream flag is received
Modifications:
- Adjust code to always enforce the validation
- Add more unit tests
Result:
Always correctly validate
---
.../http2/DefaultHttp2ConnectionDecoder.java | 5 ++-
.../codec/http2/Http2MultiplexTest.java | 45 ++++++++++++++++++-
2 files changed, 48 insertions(+), 2 deletions(-)
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java
@@ -352,10 +352,13 @@
short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception {
Http2Stream stream = connection.stream(streamId);
boolean allowHalfClosedRemote = false;
+ boolean isTrailers = false;
if (stream == null && !connection.streamMayHaveExisted(streamId)) {
stream = connection.remote().createStream(streamId, endOfStream);
// Allow the state to be HALF_CLOSE_REMOTE if we're creating it in that state.
allowHalfClosedRemote = stream.state() == HALF_CLOSED_REMOTE;
+ } else if (stream != null) {
+ isTrailers = stream.isHeadersReceived();
}
if (shouldIgnoreHeadersOrDataFrame(ctx, streamId, stream, "HEADERS")) {
@@ -393,7 +396,7 @@
stream.state());
}
- if (!stream.isHeadersReceived()) {
+ if (!isTrailers) {
// extract the content-length header
List<? extends CharSequence> contentLength = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
if (contentLength != null && !contentLength.isEmpty()) {
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2MultiplexTest.java
@@ -5,7 +5,7 @@
* "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
+ * https://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
@@ -26,12 +26,14 @@
import io.netty.channel.ChannelPromise;
import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpScheme;
import io.netty.handler.codec.http2.Http2Exception.StreamException;
import io.netty.handler.codec.http2.LastInboundHandler.Consumer;
import io.netty.util.AsciiString;
import io.netty.util.AttributeKey;
+import org.hamcrest.CoreMatchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -219,6 +221,137 @@
}
@Test
+ public void headerMultipleContentLengthValidationShouldPropagate() {
+ headerMultipleContentLengthValidationShouldPropagate(false);
+ }
+
+ @Test
+ public void headerMultipleContentLengthValidationShouldPropagateWithEndStream() {
+ headerMultipleContentLengthValidationShouldPropagate(true);
+ }
+
+ private void headerMultipleContentLengthValidationShouldPropagate(boolean endStream) {
+ LastInboundHandler inboundHandler = new LastInboundHandler();
+ request.addLong(HttpHeaderNames.CONTENT_LENGTH, 0);
+ request.addLong(HttpHeaderNames.CONTENT_LENGTH, 1);
+ Http2StreamChannel channel = newInboundStream(3, endStream, inboundHandler);
+ try {
+ inboundHandler.checkException();
+ fail();
+ } catch (Exception e) {
+ assertThat(e, CoreMatchers.<Exception>instanceOf(StreamException.class));
+ }
+ assertNull(inboundHandler.readInbound());
+ assertFalse(channel.isActive());
+ }
+
+ @Test
+ public void headerPlusSignContentLengthValidationShouldPropagate() {
+ headerSignContentLengthValidationShouldPropagateWithEndStream(false, false);
+ }
+
+ @Test
+ public void headerPlusSignContentLengthValidationShouldPropagateWithEndStream() {
+ headerSignContentLengthValidationShouldPropagateWithEndStream(false, true);
+ }
+
+ @Test
+ public void headerMinusSignContentLengthValidationShouldPropagate() {
+ headerSignContentLengthValidationShouldPropagateWithEndStream(true, false);
+ }
+
+ @Test
+ public void headerMinusSignContentLengthValidationShouldPropagateWithEndStream() {
+ headerSignContentLengthValidationShouldPropagateWithEndStream(true, true);
+ }
+
+ private void headerSignContentLengthValidationShouldPropagateWithEndStream(boolean minus, boolean endStream) {
+ LastInboundHandler inboundHandler = new LastInboundHandler();
+ request.add(HttpHeaderNames.CONTENT_LENGTH, (minus ? "-" : "+") + 1);
+ Http2StreamChannel channel = newInboundStream(3, endStream, inboundHandler);
+ try {
+ inboundHandler.checkException();
+ fail();
+ } catch (Exception e) {
+ assertThat(e, CoreMatchers.<Exception>instanceOf(StreamException.class));
+ }
+ assertNull(inboundHandler.readInbound());
+ assertFalse(channel.isActive());
+ }
+
+ @Test
+ public void headerContentLengthNotMatchValidationShouldPropagate() {
+ headerContentLengthNotMatchValidationShouldPropagate(false, false, false);
+ }
+
+ @Test
+ public void headerContentLengthNotMatchValidationShouldPropagateWithEndStream() {
+ headerContentLengthNotMatchValidationShouldPropagate(false, true, false);
+ }
+
+ @Test
+ public void headerContentLengthNotMatchValidationShouldPropagateCloseLocal() {
+ headerContentLengthNotMatchValidationShouldPropagate(true, false, false);
+ }
+
+ @Test
+ public void headerContentLengthNotMatchValidationShouldPropagateWithEndStreamCloseLocal() {
+ headerContentLengthNotMatchValidationShouldPropagate(true, true, false);
+ }
+
+ @Test
+ public void headerContentLengthNotMatchValidationShouldPropagateTrailers() {
+ headerContentLengthNotMatchValidationShouldPropagate(false, false, true);
+ }
+
+ @Test
+ public void headerContentLengthNotMatchValidationShouldPropagateWithEndStreamTrailers() {
+ headerContentLengthNotMatchValidationShouldPropagate(false, true, true);
+ }
+
+ @Test
+ public void headerContentLengthNotMatchValidationShouldPropagateCloseLocalTrailers() {
+ headerContentLengthNotMatchValidationShouldPropagate(true, false, true);
+ }
+
+ @Test
+ public void headerContentLengthNotMatchValidationShouldPropagateWithEndStreamCloseLocalTrailers() {
+ headerContentLengthNotMatchValidationShouldPropagate(true, true, true);
+ }
+
+ private void headerContentLengthNotMatchValidationShouldPropagate(
+ boolean closeLocal, boolean endStream, boolean trailer) {
+ LastInboundHandler inboundHandler = new LastInboundHandler();
+ request.addLong(HttpHeaderNames.CONTENT_LENGTH, 1);
+ Http2StreamChannel channel = newInboundStream(3, false, inboundHandler);
+ assertTrue(channel.isActive());
+
+ if (closeLocal) {
+ channel.writeAndFlush(new DefaultHttp2HeadersFrame(new DefaultHttp2Headers(), true))
+ .syncUninterruptibly();
+ assertEquals(Http2Stream.State.HALF_CLOSED_LOCAL, channel.stream().state());
+ } else {
+ assertEquals(Http2Stream.State.OPEN, channel.stream().state());
+ }
+
+ if (trailer) {
+ frameInboundWriter.writeInboundHeaders(channel.stream().id(), new DefaultHttp2Headers(), 0, endStream);
+ } else {
+ frameInboundWriter.writeInboundData(channel.stream().id(), bb("foo"), 0, endStream);
+ }
+ try {
+ inboundHandler.checkException();
+ fail();
+ } catch (Exception e) {
+ assertThat(e, CoreMatchers.<Exception>instanceOf(StreamException.class));
+ }
+ Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(request).stream(channel.stream());
+ assertEquals(headersFrame, inboundHandler.readInbound());
+ assertNull(inboundHandler.readInbound());
+ assertFalse(channel.isActive());
+ }
+
+ @Test
public void framesShouldBeMultiplexed() {
LastInboundHandler handler1 = new LastInboundHandler();
Http2StreamChannel channel1 = newInboundStream(3, false, handler1);
|