/proc/self/cwd/test/fuzz/utility.h
Line | Count | Source (jump to first uncovered line) |
1 | | #pragma once |
2 | | |
3 | | #include "envoy/config/core/v3/base.pb.h" |
4 | | |
5 | | #include "source/common/common/empty_string.h" |
6 | | #include "source/common/network/resolver_impl.h" |
7 | | #include "source/common/network/socket_impl.h" |
8 | | #include "source/common/network/utility.h" |
9 | | |
10 | | #include "test/common/stream_info/test_util.h" |
11 | | #include "test/fuzz/common.pb.h" |
12 | | #include "test/mocks/ssl/mocks.h" |
13 | | #include "test/mocks/upstream/host.h" |
14 | | #include "test/test_common/utility.h" |
15 | | |
16 | | #include "quiche/http2/adapter/header_validator.h" |
17 | | |
18 | | // Strong assertion that applies across all compilation modes and doesn't rely |
19 | | // on gtest, which only provides soft fails that don't trip oss-fuzz failures. |
20 | 9.34M | #define FUZZ_ASSERT(x) RELEASE_ASSERT(x, "") |
21 | | |
22 | | namespace Envoy { |
23 | | namespace Fuzz { |
24 | | |
25 | | // The HeaderMap code assumes that input does not contain certain characters, and |
26 | | // this is validated by the HTTP parser. Some fuzzers will create strings with |
27 | | // these characters, however, and this creates not very interesting fuzz test |
28 | | // failures as an assertion is rapidly hit in the LowerCaseString constructor |
29 | | // before we get to anything interesting. |
30 | | // |
31 | | // This method will replace any of those characters found with spaces. |
32 | 37.7k | inline std::string replaceInvalidCharacters(absl::string_view string) { |
33 | 37.7k | std::string filtered; |
34 | 37.7k | filtered.reserve(string.length()); |
35 | 23.1M | for (const char& c : string) { |
36 | 23.1M | switch (c) { |
37 | 1.23k | case '\0': |
38 | 1.23k | FALLTHRU; |
39 | 1.23k | case '\r': |
40 | 1.23k | FALLTHRU; |
41 | 3.41k | case '\n': |
42 | 3.41k | filtered.push_back(' '); |
43 | 3.41k | break; |
44 | 23.1M | default: |
45 | 23.1M | filtered.push_back(c); |
46 | 23.1M | } |
47 | 23.1M | } |
48 | 37.7k | return filtered; |
49 | 37.7k | } |
50 | | |
51 | | // Replace invalid host characters. |
52 | 211 | inline std::string replaceInvalidHostCharacters(absl::string_view string) { |
53 | 211 | std::string filtered; |
54 | 211 | filtered.reserve(string.length()); |
55 | 291k | for (const char& c : string) { |
56 | 291k | if (http2::adapter::HeaderValidator::IsValidAuthority(absl::string_view(&c, 1))) { |
57 | 288k | filtered.push_back(c); |
58 | 288k | } else { |
59 | 3.54k | filtered.push_back('0'); |
60 | 3.54k | } |
61 | 291k | } |
62 | 211 | return filtered; |
63 | 211 | } |
64 | | |
65 | | inline envoy::config::core::v3::Metadata |
66 | 10.5k | replaceInvalidStringValues(const envoy::config::core::v3::Metadata& upstream_metadata) { |
67 | 10.5k | envoy::config::core::v3::Metadata processed = upstream_metadata; |
68 | 10.5k | for (auto& metadata_struct : *processed.mutable_filter_metadata()) { |
69 | | // Metadata fields consist of keyed Structs, which is a map of dynamically typed values. These |
70 | | // values can be null, a number, a string, a boolean, a list of values, or a recursive struct. |
71 | | // This clears any invalid characters in string values. It may not be likely a coverage-driven |
72 | | // fuzzer will explore recursive structs, so this case is not handled here. |
73 | 207 | for (auto& field : *metadata_struct.second.mutable_fields()) { |
74 | 199 | if (field.second.kind_case() == ProtobufWkt::Value::kStringValue) { |
75 | 8 | field.second.set_string_value(replaceInvalidCharacters(field.second.string_value())); |
76 | 8 | } |
77 | 199 | } |
78 | 207 | } |
79 | 10.5k | return processed; |
80 | 10.5k | } |
81 | | |
82 | | // Convert from test proto Headers to a variant of TestHeaderMapImpl. Validate proto if you intend |
83 | | // to sanitize for invalid header characters. |
84 | | template <class T> |
85 | | inline T fromHeaders( |
86 | | const test::fuzz::Headers& headers, |
87 | | const absl::node_hash_set<std::string>& ignore_headers = absl::node_hash_set<std::string>(), |
88 | 358k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { |
89 | 358k | T header_map; |
90 | 747k | for (const auto& header : headers.headers()) { |
91 | 747k | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { |
92 | 747k | header_map.addCopy(header.key(), header.value()); |
93 | 747k | } |
94 | 747k | include_headers.erase(absl::AsciiStrToLower(header.key())); |
95 | 747k | } |
96 | | // Add dummy headers for non-present headers that must be included. |
97 | 458k | for (const auto& header : include_headers) { |
98 | 458k | header_map.addCopy(header, "dummy"); |
99 | 458k | } |
100 | 358k | return header_map; |
101 | 358k | } Envoy::Http::TestResponseHeaderMapImpl Envoy::Fuzz::fromHeaders<Envoy::Http::TestResponseHeaderMapImpl>(test::fuzz::Headers const&, absl::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::container_internal::StringHash, absl::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, absl::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::container_internal::StringHash, absl::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >) Line | Count | Source | 88 | 24.4k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { | 89 | 24.4k | T header_map; | 90 | 140k | for (const auto& header : headers.headers()) { | 91 | 140k | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { | 92 | 140k | header_map.addCopy(header.key(), header.value()); | 93 | 140k | } | 94 | 140k | include_headers.erase(absl::AsciiStrToLower(header.key())); | 95 | 140k | } | 96 | | // Add dummy headers for non-present headers that must be included. | 97 | 24.4k | for (const auto& header : include_headers) { | 98 | 0 | header_map.addCopy(header, "dummy"); | 99 | 0 | } | 100 | 24.4k | return header_map; | 101 | 24.4k | } |
Envoy::Http::TestResponseTrailerMapImpl Envoy::Fuzz::fromHeaders<Envoy::Http::TestResponseTrailerMapImpl>(test::fuzz::Headers const&, absl::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::container_internal::StringHash, absl::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, absl::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::container_internal::StringHash, absl::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >) Line | Count | Source | 88 | 4.26k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { | 89 | 4.26k | T header_map; | 90 | 13.4k | for (const auto& header : headers.headers()) { | 91 | 13.4k | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { | 92 | 13.4k | header_map.addCopy(header.key(), header.value()); | 93 | 13.4k | } | 94 | 13.4k | include_headers.erase(absl::AsciiStrToLower(header.key())); | 95 | 13.4k | } | 96 | | // Add dummy headers for non-present headers that must be included. | 97 | 4.26k | for (const auto& header : include_headers) { | 98 | 0 | header_map.addCopy(header, "dummy"); | 99 | 0 | } | 100 | 4.26k | return header_map; | 101 | 4.26k | } |
Envoy::Http::TestHeaderMapImplBase<Envoy::Http::RequestTrailerMap, Envoy::Http::RequestTrailerMapImpl> Envoy::Fuzz::fromHeaders<Envoy::Http::TestHeaderMapImplBase<Envoy::Http::RequestTrailerMap, Envoy::Http::RequestTrailerMapImpl> >(test::fuzz::Headers const&, absl::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::container_internal::StringHash, absl::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, absl::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::container_internal::StringHash, absl::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >) Line | Count | Source | 88 | 4.53k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { | 89 | 4.53k | T header_map; | 90 | 8.46k | for (const auto& header : headers.headers()) { | 91 | 8.46k | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { | 92 | 8.46k | header_map.addCopy(header.key(), header.value()); | 93 | 8.46k | } | 94 | 8.46k | include_headers.erase(absl::AsciiStrToLower(header.key())); | 95 | 8.46k | } | 96 | | // Add dummy headers for non-present headers that must be included. | 97 | 4.53k | for (const auto& header : include_headers) { | 98 | 0 | header_map.addCopy(header, "dummy"); | 99 | 0 | } | 100 | 4.53k | return header_map; | 101 | 4.53k | } |
Envoy::Http::TestRequestHeaderMapImpl Envoy::Fuzz::fromHeaders<Envoy::Http::TestRequestHeaderMapImpl>(test::fuzz::Headers const&, absl::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::container_internal::StringHash, absl::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, absl::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::container_internal::StringHash, absl::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >) Line | Count | Source | 88 | 325k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { | 89 | 325k | T header_map; | 90 | 584k | for (const auto& header : headers.headers()) { | 91 | 584k | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { | 92 | 584k | header_map.addCopy(header.key(), header.value()); | 93 | 584k | } | 94 | 584k | include_headers.erase(absl::AsciiStrToLower(header.key())); | 95 | 584k | } | 96 | | // Add dummy headers for non-present headers that must be included. | 97 | 458k | for (const auto& header : include_headers) { | 98 | 458k | header_map.addCopy(header, "dummy"); | 99 | 458k | } | 100 | 325k | return header_map; | 101 | 325k | } |
|
102 | | |
103 | | // Convert from test proto Metadata to MetadataMap |
104 | 10.7k | inline Http::MetadataMapVector fromMetadata(const test::fuzz::Metadata& metadata) { |
105 | 10.7k | Http::MetadataMapVector metadata_map_vector; |
106 | 10.7k | if (!metadata.metadata().empty()) { |
107 | 8.55k | Http::MetadataMap metadata_map; |
108 | 8.55k | Http::MetadataMapPtr metadata_map_ptr = std::make_unique<Http::MetadataMap>(metadata_map); |
109 | 14.0k | for (const auto& pair : metadata.metadata()) { |
110 | 14.0k | metadata_map_ptr->insert(pair); |
111 | 14.0k | } |
112 | 8.55k | metadata_map_vector.push_back(std::move(metadata_map_ptr)); |
113 | 8.55k | } |
114 | 10.7k | return metadata_map_vector; |
115 | 10.7k | } |
116 | | |
117 | | // Convert from HeaderMap to test proto Headers. |
118 | 0 | inline test::fuzz::Headers toHeaders(const Http::HeaderMap& headers) { |
119 | 0 | test::fuzz::Headers fuzz_headers; |
120 | 0 | headers.iterate([&fuzz_headers](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { |
121 | 0 | auto* fuzz_header = fuzz_headers.add_headers(); |
122 | 0 | fuzz_header->set_key(std::string(header.key().getStringView())); |
123 | 0 | fuzz_header->set_value(std::string(header.value().getStringView())); |
124 | 0 | return Http::HeaderMap::Iterate::Continue; |
125 | 0 | }); |
126 | 0 | return fuzz_headers; |
127 | 0 | } |
128 | | |
129 | | const std::string TestSubjectPeer = |
130 | | "CN=Test Server,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US"; |
131 | | |
132 | | inline std::unique_ptr<TestStreamInfo> fromStreamInfo(const test::fuzz::StreamInfo& stream_info, |
133 | 10.5k | TimeSource& time_source) { |
134 | | // Set mocks' default string return value to be an empty string. |
135 | | // TODO(asraa): Speed up this function, which is slowed because of the use of mocks. |
136 | 10.5k | testing::DefaultValue<const std::string&>::Set(EMPTY_STRING); |
137 | 10.5k | auto test_stream_info = std::make_unique<TestStreamInfo>(time_source); |
138 | 10.5k | test_stream_info->metadata_ = stream_info.dynamic_metadata(); |
139 | | // Truncate recursive filter metadata fields. |
140 | | // TODO(asraa): Resolve MessageToJsonString failure on recursive filter metadata. |
141 | 10.5k | for (auto& pair : *test_stream_info->metadata_.mutable_filter_metadata()) { |
142 | 493 | std::string value; |
143 | 493 | pair.second.SerializeToString(&value); |
144 | 493 | pair.second.ParseFromString(value.substr(0, 128)); |
145 | 493 | } |
146 | | // libc++ clocks don't track at nanosecond on macOS. |
147 | 10.5k | const auto start_time = |
148 | 10.5k | static_cast<uint64_t>(std::numeric_limits<std::chrono::nanoseconds::rep>::max()) < |
149 | 10.5k | stream_info.start_time() |
150 | 10.5k | ? 0 |
151 | 10.5k | : stream_info.start_time() / 1000; |
152 | 10.5k | test_stream_info->start_time_ = SystemTime(std::chrono::microseconds(start_time)); |
153 | 10.5k | if (stream_info.has_response_code()) { |
154 | 237 | test_stream_info->setResponseCode(stream_info.response_code().value()); |
155 | 237 | } |
156 | 10.5k | auto upstream_host = std::make_shared<NiceMock<Upstream::MockHostDescription>>(); |
157 | 10.5k | auto upstream_metadata = std::make_shared<envoy::config::core::v3::Metadata>( |
158 | 10.5k | replaceInvalidStringValues(stream_info.upstream_metadata())); |
159 | 10.5k | ON_CALL(*upstream_host, metadata()).WillByDefault(testing::Return(upstream_metadata)); |
160 | 10.5k | test_stream_info->setUpstreamInfo(std::make_shared<StreamInfo::UpstreamInfoImpl>()); |
161 | 10.5k | test_stream_info->upstreamInfo()->setUpstreamHost(upstream_host); |
162 | 10.5k | Envoy::Network::Address::InstanceConstSharedPtr address; |
163 | 10.5k | if (stream_info.has_address()) { |
164 | 293 | if (stream_info.address().address_case() == |
165 | 293 | envoy::config::core::v3::Address::AddressCase::kEnvoyInternalAddress && |
166 | 293 | stream_info.address().envoy_internal_address().address_name_specifier_case() == |
167 | 60 | envoy::config::core::v3::EnvoyInternalAddress::AddressNameSpecifierCase:: |
168 | 60 | kServerListenerName) { |
169 | 60 | address = std::make_shared<Envoy::Network::Address::EnvoyInternalInstance>( |
170 | 60 | replaceInvalidHostCharacters( |
171 | 60 | stream_info.address().envoy_internal_address().server_listener_name())); |
172 | 233 | } else { |
173 | 233 | address = Envoy::Network::Address::resolveProtoAddress(stream_info.address()); |
174 | 233 | } |
175 | 10.2k | } else { |
176 | 10.2k | address = Network::Utility::resolveUrl("tcp://10.0.0.1:443"); |
177 | 10.2k | } |
178 | 10.5k | auto upstream_local_address = |
179 | 10.5k | stream_info.has_upstream_local_address() |
180 | 10.5k | ? Envoy::Network::Address::resolveProtoAddress(stream_info.upstream_local_address()) |
181 | 10.5k | : Network::Utility::resolveUrl("tcp://10.0.0.1:10000"); |
182 | 10.5k | test_stream_info->upstreamInfo()->setUpstreamLocalAddress(upstream_local_address); |
183 | 10.5k | test_stream_info->downstream_connection_info_provider_ = |
184 | 10.5k | std::make_shared<Network::ConnectionInfoSetterImpl>(address, address); |
185 | 10.5k | test_stream_info->downstream_connection_info_provider_->setRequestedServerName( |
186 | 10.5k | stream_info.requested_server_name()); |
187 | 10.5k | auto connection_info = std::make_shared<NiceMock<Ssl::MockConnectionInfo>>(); |
188 | 10.5k | ON_CALL(*connection_info, subjectPeerCertificate()) |
189 | 10.5k | .WillByDefault(testing::ReturnRef(TestSubjectPeer)); |
190 | 10.5k | test_stream_info->downstream_connection_info_provider_->setSslConnection(connection_info); |
191 | 10.5k | return test_stream_info; |
192 | 10.5k | } |
193 | | |
194 | | // Parses http or proto body into chunks. |
195 | 12.1k | inline std::vector<std::string> parseHttpData(const test::fuzz::HttpData& data) { |
196 | 12.1k | std::vector<std::string> data_chunks; |
197 | | |
198 | 12.1k | if (data.has_http_body()) { |
199 | 1.10k | data_chunks.reserve(data.http_body().data_size()); |
200 | 2.21k | for (const std::string& http_data : data.http_body().data()) { |
201 | 2.21k | data_chunks.push_back(http_data); |
202 | 2.21k | } |
203 | 11.0k | } else if (data.has_proto_body()) { |
204 | 761 | const std::string serialized = data.proto_body().message().value(); |
205 | 761 | data_chunks = absl::StrSplit(serialized, absl::ByLength(data.proto_body().chunk_size())); |
206 | 761 | } |
207 | | |
208 | 12.1k | return data_chunks; |
209 | 12.1k | } |
210 | | |
211 | | } // namespace Fuzz |
212 | | } // namespace Envoy |