Skip to content

Commit e1ebe30

Browse files
Validate HEADERS priority self-dependency (#652)
Treat HEADERS with PRIORITY that depends on the same stream as a stream-level PROTOCOL_ERROR
1 parent f37ed19 commit e1ebe30

2 files changed

Lines changed: 57 additions & 2 deletions

File tree

httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,8 +1185,11 @@ private void consumeHeaderFrame(final RawFrame frame, final H2Stream stream) thr
11851185
if (payload == null || payload.remaining() < 5) {
11861186
throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid HEADERS priority payload");
11871187
}
1188-
payload.getInt();
1189-
payload.get();
1188+
final int dependency = payload.getInt() & 0x7fffffff;
1189+
if (dependency == streamId) {
1190+
throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream cannot depend on itself");
1191+
}
1192+
payload.get(); // weight
11901193
}
11911194
if (continuation == null) {
11921195
final List<Header> headers = decodeHeaders(payload);

httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,5 +1965,57 @@ void testInputRstStreamWithInvalidLengthOnUnseenStreamRejected() throws Exceptio
19651965
Assertions.assertEquals(H2Error.FRAME_SIZE_ERROR, H2Error.getByCode(ex.getCode()));
19661966
}
19671967

1968+
@Test
1969+
void testHeadersWithPrioritySelfDependencyIsStreamProtocolError() throws Exception {
1970+
final H2Config h2Config = H2Config.custom().build();
1971+
1972+
final AbstractH2StreamMultiplexer mux = new H2StreamMultiplexerImpl(
1973+
protocolIOSession,
1974+
FRAME_FACTORY,
1975+
StreamIdGenerator.ODD,
1976+
httpProcessor,
1977+
CharCodingConfig.DEFAULT,
1978+
h2Config,
1979+
h2StreamListener,
1980+
() -> streamHandler);
1981+
1982+
final ByteArrayBuffer headerBuf = new ByteArrayBuffer(128);
1983+
final HPackEncoder encoder = new HPackEncoder(
1984+
h2Config.getHeaderTableSize(),
1985+
CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT));
1986+
1987+
final List<Header> headers = Arrays.asList(
1988+
new BasicHeader(":method", "GET"),
1989+
new BasicHeader(":scheme", "https"),
1990+
new BasicHeader(":path", "/"),
1991+
new BasicHeader(":authority", "example.test"));
1992+
1993+
encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled());
1994+
1995+
final ByteBuffer payload = ByteBuffer.allocate(5 + headerBuf.length());
1996+
payload.putInt(0x80000002); // exclusive bit set, dependency = stream 2 (self-dependency)
1997+
payload.put((byte) 16); // weight
1998+
payload.put(headerBuf.array(), 0, headerBuf.length());
1999+
payload.flip();
2000+
2001+
final RawFrame headersFrame = new RawFrame(
2002+
FrameType.HEADERS.getValue(),
2003+
FrameFlag.PRIORITY.getValue() | FrameFlag.END_HEADERS.getValue(),
2004+
2,
2005+
payload);
2006+
2007+
Assertions.assertDoesNotThrow(() -> mux.onInput(ByteBuffer.wrap(encodeFrame(headersFrame))));
2008+
2009+
Mockito.verify(streamHandler).failed(exceptionCaptor.capture());
2010+
final Exception cause = exceptionCaptor.getValue();
2011+
Assertions.assertInstanceOf(H2StreamResetException.class, cause);
2012+
Assertions.assertEquals(
2013+
H2Error.PROTOCOL_ERROR,
2014+
H2Error.getByCode(((H2StreamResetException) cause).getCode()));
2015+
2016+
Mockito.verify(streamHandler, Mockito.never())
2017+
.consumeHeader(ArgumentMatchers.anyList(), ArgumentMatchers.anyBoolean());
2018+
}
2019+
19682020

19692021
}

0 commit comments

Comments
 (0)