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
22 changes: 16 additions & 6 deletions test/extensions/filters/http/common/fuzz/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ licenses(["notice"]) # Apache 2
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_fuzz_test",
"envoy_cc_test_library",
"envoy_package",
"envoy_proto_library",
)
Expand All @@ -22,22 +23,31 @@ envoy_proto_library(
],
)

envoy_cc_test_library(
name = "uber_filter_lib",
hdrs = ["uber_filter.h"],
deps = [
":filter_fuzz_proto_cc_proto",
"//source/common/config:utility_lib",
"//source/common/protobuf:utility_lib",
"//test/fuzz:utility_lib",
"//test/mocks/buffer:buffer_mocks",
"//test/mocks/http:http_mocks",
"//test/mocks/server:server_mocks",
],
)

envoy_cc_fuzz_test(
name = "filter_fuzz_test",
srcs = ["filter_fuzz_test.cc"],
corpus = "filter_corpus",
# All Envoy extensions must be linked to the test in order for the fuzzer to pick
# these up via the NamedHttpFilterConfigFactory.
deps = [
":filter_fuzz_proto_cc_proto",
":uber_filter_lib",
"//source/common/config:utility_lib",
"//source/common/config:version_converter_lib",
"//source/common/protobuf:utility_lib",
"//test/config:utility_lib",
"//test/fuzz:utility_lib",
"//test/mocks/buffer:buffer_mocks",
"//test/mocks/http:http_mocks",
"//test/mocks/server:server_mocks",
"@envoy_api//envoy/service/auth/v3:pkg_cc_proto",
"@envoy_api//envoy/service/auth/v2alpha:pkg_cc_proto",
] + envoy_all_extensions(),
Expand Down
138 changes: 1 addition & 137 deletions test/extensions/filters/http/common/fuzz/filter_fuzz_test.cc
Original file line number Diff line number Diff line change
@@ -1,153 +1,17 @@
#include <chrono>
#include <memory>

#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h"

#include "common/config/utility.h"
#include "common/config/version_converter.h"
#include "common/protobuf/utility.h"

#include "extensions/filters/http/well_known_names.h"

#include "test/config/utility.h"
#include "test/extensions/filters/http/common/fuzz/filter_fuzz.pb.validate.h"
#include "test/extensions/filters/http/common/fuzz/uber_filter.h"
#include "test/fuzz/fuzz_runner.h"
#include "test/fuzz/utility.h"
#include "test/mocks/buffer/mocks.h"
#include "test/mocks/http/mocks.h"
#include "test/mocks/server/mocks.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {

class UberFilterFuzzer {
public:
UberFilterFuzzer() {
// Need to set for both a decoder filter and an encoder/decoder filter.
ON_CALL(filter_callback_, addStreamDecoderFilter(_))
.WillByDefault(
Invoke([&](std::shared_ptr<Envoy::Http::StreamDecoderFilter> filter) -> void {
filter_ = filter;
filter_->setDecoderFilterCallbacks(callbacks_);
}));
ON_CALL(filter_callback_, addStreamFilter(_))
.WillByDefault(
Invoke([&](std::shared_ptr<Envoy::Http::StreamDecoderFilter> filter) -> void {
filter_ = filter;
filter_->setDecoderFilterCallbacks(callbacks_);
}));
// Ext-authz setup
prepareExtAuthz();
prepareCache();
prepareTap();
}

void prepareExtAuthz() {
// Preparing the expectations for the ext_authz filter.
addr_ = std::make_shared<Network::Address::Ipv4Instance>("1.2.3.4", 1111);
ON_CALL(connection_, remoteAddress()).WillByDefault(testing::ReturnRef(addr_));
ON_CALL(connection_, localAddress()).WillByDefault(testing::ReturnRef(addr_));
ON_CALL(callbacks_, connection()).WillByDefault(testing::Return(&connection_));
ON_CALL(callbacks_, activeSpan())
.WillByDefault(testing::ReturnRef(Tracing::NullSpan::instance()));
callbacks_.stream_info_.protocol_ = Envoy::Http::Protocol::Http2;
}

void prepareCache() {
// Prepare expectations for dynamic forward proxy.
ON_CALL(factory_context_.dispatcher_, createDnsResolver(_, _))
.WillByDefault(testing::Return(resolver_));
}

void prepareTap() {
ON_CALL(factory_context_.admin_, addHandler(_, _, _, _, _))
.WillByDefault(testing::Return(true));
ON_CALL(factory_context_.admin_, removeHandler(_)).WillByDefault(testing::Return(true));
}

// This executes the decode methods to be fuzzed.
void decode(Http::StreamDecoderFilter* 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.data().size() == 0 && !data.has_trailers()) {
end_stream = true;
}
ENVOY_LOG_MISC(debug, "Decoding headers: {} ", data.headers().DebugString());
const auto& headersStatus = filter->decodeHeaders(headers, end_stream);
if (headersStatus != Http::FilterHeadersStatus::Continue &&
headersStatus != Http::FilterHeadersStatus::StopIteration) {
return;
}

for (int i = 0; i < data.data().size(); i++) {
if (i == data.data().size() - 1 && !data.has_trailers()) {
end_stream = true;
}
Buffer::OwnedImpl buffer(data.data().Get(i));
ENVOY_LOG_MISC(debug, "Decoding data: {} ", buffer.toString());
if (filter->decodeData(buffer, end_stream) != Http::FilterDataStatus::Continue) {
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);
}
}

// This creates the filter config and runs decode.
void fuzz(const envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter&
proto_config,
const test::fuzz::HttpData& 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());
auto& factory = Config::Utility::getAndCheckFactoryByName<
Server::Configuration::NamedHttpFilterConfigFactory>(proto_config.name());
ProtobufTypes::MessagePtr message = Config::Utility::translateToFactoryConfig(
proto_config, factory_context_.messageValidationVisitor(), factory);
cb_ = factory.createFilterFactoryFromProto(*message, "stats", factory_context_);
cb_(filter_callback_);
} catch (const EnvoyException& e) {
ENVOY_LOG_MISC(debug, "Controlled exception {}", e.what());
return;
}

decode(filter_.get(), data);
reset();
}

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

NiceMock<Server::Configuration::MockFactoryContext> factory_context_;
NiceMock<Http::MockStreamDecoderFilterCallbacks> callbacks_;
NiceMock<Http::MockFilterChainFactoryCallbacks> filter_callback_;
std::shared_ptr<Network::MockDnsResolver> resolver_{std::make_shared<Network::MockDnsResolver>()};
std::shared_ptr<Http::StreamDecoderFilter> filter_;
Http::FilterFactoryCb cb_;
NiceMock<Envoy::Network::MockConnection> connection_;
Network::Address::InstanceConstSharedPtr addr_;
};

DEFINE_PROTO_FUZZER(const test::extensions::filters::http::FilterFuzzTestCase& input) {
static PostProcessorRegistration reg = {[](test::extensions::filters::http::FilterFuzzTestCase*
input,
Expand Down
147 changes: 147 additions & 0 deletions test/extensions/filters/http/common/fuzz/uber_filter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include "common/config/utility.h"
#include "common/config/version_converter.h"
#include "common/protobuf/utility.h"

#include "test/fuzz/utility.h"
#include "test/mocks/buffer/mocks.h"
#include "test/mocks/http/mocks.h"
#include "test/mocks/server/mocks.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {

class UberFilterFuzzer {
public:
UberFilterFuzzer() {
// Need to set for both a decoder filter and an encoder/decoder filter.
ON_CALL(filter_callback_, addStreamDecoderFilter(_))
.WillByDefault(
Invoke([&](std::shared_ptr<Envoy::Http::StreamDecoderFilter> filter) -> void {
filter_ = filter;
filter_->setDecoderFilterCallbacks(callbacks_);
}));
ON_CALL(filter_callback_, addStreamFilter(_))
.WillByDefault(
Invoke([&](std::shared_ptr<Envoy::Http::StreamDecoderFilter> filter) -> void {
filter_ = filter;
filter_->setDecoderFilterCallbacks(callbacks_);
}));
setExpectations();
}

void setExpectations() {
// Ext-authz setup
prepareExtAuthz();
prepareCache();
prepareTap();
}

void prepareExtAuthz() {
// Preparing the expectations for the ext_authz filter.
addr_ = std::make_shared<Network::Address::Ipv4Instance>("1.2.3.4", 1111);
ON_CALL(connection_, remoteAddress()).WillByDefault(testing::ReturnRef(addr_));
ON_CALL(connection_, localAddress()).WillByDefault(testing::ReturnRef(addr_));
ON_CALL(callbacks_, connection()).WillByDefault(testing::Return(&connection_));
ON_CALL(callbacks_, activeSpan())
.WillByDefault(testing::ReturnRef(Tracing::NullSpan::instance()));
callbacks_.stream_info_.protocol_ = Envoy::Http::Protocol::Http2;
}

void prepareCache() {
// Prepare expectations for dynamic forward proxy.
ON_CALL(factory_context_.dispatcher_, createDnsResolver(_, _))
.WillByDefault(testing::Return(resolver_));
}

void prepareTap() {
ON_CALL(factory_context_.admin_, addHandler(_, _, _, _, _))
.WillByDefault(testing::Return(true));
ON_CALL(factory_context_.admin_, removeHandler(_)).WillByDefault(testing::Return(true));
}

// This executes the decode methods to be fuzzed.
void decode(Http::StreamDecoderFilter* 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.data().size() == 0 && !data.has_trailers()) {
end_stream = true;
}
ENVOY_LOG_MISC(debug, "Decoding headers: {} ", data.headers().DebugString());
const auto& headersStatus = filter->decodeHeaders(headers, end_stream);
if (headersStatus != Http::FilterHeadersStatus::Continue &&
headersStatus != Http::FilterHeadersStatus::StopIteration) {
return;
}

for (int i = 0; i < data.data().size(); i++) {
if (i == data.data().size() - 1 && !data.has_trailers()) {
end_stream = true;
}
Buffer::OwnedImpl buffer(data.data().Get(i));
ENVOY_LOG_MISC(debug, "Decoding data: {} ", buffer.toString());
if (filter->decodeData(buffer, end_stream) != Http::FilterDataStatus::Continue) {
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);
}
}

// This creates the filter config and runs decode.
void fuzz(const envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter&
proto_config,
const test::fuzz::HttpData& 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());
auto& factory = Config::Utility::getAndCheckFactoryByName<
Server::Configuration::NamedHttpFilterConfigFactory>(proto_config.name());
ProtobufTypes::MessagePtr message = Config::Utility::translateToFactoryConfig(
proto_config, factory_context_.messageValidationVisitor(), factory);
cb_ = factory.createFilterFactoryFromProto(*message, "stats", factory_context_);
cb_(filter_callback_);
} catch (const EnvoyException& e) {
ENVOY_LOG_MISC(debug, "Controlled exception {}", e.what());
return;
}

decode(filter_.get(), data);
reset();
}

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

NiceMock<Server::Configuration::MockFactoryContext> factory_context_;
NiceMock<Http::MockStreamDecoderFilterCallbacks> callbacks_;
NiceMock<Http::MockFilterChainFactoryCallbacks> filter_callback_;
std::shared_ptr<Network::MockDnsResolver> resolver_{std::make_shared<Network::MockDnsResolver>()};
std::shared_ptr<Http::StreamDecoderFilter> filter_;
Http::FilterFactoryCb cb_;
NiceMock<Envoy::Network::MockConnection> connection_;
Network::Address::InstanceConstSharedPtr addr_;
};

} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
Loading