Do not enforce arbitrary max header list size in HpackEncoder (#7853)

Motivation:

When connecting to an HTTP/2 server that did not set any value for the
SETTINGS_MAX_HEADER_LIST_SIZE in the settings frame, the netty client was
imposing an arbitrary maximum header list size of 8kB. There should be no need
for the client to enforce such a limit if the server has not specified any
limit. This caused an issue for a grpc-java client that needed to send a large
header to a server via an Envoy proxy server. The error condition is
demonstrated here: https://github.com/JLofgren/demo-grpc-java-bug-4284

Fixes grpc-java issue #4284 - https://github.com/grpc/grpc-java/issues/4284
and netty issue #7825 - https://github.com/netty/netty/issues/7825

Modifications:

In HpackEncoder use MAX_HEADER_LIST_SIZE as default maxHeader list size.

Result:

HpackEncoder will only enforce a max header list size if the server has
specified a limit in its settings frame.
This commit is contained in:
JLofgren 2018-04-16 16:27:36 -05:00 committed by Scott Mitchell
parent 81a7d1413b
commit a4146b706c
2 changed files with 43 additions and 2 deletions

View File

@ -41,7 +41,6 @@ import java.util.Arrays;
import java.util.Map;
import static io.netty.handler.codec.http2.HpackUtil.equalsConstantTime;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_LIST_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_LIST_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
@ -86,7 +85,7 @@ final class HpackEncoder {
public HpackEncoder(boolean ignoreMaxHeaderListSize, int arraySizeHint) {
this.ignoreMaxHeaderListSize = ignoreMaxHeaderListSize;
maxHeaderTableSize = DEFAULT_HEADER_TABLE_SIZE;
maxHeaderListSize = DEFAULT_HEADER_LIST_SIZE;
maxHeaderListSize = MAX_HEADER_LIST_SIZE;
// Enforce a bound of [2, 128] because hashMask is a byte. The max possible value of hashMask is one less
// than the length of this array, and we want the mask to be > 0.
headerFields = new HeaderEntry[findNextPositivePowerOfTwo(max(2, min(arraySizeHint, 128)))];

View File

@ -56,4 +56,46 @@ public class HpackEncoderTest {
buf.release();
}
}
/**
* The encoder should not impose an arbitrary limit on the header size if
* the server has not specified any limit.
* @throws Http2Exception
*/
@Test
public void testWillEncode16MBHeaderByDefault() throws Http2Exception {
ByteBuf buf = Unpooled.buffer();
String bigHeaderName = "x-big-header";
int bigHeaderSize = 1024 * 1024 * 16;
String bigHeaderVal = new String(new char[bigHeaderSize]).replace('\0', 'X');
Http2Headers headersIn = new DefaultHttp2Headers().add(
"x-big-header", bigHeaderVal);
Http2Headers headersOut = new DefaultHttp2Headers();
try {
hpackEncoder.encodeHeaders(0, buf, headersIn, Http2HeadersEncoder.NEVER_SENSITIVE);
hpackDecoder.setMaxHeaderListSize(bigHeaderSize + 1024, bigHeaderSize + 1024);
hpackDecoder.decode(0, buf, headersOut, false);
} finally {
buf.release();
}
assertEquals(headersOut.get(bigHeaderName).toString(), bigHeaderVal);
}
@Test(expected = Http2Exception.class)
public void testSetMaxHeaderListSizeEnforcedAfterSet() throws Http2Exception {
ByteBuf buf = Unpooled.buffer();
Http2Headers headers = new DefaultHttp2Headers().add(
"x-big-header",
new String(new char[1024 * 16]).replace('\0', 'X')
);
hpackEncoder.setMaxHeaderListSize(1000);
try {
hpackEncoder.encodeHeaders(0, buf, headers, Http2HeadersEncoder.NEVER_SENSITIVE);
} finally {
buf.release();
}
}
}