diff --git a/api/envoy/extensions/filters/network/client_ssl_auth/v3/client_ssl_auth.proto b/api/envoy/extensions/filters/network/client_ssl_auth/v3/client_ssl_auth.proto index 29cd04939b8a2..b3af267a77ad1 100644 --- a/api/envoy/extensions/filters/network/client_ssl_auth/v3/client_ssl_auth.proto +++ b/api/envoy/extensions/filters/network/client_ssl_auth/v3/client_ssl_auth.proto @@ -29,7 +29,8 @@ message ClientSSLAuth { // the authentication service. The filter will connect to the service every 60s to fetch the list // of principals. The service must support the expected :ref:`REST API // `. - string auth_api_cluster = 1 [(validate.rules).string = {min_bytes: 1}]; + string auth_api_cluster = 1 + [(validate.rules).string = {min_bytes: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // The prefix to use when emitting :ref:`statistics // `. diff --git a/generated_api_shadow/envoy/extensions/filters/network/client_ssl_auth/v3/client_ssl_auth.proto b/generated_api_shadow/envoy/extensions/filters/network/client_ssl_auth/v3/client_ssl_auth.proto index 29cd04939b8a2..b3af267a77ad1 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/client_ssl_auth/v3/client_ssl_auth.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/client_ssl_auth/v3/client_ssl_auth.proto @@ -29,7 +29,8 @@ message ClientSSLAuth { // the authentication service. The filter will connect to the service every 60s to fetch the list // of principals. The service must support the expected :ref:`REST API // `. - string auth_api_cluster = 1 [(validate.rules).string = {min_bytes: 1}]; + string auth_api_cluster = 1 + [(validate.rules).string = {min_bytes: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // The prefix to use when emitting :ref:`statistics // `. diff --git a/source/extensions/all_extensions.bzl b/source/extensions/all_extensions.bzl index 8e151ad42d2da..ace7333688bca 100644 --- a/source/extensions/all_extensions.bzl +++ b/source/extensions/all_extensions.bzl @@ -38,3 +38,12 @@ def envoy_all_http_filters(): all_extensions = dicts.add(_required_extensions, EXTENSIONS) return [v for k, v in all_extensions.items() if k.startswith(_http_filter_prefix)] + +# All network-layer filters are extensions with names that have the following prefix. +_network_filter_prefix = "envoy.filters.network" + +# Return all network-layer filter extensions to be compiled into network-layer filter generic fuzzer. +def envoy_all_network_filters(): + all_extensions = dicts.add(_required_extensions, EXTENSIONS) + + return [v for k, v in all_extensions.items() if k.startswith(_network_filter_prefix)] diff --git a/test/extensions/filters/network/common/fuzz/BUILD b/test/extensions/filters/network/common/fuzz/BUILD new file mode 100644 index 0000000000000..a97370781cbcb --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/BUILD @@ -0,0 +1,58 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_fuzz_test", + "envoy_cc_test_library", + "envoy_package", + "envoy_proto_library", +) +load( + "//source/extensions:all_extensions.bzl", + "envoy_all_network_filters", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_proto_library( + name = "network_readfilter_fuzz_proto", + srcs = ["network_readfilter_fuzz.proto"], + deps = [ + "//test/fuzz:common_proto", + "@envoy_api//envoy/config/listener/v3:pkg", + ], +) + +envoy_cc_test_library( + name = "uber_readfilter_lib", + srcs = [ + "uber_per_readfilter.cc", + "uber_readfilter.cc", + ], + hdrs = ["uber_readfilter.h"], + deps = [ + ":network_readfilter_fuzz_proto_cc_proto", + "//source/common/config:utility_lib", + "//source/extensions/filters/network:well_known_names", + "//source/extensions/filters/network/common:utility_lib", + "//test/extensions/filters/common/ext_authz:ext_authz_test_common", + "//test/extensions/filters/network/common/fuzz/utils:network_filter_fuzzer_fakes_lib", + "//test/fuzz:utility_lib", + "//test/mocks/network:network_mocks", + "@envoy_api//envoy/extensions/filters/network/direct_response/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/local_ratelimit/v3:pkg_cc_proto", + ], +) + +envoy_cc_fuzz_test( + name = "network_readfilter_fuzz_test", + srcs = ["network_readfilter_fuzz_test.cc"], + corpus = "network_readfilter_corpus", + # All Envoy network filters must be linked to the test in order for the fuzzer to pick + # these up via the NamedNetworkFilterConfigFactory. + deps = [ + ":uber_readfilter_lib", + "//source/common/config:utility_lib", + "//test/config:utility_lib", + ] + envoy_all_network_filters(), +) diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/client_sslL_auth_2 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/client_sslL_auth_2 new file mode 100644 index 0000000000000..dd24c6c6c4daa --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/client_sslL_auth_2 @@ -0,0 +1,47 @@ +config { + name: "envoy.filters.network.client_ssl_auth" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.client_ssl_auth.v3.ClientSSLAuth" + value: "\n\010\177\177_p\000O\002@\022\007x-clien" + } +} +actions { + advance_time { + milliseconds: 524288 + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 524288 + } +} +actions { + advance_time { + milliseconds: 524288 + } +} +actions { + on_new_connection { + } +} +actions { + on_data { + data: "ppu" + end_stream: true + } +} +actions { + advance_time { + milliseconds: 524288 + } +} +actions { + on_data { + data: "type.googleapis.com/envoy.extensions.filters.network.client_ssl_auth.v3.ClientSSLAuth" + end_stream: true + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/client_ssl_authz_1 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/client_ssl_authz_1 new file mode 100644 index 0000000000000..44f4dfaf34d18 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/client_ssl_authz_1 @@ -0,0 +1,44 @@ +config { + name: "envoy.filters.network.client_ssl_auth" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.client_ssl_auth.v3.ClientSSLAuth" + value: "\n%envoy.filters.network.client_ssl_auth\022\0011" + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 4 + } +} +actions { + on_data { + data: "u\360" + } +} +actions { + on_data { + data: "u\360" + } +} +actions { + on_data { + data: "u\360" + } +} +actions { + advance_time { + milliseconds: 4 + } +} +actions { + on_new_connection { + } +} +actions { + on_new_connection { + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/direct_response_1 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/direct_response_1 new file mode 100644 index 0000000000000..c65354895b289 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/direct_response_1 @@ -0,0 +1,36 @@ +config { + name: "envoy.filters.network.direct_response" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.direct_response.v3.Config" + } +} +actions { + on_new_connection { + } +} +actions { + on_data { + } +} +actions { + on_data { + data: "y" + } +} +actions { + on_data { + } +} +actions { + on_data { + } +} +actions { + on_data { + data: "\006" + } +} +actions { + on_data { + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/direct_response_open_file b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/direct_response_open_file new file mode 100644 index 0000000000000..26df2e4de4ec6 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/direct_response_open_file @@ -0,0 +1,19 @@ +config { + name: "envoy.filters.network.direct_response" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.direct_response.v3.Config" + value: "\n\032\n\030*\014\n\002\020\001\"\006\020\001\"\002\030\0012\003\032\001\':\003\032\001\'" + } +} +actions { + on_new_connection { + } +} +actions{ + on_data{ + } +} +actions { + on_new_connection { + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/dubbo_proxy_1 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/dubbo_proxy_1 new file mode 100644 index 0000000000000..b9c6f893f556b --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/dubbo_proxy_1 @@ -0,0 +1,53 @@ +config { + name: "envoy.filters.network.dubbo_proxy" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.dubbo_proxy.v3.DubboProxy" + value: "\n!envoy.filters.network.dubbo_proxy" + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 268435 + } +} +actions { + on_data { + data: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + } +} +actions { + on_new_connection { + } +} +actions { + on_data { + data: "\000\013" + } +} +actions { + on_data { + data: "\000\013" + } +} +actions { + on_data { + data: "\000\013" + } +} +actions { + on_data { + data: "\000\013" + } +} +actions { + on_new_connection { + } +} +actions { + on_new_connection { + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/echo_1 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/echo_1 new file mode 100644 index 0000000000000..fd15fde5a83f8 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/echo_1 @@ -0,0 +1,31 @@ +config { + name: "envoy.filters.network.echo" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.echo.v3.Echo" + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 2097152 + } +} +actions { + advance_time { + milliseconds: 4194304 + } +} +actions { + on_data { + data: "y" + } +} +actions { + advance_time { + milliseconds: 2097152 + } +} + diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/empty b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/empty new file mode 100644 index 0000000000000..9933bd3fed12a --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/empty @@ -0,0 +1,14 @@ +config { + name: "envoy.filters.network.local_ratelimit" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.local_ratelimit.v3.LocalRateLimit" + value:"\001\n\311\001type.googleapis.com/envoy.extensions.filters.netwe\360\231\201\270\362\251\212\211\361\263\275\271\363\206\215\263\361\255\230\252\362\265\266\243\364\203\217\266\362\211\226\227\362\232\255\221\362\227\227\210\362\255\274\232\363\220\256\256\364\206\217\231\363\246\273\262\363\214\207\237\360\255\215\236\364\206\232\207\361\273\210\256\362\234\204\234\361\256\236\207\361\225\240\253\363\255\231\272\363\254\256\273\360\276\201\214\361\231\215\216\363\233\202\226\361\252\222\256\362\217\241\265\363\200\257\245voy.api.v2.route.RouteActlRateLimit\022\017\010\200\312\002\022\004\010\200\312\002\032\003\010\200^" + } +} + +actions { + on_data { + data: "\nVtype.googleapis.com/envoy.extensions.filters.network.local_ratelimit.v3.LocalRateLimit\022\002\010 \032d\n\002\010\001\022^\n2\n%envoy.filters.network.local_ratelimit\022\000\032\007\n\002\010\001\022\001+\022\000\032&\n\000\022\"\000\000\000\000\000voy.filters.network.lo\000\000\000\000\000\000+" + end_stream: true + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/ext_authz_1 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/ext_authz_1 new file mode 100644 index 0000000000000..fabd48ca01501 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/ext_authz_1 @@ -0,0 +1,20 @@ +config { + name: "envoy.filters.network.ext_authz" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.ext_authz.v3.ExtAuthz" + } +} +actions { + on_data { + data: "y" + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 655360 + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/ext_authz_2 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/ext_authz_2 new file mode 100644 index 0000000000000..cc8199f166f42 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/ext_authz_2 @@ -0,0 +1,16 @@ +config { + name: "envoy.filters.network.ext_authz" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.ext_authz.v3.ExtAuthz" + value: "\n\037envoy.filters.network.ext_authz\030\001(\001" + } +} +actions { + on_new_connection { + } +} +actions { + on_data { + data: ":" + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/local_ratelimit_1 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/local_ratelimit_1 new file mode 100644 index 0000000000000..ab8d73afbd8f8 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/local_ratelimit_1 @@ -0,0 +1,39 @@ +config { + name: "envoy.filters.network.local_ratelimit" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.local_ratelimit.v3.LocalRateLimit" + value: "\nVtype.googleapis.com/envoy.extensions.filters.network.local_ratelimit.v3.LocalRateLimit\022\013\010\001\032\007\010\200^\020\200\306\001" + } +} +actions { + on_new_connection { + } +} +actions { + on_data { + data: "\000\000" + } +} +actions { + on_data { + data: "\000\000" + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 12035000 + } +} +actions { + on_data { + data: "\000\000" + } +} +actions { + on_new_connection { + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/local_ratelimit_time_overflow b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/local_ratelimit_time_overflow new file mode 100644 index 0000000000000..a450f763024bd --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/local_ratelimit_time_overflow @@ -0,0 +1,44 @@ +config { + name: "envoy.filters.network.local_ratelimit" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.local_ratelimit.v3.LocalRateLimit" + value: "\nVtype.googleapis.com/envoy.extensions.filters.network.local_ratelimit.v3.LocalRateLimit\022\017\010\001\032\013\010\200\336\200\200\240\007\020\200\306!" + } +} +actions { + advance_time { + milliseconds: 12035000 + } +} +actions { + on_data { + data: "\000\013" + } +} +actions { + on_data { + data: "\000\000" + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 12035000 + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 53 + } +} +actions { + on_new_connection { + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/redis_proxy_1 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/redis_proxy_1 new file mode 100644 index 0000000000000..15ac639614e89 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/redis_proxy_1 @@ -0,0 +1,28 @@ +config { + name: "envoy.filters.network.redis_proxy" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy" + value: "\n\001N\032\032\n\005\020\200\200\200\030\030\001 \377\377\377\337\017*\005\020\200\200\200\0302\000@\001*\010\n\006\032\004\001\000\000\010" + } +} +actions { + on_new_connection { + + } +} +actions { + on_new_connection { + } +} +actions { + on_data { + data: "0" + end_stream: true + } +} +actions { + on_data { + data: "0" + end_stream: true + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/sni_cluster_1 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/sni_cluster_1 new file mode 100644 index 0000000000000..e657e3b116a2f --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/sni_cluster_1 @@ -0,0 +1,35 @@ +config { + name: "envoy.filters.network.sni_cluster" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.sni_cluster.v3.SniCluster" + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 268435 + } +} +actions { + on_data { + data: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + } +} +actions { + on_data { + data: "IIIIIIIIIIIIIIIIIIII\000\000\000\000\000\000\000;IIIIIIIIIIIIIIIIIIIIIIIIIIIIII" + } +} +actions { + advance_time { + milliseconds: 16384 + } +} +actions { + advance_time { + milliseconds: 13 + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/sni_cluster_2 b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/sni_cluster_2 new file mode 100644 index 0000000000000..25a5c974299ad --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_corpus/sni_cluster_2 @@ -0,0 +1,25 @@ +config { + name: "envoy.filters.network.sni_cluster" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.network.sni_cluster.v3.SniCluster" + } +} +actions { + on_new_connection { + } +} +actions { + advance_time { + milliseconds: 268435 + } +} +actions { + on_data { + data: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + } +} +actions { + advance_time { + milliseconds: 1677721 + } +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz.proto b/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz.proto new file mode 100644 index 0000000000000..e8205658d25e1 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package test.extensions.filters.network; +import "google/protobuf/empty.proto"; +import "validate/validate.proto"; +import "envoy/config/listener/v3/listener_components.proto"; + +message OnData { + bytes data = 1; + bool end_stream = 2; +} + +message AdvanceTime { + // Advance the system time by (0,24] hours. + uint32 milliseconds = 1 [(validate.rules).uint32 = {gt: 0 lt: 86400000}]; +} + +message Action { + oneof action_selector { + option (validate.required) = true; + // Call onNewConnection() + google.protobuf.Empty on_new_connection = 1; + // Call onData() + OnData on_data = 2; + // Advance time_source_ + AdvanceTime advance_time = 3; + } +} + +message FilterFuzzTestCase { + // This is actually a protobuf type for the config of network filters. + envoy.config.listener.v3.Filter config = 1; + repeated Action actions = 2; +} diff --git a/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz_test.cc b/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz_test.cc new file mode 100644 index 0000000000000..cacff3aa8938e --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/network_readfilter_fuzz_test.cc @@ -0,0 +1,61 @@ +#include "common/config/utility.h" +#include "common/protobuf/utility.h" + +#include "extensions/filters/network/well_known_names.h" + +#include "test/config/utility.h" +#include "test/extensions/filters/network/common/fuzz/network_readfilter_fuzz.pb.validate.h" +#include "test/extensions/filters/network/common/fuzz/uber_readfilter.h" +#include "test/fuzz/fuzz_runner.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +DEFINE_PROTO_FUZZER(const test::extensions::filters::network::FilterFuzzTestCase& input) { + ABSL_ATTRIBUTE_UNUSED static PostProcessorRegistration reg = { + [](test::extensions::filters::network::FilterFuzzTestCase* input, unsigned int seed) { + // This post-processor mutation is applied only when libprotobuf-mutator + // calls mutate on an input, and *not* during fuzz target execution. + // Replaying a corpus through the fuzzer will not be affected by the + // post-processor mutation. + + // TODO(jianwendong): After extending to cover all the filters, we can use + // `Registry::FactoryRegistry< + // Server::Configuration::NamedNetworkFilterConfigFactory>::registeredNames()` + // to get all the filter names instead of calling `UberFilterFuzzer::filter_names()`. + static const auto filter_names = UberFilterFuzzer::filterNames(); + static const auto factories = Registry::FactoryRegistry< + Server::Configuration::NamedNetworkFilterConfigFactory>::factories(); + // Choose a valid filter name. + if (std::find(filter_names.begin(), filter_names.end(), input->config().name()) == + std::end(filter_names)) { + absl::string_view filter_name = filter_names[seed % filter_names.size()]; + input->mutable_config()->set_name(std::string(filter_name)); + } + // Set the corresponding type_url for Any. + auto& factory = factories.at(input->config().name()); + input->mutable_config()->mutable_typed_config()->set_type_url( + absl::StrCat("type.googleapis.com/", + factory->createEmptyConfigProto()->GetDescriptor()->full_name())); + }}; + + try { + TestUtility::validate(input); + // Check the filter's name in case some filters are not supported yet. + static const auto filter_names = UberFilterFuzzer::filterNames(); + // TODO(jianwendong): remove this if block after covering all the filters. + if (std::find(filter_names.begin(), filter_names.end(), input.config().name()) == + std::end(filter_names)) { + ENVOY_LOG_MISC(debug, "Test case with unsupported filter type: {}", input.config().name()); + return; + } + static UberFilterFuzzer fuzzer; + fuzzer.fuzz(input.config(), input.actions()); + } catch (const ProtoValidationException& e) { + ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what()); + } +} + +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/test/extensions/filters/network/common/fuzz/uber_per_readfilter.cc b/test/extensions/filters/network/common/fuzz/uber_per_readfilter.cc new file mode 100644 index 0000000000000..7507dd72d4e3a --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/uber_per_readfilter.cc @@ -0,0 +1,102 @@ +#include "envoy/extensions/filters/network/direct_response/v3/config.pb.h" +#include "envoy/extensions/filters/network/local_ratelimit/v3/local_rate_limit.pb.h" + +#include "extensions/filters/network/common/utility.h" +#include "extensions/filters/network/well_known_names.h" + +#include "test/extensions/filters/common/ext_authz/test_common.h" +#include "test/extensions/filters/network/common/fuzz/uber_readfilter.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace { +// Limit the fill_interval in the config of local_ratelimit filter prevent overflow in +// std::chrono::time_point. +static const int SecondsPerDay = 86400; +} // namespace +std::vector UberFilterFuzzer::filterNames() { + // These filters have already been covered by this fuzzer. + // Will extend to cover other network filters one by one. + static std::vector filter_names; + if (filter_names.empty()) { + filter_names = {NetworkFilterNames::get().ExtAuthorization, + NetworkFilterNames::get().LocalRateLimit, + NetworkFilterNames::get().RedisProxy, + NetworkFilterNames::get().ClientSslAuth, + NetworkFilterNames::get().Echo, + NetworkFilterNames::get().DirectResponse, + NetworkFilterNames::get().DubboProxy, + NetworkFilterNames::get().SniCluster}; + } + return filter_names; +} + +void UberFilterFuzzer::perFilterSetup(const std::string& filter_name) { + // Set up response for ext_authz filter + if (filter_name == NetworkFilterNames::get().ExtAuthorization) { + + async_client_factory_ = std::make_unique(); + async_client_ = std::make_unique(); + // TODO(jianwendong): consider testing on different kinds of responses. + ON_CALL(*async_client_, sendRaw(_, _, _, _, _, _)) + .WillByDefault(testing::WithArgs<3>(Invoke([&](Grpc::RawAsyncRequestCallbacks& callbacks) { + Filters::Common::ExtAuthz::GrpcClientImpl* grpc_client_impl = + dynamic_cast(&callbacks); + const std::string empty_body{}; + const auto expected_headers = + Filters::Common::ExtAuthz::TestCommon::makeHeaderValueOption({}); + auto check_response = Filters::Common::ExtAuthz::TestCommon::makeCheckResponse( + Grpc::Status::WellKnownGrpcStatus::Ok, envoy::type::v3::OK, empty_body, + expected_headers); + // Give response to the grpc_client by calling onSuccess(). + grpc_client_impl->onSuccess(std::move(check_response), span_); + return async_request_.get(); + }))); + + EXPECT_CALL(*async_client_factory_, create()).WillOnce(Invoke([&] { + return std::move(async_client_); + })); + + EXPECT_CALL(factory_context_.cluster_manager_.async_client_manager_, + factoryForGrpcService(_, _, _)) + .WillOnce(Invoke([&](const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) { + return std::move(async_client_factory_); + })); + } +} + +void UberFilterFuzzer::checkInvalidInputForFuzzer(const std::string& filter_name, + Protobuf::Message* config_message) { + // System calls such as reading files are prohibited in this fuzzer. Some input that crashes the + // mock/fake objects are also prohibited. For now there are only two filters {DirectResponse, + // LocalRateLimit} on which we have constraints. + const std::string name = Extensions::NetworkFilters::Common::FilterNameUtil::canonicalFilterName( + std::string(filter_name)); + if (filter_name == NetworkFilterNames::get().DirectResponse) { + envoy::extensions::filters::network::direct_response::v3::Config& config = + dynamic_cast( + *config_message); + if (config.response().specifier_case() == + envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { + throw EnvoyException( + absl::StrCat("direct_response trying to open a file. Config:\n{}", config.DebugString())); + } + } else if (filter_name == NetworkFilterNames::get().LocalRateLimit) { + envoy::extensions::filters::network::local_ratelimit::v3::LocalRateLimit& config = + dynamic_cast( + *config_message); + if (config.token_bucket().fill_interval().seconds() > SecondsPerDay) { + // Too large fill_interval may cause "c++/v1/chrono" overflow when simulated_time_system_ is + // converting it to a smaller unit. Constraining fill_interval to no greater than one day is + // reasonable. + throw EnvoyException( + absl::StrCat("local_ratelimit trying to set a large fill_interval. Config:\n{}", + config.DebugString())); + } + } +} + +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/common/fuzz/uber_readfilter.cc b/test/extensions/filters/network/common/fuzz/uber_readfilter.cc new file mode 100644 index 0000000000000..cd984f47351b1 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/uber_readfilter.cc @@ -0,0 +1,108 @@ +#include "test/extensions/filters/network/common/fuzz/uber_readfilter.h" + +#include "common/config/utility.h" +#include "common/config/version_converter.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { + +void UberFilterFuzzer::reset() { + // Reset some changes made by current filter on some mock objects. + + // Close the connection to make sure the filter's callback is set to nullptr. + read_filter_callbacks_->connection_.raiseEvent(Network::ConnectionEvent::LocalClose); + // Clear the filter's raw pointer stored inside the connection_ and reset the connection_'s state. + read_filter_callbacks_->connection_.callbacks_.clear(); + read_filter_callbacks_->connection_.bytes_sent_callbacks_.clear(); + read_filter_callbacks_->connection_.state_ = Network::Connection::State::Open; + read_filter_.reset(); +} + +void UberFilterFuzzer::fuzzerSetup() { + // Setup process when this fuzzer object is constructed. + // For a static fuzzer, this will only be executed once. + + // Get the pointer of read_filter when the read_filter is being added to connection_. + read_filter_callbacks_ = std::make_shared>(); + ON_CALL(read_filter_callbacks_->connection_, addReadFilter(_)) + .WillByDefault(Invoke([&](Network::ReadFilterSharedPtr read_filter) -> void { + read_filter_ = read_filter; + read_filter_->initializeReadFilterCallbacks(*read_filter_callbacks_); + })); + // Prepare sni for sni_cluster filter and sni_dynamic_forward_proxy filter. + ON_CALL(read_filter_callbacks_->connection_, requestedServerName()) + .WillByDefault(testing::Return("fake_cluster")); + // Prepare time source for filters such as local_ratelimit filter. + factory_context_.prepareSimulatedSystemTime(); + // Prepare address for filters such as ext_authz filter. + addr_ = std::make_shared("/test/test.sock"); + read_filter_callbacks_->connection_.remote_address_ = addr_; + read_filter_callbacks_->connection_.local_address_ = addr_; + async_request_ = std::make_unique(); +} + +UberFilterFuzzer::UberFilterFuzzer() : time_source_(factory_context_.simulatedTimeSystem()) { + fuzzerSetup(); +} + +void UberFilterFuzzer::fuzz( + const envoy::config::listener::v3::Filter& proto_config, + const Protobuf::RepeatedPtrField<::test::extensions::filters::network::Action>& actions) { + try { + // Try to create the filter callback(cb_). Exit early if the config is invalid or violates PGV + // constraints. + const std::string& filter_name = proto_config.name(); + ENVOY_LOG_MISC(info, "filter name {}", filter_name); + auto& factory = Config::Utility::getAndCheckFactoryByName< + Server::Configuration::NamedNetworkFilterConfigFactory>(filter_name); + ProtobufTypes::MessagePtr message = Config::Utility::translateToFactoryConfig( + proto_config, factory_context_.messageValidationVisitor(), factory); + // Make sure no invalid system calls are executed in fuzzer. + checkInvalidInputForFuzzer(filter_name, message.get()); + ENVOY_LOG_MISC(info, "Config content after decoded: {}", message->DebugString()); + cb_ = factory.createFilterFactoryFromProto(*message, factory_context_); + + } catch (const EnvoyException& e) { + ENVOY_LOG_MISC(debug, "Controlled exception in filter setup {}", e.what()); + return; + } + perFilterSetup(proto_config.name()); + // Add filter to connection_. + cb_(read_filter_callbacks_->connection_); + for (const auto& action : actions) { + ENVOY_LOG_MISC(trace, "action {}", action.DebugString()); + switch (action.action_selector_case()) { + case test::extensions::filters::network::Action::kOnData: { + ASSERT(read_filter_ != nullptr); + Buffer::OwnedImpl buffer(action.on_data().data()); + read_filter_->onData(buffer, action.on_data().end_stream()); + + break; + } + case test::extensions::filters::network::Action::kOnNewConnection: { + ASSERT(read_filter_ != nullptr); + read_filter_->onNewConnection(); + + break; + } + case test::extensions::filters::network::Action::kAdvanceTime: { + time_source_.advanceTimeAsync( + std::chrono::milliseconds(action.advance_time().milliseconds())); + factory_context_.dispatcher().run(Event::Dispatcher::RunType::NonBlock); + break; + } + default: { + // Unhandled actions. + ENVOY_LOG_MISC(debug, "Action support is missing for:\n{}", action.DebugString()); + PANIC("A case is missing for an action"); + } + } + } + + reset(); +} + +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/common/fuzz/uber_readfilter.h b/test/extensions/filters/network/common/fuzz/uber_readfilter.h new file mode 100644 index 0000000000000..31a5bbc1d91e0 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/uber_readfilter.h @@ -0,0 +1,49 @@ +#include "envoy/network/filter.h" + +#include "common/protobuf/protobuf.h" + +#include "test/extensions/filters/network/common/fuzz/network_readfilter_fuzz.pb.validate.h" +#include "test/extensions/filters/network/common/fuzz/utils/fakes.h" +#include "test/mocks/network/mocks.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { + +class UberFilterFuzzer { +public: + UberFilterFuzzer(); + // This creates the filter config and runs the fuzzed data against the filter. + void + fuzz(const envoy::config::listener::v3::Filter& proto_config, + const Protobuf::RepeatedPtrField<::test::extensions::filters::network::Action>& actions); + // Get the name of filters which has been covered by this fuzzer. + static std::vector filterNames(); + // Check whether the filter's config is invalid for fuzzer(e.g. system call). + void checkInvalidInputForFuzzer(const std::string& filter_name, + Protobuf::Message* config_message); + +protected: + // Set-up filter specific mock expectations in constructor. + void fuzzerSetup(); + // Reset the states of the mock objects. + void reset(); + // Mock behaviors for specific filters. + void perFilterSetup(const std::string& filter_name); + +private: + Server::Configuration::FakeFactoryContext factory_context_; + Network::ReadFilterSharedPtr read_filter_; + Network::FilterFactoryCb cb_; + Network::Address::InstanceConstSharedPtr addr_; + Event::SimulatedTimeSystem& time_source_; + std::shared_ptr> read_filter_callbacks_; + std::unique_ptr async_request_; + std::unique_ptr async_client_; + std::unique_ptr async_client_factory_; + Tracing::MockSpan span_; +}; + +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/common/fuzz/utils/BUILD b/test/extensions/filters/network/common/fuzz/utils/BUILD new file mode 100644 index 0000000000000..6c231c2a185f0 --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/utils/BUILD @@ -0,0 +1,17 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test_library( + name = "network_filter_fuzzer_fakes_lib", + hdrs = ["fakes.h"], + deps = [ + "//test/mocks/server:factory_context_mocks", + ], +) diff --git a/test/extensions/filters/network/common/fuzz/utils/fakes.h b/test/extensions/filters/network/common/fuzz/utils/fakes.h new file mode 100644 index 0000000000000..035dcb3e29cac --- /dev/null +++ b/test/extensions/filters/network/common/fuzz/utils/fakes.h @@ -0,0 +1,49 @@ +#include "test/mocks/server/factory_context.h" + +namespace Envoy { +namespace Server { +namespace Configuration { +class FakeFactoryContext : public MockFactoryContext { +public: + void prepareSimulatedSystemTime() { + api_ = Api::createApiForTest(time_system_); + dispatcher_ = api_->allocateDispatcher("test_thread"); + } + AccessLog::AccessLogManager& accessLogManager() override { return access_log_manager_; } + Upstream::ClusterManager& clusterManager() override { return cluster_manager_; } + Event::Dispatcher& dispatcher() override { return *dispatcher_; } + const Network::DrainDecision& drainDecision() override { return drain_manager_; } + Init::Manager& initManager() override { return init_manager_; } + ServerLifecycleNotifier& lifecycleNotifier() override { return lifecycle_notifier_; } + const LocalInfo::LocalInfo& localInfo() const override { return local_info_; } + Envoy::Random::RandomGenerator& random() override { return random_; } + Envoy::Runtime::Loader& runtime() override { return runtime_loader_; } + Stats::Scope& scope() override { return scope_; } + Singleton::Manager& singletonManager() override { return *singleton_manager_; } + ThreadLocal::Instance& threadLocal() override { return thread_local_; } + Server::Admin& admin() override { return admin_; } + Stats::Scope& listenerScope() override { return listener_scope_; } + Api::Api& api() override { return *api_; } + TimeSource& timeSource() override { return time_system_; } + OverloadManager& overloadManager() override { return overload_manager_; } + ProtobufMessage::ValidationContext& messageValidationContext() override { + return validation_context_; + } + ProtobufMessage::ValidationVisitor& messageValidationVisitor() override { + return ProtobufMessage::getStrictValidationVisitor(); + } + Event::SimulatedTimeSystem& simulatedTimeSystem() { + return dynamic_cast(time_system_); + } + Event::TestTimeSystem& timeSystem() { return time_system_; } + Grpc::Context& grpcContext() override { return grpc_context_; } + Http::Context& httpContext() override { return http_context_; } + + Event::DispatcherPtr dispatcher_; + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; +}; + +} // namespace Configuration +} // namespace Server +} // namespace Envoy