Skip to content

Commit 4a0136c

Browse files
authored
Harden HTTP/2 receive path against unbounded resource growth (#3474)
* Harden HTTP/2 receive path against unbounded resource growth The HTTP/2 (h2) parser advertised several receive-side limits that were never enforced, letting one hostile peer grow process memory without bound: - SETTINGS_MAX_CONCURRENT_STREAMS defaulted to unlimited and was never checked on the server; every odd stream id was accepted, so a peer could pin an unbounded number of streams per connection. The default is now a bounded 1024 and the server rejects streams beyond it with RST_STREAM(REFUSED_STREAM). - SETTINGS_MAX_HEADER_LIST_SIZE defaulted to unlimited and was never enforced on receive; decoded headers amplified by 1-byte HPACK indexed references ("HPACK bomb") and never-completed HEADERS/CONTINUATION fragments accumulated without bound. Both are now capped at a 1MB default. - The HPACK dynamic table size update was validated against the protocol default instead of the size this endpoint advertised, so a decoder configured with a smaller table could be forced to grow it beyond the capacity it was sized for. Updates above the advertised beyond the capacity it was sized for. Updates above the advertisedondi beyond the capacity it was sized for. Updates above the advertisg P beyETT beyond the capacity it was sized for. Updates above the advertisy wit beyond the capacity it was sized for. Updates above the advertisek_ beyond the capacity it was sized for. Updates above the advertisis de beyond the capacity it was sized for. Updates above the advertisedse beyond the capacity it wasfo beyond the capacity it was sized for. Updates above the adver continuing to parse whatever the peer keeps sending. Tests: new casesTests: new casesTests: new casesTests: new casesTests: nest cover each enforcement; the full brpc_hpack_unittest, brpc_http_rpc_protocol_unittest, brpc_h2_unsent_message_unittest and brpc_grpc_protocol_unittest suites pass. * Reset h2 header-list budget per header block and cap initial HEADERS fragment Two follow-up fixes from code review of the HTTP/2 receive-path hardening: - The decoded header-list size counter was never reset when a new HEADERS block began, so trailers on the same stream inherited the budget of the initial header list and could be rejected even though they form a separate list (RFC 7540 section 10.5.1 applies per header block). Reset the counter at the start of OnHeaders; it stays cumulative across the CONTINUATION frames that complete the same block. - The _remaining_header_fragment size cap was only checked when CONTINUATION frames were processed, but a single oversized HEADERS frame appends its unconsumed tail into the fragment too. Apply the same cap in OnHeaders via a shared HeaderFragmentTooLarge() helper and fail with ENHANCE_YOUR_CALM. Tests: brpc_http_rpc_protocol_unittest gains two cases (h2_header_list_budget_resets_per_block and h2_oversized_single_headers_bloh2_oversized_single_headers_bloh2_oversized_single_headers_bloh2_oversized_single_headers_b_message_unittest and brpc_grpc_protocol_unittest suites pass.
1 parent dd0af2b commit 4a0136c

8 files changed

Lines changed: 406 additions & 11 deletions

File tree

src/brpc/details/hpack.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,8 @@ inline ssize_t DecodeString(butil::IOBufBytesIterator& iter, std::string* out) {
657657

658658
HPacker::HPacker()
659659
: _encode_table(nullptr)
660-
, _decode_table(nullptr) {
660+
, _decode_table(nullptr)
661+
, _max_table_size(0) {
661662
CreateStaticTableOnceOrDie();
662663
}
663664

@@ -675,6 +676,7 @@ HPacker::~HPacker() {
675676
int HPacker::Init(size_t max_table_size) {
676677
CHECK(!_encode_table);
677678
CHECK(!_decode_table);
679+
_max_table_size = max_table_size;
678680
IndexTableOptions encode_table_options;
679681
encode_table_options.max_size = max_table_size;
680682
encode_table_options.start_index = s_static_table->end_index();
@@ -842,7 +844,13 @@ ssize_t HPacker::Decode(butil::IOBufBytesIterator& iter, Header* h) {
842844
if (read_bytes <= 0) {
843845
return read_bytes;
844846
}
845-
if (max_size > H2Settings::DEFAULT_HEADER_TABLE_SIZE) {
847+
if (max_size > _max_table_size) {
848+
// RFC 7541 section 6.3: the new maximum size MUST be lower
849+
// than or equal to the limit determined by the protocol using
850+
// HPACK, i.e. the SETTINGS_HEADER_TABLE_SIZE this decoder
851+
// advertised (what Init() was called with), not the protocol
852+
// default. Growing past the Init-time size would also desync
853+
// the fixed-capacity header queue from the byte accounting.
846854
LOG(ERROR) << "Invalid max_size=" << max_size;
847855
return -1;
848856
}

src/brpc/details/hpack.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ class HPacker : public Describable {
132132

133133
IndexTable* _encode_table;
134134
IndexTable* _decode_table;
135+
// The max dynamic table size this decoder was initialized with, i.e.
136+
// the SETTINGS_HEADER_TABLE_SIZE we advertised to the peer. Per RFC
137+
// 7541 section 4.2/6.3 a dynamic table size update exceeding it must
138+
// be treated as a decoding error.
139+
size_t _max_table_size;
135140
};
136141

137142
// Lowercase the input string, a fast implementation.

src/brpc/http2.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,20 @@
2323

2424
namespace brpc {
2525

26+
// Out-of-class definitions for the in-class initialized integral constants:
27+
// required when the constants are odr-used (e.g. passed by reference in
28+
// ASSERT_EQ).
29+
const uint32_t H2Settings::DEFAULT_MAX_CONCURRENT_STREAMS;
30+
const uint32_t H2Settings::DEFAULT_MAX_HEADER_LIST_SIZE;
31+
2632
H2Settings::H2Settings()
2733
: header_table_size(DEFAULT_HEADER_TABLE_SIZE)
2834
, enable_push(false)
29-
, max_concurrent_streams(std::numeric_limits<uint32_t>::max())
35+
, max_concurrent_streams(DEFAULT_MAX_CONCURRENT_STREAMS)
3036
, stream_window_size(256 * 1024)
3137
, connection_window_size(1024 * 1024)
3238
, max_frame_size(DEFAULT_MAX_FRAME_SIZE)
33-
, max_header_list_size(std::numeric_limits<uint32_t>::max()) {
39+
, max_header_list_size(DEFAULT_MAX_HEADER_LIST_SIZE) {
3440
}
3541

3642
bool H2Settings::IsValid(bool log_error) const {

src/brpc/http2.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ struct H2Settings {
6060
// for any limit that is exhausted with active streams. Servers SHOULD only
6161
// set a zero value for short durations; if a server does not wish to
6262
// accept requests, closing the connection is more appropriate.
63-
// Default: unlimited
63+
// The server enforces this limit: streams opened beyond it are rejected
64+
// with RST_STREAM(REFUSED_STREAM). Set to
65+
// std::numeric_limits<uint32_t>::max() explicitly to restore the old
66+
// unlimited (unenforced) behavior.
67+
// Default: 1024
68+
static const uint32_t DEFAULT_MAX_CONCURRENT_STREAMS = 1024;
6469
uint32_t max_concurrent_streams;
6570

6671
// Sender's initial window size (in octets) for stream-level flow control.
@@ -92,7 +97,14 @@ struct H2Settings {
9297
// and value in octets plus an overhead of 32 octets for each header field.
9398
// For any given request, a lower limit than what is advertised MAY be
9499
// enforced.
95-
// Default: unlimited.
100+
// brpc enforces this limit on received header blocks to bound per-stream
101+
// memory: HPACK indexed references would otherwise let a small
102+
// HEADERS/CONTINUATION frame expand into an unbounded header list
103+
// ("HPACK bomb"). Set to std::numeric_limits<uint32_t>::max() to restore
104+
// the old unlimited behavior (not recommended for servers exposed to
105+
// untrusted peers).
106+
// Default: 1MB
107+
static const uint32_t DEFAULT_MAX_HEADER_LIST_SIZE = 1024 * 1024;
96108
uint32_t max_header_list_size;
97109
};
98110

src/brpc/policy/http2_rpc_protocol.cpp

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,22 @@ DEFINE_int32(h2_client_connection_window_size, 1024 * 1024,
4242
DEFINE_int32(h2_client_max_frame_size,
4343
H2Settings::DEFAULT_MAX_FRAME_SIZE,
4444
"Size of the largest frame payload that client is willing to receive");
45+
DEFINE_int32(h2_client_max_header_list_size,
46+
H2Settings::DEFAULT_MAX_HEADER_LIST_SIZE,
47+
"Maximum decoded size of a header list that the client accepts"
48+
" on a received stream, 0 or negative means unlimited");
4549

4650
DEFINE_bool(h2_hpack_encode_name, false,
4751
"Encode name in HTTP2 headers with huffman encoding");
4852
DEFINE_bool(h2_hpack_encode_value, false,
4953
"Encode value in HTTP2 headers with huffman encoding");
5054

55+
DEFINE_bool(h2_ack_ignore_eovercrowded, false,
56+
"Let h2 control-frame replies (PING/SETTINGS acks, RST_STREAM, "
57+
"GOAWAY, WINDOW_UPDATE) bypass -socket_max_unwritten_bytes. "
58+
"Dangerous: a peer flooding PING frames while withholding TCP "
59+
"reads then grows this process's memory without bound.");
60+
5161
static bool CheckStreamWindowSize(const char*, int32_t val) {
5262
return val >= 0;
5363
}
@@ -144,13 +154,19 @@ static int WriteAck(Socket* s, const void* data, size_t n) {
144154
butil::IOBuf sendbuf;
145155
sendbuf.append(data, n);
146156
Socket::WriteOptions wopt;
147-
wopt.ignore_eovercrowded = true;
157+
// These writes historically ignored EOVERCROWDED so control replies
158+
// could always be queued, but that defeats the explicit memory bound of
159+
// -socket_max_unwritten_bytes: a peer flooding PING/SETTINGS frames
160+
// while withholding TCP reads makes this process buffer ack frames
161+
// without limit. Respect the bound by default; callers treat a failed
162+
// ack write as a connection error and close the overcrowded connection.
163+
wopt.ignore_eovercrowded = FLAGS_h2_ack_ignore_eovercrowded;
148164
return s->Write(&sendbuf, &wopt);
149165
}
150166

151167
static int WriteAck(Socket* s, butil::IOBuf* data) {
152168
Socket::WriteOptions wopt;
153-
wopt.ignore_eovercrowded = true;
169+
wopt.ignore_eovercrowded = FLAGS_h2_ack_ignore_eovercrowded;
154170
return s->Write(data, &wopt);
155171
}
156172

@@ -338,13 +354,21 @@ H2Context::H2Context(Socket* socket, const Server* server)
338354
// SETTINGS_INITIAL_WINDOW_SIZE defaults to 65535 until the peer sends a
339355
// different value. Larger requests are resumed by WINDOW_UPDATE.
340356
_remote_settings.stream_window_size = H2Settings::DEFAULT_INITIAL_WINDOW_SIZE;
357+
// RFC 7540 section 6.5.2: the peer's SETTINGS_MAX_CONCURRENT_STREAMS
358+
// defaults to unlimited until its SETTINGS frame is received.
359+
_remote_settings.max_concurrent_streams =
360+
std::numeric_limits<uint32_t>::max();
341361
if (server) {
342362
_unack_local_settings = server->options().h2_settings;
343363
} else {
344364
_unack_local_settings.header_table_size = FLAGS_h2_client_header_table_size;
345365
_unack_local_settings.stream_window_size = FLAGS_h2_client_stream_window_size;
346366
_unack_local_settings.max_frame_size = FLAGS_h2_client_max_frame_size;
347367
_unack_local_settings.connection_window_size = FLAGS_h2_client_connection_window_size;
368+
_unack_local_settings.max_header_list_size =
369+
FLAGS_h2_client_max_header_list_size > 0
370+
? (uint32_t)FLAGS_h2_client_max_header_list_size
371+
: std::numeric_limits<uint32_t>::max();
348372
}
349373
#if defined(UNIT_TEST)
350374
// In ut, we hope _last_sent_stream_id run out quickly to test the correctness
@@ -444,6 +468,16 @@ int H2Context::TryToInsertStream(int stream_id, H2StreamContext* ctx) {
444468
if (_goaway_stream_id >= 0 && stream_id > _goaway_stream_id) {
445469
return 1;
446470
}
471+
// Enforce the SETTINGS_MAX_CONCURRENT_STREAMS value the server
472+
// advertised. Use _unack_local_settings so that a client which never
473+
// ACKs our SETTINGS frame cannot dodge the limit (_local_settings is
474+
// only synchronized on ACK). Client-side streams are already bounded
475+
// against the remote peer's setting before insertion (see
476+
// H2UnsentRequest::AppendAndDestroySelf).
477+
if (is_server_side() &&
478+
_pending_streams.size() >= _unack_local_settings.max_concurrent_streams) {
479+
return 2;
480+
}
447481
H2StreamContext*& sctx = _pending_streams[stream_id];
448482
if (sctx == nullptr) {
449483
// Synchronize creation with SETTINGS_INITIAL_WINDOW_SIZE updates.
@@ -555,7 +589,11 @@ ParseResult H2Context::Consume(
555589
LOG(WARNING) << "Fail to send GOAWAY to " << *_socket;
556590
return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
557591
}
558-
return MakeMessage(nullptr);
592+
// https://tools.ietf.org/html/rfc7540#section-5.4.1
593+
// A connection error is unrecoverable: close the connection
594+
// after sending GOAWAY instead of continuing to parse (and
595+
// buffer) whatever the misbehaving peer keeps sending.
596+
return MakeParseError(PARSE_ERROR_ABSOLUTELY_WRONG);
559597
}
560598
} else {
561599
return MakeParseError(PARSE_ERROR_NO_RESOURCE);
@@ -610,6 +648,18 @@ H2ParseResult H2Context::OnHeaders(
610648
delete sctx;
611649
LOG(ERROR) << "Fail to insert existing stream_id=" << frame_head.stream_id;
612650
return MakeH2Error(H2_PROTOCOL_ERROR);
651+
} else if (rc == 2) {
652+
delete sctx;
653+
LOG_EVERY_SECOND(WARNING)
654+
<< "Refused stream_id=" << frame_head.stream_id
655+
<< " since concurrent streams reached max_concurrent_streams="
656+
<< _unack_local_settings.max_concurrent_streams
657+
<< " on " << *_socket;
658+
// A stream error (RST_STREAM) rather than a connection error:
659+
// RFC 7540 section 5.1.2 requires REFUSED_STREAM (or
660+
// PROTOCOL_ERROR) for streams exceeding the advertised limit,
661+
// and REFUSED_STREAM lets a compliant client retry later.
662+
return MakeH2Error(H2_REFUSED_STREAM, frame_head.stream_id);
613663
} else if (rc > 0) {
614664
delete sctx;
615665
return MakeH2Error(H2_REFUSED_STREAM);
@@ -633,13 +683,24 @@ H2ParseResult H2Context::OnHeaders(
633683
return sctx->OnHeaders(it, frame_head, frag_size, pad_length);
634684
}
635685

686+
bool H2StreamContext::HeaderFragmentTooLarge() const {
687+
return _remaining_header_fragment.size() >
688+
_conn_ctx->_unack_local_settings.max_header_list_size;
689+
}
690+
636691
H2ParseResult H2StreamContext::OnHeaders(
637692
butil::IOBufBytesIterator& it, const H2FrameHead& frame_head,
638693
uint32_t frag_size, uint8_t pad_length) {
639694
_parsed_length += FRAME_HEAD_SIZE + frame_head.payload_size;
640695
#if defined(BRPC_H2_STREAM_STATE)
641696
SetState(H2_STREAM_OPEN);
642697
#endif
698+
// A new HEADERS block (which may be an initial request header set, or
699+
// trailing headers on the same stream) starts here. The decoded header
700+
// list budget is per header block (RFC 7540 section 10.5.1), so reset the
701+
// counter; it stays cumulative across the CONTINUATION frames that finish
702+
// this same block.
703+
_decoded_header_list_size = 0;
643704
butil::IOBufBytesIterator it2(it, frag_size);
644705
if (ConsumeHeaders(it2) < 0) {
645706
LOG(ERROR) << "Invalid header, frag_size=" << frag_size
@@ -651,6 +712,15 @@ H2ParseResult H2StreamContext::OnHeaders(
651712
if (it2.bytes_left()) {
652713
it.append_and_forward(&_remaining_header_fragment,
653714
it2.bytes_left());
715+
// A single HEADERS frame can carry more than max_header_list_size of
716+
// an incomplete header field; cap it here just like CONTINUATION.
717+
if (HeaderFragmentTooLarge()) {
718+
LOG(ERROR) << "Accumulated header fragment exceeds"
719+
" max_header_list_size="
720+
<< _conn_ctx->_unack_local_settings.max_header_list_size
721+
<< ", stream_id=" << frame_head.stream_id;
722+
return MakeH2Error(H2_ENHANCE_YOUR_CALM);
723+
}
654724
}
655725
it.forward(pad_length);
656726
if (frame_head.flags & H2_FLAGS_END_HEADERS) {
@@ -695,6 +765,18 @@ H2ParseResult H2StreamContext::OnContinuation(
695765
butil::IOBufBytesIterator& it, const H2FrameHead& frame_head) {
696766
_parsed_length += FRAME_HEAD_SIZE + frame_head.payload_size;
697767
it.append_and_forward(&_remaining_header_fragment, frame_head.payload_size);
768+
// A header block may span many CONTINUATION frames; ConsumeHeaders()
769+
// drains complete fields, so the fragment only buffers one incomplete
770+
// field, whose wire size never legitimately exceeds the decoded header
771+
// list limit. Without this cap a never-completed field (e.g. a huge
772+
// declared string length) accumulates unbounded memory.
773+
if (HeaderFragmentTooLarge()) {
774+
LOG(ERROR) << "Accumulated header fragment exceeds"
775+
" max_header_list_size="
776+
<< _conn_ctx->_unack_local_settings.max_header_list_size
777+
<< ", stream_id=" << frame_head.stream_id;
778+
return MakeH2Error(H2_ENHANCE_YOUR_CALM);
779+
}
698780
const size_t size = _remaining_header_fragment.size();
699781
butil::IOBufBytesIterator it2(_remaining_header_fragment);
700782
if (ConsumeHeaders(it2) < 0) {
@@ -1130,6 +1212,10 @@ void H2Context::DeferWindowUpdate(int64_t size) {
11301212
SaveUint32(winbuf + FRAME_HEAD_SIZE, conn_wu);
11311213
if (WriteAck(_socket, winbuf, sizeof(winbuf)) != 0) {
11321214
LOG(WARNING) << "Fail to send WINDOW_UPDATE";
1215+
// Retry on a later DATA frame instead of silently losing
1216+
// the window bytes (the peer would stall otherwise).
1217+
_deferred_window_update.fetch_add(
1218+
conn_wu, butil::memory_order_relaxed);
11331219
}
11341220
}
11351221
}
@@ -1207,7 +1293,8 @@ H2StreamContext::H2StreamContext(bool read_body_progressively)
12071293
, _stream_ended(false)
12081294
, _remote_window_left(0)
12091295
, _deferred_window_update(0)
1210-
, _correlation_id(INVALID_BTHREAD_ID.value) {
1296+
, _correlation_id(INVALID_BTHREAD_ID.value)
1297+
, _decoded_header_list_size(0) {
12111298
header().set_version(2, 0);
12121299
#ifndef NDEBUG
12131300
get_h2_bvars()->h2_stream_context_count << 1;
@@ -1240,6 +1327,13 @@ void H2StreamContext::SetState(H2StreamState state) {
12401327
int H2StreamContext::ConsumeHeaders(butil::IOBufBytesIterator& it) {
12411328
HPacker& hpacker = _conn_ctx->hpacker();
12421329
HttpHeader& h = header();
1330+
// https://tools.ietf.org/html/rfc7540#section-10.5.1
1331+
// Bound the cumulative decoded size of the header list. Without this
1332+
// check nothing limits header bytes (-max_body_size covers DATA only)
1333+
// and 1-byte HPACK indexed references to a large dynamic-table entry
1334+
// amplify a small HEADERS/CONTINUATION frame ~4000x ("HPACK bomb").
1335+
const uint32_t max_header_list_size =
1336+
_conn_ctx->_unack_local_settings.max_header_list_size;
12431337
while (it) {
12441338
HPacker::Header pair;
12451339
const int rc = hpacker.Decode(it, &pair);
@@ -1249,6 +1343,12 @@ int H2StreamContext::ConsumeHeaders(butil::IOBufBytesIterator& it) {
12491343
if (rc == 0) {
12501344
break;
12511345
}
1346+
_decoded_header_list_size += pair.name.size() + pair.value.size() + 32;
1347+
if (_decoded_header_list_size > max_header_list_size) {
1348+
LOG(ERROR) << "Decoded header list exceeds max_header_list_size="
1349+
<< max_header_list_size << ", stream_id=" << _stream_id;
1350+
return -1;
1351+
}
12521352
const char* const name = pair.name.c_str();
12531353
bool matched = false;
12541354
if (name[0] == ':') { // reserved names

src/brpc/policy/http2_rpc_protocol.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ class H2StreamContext : public HttpContext {
244244
uint32_t frag_size, uint8_t pad_length);
245245
H2ParseResult OnContinuation(butil::IOBufBytesIterator&, const H2FrameHead&);
246246
H2ParseResult OnResetStream(H2Error h2_error, const H2FrameHead&);
247+
248+
// True if the accumulated HEADERS/CONTINUATION fragment has grown past the
249+
// local max_header_list_size. Bound every place the fragment is appended.
250+
bool HeaderFragmentTooLarge() const;
247251

248252
uint64_t correlation_id() const { return _correlation_id; }
249253
void set_correlation_id(uint64_t cid) { _correlation_id = cid; }
@@ -273,6 +277,10 @@ friend class H2Context;
273277
butil::atomic<int64_t> _remote_window_left;
274278
butil::atomic<int64_t> _deferred_window_update;
275279
uint64_t _correlation_id;
280+
// Cumulative decoded size of the header list of this stream
281+
// (name + value + 32 per field, RFC 7540 section 10.5.1), checked
282+
// against the local max_header_list_size in ConsumeHeaders().
283+
uint64_t _decoded_header_list_size;
276284
butil::IOBuf _remaining_header_fragment;
277285
// Request body which cannot be sent yet due to remote flow control.
278286
// Accessed under H2Context::_stream_mutex.
@@ -336,7 +344,8 @@ class H2Context : public Destroyable, public Describable {
336344
int AllocateClientStreamId();
337345
bool RunOutStreams() const;
338346
// Try to map stream_id to ctx if stream_id does not exist before
339-
// Returns 0 on success, -1 on exist, 1 on goaway.
347+
// Returns 0 on success, -1 on exist, 1 on goaway, 2 on exceeding
348+
// the local max_concurrent_streams limit (server side).
340349
int TryToInsertStream(int stream_id, H2StreamContext* ctx);
341350
size_t VolatilePendingStreamSize() const;
342351
bool PendingDataOvercrowded() const;

test/brpc_hpack_unittest.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,34 @@ TEST_F(HPackTest, dynamic_table_size_update_before_header) {
7777
ASSERT_EQ("GET", h.value);
7878
}
7979

80+
TEST_F(HPackTest, dynamic_table_size_update_over_advertised_limit) {
81+
// RFC 7541 section 4.2/6.3: a dynamic table size update must not exceed
82+
// the max size this decoder advertised via SETTINGS_HEADER_TABLE_SIZE
83+
// (i.e. what Init() was called with), not the protocol default of 4096.
84+
// Previously the update was validated against the compile-time default,
85+
// so a decoder initialized with a smaller table accepted a peer update
86+
// that regrew the table beyond the queue it was sized for.
87+
brpc::HPacker p;
88+
ASSERT_EQ(0, p.Init(256));
89+
{
90+
// Update to 256 (== advertised limit) is acceptable.
91+
butil::IOBuf buf;
92+
uint8_t ok[] = { 0x3F, 0xE1, 0x01 }; // size update to 256
93+
buf.append(ok, sizeof(ok));
94+
brpc::HPacker::Header h;
95+
ASSERT_GE(p.Decode(&buf, &h), 0);
96+
}
97+
{
98+
// Update to 4096 (> advertised 256) must be rejected as malformed,
99+
// even though it equals the protocol default.
100+
butil::IOBuf buf;
101+
uint8_t bad[] = { 0x3F, 0xE1, 0x1F }; // size update to 4096
102+
buf.append(bad, sizeof(bad));
103+
brpc::HPacker::Header h;
104+
ASSERT_EQ(-1, p.Decode(&buf, &h));
105+
}
106+
}
107+
80108
TEST_F(HPackTest, integer_with_overlong_continuation) {
81109
brpc::HPacker p;
82110
ASSERT_EQ(0, p.Init(4096));

0 commit comments

Comments
 (0)