Skip to content

Commit

Permalink
fuzz: avoid false positives in HCM fuzzer. (envoyproxy#4262)
Browse files Browse the repository at this point in the history
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 <htuch@google.com>
  • Loading branch information
htuch authored Aug 29, 2018
1 parent 9d094e5 commit 0057e22
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 4 deletions.
3 changes: 2 additions & 1 deletion include/envoy/http/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class StreamEncoder {
virtual void encode100ContinueHeaders(const HeaderMap& headers) PURE;

/**
* Encode headers, optionally indicating end of stream.
* Encode headers, optionally indicating end of stream. Response headers must
* have a valid :status set.
* @param headers supplies the header map to encode.
* @param end_stream supplies whether this is a header only request/response.
*/
Expand Down
5 changes: 5 additions & 0 deletions source/common/http/http2/codec_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable<Log
using StreamImpl::StreamImpl;

// StreamImpl
void encodeHeaders(const HeaderMap& headers, bool end_stream) override {
// The contract is that client codecs must ensure that :status is present.
ASSERT(headers.Status() != nullptr);
StreamImpl::encodeHeaders(headers, end_stream);
}
void submitHeaders(const std::vector<nghttp2_nv>& final_headers,
nghttp2_data_provider* provider) override;
void transformUpgradeFromH1toH2(HeaderMap& headers) override {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
actions {
new_stream {
request_headers {
headers {
key: ":method"
}
headers {
key: "foo"
value: "/"
}
headers {
key: "cookie"
}
headers {
key: "/"
value: "foo.com"
}
headers {
key: "/"
value: "GT"
}
headers {
value: "/"
}
}
}
}
actions {
stream_action {
response {
headers {
}
}
}
}
actions {
}
actions {
}
actions {
}
actions {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
actions {
}
actions {
stream_action {
}
}
actions {
new_stream {
request_headers {
headers {
key: ":method"
value: "GET"
}
headers {
key: ":path"
value: "/"
}
headers {
key: ":scheme"
value: "GET"
}
headers {
key: ":authority"
value: "foo.com"
}
headers {
value: "nosniff"
}
headers {
key: "GET"
value: "foo=bar"
}
headers {
key: "cookie"
value: "foo2=bar2"
}
}
}
}
actions {
stream_action {
response {
headers {
}
}
}
}
actions {
}
actions {
}
actions {
}
21 changes: 18 additions & 3 deletions test/common/http/conn_manager_impl_fuzz_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,16 @@ class FuzzStream {
}
case test::common::http::ResponseAction::kHeaders: {
if (state == StreamState::PendingHeaders) {
decoder_filter_->callbacks_->encodeHeaders(
std::make_unique<TestHeaderMapImpl>(Fuzz::fromHeaders(response_action.headers())),
end_stream);
auto headers =
std::make_unique<TestHeaderMapImpl>(Fuzz::fromHeaders(response_action.headers()));
// The client codec will ensure we always have a valid :status.
// Similarly, local replies should always contain this.
try {
Utility::getResponseStatus(*headers);
} catch (const CodecClientException&) {
headers->setReferenceKey(Headers::get().Status, "200");
}
decoder_filter_->callbacks_->encodeHeaders(std::move(headers), end_stream);
state = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers;
}
break;
Expand Down Expand Up @@ -368,9 +375,12 @@ DEFINE_PROTO_FUZZER(const test::common::http::ConnManagerImplTestCase& input) {
NiceMock<Upstream::MockClusterManager> cluster_manager;
NiceMock<Network::MockReadFilterCallbacks> filter_callbacks;
std::unique_ptr<Ssl::MockConnection> ssl_connection;
bool connection_alive = true;

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

for (const auto& action : input.actions()) {
ENVOY_LOG_MISC(trace, "action {} with {} streams", action.DebugString(), streams.size());
if (!connection_alive) {
ENVOY_LOG_MISC(trace, "skipping due to dead connection");
break;
}

switch (action.action_selector_case()) {
case test::common::http::Action::kNewStream: {
streams.emplace_back(new FuzzStream(conn_manager, config,
Expand Down

0 comments on commit 0057e22

Please sign in to comment.