Fixed bug in the calculation of the header block fragment length
Co-authored-by: Masaori Koshiba <masaori@apache.org>
diff --git a/proxy/http2/HPACK.cc b/proxy/http2/HPACK.cc
index e06b7b7..cd6aee6 100644
--- a/proxy/http2/HPACK.cc
+++ b/proxy/http2/HPACK.cc
@@ -941,7 +941,11 @@
field->name_get(&name_len);
field->value_get(&value_len);
- total_header_size += name_len + value_len;
+
+ // [RFC 7540] 6.5.2. SETTINGS_MAX_HEADER_LIST_SIZE:
+ // The value is based on the uncompressed size of header fields, including the length of the name and value in octets plus an
+ // overhead of 32 octets for each header field.
+ total_header_size += name_len + value_len + ADDITIONAL_OCTETS;
if (total_header_size > max_header_size) {
return HPACK_ERROR_SIZE_EXCEEDED_ERROR;
diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc
index e33a08e..75ef601 100644
--- a/proxy/http2/Http2ConnectionState.cc
+++ b/proxy/http2/Http2ConnectionState.cc
@@ -218,13 +218,6 @@
}
}
- // keep track of how many bytes we get in the frame
- stream->request_header_length += payload_length;
- if (stream->request_header_length > Http2::max_header_list_size) {
- return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_STREAM, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
- "recv headers payload for headers greater than header length");
- }
-
Http2HeadersParameter params;
uint32_t header_block_fragment_offset = 0;
uint32_t header_block_fragment_length = payload_length;
@@ -243,7 +236,8 @@
"recv headers failed to parse");
}
- if (params.pad_length > payload_length) {
+ // Payload length can't be smaller than the pad length
+ if ((params.pad_length + HTTP2_HEADERS_PADLEN_LEN) > header_block_fragment_length) {
return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
"recv headers pad > payload length");
}
@@ -259,7 +253,7 @@
frame.reader()->memcpy(buf, HTTP2_PRIORITY_LEN, header_block_fragment_offset);
if (!http2_parse_priority_parameter(make_iovec(buf, HTTP2_PRIORITY_LEN), params.priority)) {
return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
- "recv headers prioirity parameters failed parse");
+ "recv headers priority parameters failed parse");
}
// Protocol error if the stream depends on itself
if (stream_id == params.priority.stream_dependency) {
@@ -267,6 +261,12 @@
"recv headers self dependency");
}
+ // Payload length can't be smaller than the priority length
+ if (HTTP2_PRIORITY_LEN > header_block_fragment_length) {
+ return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
+ "recv priority length > payload length");
+ }
+
header_block_fragment_offset += HTTP2_PRIORITY_LEN;
header_block_fragment_length -= HTTP2_PRIORITY_LEN;
}
@@ -286,11 +286,19 @@
}
}
+ stream->header_blocks_length = header_block_fragment_length;
+
+ // ATS advertises SETTINGS_MAX_HEADER_LIST_SIZE as a limit of total header blocks length. (Details in [RFC 7560] 10.5.1.)
+ // Make it double to relax the limit in cases of 1) HPACK is used naively, or 2) Huffman Encoding generates large header blocks.
+ // The total "decoded" header length is strictly checked by hpack_decode_header_block().
+ if (stream->header_blocks_length > std::max(Http2::max_header_list_size, Http2::max_header_list_size * 2)) {
+ return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
+ "header blocks too large");
+ }
+
stream->header_blocks = static_cast<uint8_t *>(ats_malloc(header_block_fragment_length));
frame.reader()->memcpy(stream->header_blocks, header_block_fragment_length, header_block_fragment_offset);
- stream->header_blocks_length = header_block_fragment_length;
-
if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS) {
// NOTE: If there are END_HEADERS flag, decode stored Header Blocks.
if (!stream->change_state(HTTP2_FRAME_TYPE_HEADERS, frame.header().flags) && stream->has_trailing_header() == false) {
@@ -831,21 +839,20 @@
}
}
- // keep track of how many bytes we get in the frame
- stream->request_header_length += payload_length;
- if (stream->request_header_length > Http2::max_header_list_size) {
- return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR,
- "continuation payload for headers exceeded");
- }
-
uint32_t header_blocks_offset = stream->header_blocks_length;
stream->header_blocks_length += payload_length;
- if (stream->header_blocks_length > 0) {
- stream->header_blocks = static_cast<uint8_t *>(ats_realloc(stream->header_blocks, stream->header_blocks_length));
- frame.reader()->memcpy(stream->header_blocks + header_blocks_offset, payload_length);
+ // ATS advertises SETTINGS_MAX_HEADER_LIST_SIZE as a limit of total header blocks length. (Details in [RFC 7560] 10.5.1.)
+ // Make it double to relax the limit in cases of 1) HPACK is used naively, or 2) Huffman Encoding generates large header blocks.
+ // The total "decoded" header length is strictly checked by hpack_decode_header_block().
+ if (stream->header_blocks_length > std::max(Http2::max_header_list_size, Http2::max_header_list_size * 2)) {
+ return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM,
+ "header blocks too large");
}
+ stream->header_blocks = static_cast<uint8_t *>(ats_realloc(stream->header_blocks, stream->header_blocks_length));
+ frame.reader()->memcpy(stream->header_blocks + header_blocks_offset, payload_length);
+
if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_HEADERS) {
// NOTE: If there are END_HEADERS flag, decode stored Header Blocks.
cstate.clear_continued_stream_id();
diff --git a/proxy/http2/Http2Stream.h b/proxy/http2/Http2Stream.h
index 591f5b5..115a34a 100644
--- a/proxy/http2/Http2Stream.h
+++ b/proxy/http2/Http2Stream.h
@@ -41,7 +41,6 @@
Http2Stream(Http2StreamId sid = 0, ssize_t initial_rwnd = Http2::initial_window_size)
: header_blocks(NULL),
header_blocks_length(0),
- request_header_length(0),
recv_end_stream(false),
send_end_stream(false),
sent_request_header(false),
@@ -190,10 +189,7 @@
int64_t read_vio_read_avail();
uint8_t *header_blocks;
- uint32_t header_blocks_length; // total length of header blocks (not include
- // Padding or other fields)
- uint32_t request_header_length; // total length of payload (include Padding
- // and other fields)
+ uint32_t header_blocks_length = 0; // total length of header blocks (not include Padding or other fields)
bool recv_end_stream;
bool send_end_stream;