/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 | 10.4M | #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 | 103k | inline std::string replaceInvalidCharacters(absl::string_view string) { |
33 | 103k | std::string filtered; |
34 | 103k | filtered.reserve(string.length()); |
35 | 19.0M | for (const char& c : string) { |
36 | 19.0M | switch (c) { |
37 | 908 | case '\0': |
38 | 908 | FALLTHRU; |
39 | 909 | case '\r': |
40 | 909 | FALLTHRU; |
41 | 3.40k | case '\n': |
42 | 3.40k | filtered.push_back(' '); |
43 | 3.40k | break; |
44 | 19.0M | default: |
45 | 19.0M | filtered.push_back(c); |
46 | 19.0M | } |
47 | 19.0M | } |
48 | 103k | return filtered; |
49 | 103k | } |
50 | | |
51 | | // Replace invalid host characters. |
52 | 201 | inline std::string replaceInvalidHostCharacters(absl::string_view string) { |
53 | 201 | std::string filtered; |
54 | 201 | filtered.reserve(string.length()); |
55 | 926k | for (const char& c : string) { |
56 | 926k | if (http2::adapter::HeaderValidator::IsValidAuthority(absl::string_view(&c, 1))) { |
57 | 408k | filtered.push_back(c); |
58 | 517k | } else { |
59 | 517k | filtered.push_back('0'); |
60 | 517k | } |
61 | 926k | } |
62 | 201 | return filtered; |
63 | 201 | } |
64 | | |
65 | | inline envoy::config::core::v3::Metadata |
66 | 24.0k | replaceInvalidStringValues(const envoy::config::core::v3::Metadata& upstream_metadata) { |
67 | 24.0k | envoy::config::core::v3::Metadata processed = upstream_metadata; |
68 | 24.0k | 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 | 69 | for (auto& field : *metadata_struct.second.mutable_fields()) { |
74 | 69 | if (field.second.kind_case() == ProtobufWkt::Value::kStringValue) { |
75 | 1 | field.second.set_string_value(replaceInvalidCharacters(field.second.string_value())); |
76 | 1 | } |
77 | 69 | } |
78 | 57 | } |
79 | 24.0k | return processed; |
80 | 24.0k | } |
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 | 436k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { |
89 | 436k | T header_map; |
90 | 1.08M | for (const auto& header : headers.headers()) { |
91 | 1.08M | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { |
92 | 1.08M | header_map.addCopy(header.key(), header.value()); |
93 | 1.08M | } |
94 | 1.08M | include_headers.erase(absl::AsciiStrToLower(header.key())); |
95 | 1.08M | } |
96 | | // Add dummy headers for non-present headers that must be included. |
97 | 452k | for (const auto& header : include_headers) { |
98 | 452k | header_map.addCopy(header, "dummy"); |
99 | 452k | } |
100 | 436k | return header_map; |
101 | 436k | } Envoy::Http::TestRequestHeaderMapImpl Envoy::Fuzz::fromHeaders<Envoy::Http::TestRequestHeaderMapImpl>(test::fuzz::Headers const&, absl::lts_20230802::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::lts_20230802::container_internal::StringHash, absl::lts_20230802::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, absl::lts_20230802::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::lts_20230802::container_internal::StringHash, absl::lts_20230802::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >) Line | Count | Source | 88 | 364k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { | 89 | 364k | T header_map; | 90 | 704k | for (const auto& header : headers.headers()) { | 91 | 704k | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { | 92 | 704k | header_map.addCopy(header.key(), header.value()); | 93 | 704k | } | 94 | 704k | include_headers.erase(absl::AsciiStrToLower(header.key())); | 95 | 704k | } | 96 | | // Add dummy headers for non-present headers that must be included. | 97 | 452k | for (const auto& header : include_headers) { | 98 | 452k | header_map.addCopy(header, "dummy"); | 99 | 452k | } | 100 | 364k | return header_map; | 101 | 364k | } |
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::lts_20230802::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::lts_20230802::container_internal::StringHash, absl::lts_20230802::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, absl::lts_20230802::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::lts_20230802::container_internal::StringHash, absl::lts_20230802::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >) Line | Count | Source | 88 | 6.39k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { | 89 | 6.39k | T header_map; | 90 | 60.1k | for (const auto& header : headers.headers()) { | 91 | 60.1k | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { | 92 | 60.1k | header_map.addCopy(header.key(), header.value()); | 93 | 60.1k | } | 94 | 60.1k | include_headers.erase(absl::AsciiStrToLower(header.key())); | 95 | 60.1k | } | 96 | | // Add dummy headers for non-present headers that must be included. | 97 | 6.39k | for (const auto& header : include_headers) { | 98 | 0 | header_map.addCopy(header, "dummy"); | 99 | 0 | } | 100 | 6.39k | return header_map; | 101 | 6.39k | } |
Envoy::Http::TestResponseHeaderMapImpl Envoy::Fuzz::fromHeaders<Envoy::Http::TestResponseHeaderMapImpl>(test::fuzz::Headers const&, absl::lts_20230802::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::lts_20230802::container_internal::StringHash, absl::lts_20230802::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, absl::lts_20230802::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::lts_20230802::container_internal::StringHash, absl::lts_20230802::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >) Line | Count | Source | 88 | 42.7k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { | 89 | 42.7k | T header_map; | 90 | 310k | for (const auto& header : headers.headers()) { | 91 | 310k | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { | 92 | 310k | header_map.addCopy(header.key(), header.value()); | 93 | 310k | } | 94 | 310k | include_headers.erase(absl::AsciiStrToLower(header.key())); | 95 | 310k | } | 96 | | // Add dummy headers for non-present headers that must be included. | 97 | 42.7k | for (const auto& header : include_headers) { | 98 | 0 | header_map.addCopy(header, "dummy"); | 99 | 0 | } | 100 | 42.7k | return header_map; | 101 | 42.7k | } |
Envoy::Http::TestResponseTrailerMapImpl Envoy::Fuzz::fromHeaders<Envoy::Http::TestResponseTrailerMapImpl>(test::fuzz::Headers const&, absl::lts_20230802::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::lts_20230802::container_internal::StringHash, absl::lts_20230802::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&, absl::lts_20230802::node_hash_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, absl::lts_20230802::container_internal::StringHash, absl::lts_20230802::container_internal::StringEq, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >) Line | Count | Source | 88 | 22.4k | absl::node_hash_set<std::string> include_headers = absl::node_hash_set<std::string>()) { | 89 | 22.4k | T header_map; | 90 | 22.4k | for (const auto& header : headers.headers()) { | 91 | 11.3k | if (ignore_headers.find(absl::AsciiStrToLower(header.key())) == ignore_headers.end()) { | 92 | 11.3k | header_map.addCopy(header.key(), header.value()); | 93 | 11.3k | } | 94 | 11.3k | include_headers.erase(absl::AsciiStrToLower(header.key())); | 95 | 11.3k | } | 96 | | // Add dummy headers for non-present headers that must be included. | 97 | 22.4k | for (const auto& header : include_headers) { | 98 | 0 | header_map.addCopy(header, "dummy"); | 99 | 0 | } | 100 | 22.4k | return header_map; | 101 | 22.4k | } |
|
102 | | |
103 | | // Convert from test proto Metadata to MetadataMap |
104 | 8.16k | inline Http::MetadataMapVector fromMetadata(const test::fuzz::Metadata& metadata) { |
105 | 8.16k | Http::MetadataMapVector metadata_map_vector; |
106 | 8.16k | if (!metadata.metadata().empty()) { |
107 | 7.10k | Http::MetadataMap metadata_map; |
108 | 7.10k | Http::MetadataMapPtr metadata_map_ptr = std::make_unique<Http::MetadataMap>(metadata_map); |
109 | 15.5k | for (const auto& pair : metadata.metadata()) { |
110 | 15.5k | metadata_map_ptr->insert(pair); |
111 | 15.5k | } |
112 | 7.10k | metadata_map_vector.push_back(std::move(metadata_map_ptr)); |
113 | 7.10k | } |
114 | 8.16k | return metadata_map_vector; |
115 | 8.16k | } |
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 | 24.0k | 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 | 24.0k | testing::DefaultValue<const std::string&>::Set(EMPTY_STRING); |
137 | 24.0k | auto test_stream_info = std::make_unique<TestStreamInfo>(time_source); |
138 | 24.0k | 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 | 24.0k | for (auto& pair : *test_stream_info->metadata_.mutable_filter_metadata()) { |
142 | 280 | std::string value; |
143 | 280 | pair.second.SerializeToString(&value); |
144 | 280 | pair.second.ParseFromString(value.substr(0, 128)); |
145 | 280 | } |
146 | | // libc++ clocks don't track at nanosecond on macOS. |
147 | 24.0k | const auto start_time = |
148 | 24.0k | static_cast<uint64_t>(std::numeric_limits<std::chrono::nanoseconds::rep>::max()) < |
149 | 24.0k | stream_info.start_time() |
150 | 24.0k | ? 0 |
151 | 24.0k | : stream_info.start_time() / 1000; |
152 | 24.0k | test_stream_info->start_time_ = SystemTime(std::chrono::microseconds(start_time)); |
153 | 24.0k | if (stream_info.has_response_code()) { |
154 | 336 | test_stream_info->setResponseCode(stream_info.response_code().value()); |
155 | 336 | } |
156 | 24.0k | auto upstream_host = std::make_shared<NiceMock<Upstream::MockHostDescription>>(); |
157 | 24.0k | auto upstream_metadata = std::make_shared<envoy::config::core::v3::Metadata>( |
158 | 24.0k | replaceInvalidStringValues(stream_info.upstream_metadata())); |
159 | 24.0k | ON_CALL(*upstream_host, metadata()).WillByDefault(testing::Return(upstream_metadata)); |
160 | 24.0k | test_stream_info->setUpstreamInfo(std::make_shared<StreamInfo::UpstreamInfoImpl>()); |
161 | 24.0k | test_stream_info->upstreamInfo()->setUpstreamHost(upstream_host); |
162 | 24.0k | Envoy::Network::Address::InstanceConstSharedPtr address; |
163 | 24.0k | if (stream_info.has_address()) { |
164 | 526 | if (stream_info.address().address_case() == |
165 | 526 | envoy::config::core::v3::Address::AddressCase::kEnvoyInternalAddress && |
166 | 526 | stream_info.address().envoy_internal_address().address_name_specifier_case() == |
167 | 127 | envoy::config::core::v3::EnvoyInternalAddress::AddressNameSpecifierCase:: |
168 | 127 | kServerListenerName) { |
169 | 127 | address = std::make_shared<Envoy::Network::Address::EnvoyInternalInstance>( |
170 | 127 | replaceInvalidHostCharacters( |
171 | 127 | stream_info.address().envoy_internal_address().server_listener_name())); |
172 | 399 | } else { |
173 | 399 | auto address_or_error = Envoy::Network::Address::resolveProtoAddress(stream_info.address()); |
174 | 399 | THROW_IF_NOT_OK_REF(address_or_error.status()); |
175 | 190 | address = address_or_error.value(); |
176 | 190 | } |
177 | 23.5k | } else { |
178 | 23.5k | address = *Network::Utility::resolveUrl("tcp://10.0.0.1:443"); |
179 | 23.5k | } |
180 | 23.8k | Envoy::Network::Address::InstanceConstSharedPtr upstream_local_address; |
181 | 23.8k | if (stream_info.has_upstream_local_address()) { |
182 | 175 | auto upstream_local_address_or_error = |
183 | 175 | Envoy::Network::Address::resolveProtoAddress(stream_info.upstream_local_address()); |
184 | 175 | THROW_IF_NOT_OK_REF(upstream_local_address_or_error.status()); |
185 | 126 | upstream_local_address = upstream_local_address_or_error.value(); |
186 | 23.6k | } else { |
187 | 23.6k | upstream_local_address = *Network::Utility::resolveUrl("tcp://10.0.0.1:10000"); |
188 | 23.6k | } |
189 | 23.8k | test_stream_info->upstreamInfo()->setUpstreamLocalAddress(upstream_local_address); |
190 | 23.8k | test_stream_info->downstream_connection_info_provider_ = |
191 | 23.8k | std::make_shared<Network::ConnectionInfoSetterImpl>(address, address); |
192 | 23.8k | test_stream_info->downstream_connection_info_provider_->setRequestedServerName( |
193 | 23.8k | stream_info.requested_server_name()); |
194 | 23.8k | auto connection_info = std::make_shared<NiceMock<Ssl::MockConnectionInfo>>(); |
195 | 23.8k | ON_CALL(*connection_info, subjectPeerCertificate()) |
196 | 23.8k | .WillByDefault(testing::ReturnRef(TestSubjectPeer)); |
197 | 23.8k | test_stream_info->downstream_connection_info_provider_->setSslConnection(connection_info); |
198 | 23.8k | return test_stream_info; |
199 | 23.8k | } |
200 | | |
201 | | // Parses http or proto body into chunks. |
202 | 21.7k | inline std::vector<std::string> parseHttpData(const test::fuzz::HttpData& data) { |
203 | 21.7k | std::vector<std::string> data_chunks; |
204 | | |
205 | 21.7k | if (data.has_http_body()) { |
206 | 1.43k | data_chunks.reserve(data.http_body().data_size()); |
207 | 2.50k | for (const std::string& http_data : data.http_body().data()) { |
208 | 2.50k | data_chunks.push_back(http_data); |
209 | 2.50k | } |
210 | 20.3k | } else if (data.has_proto_body()) { |
211 | 720 | const std::string serialized = data.proto_body().message().value(); |
212 | 720 | data_chunks = absl::StrSplit(serialized, absl::ByLength(data.proto_body().chunk_size())); |
213 | 720 | } |
214 | | |
215 | 21.7k | return data_chunks; |
216 | 21.7k | } |
217 | | |
218 | | } // namespace Fuzz |
219 | | } // namespace Envoy |