Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions test/extensions/filters/http/common/fuzz/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ envoy_cc_test_library(
deps = [
":filter_fuzz_proto_cc_proto",
"//source/common/config:utility_lib",
"//source/common/http:utility_lib",
"//source/common/protobuf:utility_lib",
"//source/extensions/filters/http:well_known_names",
"//test/fuzz:utility_lib",
Expand Down
47 changes: 47 additions & 0 deletions test/extensions/filters/http/common/fuzz/filter_corpus/grpc_stats

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions test/extensions/filters/http/common/fuzz/filter_fuzz.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ import "envoy/extensions/filters/network/http_connection_manager/v3/http_connect
message FilterFuzzTestCase {
envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter config = 1;

// Downstream data (named for backwards compatibility).
test.fuzz.HttpData data = 2;

// Upstream data.
test.fuzz.HttpData upstream_data = 3;
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ DEFINE_PROTO_FUZZER(const test::extensions::filters::http::FilterFuzzTestCase& i
TestUtility::validate(input);
// Fuzz filter.
static UberFilterFuzzer fuzzer;
fuzzer.fuzz(input.config(), input.data());
fuzzer.fuzz(input.config(), input.data(), input.upstream_data());
} catch (const ProtoValidationException& e) {
ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what());
}
Expand Down
148 changes: 115 additions & 33 deletions test/extensions/filters/http/common/fuzz/uber_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "common/config/utility.h"
#include "common/config/version_converter.h"
#include "common/http/message_impl.h"
#include "common/http/utility.h"
#include "common/protobuf/protobuf.h"
#include "common/protobuf/utility.h"

Expand All @@ -13,16 +14,25 @@ namespace Extensions {
namespace HttpFilters {

UberFilterFuzzer::UberFilterFuzzer() {
// Need to set for both a decoder filter and an encoder/decoder filter.
// This is a decoder filter.
ON_CALL(filter_callback_, addStreamDecoderFilter(_))
.WillByDefault(Invoke([&](std::shared_ptr<Envoy::Http::StreamDecoderFilter> filter) -> void {
filter_ = filter;
filter_->setDecoderFilterCallbacks(callbacks_);
.WillByDefault(Invoke([&](Http::StreamDecoderFilterSharedPtr filter) -> void {
decoder_filter_ = filter;
decoder_filter_->setDecoderFilterCallbacks(decoder_callbacks_);
}));
// This is an encoded filter.
ON_CALL(filter_callback_, addStreamEncoderFilter(_))
.WillByDefault(Invoke([&](Http::StreamEncoderFilterSharedPtr filter) -> void {
encoder_filter_ = filter;
encoder_filter_->setEncoderFilterCallbacks(encoder_callbacks_);
}));
// This is a decoder and encoder filter.
ON_CALL(filter_callback_, addStreamFilter(_))
.WillByDefault(Invoke([&](std::shared_ptr<Envoy::Http::StreamDecoderFilter> filter) -> void {
filter_ = filter;
filter_->setDecoderFilterCallbacks(callbacks_);
.WillByDefault(Invoke([&](Http::StreamFilterSharedPtr filter) -> void {
decoder_filter_ = filter;
decoder_filter_->setDecoderFilterCallbacks(decoder_callbacks_);
encoder_filter_ = filter;
encoder_filter_->setEncoderFilterCallbacks(encoder_callbacks_);
}));
// Set expectations for particular filters that may get fuzzed.
perFilterSetup();
Expand All @@ -44,28 +54,16 @@ std::vector<std::string> UberFilterFuzzer::parseHttpData(const test::fuzz::HttpD
return data_chunks;
}

void UberFilterFuzzer::decode(Http::StreamDecoderFilter* filter, const test::fuzz::HttpData& data) {
template <class FilterType>
void UberFilterFuzzer::runData(FilterType* filter, const test::fuzz::HttpData& data) {
bool end_stream = false;

auto headers = Fuzz::fromHeaders<Http::TestRequestHeaderMapImpl>(data.headers());
if (headers.Path() == nullptr) {
headers.setPath("/foo");
}
if (headers.Method() == nullptr) {
headers.setMethod("GET");
}
if (headers.Host() == nullptr) {
headers.setHost("foo.com");
}

if (data.body_case() == test::fuzz::HttpData::BODY_NOT_SET && !data.has_trailers()) {
end_stream = true;
}
ENVOY_LOG_MISC(debug, "Decoding headers (end_stream={}): {} ", end_stream,
data.headers().DebugString());
const auto& headersStatus = filter->decodeHeaders(headers, end_stream);
const auto& headersStatus = sendHeaders(filter, data, end_stream);
if (headersStatus != Http::FilterHeadersStatus::Continue &&
headersStatus != Http::FilterHeadersStatus::StopIteration) {
ENVOY_LOG_MISC(debug, "Finished with FilterHeadersStatus: {}", headersStatus);
return;
}

Expand All @@ -75,23 +73,90 @@ void UberFilterFuzzer::decode(Http::StreamDecoderFilter* filter, const test::fuz
end_stream = true;
}
Buffer::OwnedImpl buffer(data_chunks[i]);
ENVOY_LOG_MISC(debug, "Decoding data (end_stream={}): {} ", end_stream, buffer.toString());
if (filter->decodeData(buffer, end_stream) != Http::FilterDataStatus::Continue) {
const auto& dataStatus = sendData(filter, buffer, end_stream);
if (dataStatus != Http::FilterDataStatus::Continue) {
ENVOY_LOG_MISC(debug, "Finished with FilterDataStatus: {}", dataStatus);
return;
}
}

if (data.has_trailers()) {
ENVOY_LOG_MISC(debug, "Decoding trailers: {} ", data.trailers().DebugString());
auto trailers = Fuzz::fromHeaders<Http::TestRequestTrailerMapImpl>(data.trailers());
filter->decodeTrailers(trailers);
sendTrailers(filter, data);
}
}

template <>
Http::FilterHeadersStatus UberFilterFuzzer::sendHeaders(Http::StreamDecoderFilter* filter,
const test::fuzz::HttpData& data,
bool end_stream) {
request_headers_ = Fuzz::fromHeaders<Http::TestRequestHeaderMapImpl>(data.headers());
if (request_headers_.Path() == nullptr) {
request_headers_.setPath("/foo");
}
if (request_headers_.Method() == nullptr) {
request_headers_.setMethod("GET");
}
if (request_headers_.Host() == nullptr) {
request_headers_.setHost("foo.com");
}

ENVOY_LOG_MISC(debug, "Decoding headers (end_stream={}):\n{} ", end_stream, request_headers_);
return filter->decodeHeaders(request_headers_, end_stream);
}

template <>
Http::FilterHeadersStatus UberFilterFuzzer::sendHeaders(Http::StreamEncoderFilter* filter,
const test::fuzz::HttpData& data,
bool end_stream) {
response_headers_ = Fuzz::fromHeaders<Http::TestResponseHeaderMapImpl>(data.headers());

// Status must be a valid unsigned long. If not set, the utility function below will throw
// an exception on the data path of some filters. This should never happen in production, so catch
// the exception and set to a default value.
try {
(void)Http::Utility::getResponseStatus(response_headers_);
} catch (const Http::CodecClientException& e) {
response_headers_.setStatus(200);
}

ENVOY_LOG_MISC(debug, "Encoding headers (end_stream={}):\n{} ", end_stream, response_headers_);
return filter->encodeHeaders(response_headers_, end_stream);
}

template <>
Http::FilterDataStatus UberFilterFuzzer::sendData(Http::StreamDecoderFilter* filter,
Buffer::Instance& buffer, bool end_stream) {
ENVOY_LOG_MISC(debug, "Decoding data (end_stream={}): {} ", end_stream, buffer.toString());
return filter->decodeData(buffer, end_stream);
}

template <>
Http::FilterDataStatus UberFilterFuzzer::sendData(Http::StreamEncoderFilter* filter,
Buffer::Instance& buffer, bool end_stream) {
ENVOY_LOG_MISC(debug, "Encoding data (end_stream={}): {} ", end_stream, buffer.toString());
return filter->encodeData(buffer, end_stream);
}

template <>
void UberFilterFuzzer::sendTrailers(Http::StreamDecoderFilter* filter,
const test::fuzz::HttpData& data) {
request_trailers_ = Fuzz::fromHeaders<Http::TestRequestTrailerMapImpl>(data.trailers());
ENVOY_LOG_MISC(debug, "Decoding trailers:\n{} ", request_trailers_);
filter->decodeTrailers(request_trailers_);
}

template <>
void UberFilterFuzzer::sendTrailers(Http::StreamEncoderFilter* filter,
const test::fuzz::HttpData& data) {
response_trailers_ = Fuzz::fromHeaders<Http::TestResponseTrailerMapImpl>(data.trailers());
ENVOY_LOG_MISC(debug, "Encoding trailers:\n{} ", response_trailers_);
filter->encodeTrailers(response_trailers_);
}

void UberFilterFuzzer::fuzz(
const envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter&
proto_config,
const test::fuzz::HttpData& data) {
const test::fuzz::HttpData& downstream_data, const test::fuzz::HttpData& upstream_data) {
try {
// Try to create the filter. Exit early if the config is invalid or violates PGV constraints.
ENVOY_LOG_MISC(info, "filter name {}", proto_config.name());
Expand All @@ -108,15 +173,32 @@ void UberFilterFuzzer::fuzz(
return;
}

decode(filter_.get(), data);
// Data path should not throw exceptions.
if (decoder_filter_ != nullptr) {
runData(decoder_filter_.get(), downstream_data);
}
if (encoder_filter_ != nullptr) {
runData(encoder_filter_.get(), upstream_data);
}

reset();
}

void UberFilterFuzzer::reset() {
if (filter_ != nullptr) {
filter_->onDestroy();
if (decoder_filter_ != nullptr) {
decoder_filter_->onDestroy();
}
decoder_filter_.reset();

if (encoder_filter_ != nullptr) {
encoder_filter_->onDestroy();
}
filter_.reset();
encoder_filter_.reset();

request_headers_.clear();
response_headers_.clear();
request_trailers_.clear();
response_trailers_.clear();
}

} // namespace HttpFilters
Expand Down
Loading