Skip to content

Commit 898b77c

Browse files
HTTP/2: handle zero WINDOW_UPDATE increment (#634)
Treat WINDOW_UPDATE with a window size increment of 0 as PROTOCOL_ERROR. Apply stream error semantics for stream windows and connection error semantics for stream 0, as required by RFC 9113.
1 parent b945423 commit 898b77c

File tree

2 files changed

+65
-3
lines changed

2 files changed

+65
-3
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,15 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio
878878
}
879879
final int delta = payload.getInt() & 0x7fffffff;
880880
if (delta == 0) {
881-
throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Invalid WINDOW_UPDATE delta");
881+
if (streamId == 0) {
882+
throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Invalid WINDOW_UPDATE delta");
883+
}
884+
final H2Stream stream = streams.lookup(streamId);
885+
if (stream != null) {
886+
stream.localReset(new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Invalid WINDOW_UPDATE delta"));
887+
requestSessionOutput();
888+
}
889+
break;
882890
}
883891
if (streamId == 0) {
884892
try {
@@ -947,6 +955,7 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio
947955
throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id");
948956
}
949957
if (frame.isFlagSet(FrameFlag.ACK)) {
958+
// RFC 9113, Section 6.5: SETTINGS with ACK set MUST have an empty payload.
950959
final ByteBuffer payload = frame.getPayload();
951960
if (payload != null && payload.hasRemaining()) {
952961
throw new H2ConnectionException(H2Error.FRAME_SIZE_ERROR, "Invalid SETTINGS ACK payload");

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

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,8 @@ void testZeroIncrement() throws Exception {
380380
final RawFrame incrementFrame = new RawFrame(FrameType.WINDOW_UPDATE.getValue(), 0, 1, payload);
381381
outBuffer.write(incrementFrame, writableChannel);
382382

383-
final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () ->
383+
Assertions.assertDoesNotThrow(() ->
384384
streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())));
385-
Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(exception.getCode()));
386385
}
387386

388387
@Test
@@ -1810,4 +1809,58 @@ void testGoAwayReservedBitInLastStreamIdAffectsStreamCulling() throws Exception
18101809
Assertions.assertInstanceOf(org.apache.hc.core5.http.RequestNotExecutedException.class, exceptionCaptor.getValue());
18111810
}
18121811

1812+
@Test
1813+
void testWindowUpdateZeroIncrementOnConnectionIsConnectionError() throws Exception {
1814+
final AbstractH2StreamMultiplexer mux = new H2StreamMultiplexerImpl(
1815+
protocolIOSession,
1816+
FRAME_FACTORY,
1817+
StreamIdGenerator.ODD,
1818+
httpProcessor,
1819+
CharCodingConfig.DEFAULT,
1820+
H2Config.custom().build(),
1821+
h2StreamListener,
1822+
() -> streamHandler);
1823+
1824+
final ByteBuffer payload = ByteBuffer.allocate(4);
1825+
payload.putInt(0);
1826+
payload.flip();
1827+
1828+
final RawFrame windowUpdate = new RawFrame(FrameType.WINDOW_UPDATE.getValue(), 0, 0, payload);
1829+
1830+
final H2ConnectionException ex = Assertions.assertThrows(
1831+
H2ConnectionException.class,
1832+
() -> mux.onInput(ByteBuffer.wrap(encodeFrame(windowUpdate))));
1833+
1834+
Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(ex.getCode()));
1835+
}
1836+
1837+
@Test
1838+
void testWindowUpdateZeroIncrementOnStreamIsStreamError() throws Exception {
1839+
final AbstractH2StreamMultiplexer mux = new H2StreamMultiplexerImpl(
1840+
protocolIOSession,
1841+
FRAME_FACTORY,
1842+
StreamIdGenerator.ODD,
1843+
httpProcessor,
1844+
CharCodingConfig.DEFAULT,
1845+
H2Config.custom().build(),
1846+
h2StreamListener,
1847+
() -> streamHandler);
1848+
1849+
final H2StreamChannel channel = mux.createChannel(1);
1850+
mux.createStream(channel, streamHandler);
1851+
1852+
final ByteBuffer payload = ByteBuffer.allocate(4);
1853+
payload.putInt(0);
1854+
payload.flip();
1855+
1856+
final RawFrame windowUpdate = new RawFrame(FrameType.WINDOW_UPDATE.getValue(), 0, 1, payload);
1857+
1858+
Assertions.assertDoesNotThrow(() -> mux.onInput(ByteBuffer.wrap(encodeFrame(windowUpdate))));
1859+
1860+
Mockito.verify(streamHandler).failed(exceptionCaptor.capture());
1861+
final Exception cause = exceptionCaptor.getValue();
1862+
Assertions.assertInstanceOf(H2StreamResetException.class, cause);
1863+
Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(((H2StreamResetException) cause).getCode()));
1864+
}
1865+
18131866
}

0 commit comments

Comments
 (0)