Skip to content

Commit 9321577

Browse files
authored
Merge pull request from GHSA-ccw9-q5h2-8c2w
* Validate HEADERS frame length accounts for priority data Motivation: When parsing a HEADERS frame payload which includes priority data the expected length of the frame is not validated to ensure that it is large enough to account for the stream data. This can lead to the expected payload length being negative. Modifications: - Validate that when stream priority data is present that the frame length is at least that size - Add fuzz testing failure case Result: The frame decoder will throw a protocol error when parsing a HEADERS frame if the length of the frame is less than the number of bytes required for stream priority data and the priority flag is set. * Add additional tests * Fix weirdo formatting
1 parent 3cbebaf commit 9321577

File tree

4 files changed

+49
-0
lines changed

4 files changed

+49
-0
lines changed
Binary file not shown.

Sources/NIOHTTP2/HTTP2FrameParser.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,11 @@ struct HTTP2FrameDecoder {
686686

687687
let priorityData: HTTP2Frame.StreamPriorityData?
688688
if flags.contains(.priority) {
689+
// Validate that the length includes the priority data.
690+
guard bytesToRead >= 5 else {
691+
throw InternalError.codecError(code: .protocolError)
692+
}
693+
689694
let raw: UInt32 = bytes.readInteger()!
690695
priorityData = HTTP2Frame.StreamPriorityData(exclusive: (raw & 0x8000_0000 != 0),
691696
dependency: HTTP2StreamID(networkID: raw),

Tests/NIOHTTP2Tests/HTTP2FrameParserTests+XCTest.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ extension HTTP2FrameParserTests {
4848
("testHeadersFrameDecodingWithPriorityNoPadding", testHeadersFrameDecodingWithPriorityNoPadding),
4949
("testHeadersFrameDecodingWithPriorityWithPadding", testHeadersFrameDecodingWithPriorityWithPadding),
5050
("testHeadersFrameDecodeFailures", testHeadersFrameDecodeFailures),
51+
("testHeadersFrameDecodingWithPriorityAndIncorrectLength", testHeadersFrameDecodingWithPriorityAndIncorrectLength),
52+
("testHeadersFrameDecodingWithPriorityAndCorrectLength", testHeadersFrameDecodingWithPriorityAndCorrectLength),
5153
("testHeadersFrameEncodingNoPriority", testHeadersFrameEncodingNoPriority),
5254
("testHeadersFrameEncodingWithPriority", testHeadersFrameEncodingWithPriority),
5355
("testPriorityFrameDecoding", testPriorityFrameDecoding),

Tests/NIOHTTP2Tests/HTTP2FrameParserTests.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,48 @@ class HTTP2FrameParserTests: XCTestCase {
647647
}
648648
})
649649
}
650+
651+
func testHeadersFrameDecodingWithPriorityAndIncorrectLength() throws {
652+
for invalidLength in UInt8(0) ... UInt8(4) {
653+
let frameBytes: [UInt8] = [
654+
0x00, 0x00, invalidLength, // 3-byte payload length ('invalidLength' bytes)
655+
0x01, // 1-byte frame type (HEADERS)
656+
0x24, // 1-byte flags (END_HEADERS, PRIORITY)
657+
0x00, 0x00, 0x00, 0x03, // 4-byte stream identifier
658+
0x80, 0x00, 0x00, 0x01, // 4-byte stream dependency (top bit = exclusive)
659+
0x01, // 1-byte weight (1)
660+
]
661+
662+
var buf = self.byteBuffer(withBytes: frameBytes)
663+
var decoder = HTTP2FrameDecoder(allocator: self.allocator, expectClientMagic: false)
664+
665+
decoder.append(bytes: &buf)
666+
XCTAssertThrowsError(try decoder.nextFrame(), "Should throw a protocol error", { err in
667+
guard let connErr = err as? InternalError, case .codecError(code: .protocolError) = connErr else {
668+
XCTFail("Should have thrown a codec error of type PROTOCOL_ERROR")
669+
return
670+
}
671+
})
672+
}
673+
}
674+
675+
func testHeadersFrameDecodingWithPriorityAndCorrectLength() throws {
676+
let frameBytes: [UInt8] = [
677+
0x00, 0x00, 0x05, // 3-byte payload length (5 bytes)
678+
0x01, // 1-byte frame type (HEADERS)
679+
0x24, // 1-byte flags (END_HEADERS, PRIORITY)
680+
0x00, 0x00, 0x00, 0x03, // 4-byte stream identifier
681+
0x80, 0x00, 0x00, 0x01, // 4-byte stream dependency (top bit = exclusive)
682+
0x01, // 1-byte weight (1)
683+
]
684+
685+
let priorityData = HTTP2Frame.StreamPriorityData(exclusive: true, dependency: HTTP2StreamID(1), weight: 1)
686+
let expectedFrame = HTTP2Frame(streamID: HTTP2StreamID(3),
687+
payload: .headers(.init(headers: [:], priorityData: priorityData, endStream: false)))
688+
689+
var buf = byteBuffer(withBytes: frameBytes)
690+
try assertReadsFrame(from: &buf, matching: expectedFrame)
691+
}
650692

651693
func testHeadersFrameEncodingNoPriority() throws {
652694
let streamID = HTTP2StreamID(1)

0 commit comments

Comments
 (0)