From ebc1a5e5e68314581fc554c01c0248f0af2c1cab Mon Sep 17 00:00:00 2001 From: Arturo Bernal Date: Wed, 11 Mar 2026 21:17:47 +0100 Subject: [PATCH] Validate HEADERS priority self-dependency Treat HEADERS with PRIORITY that depends on the same stream as a stream-level PROTOCOL_ERROR --- .../impl/nio/AbstractH2StreamMultiplexer.java | 7 ++- .../nio/TestAbstractH2StreamMultiplexer.java | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java index 11c5c39f7..8e9171b57 100644 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java +++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java @@ -1185,8 +1185,11 @@ private void consumeHeaderFrame(final RawFrame frame, final H2Stream stream) thr if (payload == null || payload.remaining() < 5) { throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid HEADERS priority payload"); } - payload.getInt(); - payload.get(); + final int dependency = payload.getInt() & 0x7fffffff; + if (dependency == streamId) { + throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream cannot depend on itself"); + } + payload.get(); // weight } if (continuation == null) { final List
headers = decodeHeaders(payload); diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java index e792f0405..6c9e88e4f 100644 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java +++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/nio/TestAbstractH2StreamMultiplexer.java @@ -1965,5 +1965,57 @@ void testInputRstStreamWithInvalidLengthOnUnseenStreamRejected() throws Exceptio Assertions.assertEquals(H2Error.FRAME_SIZE_ERROR, H2Error.getByCode(ex.getCode())); } + @Test + void testHeadersWithPrioritySelfDependencyIsStreamProtocolError() throws Exception { + final H2Config h2Config = H2Config.custom().build(); + + final AbstractH2StreamMultiplexer mux = new H2StreamMultiplexerImpl( + protocolIOSession, + FRAME_FACTORY, + StreamIdGenerator.ODD, + httpProcessor, + CharCodingConfig.DEFAULT, + h2Config, + h2StreamListener, + () -> streamHandler); + + final ByteArrayBuffer headerBuf = new ByteArrayBuffer(128); + final HPackEncoder encoder = new HPackEncoder( + h2Config.getHeaderTableSize(), + CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); + + final List
headers = Arrays.asList( + new BasicHeader(":method", "GET"), + new BasicHeader(":scheme", "https"), + new BasicHeader(":path", "/"), + new BasicHeader(":authority", "example.test")); + + encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); + + final ByteBuffer payload = ByteBuffer.allocate(5 + headerBuf.length()); + payload.putInt(0x80000002); // exclusive bit set, dependency = stream 2 (self-dependency) + payload.put((byte) 16); // weight + payload.put(headerBuf.array(), 0, headerBuf.length()); + payload.flip(); + + final RawFrame headersFrame = new RawFrame( + FrameType.HEADERS.getValue(), + FrameFlag.PRIORITY.getValue() | FrameFlag.END_HEADERS.getValue(), + 2, + payload); + + Assertions.assertDoesNotThrow(() -> mux.onInput(ByteBuffer.wrap(encodeFrame(headersFrame)))); + + Mockito.verify(streamHandler).failed(exceptionCaptor.capture()); + final Exception cause = exceptionCaptor.getValue(); + Assertions.assertInstanceOf(H2StreamResetException.class, cause); + Assertions.assertEquals( + H2Error.PROTOCOL_ERROR, + H2Error.getByCode(((H2StreamResetException) cause).getCode())); + + Mockito.verify(streamHandler, Mockito.never()) + .consumeHeader(ArgumentMatchers.anyList(), ArgumentMatchers.anyBoolean()); + } + } \ No newline at end of file