Skip to content

Commit 0057e22

Browse files
authored
fuzz: avoid false positives in HCM fuzzer. (#4262)
Previously we were not respecting the encodeHeaders() :status contract or the connection close callbacks. Fixes oss-fuzz issue https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=10025. Risk level: Low Testing: Corpus entries added. Signed-off-by: Harvey Tuch <[email protected]>
1 parent 9d094e5 commit 0057e22

File tree

5 files changed

+121
-4
lines changed

5 files changed

+121
-4
lines changed

include/envoy/http/codec.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ class StreamEncoder {
2828
virtual void encode100ContinueHeaders(const HeaderMap& headers) PURE;
2929

3030
/**
31-
* Encode headers, optionally indicating end of stream.
31+
* Encode headers, optionally indicating end of stream. Response headers must
32+
* have a valid :status set.
3233
* @param headers supplies the header map to encode.
3334
* @param end_stream supplies whether this is a header only request/response.
3435
*/

source/common/http/http2/codec_impl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,11 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
251251
using StreamImpl::StreamImpl;
252252

253253
// StreamImpl
254+
void encodeHeaders(const HeaderMap& headers, bool end_stream) override {
255+
// The contract is that client codecs must ensure that :status is present.
256+
ASSERT(headers.Status() != nullptr);
257+
StreamImpl::encodeHeaders(headers, end_stream);
258+
}
254259
void submitHeaders(const std::vector<nghttp2_nv>& final_headers,
255260
nghttp2_data_provider* provider) override;
256261
void transformUpgradeFromH1toH2(HeaderMap& headers) override {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
actions {
2+
new_stream {
3+
request_headers {
4+
headers {
5+
key: ":method"
6+
}
7+
headers {
8+
key: "foo"
9+
value: "/"
10+
}
11+
headers {
12+
key: "cookie"
13+
}
14+
headers {
15+
key: "/"
16+
value: "foo.com"
17+
}
18+
headers {
19+
key: "/"
20+
value: "GT"
21+
}
22+
headers {
23+
value: "/"
24+
}
25+
}
26+
}
27+
}
28+
actions {
29+
stream_action {
30+
response {
31+
headers {
32+
}
33+
}
34+
}
35+
}
36+
actions {
37+
}
38+
actions {
39+
}
40+
actions {
41+
}
42+
actions {
43+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
actions {
2+
}
3+
actions {
4+
stream_action {
5+
}
6+
}
7+
actions {
8+
new_stream {
9+
request_headers {
10+
headers {
11+
key: ":method"
12+
value: "GET"
13+
}
14+
headers {
15+
key: ":path"
16+
value: "/"
17+
}
18+
headers {
19+
key: ":scheme"
20+
value: "GET"
21+
}
22+
headers {
23+
key: ":authority"
24+
value: "foo.com"
25+
}
26+
headers {
27+
value: "nosniff"
28+
}
29+
headers {
30+
key: "GET"
31+
value: "foo=bar"
32+
}
33+
headers {
34+
key: "cookie"
35+
value: "foo2=bar2"
36+
}
37+
}
38+
}
39+
}
40+
actions {
41+
stream_action {
42+
response {
43+
headers {
44+
}
45+
}
46+
}
47+
}
48+
actions {
49+
}
50+
actions {
51+
}
52+
actions {
53+
}

test/common/http/conn_manager_impl_fuzz_test.cc

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,16 @@ class FuzzStream {
301301
}
302302
case test::common::http::ResponseAction::kHeaders: {
303303
if (state == StreamState::PendingHeaders) {
304-
decoder_filter_->callbacks_->encodeHeaders(
305-
std::make_unique<TestHeaderMapImpl>(Fuzz::fromHeaders(response_action.headers())),
306-
end_stream);
304+
auto headers =
305+
std::make_unique<TestHeaderMapImpl>(Fuzz::fromHeaders(response_action.headers()));
306+
// The client codec will ensure we always have a valid :status.
307+
// Similarly, local replies should always contain this.
308+
try {
309+
Utility::getResponseStatus(*headers);
310+
} catch (const CodecClientException&) {
311+
headers->setReferenceKey(Headers::get().Status, "200");
312+
}
313+
decoder_filter_->callbacks_->encodeHeaders(std::move(headers), end_stream);
307314
state = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers;
308315
}
309316
break;
@@ -368,9 +375,12 @@ DEFINE_PROTO_FUZZER(const test::common::http::ConnManagerImplTestCase& input) {
368375
NiceMock<Upstream::MockClusterManager> cluster_manager;
369376
NiceMock<Network::MockReadFilterCallbacks> filter_callbacks;
370377
std::unique_ptr<Ssl::MockConnection> ssl_connection;
378+
bool connection_alive = true;
371379

372380
ON_CALL(filter_callbacks.connection_, ssl()).WillByDefault(Return(ssl_connection.get()));
373381
ON_CALL(Const(filter_callbacks.connection_), ssl()).WillByDefault(Return(ssl_connection.get()));
382+
ON_CALL(filter_callbacks.connection_, close(_))
383+
.WillByDefault(InvokeWithoutArgs([&connection_alive] { connection_alive = false; }));
374384
filter_callbacks.connection_.local_address_ =
375385
std::make_shared<Network::Address::Ipv4Instance>("127.0.0.1");
376386
filter_callbacks.connection_.remote_address_ =
@@ -384,6 +394,11 @@ DEFINE_PROTO_FUZZER(const test::common::http::ConnManagerImplTestCase& input) {
384394

385395
for (const auto& action : input.actions()) {
386396
ENVOY_LOG_MISC(trace, "action {} with {} streams", action.DebugString(), streams.size());
397+
if (!connection_alive) {
398+
ENVOY_LOG_MISC(trace, "skipping due to dead connection");
399+
break;
400+
}
401+
387402
switch (action.action_selector_case()) {
388403
case test::common::http::Action::kNewStream: {
389404
streams.emplace_back(new FuzzStream(conn_manager, config,

0 commit comments

Comments
 (0)