diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/buffer1 b/test/extensions/filters/http/common/fuzz/filter_corpus/buffer1 index a1bf00f67a61c..9b8bf63c7ea9c 100644 --- a/test/extensions/filters/http/common/fuzz/filter_corpus/buffer1 +++ b/test/extensions/filters/http/common/fuzz/filter_corpus/buffer1 @@ -15,5 +15,7 @@ data { "a" value : "b" } } - data: "hello" + http_body { + data: "hello" + } } diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/crash-3014465358f0947e73ac12ccb40b299d5b0646b3 b/test/extensions/filters/http/common/fuzz/filter_corpus/crash-3014465358f0947e73ac12ccb40b299d5b0646b3 index fb63866ea5a51..72bcfa0b0baed 100644 --- a/test/extensions/filters/http/common/fuzz/filter_corpus/crash-3014465358f0947e73ac12ccb40b299d5b0646b3 +++ b/test/extensions/filters/http/common/fuzz/filter_corpus/crash-3014465358f0947e73ac12ccb40b299d5b0646b3 @@ -7,8 +7,10 @@ data { value: "\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\360\240\240\240\314\255" } } - data: "\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177" - data: "\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177" + http_body { + data: "\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177" + data: "\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177" + } trailers { headers { key: "6" diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/crash-bb74d7280823776808e881b20c0a9c87f7a2163b b/test/extensions/filters/http/common/fuzz/filter_corpus/crash-bb74d7280823776808e881b20c0a9c87f7a2163b index 3eea853ad21eb..7c2bbfbae7f7e 100644 --- a/test/extensions/filters/http/common/fuzz/filter_corpus/crash-bb74d7280823776808e881b20c0a9c87f7a2163b +++ b/test/extensions/filters/http/common/fuzz/filter_corpus/crash-bb74d7280823776808e881b20c0a9c87f7a2163b @@ -6,7 +6,9 @@ config { } } data { - data: "\001\000\000\t" + http_body { + data: "\001\000\000\t" + } trailers { headers { key: "0" diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/grpc_json b/test/extensions/filters/http/common/fuzz/filter_corpus/grpc_json deleted file mode 100644 index 3846826fa9d84..0000000000000 --- a/test/extensions/filters/http/common/fuzz/filter_corpus/grpc_json +++ /dev/null @@ -1,23 +0,0 @@ -config { -name: "envoy.filters.http.grpc_json_transcoder" -typed_config: { -} -} - -data { -headers { -headers { - key: "content-type" - value: "application/json" -} -headers { - key: ":method" - value: "POST" -} -headers { - key: ":path" - value: "/bookstore.Bookstore/CreateShelfWithPackageServiceAndMethod" -} -} -data: "{\"theme\": \"Children\"}" -} \ No newline at end of file diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/grpc_transcoding_http_data b/test/extensions/filters/http/common/fuzz/filter_corpus/grpc_transcoding_http_data new file mode 100644 index 0000000000000..cf0e8282a0830 --- /dev/null +++ b/test/extensions/filters/http/common/fuzz/filter_corpus/grpc_transcoding_http_data @@ -0,0 +1,24 @@ +config { + name: "envoy.filters.http.grpc_json_transcoder" + typed_config: {} +} + +data { + headers { + headers { + key: "content-type" + value: "application/json" + } + headers { + key: ":method" + value: "POST" + } + headers { + key: ":path" + value: "/bookstore.Bookstore/CreateShelfWithPackageServiceAndMethod" + } + } + http_body { + data: "{\"theme\": \"Children\"}" + } +} \ No newline at end of file diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/grpc_transcoding_proto_data b/test/extensions/filters/http/common/fuzz/filter_corpus/grpc_transcoding_proto_data new file mode 100644 index 0000000000000..711ea9f66ec5d --- /dev/null +++ b/test/extensions/filters/http/common/fuzz/filter_corpus/grpc_transcoding_proto_data @@ -0,0 +1,32 @@ +config { + name: "envoy.filters.http.grpc_json_transcoder" + typed_config: {} +} + +data { + headers { + headers { + key: "content-type" + value: "application/json" + } + headers { + key: ":method" + value: "POST" + } + headers { + key: ":path" + value: "/bookstore.Bookstore/CreateShelf" + } + } + proto_body { + message { + [type.googleapis.com/bookstore.CreateShelfRequest] { + shelf: { + id: 32 + theme: "Children" + } + } + } + chunk_size: 3 + } +} \ No newline at end of file diff --git a/test/extensions/filters/http/common/fuzz/filter_fuzz_test.cc b/test/extensions/filters/http/common/fuzz/filter_fuzz_test.cc index 8a20604d7a03f..edfa89f917c77 100644 --- a/test/extensions/filters/http/common/fuzz/filter_fuzz_test.cc +++ b/test/extensions/filters/http/common/fuzz/filter_fuzz_test.cc @@ -36,6 +36,13 @@ DEFINE_PROTO_FUZZER(const test::extensions::filters::http::FilterFuzzTestCase& i input->mutable_config()->mutable_typed_config()->set_type_url( absl::StrCat("type.googleapis.com/", factory->createEmptyConfigProto()->GetDescriptor()->full_name())); + + // For fuzzing proto data, guide the mutator to useful 'Any' types half + // the time. The other half the time, let the fuzzing engine choose + // any message to serialize. + if (seed % 2 == 0 && input->data().has_proto_body()) { + UberFilterFuzzer::guideAnyProtoType(input->mutable_data(), seed / 2); + } }}; try { diff --git a/test/extensions/filters/http/common/fuzz/uber_filter.cc b/test/extensions/filters/http/common/fuzz/uber_filter.cc index 7ec9b022b99a5..a88cc585a72ef 100644 --- a/test/extensions/filters/http/common/fuzz/uber_filter.cc +++ b/test/extensions/filters/http/common/fuzz/uber_filter.cc @@ -28,6 +28,22 @@ UberFilterFuzzer::UberFilterFuzzer() { perFilterSetup(); } +std::vector UberFilterFuzzer::parseHttpData(const test::fuzz::HttpData& data) { + std::vector data_chunks; + + if (data.has_http_body()) { + data_chunks.reserve(data.http_body().data_size()); + for (const std::string& http_data : data.http_body().data()) { + data_chunks.push_back(http_data); + } + } else if (data.has_proto_body()) { + const std::string serialized = data.proto_body().message().value(); + data_chunks = absl::StrSplit(serialized, absl::ByLength(data.proto_body().chunk_size())); + } + + return data_chunks; +} + void UberFilterFuzzer::decode(Http::StreamDecoderFilter* filter, const test::fuzz::HttpData& data) { bool end_stream = false; @@ -42,22 +58,24 @@ void UberFilterFuzzer::decode(Http::StreamDecoderFilter* filter, const test::fuz headers.setHost("foo.com"); } - if (data.data().empty() && !data.has_trailers()) { + if (data.body_case() == test::fuzz::HttpData::BODY_NOT_SET && !data.has_trailers()) { end_stream = true; } - ENVOY_LOG_MISC(debug, "Decoding headers: {} ", data.headers().DebugString()); + ENVOY_LOG_MISC(debug, "Decoding headers (end_stream={}): {} ", end_stream, + 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()) { + const std::vector data_chunks = parseHttpData(data); + for (size_t i = 0; i < data_chunks.size(); i++) { + if (!data.has_trailers() && i == data_chunks.size() - 1) { end_stream = true; } - Buffer::OwnedImpl buffer(data.data().Get(i)); - ENVOY_LOG_MISC(debug, "Decoding data: {} ", buffer.toString()); + 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) { return; } diff --git a/test/extensions/filters/http/common/fuzz/uber_filter.h b/test/extensions/filters/http/common/fuzz/uber_filter.h index a18d1ae8057ba..511c587a6e625 100644 --- a/test/extensions/filters/http/common/fuzz/uber_filter.h +++ b/test/extensions/filters/http/common/fuzz/uber_filter.h @@ -16,14 +16,21 @@ class UberFilterFuzzer { proto_config, const test::fuzz::HttpData& data); + // For fuzzing proto data, guide the mutator to useful 'Any' types. + static void guideAnyProtoType(test::fuzz::HttpData* mutable_data, uint choice); + protected: // Set-up filter specific mock expectations in constructor. void perFilterSetup(); // Filter specific input cleanup. void cleanFuzzedConfig(absl::string_view filter_name, Protobuf::Message* message); + // Parses http or proto body into chunks. + std::vector parseHttpData(const test::fuzz::HttpData& data); + // This executes the decode methods to be fuzzed. void decode(Http::StreamDecoderFilter* filter, const test::fuzz::HttpData& data); + void reset(); private: diff --git a/test/extensions/filters/http/common/fuzz/uber_per_filter.cc b/test/extensions/filters/http/common/fuzz/uber_per_filter.cc index 55ba9d253eae8..353eea56f0bee 100644 --- a/test/extensions/filters/http/common/fuzz/uber_per_filter.cc +++ b/test/extensions/filters/http/common/fuzz/uber_per_filter.cc @@ -44,6 +44,32 @@ void addBookstoreProtoDescriptor(Protobuf::Message* message) { } } // namespace +void UberFilterFuzzer::guideAnyProtoType(test::fuzz::HttpData* mutable_data, uint choice) { + // These types are request/response from the test Bookstore service + // for the gRPC Transcoding filter. + static const std::vector expected_types = { + "type.googleapis.com/bookstore.ListShelvesResponse", + "type.googleapis.com/bookstore.CreateShelfRequest", + "type.googleapis.com/bookstore.GetShelfRequest", + "type.googleapis.com/bookstore.DeleteShelfRequest", + "type.googleapis.com/bookstore.ListBooksRequest", + "type.googleapis.com/bookstore.CreateBookRequest", + "type.googleapis.com/bookstore.GetBookRequest", + "type.googleapis.com/bookstore.UpdateBookRequest", + "type.googleapis.com/bookstore.DeleteBookRequest", + "type.googleapis.com/bookstore.GetAuthorRequest", + "type.googleapis.com/bookstore.EchoBodyRequest", + "type.googleapis.com/bookstore.EchoStructReqResp", + "type.googleapis.com/bookstore.Shelf", + "type.googleapis.com/bookstore.Book", + "type.googleapis.com/google.protobuf.Empty", + "type.googleapis.com/google.api.HttpBody", + }; + ProtobufWkt::Any* mutable_any = mutable_data->mutable_proto_body()->mutable_message(); + const std::string& type_url = expected_types[choice % expected_types.size()]; + mutable_any->set_type_url(type_url); +} + void UberFilterFuzzer::cleanFuzzedConfig(absl::string_view filter_name, Protobuf::Message* message) { // Map filter name to clean-up function. diff --git a/test/fuzz/common.proto b/test/fuzz/common.proto index b32db65c98e09..92df9a1b40219 100644 --- a/test/fuzz/common.proto +++ b/test/fuzz/common.proto @@ -5,6 +5,7 @@ package test.fuzz; import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/address.proto"; +import "google/protobuf/any.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; @@ -15,9 +16,29 @@ message Headers { repeated envoy.config.core.v3.HeaderValue headers = 1; } +message HttpBody { + // The bytes that will be used as the request body. + repeated string data = 1 [(validate.rules).repeated .min_items = 1]; +} + +// HttpBody cannot efficiently create serialized protos. +// Use ProtoBody instead to test grpc data. +message ProtoBody { + // The proto message that will be serialized and used as the request body. + google.protobuf.Any message = 1 [(validate.rules).any.required = true]; + + // The size (in bytes) of each buffer when forming the requests. + uint64 chunk_size = 2 [(validate.rules).uint64.gt = 0]; +} + message HttpData { Headers headers = 1; - repeated string data = 2; + + oneof body { + HttpBody http_body = 2; + ProtoBody proto_body = 4; + } + Headers trailers = 3; } diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index 27bc32dbe9267..d54d2a36abc4b 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -58,6 +58,7 @@ "./test/common/config/version_converter_test.cc", "./test/common/grpc/codec_test.cc", "./test/common/grpc/codec_fuzz_test.cc", + "./test/extensions/filters/http/common/fuzz/uber_filter.h", ) # Files in these paths can use Protobuf::util::JsonStringToMessage