/proc/self/cwd/test/common/http/codec_impl_fuzz_test.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "envoy/stats/scope.h" |
2 | | |
3 | | // Fuzzer for the H1/H2 codecs. This is similar in structure to |
4 | | // //test/common/http/http2:codec_impl_test, where a client H2 codec is wired |
5 | | // via shared memory to a server H2 codec and stream actions are applied. We |
6 | | // fuzz the various client/server H1/H2 codec API operations and in addition |
7 | | // apply fuzzing at the wire level by modeling explicit mutation, reordering and |
8 | | // drain operations on the connection buffers between client and server. |
9 | | |
10 | | #include <functional> |
11 | | |
12 | | #include "source/common/common/assert.h" |
13 | | #include "source/common/common/logger.h" |
14 | | #include "source/common/http/exception.h" |
15 | | #include "source/common/http/header_map_impl.h" |
16 | | #include "source/common/http/http1/codec_impl.h" |
17 | | #include "source/common/http/http2/codec_impl.h" |
18 | | #include "source/common/http/conn_manager_utility.h" |
19 | | |
20 | | #include "test/common/http/codec_impl_fuzz.pb.validate.h" |
21 | | #include "test/common/http/http2/codec_impl_test_util.h" |
22 | | #include "test/fuzz/fuzz_runner.h" |
23 | | #include "test/fuzz/utility.h" |
24 | | #include "test/mocks/http/mocks.h" |
25 | | #include "test/mocks/network/mocks.h" |
26 | | #include "test/mocks/server/overload_manager.h" |
27 | | #include "test/test_common/test_runtime.h" |
28 | | |
29 | | #include "gmock/gmock.h" |
30 | | |
31 | | #include "quiche/common/platform/api/quiche_logging.h" |
32 | | |
33 | | using testing::_; |
34 | | using testing::Invoke; |
35 | | using testing::InvokeWithoutArgs; |
36 | | |
37 | | namespace Envoy { |
38 | | namespace Http { |
39 | | |
40 | | namespace Http2Utility = ::Envoy::Http2::Utility; |
41 | | |
42 | | // Force drain on each action, useful for figuring out what is going on when |
43 | | // debugging. |
44 | | constexpr bool DebugMode = false; |
45 | | |
46 | 17.9k | template <class T> T fromSanitizedHeaders(const test::fuzz::Headers& headers) { |
47 | 17.9k | return Fuzz::fromHeaders<T>(headers, {"transfer-encoding"}); |
48 | 17.9k | } Envoy::Http::TestResponseHeaderMapImpl Envoy::Http::fromSanitizedHeaders<Envoy::Http::TestResponseHeaderMapImpl>(test::fuzz::Headers const&) Line | Count | Source | 46 | 14.8k | template <class T> T fromSanitizedHeaders(const test::fuzz::Headers& headers) { | 47 | 14.8k | return Fuzz::fromHeaders<T>(headers, {"transfer-encoding"}); | 48 | 14.8k | } |
Envoy::Http::TestResponseTrailerMapImpl Envoy::Http::fromSanitizedHeaders<Envoy::Http::TestResponseTrailerMapImpl>(test::fuzz::Headers const&) Line | Count | Source | 46 | 128 | template <class T> T fromSanitizedHeaders(const test::fuzz::Headers& headers) { | 47 | 128 | return Fuzz::fromHeaders<T>(headers, {"transfer-encoding"}); | 48 | 128 | } |
Envoy::Http::TestHeaderMapImplBase<Envoy::Http::RequestTrailerMap, Envoy::Http::RequestTrailerMapImpl> Envoy::Http::fromSanitizedHeaders<Envoy::Http::TestHeaderMapImplBase<Envoy::Http::RequestTrailerMap, Envoy::Http::RequestTrailerMapImpl> >(test::fuzz::Headers const&) Line | Count | Source | 46 | 2.96k | template <class T> T fromSanitizedHeaders(const test::fuzz::Headers& headers) { | 47 | 2.96k | return Fuzz::fromHeaders<T>(headers, {"transfer-encoding"}); | 48 | 2.96k | } |
|
49 | | |
50 | | // Template specialization for TestRequestHeaderMapImpl to include a Host header. This guards |
51 | | // against missing host headers in CONNECT requests that would have failed parsing on ingress. |
52 | | // TODO(#10878): When proper error handling is introduced for non-dispatching codec calls, remove |
53 | | // this and fail gracefully. |
54 | | template <> |
55 | | TestRequestHeaderMapImpl |
56 | 109k | fromSanitizedHeaders<TestRequestHeaderMapImpl>(const test::fuzz::Headers& headers) { |
57 | 109k | return Fuzz::fromHeaders<TestRequestHeaderMapImpl>(headers, {"transfer-encoding"}, |
58 | 109k | {":authority", ":method", ":path"}); |
59 | 109k | } |
60 | | |
61 | | // Convert from test proto Http1ServerSettings to Http1Settings. |
62 | 6.49k | Http1Settings fromHttp1Settings(const test::common::http::Http1ServerSettings& settings) { |
63 | 6.49k | Http1Settings h1_settings; |
64 | | |
65 | 6.49k | h1_settings.allow_absolute_url_ = settings.allow_absolute_url(); |
66 | 6.49k | h1_settings.accept_http_10_ = settings.accept_http_10(); |
67 | 6.49k | h1_settings.default_host_for_http_10_ = settings.default_host_for_http_10(); |
68 | | |
69 | | // If the server accepts a HTTP/1.0 then the default host must be valid. |
70 | 6.49k | if (h1_settings.accept_http_10_ && |
71 | 6.49k | !HeaderUtility::authorityIsValid(h1_settings.default_host_for_http_10_)) { |
72 | 9 | throw EnvoyException("Invalid Http1ServerSettings, HTTP/1.0 is enabled and " |
73 | 9 | "'default_host_for_http_10' has invalid hostname, skipping test."); |
74 | 9 | } |
75 | 6.48k | return h1_settings; |
76 | 6.49k | } |
77 | | |
78 | | envoy::config::core::v3::Http2ProtocolOptions |
79 | 42.4k | fromHttp2Settings(const test::common::http::Http2Settings& settings) { |
80 | 42.4k | envoy::config::core::v3::Http2ProtocolOptions options( |
81 | 42.4k | ::Envoy::Http2::Utility::initializeAndValidateOptions( |
82 | 42.4k | envoy::config::core::v3::Http2ProtocolOptions())); |
83 | | // We apply an offset and modulo interpretation to settings to ensure that |
84 | | // they are valid. Rejecting invalid settings is orthogonal to the fuzzed |
85 | | // code. |
86 | 42.4k | options.mutable_hpack_table_size()->set_value(settings.hpack_table_size()); |
87 | 42.4k | options.mutable_max_concurrent_streams()->set_value( |
88 | 42.4k | Http2Utility::OptionsLimits::MIN_MAX_CONCURRENT_STREAMS + |
89 | 42.4k | settings.max_concurrent_streams() % |
90 | 42.4k | (1 + Http2Utility::OptionsLimits::MAX_MAX_CONCURRENT_STREAMS - |
91 | 42.4k | Http2Utility::OptionsLimits::MIN_MAX_CONCURRENT_STREAMS)); |
92 | 42.4k | options.mutable_initial_stream_window_size()->set_value( |
93 | 42.4k | Http2Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE + |
94 | 42.4k | settings.initial_stream_window_size() % |
95 | 42.4k | (1 + Http2Utility::OptionsLimits::MAX_INITIAL_STREAM_WINDOW_SIZE - |
96 | 42.4k | Http2Utility::OptionsLimits::MIN_INITIAL_STREAM_WINDOW_SIZE)); |
97 | 42.4k | options.mutable_initial_connection_window_size()->set_value( |
98 | 42.4k | Http2Utility::OptionsLimits::MIN_INITIAL_CONNECTION_WINDOW_SIZE + |
99 | 42.4k | settings.initial_connection_window_size() % |
100 | 42.4k | (1 + Http2Utility::OptionsLimits::MAX_INITIAL_CONNECTION_WINDOW_SIZE - |
101 | 42.4k | Http2Utility::OptionsLimits::MIN_INITIAL_CONNECTION_WINDOW_SIZE)); |
102 | 42.4k | options.set_allow_metadata(true); |
103 | 42.4k | return options; |
104 | 42.4k | } |
105 | | |
106 | | using StreamResetCallbackFn = std::function<void()>; |
107 | | |
108 | | // Internal representation of stream state. Encapsulates the stream state, mocks |
109 | | // and encoders for both the request/response. |
110 | | class HttpStream : public LinkedObject<HttpStream> { |
111 | | public: |
112 | | // We track stream state here to prevent illegal operations, e.g. applying an |
113 | | // encodeData() to the codec after encodeTrailers(). This is necessary to |
114 | | // maintain the preconditions for operations on the codec at the API level. Of |
115 | | // course, it's the codecs must be robust to wire-level violations. We |
116 | | // explore these violations via MutateAction and SwapAction at the connection |
117 | | // buffer level. |
118 | | enum class StreamState : int { PendingHeaders, PendingDataOrTrailers, Closed }; |
119 | 0 | static absl::string_view streamStateToString(StreamState state) { |
120 | 0 | static std::array<std::string, 3> stream_state_strings = {"PendingHeaders", |
121 | 0 | "PendingDataOrTrailers", "Closed"}; |
122 | 0 | return stream_state_strings[static_cast<int>(state)]; |
123 | 0 | } |
124 | | |
125 | | struct DirectionalState { |
126 | | // TODO(mattklein123): Split this more clearly into request and response directional state. |
127 | | RequestEncoder* request_encoder_; |
128 | | ResponseEncoder* response_encoder_; |
129 | | TestRequestHeaderMapImpl request_headers_; |
130 | | NiceMock<MockResponseDecoder> response_decoder_; |
131 | | NiceMock<MockRequestDecoder> request_decoder_; |
132 | | NiceMock<MockStreamCallbacks> stream_callbacks_; |
133 | | StreamState stream_state_; |
134 | | bool local_closed_{false}; |
135 | | bool remote_closed_{false}; |
136 | | uint32_t read_disable_count_{}; |
137 | | bool created_schedulable_callback_{false}; |
138 | | |
139 | 51.0k | bool isLocalOpen() const { return !local_closed_; } |
140 | | |
141 | 58.5k | void closeLocal() { |
142 | 58.5k | local_closed_ = true; |
143 | 58.5k | if (local_closed_ && remote_closed_) { |
144 | 3.28k | stream_state_ = StreamState::Closed; |
145 | 3.28k | } |
146 | 58.5k | } |
147 | | |
148 | 53.4k | void closeRemote() { |
149 | 53.4k | remote_closed_ = true; |
150 | 53.4k | if (local_closed_ && remote_closed_) { |
151 | 53.4k | stream_state_ = StreamState::Closed; |
152 | 53.4k | } |
153 | 53.4k | } |
154 | | |
155 | 2.74k | void closeLocalAndRemote() { |
156 | 2.74k | remote_closed_ = true; |
157 | 2.74k | local_closed_ = true; |
158 | 2.74k | stream_state_ = StreamState::Closed; |
159 | 2.74k | } |
160 | | |
161 | | } request_, response_; |
162 | | |
163 | | // Encapsulates configuration, connections information used in the HttpStream. |
164 | | struct ConnectionContext { |
165 | | MockConnectionManagerConfig* conn_manager_config_; |
166 | | NiceMock<Network::MockConnection>& server_connection_; |
167 | | NiceMock<Network::MockConnection>& client_connection_; |
168 | | |
169 | | ConnectionContext(MockConnectionManagerConfig* conn_manager_config, |
170 | | NiceMock<Network::MockConnection>& server_connection, |
171 | | NiceMock<Network::MockConnection>& client_connection) |
172 | | : conn_manager_config_(conn_manager_config), server_connection_(server_connection), |
173 | 24.4k | client_connection_(client_connection) {} |
174 | | }; |
175 | | |
176 | | HttpStream(ClientConnection& client, const TestRequestHeaderMapImpl& request_headers, |
177 | | bool end_stream, StreamResetCallbackFn stream_reset_callback, |
178 | | ConnectionContext& context) |
179 | | : http_protocol_(client.protocol()), stream_reset_callback_(stream_reset_callback), |
180 | 109k | context_(context) { |
181 | 109k | request_.request_encoder_ = &client.newStream(response_.response_decoder_); |
182 | | |
183 | 109k | ON_CALL(request_.stream_callbacks_, onResetStream(_, _)) |
184 | 109k | .WillByDefault(InvokeWithoutArgs([this] { |
185 | 22.9k | ENVOY_LOG_MISC(trace, "reset request for stream index {}", stream_index_); |
186 | 22.9k | resetStream(); |
187 | 22.9k | stream_reset_callback_(); |
188 | 22.9k | })); |
189 | 109k | ON_CALL(response_.stream_callbacks_, onResetStream(_, _)) |
190 | 109k | .WillByDefault(InvokeWithoutArgs([this] { |
191 | 2.75k | ENVOY_LOG_MISC(trace, "reset response for stream index {}", stream_index_); |
192 | | // Reset the client stream when we know the server stream has been reset. This ensures |
193 | | // that the internal book keeping resetStream() below is consistent with the state of the |
194 | | // client codec state, which is necessary to prevent multiple simultaneous streams for the |
195 | | // HTTP/1 codec. |
196 | 2.75k | if (response_.stream_state_ != StreamState::Closed) { |
197 | 2.52k | request_.request_encoder_->getStream().resetStream(StreamResetReason::LocalReset); |
198 | 2.52k | } |
199 | 2.75k | resetStream(); |
200 | 2.75k | stream_reset_callback_(); |
201 | 2.75k | })); |
202 | 109k | ON_CALL(request_.request_decoder_, decodeHeaders_(_, true)) |
203 | 109k | .WillByDefault(InvokeWithoutArgs([this] { |
204 | | // The HTTP/1 codec needs this to cleanup any latent stream resources. |
205 | 1.15k | response_.response_encoder_->getStream().resetStream(StreamResetReason::LocalReset); |
206 | 1.15k | request_.closeRemote(); |
207 | 1.15k | })); |
208 | 109k | ON_CALL(request_.request_decoder_, decodeData(_, true)).WillByDefault(InvokeWithoutArgs([this] { |
209 | | // The HTTP/1 codec needs this to cleanup any latent stream resources. |
210 | 746 | response_.response_encoder_->getStream().resetStream(StreamResetReason::LocalReset); |
211 | 746 | request_.closeRemote(); |
212 | 746 | })); |
213 | 109k | ON_CALL(request_.request_decoder_, decodeTrailers_(_)).WillByDefault(InvokeWithoutArgs([this] { |
214 | | // The HTTP/1 codec needs this to cleanup any latent stream resources. |
215 | 112 | response_.response_encoder_->getStream().resetStream(StreamResetReason::LocalReset); |
216 | 112 | request_.closeRemote(); |
217 | 112 | })); |
218 | 109k | ON_CALL(response_.response_decoder_, decodeHeaders_(_, true)) |
219 | 109k | .WillByDefault(InvokeWithoutArgs([this] { response_.closeLocalAndRemote(); })); |
220 | 109k | ON_CALL(response_.response_decoder_, decodeData(_, true)) |
221 | 109k | .WillByDefault(InvokeWithoutArgs([this] { response_.closeLocalAndRemote(); })); |
222 | 109k | ON_CALL(response_.response_decoder_, decodeTrailers_(_)) |
223 | 109k | .WillByDefault(InvokeWithoutArgs([this] { response_.closeLocalAndRemote(); })); |
224 | 109k | if (!end_stream) { |
225 | 90.6k | request_.request_encoder_->getStream().addCallbacks(request_.stream_callbacks_); |
226 | 90.6k | } |
227 | 109k | request_.request_headers_ = request_headers; |
228 | 109k | request_.request_encoder_->encodeHeaders(request_headers, end_stream).IgnoreError(); |
229 | 109k | request_.stream_state_ = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; |
230 | 109k | response_.stream_state_ = StreamState::PendingHeaders; |
231 | 109k | } |
232 | | |
233 | 25.7k | void resetStream() { |
234 | 25.7k | request_.closeLocal(); |
235 | 25.7k | request_.closeRemote(); |
236 | 25.7k | response_.closeLocal(); |
237 | 25.7k | response_.closeRemote(); |
238 | 25.7k | } |
239 | | |
240 | | // Some stream action applied in either the request or response direction. |
241 | | void directionalAction(DirectionalState& state, |
242 | 54.9k | const test::common::http::DirectionalAction& directional_action) { |
243 | 54.9k | const bool end_stream = directional_action.end_stream(); |
244 | 54.9k | const bool response = &state == &response_; |
245 | 54.9k | switch (directional_action.directional_action_selector_case()) { |
246 | 10.7k | case test::common::http::DirectionalAction::kContinueHeaders: { |
247 | 10.7k | if (state.isLocalOpen() && state.stream_state_ == StreamState::PendingHeaders) { |
248 | 10.5k | auto headers = |
249 | 10.5k | fromSanitizedHeaders<TestResponseHeaderMapImpl>(directional_action.continue_headers()); |
250 | 10.5k | headers.setReferenceKey(Headers::get().Status, "100"); |
251 | 10.5k | state.response_encoder_->encode1xxHeaders(headers); |
252 | 10.5k | } |
253 | 10.7k | break; |
254 | 0 | } |
255 | 4.54k | case test::common::http::DirectionalAction::kHeaders: { |
256 | 4.54k | if (state.isLocalOpen() && state.stream_state_ == StreamState::PendingHeaders) { |
257 | 4.36k | if (response) { |
258 | 4.36k | auto headers = |
259 | 4.36k | fromSanitizedHeaders<TestResponseHeaderMapImpl>(directional_action.headers()); |
260 | | // Check for validity of response-status explicitly, as mutateResponseHeaders() and |
261 | | // encodeHeaders() might bug. |
262 | 4.36k | if (!Utility::getResponseStatusOrNullopt(headers).has_value()) { |
263 | 3.72k | headers.setReferenceKey(Headers::get().Status, "200"); |
264 | 3.72k | } |
265 | 4.36k | ConnectionManagerUtility::mutateResponseHeaders(headers, &request_.request_headers_, |
266 | 4.36k | *context_.conn_manager_config_, |
267 | 4.36k | /*via=*/"", stream_info_, /*node_id=*/""); |
268 | 4.36k | state.response_encoder_->encodeHeaders(headers, end_stream); |
269 | 4.36k | } else { |
270 | 0 | state.request_encoder_ |
271 | 0 | ->encodeHeaders( |
272 | 0 | fromSanitizedHeaders<TestRequestHeaderMapImpl>(directional_action.headers()), |
273 | 0 | end_stream) |
274 | 0 | .IgnoreError(); |
275 | 0 | } |
276 | 4.36k | if (end_stream) { |
277 | 2.80k | state.closeLocal(); |
278 | 2.80k | } else { |
279 | 1.55k | state.stream_state_ = StreamState::PendingDataOrTrailers; |
280 | 1.55k | } |
281 | 4.36k | } |
282 | 4.54k | break; |
283 | 0 | } |
284 | 18.4k | case test::common::http::DirectionalAction::kData: { |
285 | 18.4k | if (state.isLocalOpen() && state.stream_state_ == StreamState::PendingDataOrTrailers) { |
286 | 14.9k | Buffer::OwnedImpl buf(std::string(directional_action.data() % (1024 * 1024), 'a')); |
287 | 14.9k | if (response) { |
288 | 1.01k | state.response_encoder_->encodeData(buf, end_stream); |
289 | 13.9k | } else { |
290 | 13.9k | state.request_encoder_->encodeData(buf, end_stream); |
291 | 13.9k | } |
292 | 14.9k | if (end_stream) { |
293 | 982 | state.closeLocal(); |
294 | 982 | } |
295 | 14.9k | } |
296 | 18.4k | break; |
297 | 0 | } |
298 | 1.80k | case test::common::http::DirectionalAction::kDataValue: { |
299 | 1.80k | if (state.isLocalOpen() && state.stream_state_ == StreamState::PendingDataOrTrailers) { |
300 | 1.38k | Buffer::OwnedImpl buf(directional_action.data_value()); |
301 | 1.38k | if (response) { |
302 | 190 | state.response_encoder_->encodeData(buf, end_stream); |
303 | 1.19k | } else { |
304 | 1.19k | state.request_encoder_->encodeData(buf, end_stream); |
305 | 1.19k | } |
306 | 1.38k | if (end_stream) { |
307 | 204 | state.closeLocal(); |
308 | 204 | } |
309 | 1.38k | } |
310 | 1.80k | break; |
311 | 0 | } |
312 | 3.26k | case test::common::http::DirectionalAction::kTrailers: { |
313 | 3.26k | if (state.isLocalOpen() && state.stream_state_ == StreamState::PendingDataOrTrailers) { |
314 | 3.08k | if (response) { |
315 | 128 | state.response_encoder_->encodeTrailers( |
316 | 128 | fromSanitizedHeaders<TestResponseTrailerMapImpl>(directional_action.trailers())); |
317 | 2.96k | } else { |
318 | 2.96k | state.request_encoder_->encodeTrailers( |
319 | 2.96k | fromSanitizedHeaders<TestRequestTrailerMapImpl>(directional_action.trailers())); |
320 | 2.96k | } |
321 | 3.08k | state.stream_state_ = StreamState::Closed; |
322 | 3.08k | state.closeLocal(); |
323 | 3.08k | } |
324 | 3.26k | break; |
325 | 0 | } |
326 | 12.2k | case test::common::http::DirectionalAction::kMetadata: { |
327 | 12.2k | if (state.isLocalOpen() && state.stream_state_ != StreamState::Closed) { |
328 | 10.7k | if (response) { |
329 | 6.22k | state.response_encoder_->encodeMetadata( |
330 | 6.22k | Fuzz::fromMetadata(directional_action.metadata())); |
331 | 6.22k | } else { |
332 | 4.51k | state.request_encoder_->encodeMetadata(Fuzz::fromMetadata(directional_action.metadata())); |
333 | 4.51k | } |
334 | 10.7k | } |
335 | 12.2k | break; |
336 | 0 | } |
337 | 561 | case test::common::http::DirectionalAction::kResetStream: { |
338 | 561 | if (state.stream_state_ != StreamState::Closed) { |
339 | 491 | StreamEncoder* encoder; |
340 | 491 | if (response) { |
341 | 299 | encoder = state.response_encoder_; |
342 | 299 | } else { |
343 | 192 | encoder = state.request_encoder_; |
344 | 192 | } |
345 | 491 | encoder->getStream().resetStream( |
346 | 491 | static_cast<Http::StreamResetReason>(directional_action.reset_stream())); |
347 | 491 | if (http_protocol_ < Protocol::Http2 && response) { |
348 | | // Invoke the stream reset callback in case the HTTP response has been |
349 | | // encoded and then the fuzzer does a reset stream call which for |
350 | | // HTTP/1 should lead to the connection being closed. |
351 | 9 | stream_reset_callback_(); |
352 | 9 | } |
353 | 491 | request_.stream_state_ = response_.stream_state_ = StreamState::Closed; |
354 | 491 | } |
355 | 561 | break; |
356 | 0 | } |
357 | 3.33k | case test::common::http::DirectionalAction::kReadDisable: { |
358 | 3.33k | if (state.stream_state_ != StreamState::Closed) { |
359 | 3.14k | const bool disable = directional_action.read_disable(); |
360 | 3.14k | if (state.read_disable_count_ == 0 && !disable) { |
361 | 119 | return; |
362 | 119 | } |
363 | 3.02k | if (disable) { |
364 | 2.81k | ++state.read_disable_count_; |
365 | 2.81k | } else { |
366 | 207 | --state.read_disable_count_; |
367 | 207 | } |
368 | | |
369 | 3.02k | StreamEncoder* encoder; |
370 | 3.02k | Event::MockDispatcher* dispatcher{nullptr}; |
371 | | |
372 | 3.02k | if (response) { |
373 | 960 | encoder = state.response_encoder_; |
374 | 960 | dispatcher = &context_.server_connection_.dispatcher_; |
375 | 2.06k | } else { |
376 | 2.06k | encoder = state.request_encoder_; |
377 | 2.06k | dispatcher = &context_.client_connection_.dispatcher_; |
378 | 2.06k | } |
379 | | |
380 | | // With this feature enabled for http2 the codec may end up creating a |
381 | | // schedulable callback the first time it re-enables reading as it's used |
382 | | // to process the backed up data if there's any to process. |
383 | 3.02k | if (Runtime::runtimeFeatureEnabled(Runtime::defer_processing_backedup_streams)) { |
384 | 3.02k | const bool might_schedulable_callback_creation = |
385 | 3.02k | http_protocol_ == Protocol::Http2 && state.read_disable_count_ == 0 && !disable && |
386 | 3.02k | !state.created_schedulable_callback_; |
387 | | |
388 | 3.02k | if (might_schedulable_callback_creation) { |
389 | 58 | ASSERT(dispatcher != nullptr); |
390 | 58 | state.created_schedulable_callback_ = true; |
391 | 58 | ON_CALL(*dispatcher, createSchedulableCallback_(_)) |
392 | 58 | .WillByDefault(testing::Invoke([dispatcher](std::function<void()> cb) { |
393 | | // The unique pointer of this object will be returned in |
394 | | // createSchedulableCallback_ of dispatcher, so there is no risk of this object |
395 | | // leaking. |
396 | 25 | return new Event::MockSchedulableCallback(dispatcher, cb); |
397 | 25 | })); |
398 | 58 | } |
399 | 3.02k | } |
400 | | |
401 | 3.02k | encoder->getStream().readDisable(disable); |
402 | 3.02k | } |
403 | 3.21k | break; |
404 | 3.33k | } |
405 | 3.21k | default: |
406 | | // Maybe nothing is set? |
407 | 0 | break; |
408 | 54.9k | } |
409 | 54.9k | } |
410 | | |
411 | 56.3k | void streamAction(const test::common::http::StreamAction& stream_action) { |
412 | 56.3k | switch (stream_action.stream_action_selector_case()) { |
413 | 28.1k | case test::common::http::StreamAction::kRequest: { |
414 | 28.1k | ENVOY_LOG_MISC(debug, "Request stream action on {} in state request({}) response({})", |
415 | 28.1k | stream_index_, streamStateToString(request_.stream_state_), |
416 | 28.1k | streamStateToString(response_.stream_state_)); |
417 | 28.1k | stream_action_active_ = true; |
418 | 28.1k | if (stream_action.has_dispatching_action()) { |
419 | | // Simulate some response action while dispatching request headers, data, or trailers. This |
420 | | // may happen as a result of a filter sending a direct response. |
421 | 3.39k | ENVOY_LOG_MISC(debug, "Setting dispatching action on {} in state request({}) response({})", |
422 | 3.39k | stream_index_, streamStateToString(request_.stream_state_), |
423 | 3.39k | streamStateToString(response_.stream_state_)); |
424 | 3.39k | auto request_action = stream_action.dispatching_action().directional_action_selector_case(); |
425 | 3.39k | if (request_action == test::common::http::DirectionalAction::kHeaders) { |
426 | 31 | EXPECT_CALL(request_.request_decoder_, decodeHeaders_(_, _)) |
427 | 31 | .WillOnce(InvokeWithoutArgs( |
428 | 31 | [&] { directionalAction(response_, stream_action.dispatching_action()); })); |
429 | 3.36k | } else if (request_action == test::common::http::DirectionalAction::kData) { |
430 | 3.03k | EXPECT_CALL(request_.request_decoder_, decodeData(_, _)) |
431 | 3.03k | .Times(testing::AtLeast(1)) |
432 | 155k | .WillRepeatedly(InvokeWithoutArgs([&] { |
433 | | // Only simulate response action if the stream action is active |
434 | | // otherwise the expectation could trigger in other moments |
435 | | // causing the fuzzer to OOM. |
436 | | // TODO(kbaichoo): In the future if the fuzzer invokes |
437 | | // decodeData from deferred processing callbacks as part of |
438 | | // a request data step, we should allow response data |
439 | | // generation. |
440 | 155k | if (stream_action_active_) { |
441 | 0 | directionalAction(response_, stream_action.dispatching_action()); |
442 | 0 | } |
443 | 155k | })); |
444 | 3.03k | } else if (request_action == test::common::http::DirectionalAction::kTrailers) { |
445 | 62 | EXPECT_CALL(request_.request_decoder_, decodeTrailers_(_)) |
446 | 62 | .WillOnce(InvokeWithoutArgs( |
447 | 62 | [&] { directionalAction(response_, stream_action.dispatching_action()); })); |
448 | 62 | } |
449 | 3.39k | } |
450 | | // Perform the stream action. |
451 | | // The request_.request_encoder_ is initialized from the response_.response_decoder_. |
452 | | // Fuzz test codec_impl_fuzz_test-5766628005642240 created a situation where the response |
453 | | // stream was in closed state leading to the state.request_encoder_ in directionalAction() |
454 | | // kData case no longer being a valid address. |
455 | 28.1k | if (response_.stream_state_ != HttpStream::StreamState::Closed) { |
456 | 26.7k | directionalAction(request_, stream_action.request()); |
457 | 26.7k | } |
458 | 28.1k | stream_action_active_ = false; |
459 | 28.1k | break; |
460 | 0 | } |
461 | 28.1k | case test::common::http::StreamAction::kResponse: { |
462 | 28.1k | ENVOY_LOG_MISC(debug, "Response stream action on {} in state request({}) response({})", |
463 | 28.1k | stream_index_, streamStateToString(request_.stream_state_), |
464 | 28.1k | streamStateToString(response_.stream_state_)); |
465 | 28.1k | directionalAction(response_, stream_action.response()); |
466 | 28.1k | break; |
467 | 0 | } |
468 | 0 | default: |
469 | | // Maybe nothing is set? |
470 | 0 | break; |
471 | 56.3k | } |
472 | 56.3k | ENVOY_LOG_MISC(debug, "Stream action complete"); |
473 | 56.3k | } |
474 | | |
475 | 2.68k | bool active() const { |
476 | 2.68k | return request_.stream_state_ != StreamState::Closed || |
477 | 2.68k | response_.stream_state_ != StreamState::Closed; |
478 | 2.68k | } |
479 | | |
480 | | Protocol http_protocol_; |
481 | | int32_t stream_index_{-1}; |
482 | | // Whether we're currently dispatching a stream action. |
483 | | bool stream_action_active_{false}; |
484 | | StreamResetCallbackFn stream_reset_callback_; |
485 | | ConnectionContext context_; |
486 | | testing::NiceMock<StreamInfo::MockStreamInfo> stream_info_; |
487 | | }; |
488 | | |
489 | | // Buffer between client and server H1/H2 codecs. This models each write operation |
490 | | // as adding a distinct fragment that might be reordered with other fragments in |
491 | | // the buffer via swap() or modified with mutate(). |
492 | | class ReorderBuffer { |
493 | | public: |
494 | | ReorderBuffer(Connection& connection, const bool& should_close_connection) |
495 | 48.8k | : connection_(connection), should_close_connection_(should_close_connection) {} |
496 | | |
497 | 816k | void add(Buffer::Instance& data) { |
498 | 816k | bufs_.emplace_back(); |
499 | 816k | bufs_.back().move(data); |
500 | 816k | } |
501 | | |
502 | 292k | Http::Status drain() { |
503 | 292k | Status status = Http::okStatus(); |
504 | 1.02M | while (!bufs_.empty()) { |
505 | 748k | Buffer::OwnedImpl& buf = bufs_.front(); |
506 | 1.48M | while (buf.length() > 0) { |
507 | 748k | const auto buf_length_old = buf.length(); |
508 | 748k | if (should_close_connection_) { |
509 | 43 | ENVOY_LOG_MISC(trace, "Buffer dispatch disabled, stopping drain"); |
510 | 43 | return codecClientError("preventing buffer drain due to connection closure"); |
511 | 43 | } |
512 | 748k | status = connection_.dispatch(buf); |
513 | 748k | if (!status.ok()) { |
514 | 16.3k | ENVOY_LOG_MISC(trace, "Error status: {}", status.message()); |
515 | 16.3k | return status; |
516 | 16.3k | } |
517 | 732k | if (buf_length_old == buf.length()) { |
518 | 20 | return Http::codecProtocolError("No progress in draining buffer. Breaking endless loop."); |
519 | 20 | } |
520 | 732k | } |
521 | 732k | bufs_.pop_front(); |
522 | 732k | } |
523 | 275k | return status; |
524 | 292k | } |
525 | | |
526 | 35.6k | void mutate(uint32_t buffer, uint32_t offset, uint8_t value) { |
527 | 35.6k | if (bufs_.empty()) { |
528 | 18.8k | return; |
529 | 18.8k | } |
530 | 16.7k | Buffer::OwnedImpl& buf = bufs_[buffer % bufs_.size()]; |
531 | 16.7k | if (buf.length() == 0) { |
532 | 419 | return; |
533 | 419 | } |
534 | 16.3k | uint8_t* p = reinterpret_cast<uint8_t*>(buf.linearize(buf.length())) + offset % buf.length(); |
535 | 16.3k | ENVOY_LOG_MISC(trace, "Mutating {} to {}", *p, value); |
536 | 16.3k | *p = value; |
537 | 16.3k | } |
538 | | |
539 | 20.0k | void swap(uint32_t buffer) { |
540 | 20.0k | if (bufs_.empty()) { |
541 | 10.9k | return; |
542 | 10.9k | } |
543 | 9.15k | const uint32_t effective_index = buffer % bufs_.size(); |
544 | 9.15k | if (effective_index == 0) { |
545 | 6.78k | return; |
546 | 6.78k | } |
547 | 2.36k | Buffer::OwnedImpl tmp; |
548 | 2.36k | tmp.move(bufs_[0]); |
549 | 2.36k | bufs_[0].move(bufs_[effective_index]); |
550 | 2.36k | bufs_[effective_index].move(tmp); |
551 | 2.36k | } |
552 | | |
553 | 191k | bool empty() const { return bufs_.empty(); } |
554 | | |
555 | | Connection& connection_; |
556 | | std::deque<Buffer::OwnedImpl> bufs_; |
557 | | // A reference to a flag indicating whether the reorder buffer is allowed to dispatch data to |
558 | | // the connection (reference to should_close_connection). |
559 | | const bool& should_close_connection_; |
560 | | }; |
561 | | |
562 | | using HttpStreamPtr = std::unique_ptr<HttpStream>; |
563 | | |
564 | | namespace { |
565 | | |
566 | | enum class HttpVersion { Http1, Http2Nghttp2, Http2Oghttp2 }; |
567 | | |
568 | 24.4k | void codecFuzz(const test::common::http::CodecImplFuzzTestCase& input, HttpVersion http_version) { |
569 | 24.4k | Stats::IsolatedStoreImpl stats_store; |
570 | 24.4k | Stats::Scope& scope = *stats_store.rootScope(); |
571 | 24.4k | NiceMock<Network::MockConnection> client_connection; |
572 | 24.4k | const envoy::config::core::v3::Http2ProtocolOptions client_http2_options{ |
573 | 24.4k | fromHttp2Settings(input.h2_settings().client())}; |
574 | 24.4k | const Http1Settings client_http1settings; |
575 | 24.4k | NiceMock<MockConnectionCallbacks> client_callbacks; |
576 | 24.4k | NiceMock<Network::MockConnection> server_connection; |
577 | 24.4k | NiceMock<MockServerConnectionCallbacks> server_callbacks; |
578 | 24.4k | NiceMock<Random::MockRandomGenerator> random; |
579 | 24.4k | NiceMock<Server::MockOverloadManager> overload_manager_; |
580 | 24.4k | NiceMock<MockConnectionManagerConfig> conn_manager_config; |
581 | 24.4k | uint32_t max_request_headers_kb = Http::DEFAULT_MAX_REQUEST_HEADERS_KB; |
582 | 24.4k | uint32_t max_request_headers_count = Http::DEFAULT_MAX_HEADERS_COUNT; |
583 | 24.4k | uint32_t max_response_headers_count = Http::DEFAULT_MAX_HEADERS_COUNT; |
584 | 24.4k | const envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction |
585 | 24.4k | headers_with_underscores_action = envoy::config::core::v3::HttpProtocolOptions::ALLOW; |
586 | | |
587 | 24.4k | HttpStream::ConnectionContext connection_context(&conn_manager_config, server_connection, |
588 | 24.4k | client_connection); |
589 | 24.4k | TestScopedRuntime scoped_runtime; |
590 | | |
591 | 24.4k | Http1::CodecStats::AtomicPtr http1_stats; |
592 | 24.4k | Http2::CodecStats::AtomicPtr http2_stats; |
593 | 24.4k | ClientConnectionPtr client; |
594 | 24.4k | ServerConnectionPtr server; |
595 | 24.4k | bool http2 = false; |
596 | | |
597 | 24.4k | switch (http_version) { |
598 | 6.49k | case HttpVersion::Http1: |
599 | 6.49k | break; |
600 | 8.98k | case HttpVersion::Http2Nghttp2: |
601 | 8.98k | http2 = true; |
602 | 8.98k | scoped_runtime.mergeValues({{"envoy.reloadable_features.http2_use_oghttp2", "false"}}); |
603 | 8.98k | break; |
604 | 8.98k | case HttpVersion::Http2Oghttp2: |
605 | 8.98k | http2 = true; |
606 | 8.98k | scoped_runtime.mergeValues({{"envoy.reloadable_features.http2_use_oghttp2", "true"}}); |
607 | 8.98k | break; |
608 | 24.4k | } |
609 | | |
610 | 24.4k | if (http2) { |
611 | 17.9k | client = std::make_unique<Http2::ClientConnectionImpl>( |
612 | 17.9k | client_connection, client_callbacks, Http2::CodecStats::atomicGet(http2_stats, scope), |
613 | 17.9k | random, client_http2_options, max_request_headers_kb, max_response_headers_count, |
614 | 17.9k | Http2::ProdNghttp2SessionFactory::get()); |
615 | 17.9k | } else { |
616 | 6.49k | client = std::make_unique<Http1::ClientConnectionImpl>( |
617 | 6.49k | client_connection, Http1::CodecStats::atomicGet(http1_stats, scope), client_callbacks, |
618 | 6.49k | client_http1settings, max_response_headers_count); |
619 | 6.49k | } |
620 | | |
621 | 24.4k | if (http2) { |
622 | 17.9k | const envoy::config::core::v3::Http2ProtocolOptions server_http2_options{ |
623 | 17.9k | fromHttp2Settings(input.h2_settings().server())}; |
624 | 17.9k | server = std::make_unique<Http2::ServerConnectionImpl>( |
625 | 17.9k | server_connection, server_callbacks, Http2::CodecStats::atomicGet(http2_stats, scope), |
626 | 17.9k | random, server_http2_options, max_request_headers_kb, max_request_headers_count, |
627 | 17.9k | headers_with_underscores_action, overload_manager_); |
628 | 17.9k | } else { |
629 | 6.49k | const Http1Settings server_http1settings{fromHttp1Settings(input.h1_settings().server())}; |
630 | 6.49k | server = std::make_unique<Http1::ServerConnectionImpl>( |
631 | 6.49k | server_connection, Http1::CodecStats::atomicGet(http1_stats, scope), server_callbacks, |
632 | 6.49k | server_http1settings, max_request_headers_kb, max_request_headers_count, |
633 | 6.49k | headers_with_underscores_action, overload_manager_); |
634 | 6.49k | } |
635 | | |
636 | | // We track whether the connection should be closed for HTTP/1, since stream resets imply |
637 | | // connection closes. |
638 | 24.4k | bool should_close_connection = false; |
639 | | |
640 | | // The buffers will be blocked from dispatching data if should_close_connection is set to true. |
641 | | // This prevents sending data if a stream reset occurs during the test cleanup when using HTTP/1. |
642 | 24.4k | ReorderBuffer client_write_buf{*server, should_close_connection}; |
643 | 24.4k | ReorderBuffer server_write_buf{*client, should_close_connection}; |
644 | | |
645 | 24.4k | ON_CALL(client_connection, write(_, _)) |
646 | 473k | .WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void { |
647 | 473k | ENVOY_LOG_MISC(trace, "client -> server {} bytes", data.length()); |
648 | 473k | client_write_buf.add(data); |
649 | 473k | })); |
650 | 24.4k | ON_CALL(server_connection, write(_, _)) |
651 | 343k | .WillByDefault(Invoke([&](Buffer::Instance& data, bool) -> void { |
652 | 343k | ENVOY_LOG_MISC(trace, "server -> client {} bytes: {}", data.length(), data.toString()); |
653 | 343k | server_write_buf.add(data); |
654 | 343k | })); |
655 | | |
656 | | // We hold Streams in pending_streams between the request encodeHeaders in the |
657 | | // Stream constructor and server newStream() callback, where we learn about |
658 | | // the response encoder and can complete Stream initialization. |
659 | 24.4k | std::list<HttpStreamPtr> pending_streams; |
660 | 24.4k | std::list<HttpStreamPtr> streams; |
661 | | // For new streams when we aren't expecting one (e.g. as a result of a mutation). |
662 | 24.4k | NiceMock<MockRequestDecoder> orphan_request_decoder; |
663 | | |
664 | 24.4k | ON_CALL(server_callbacks, newStream(_, _)) |
665 | 26.4k | .WillByDefault(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { |
666 | 26.4k | if (pending_streams.empty()) { |
667 | 118 | return orphan_request_decoder; |
668 | 118 | } |
669 | 26.3k | auto stream_ptr = pending_streams.front()->removeFromList(pending_streams); |
670 | 26.3k | HttpStream* const stream = stream_ptr.get(); |
671 | 26.3k | LinkedList::moveIntoListBack(std::move(stream_ptr), streams); |
672 | 26.3k | stream->response_.response_encoder_ = &encoder; |
673 | 26.3k | encoder.getStream().addCallbacks(stream->response_.stream_callbacks_); |
674 | 26.3k | stream->stream_index_ = streams.size() - 1; |
675 | 26.3k | return stream->request_.request_decoder_; |
676 | 26.4k | })); |
677 | | |
678 | 44.4k | auto client_server_buf_drain = [&client_write_buf, &server_write_buf] { |
679 | 44.4k | Http::Status status = Http::okStatus(); |
680 | 155k | while (!client_write_buf.empty() || !server_write_buf.empty()) { |
681 | 125k | status = client_write_buf.drain(); |
682 | 125k | if (!status.ok()) { |
683 | 13.3k | return status; |
684 | 13.3k | } |
685 | 111k | status = server_write_buf.drain(); |
686 | 111k | if (!status.ok()) { |
687 | 1.04k | return status; |
688 | 1.04k | } |
689 | 111k | } |
690 | 30.0k | return status; |
691 | 44.4k | }; |
692 | | |
693 | 24.4k | constexpr auto max_actions = 1024; |
694 | 24.4k | bool codec_error = false; |
695 | 24.4k | const auto num_actions = std::min(max_actions, input.actions().size()); |
696 | 316k | for (int i = 0; i < num_actions && !should_close_connection && !codec_error; ++i) { |
697 | 291k | const auto& action = input.actions(i); |
698 | 291k | ENVOY_LOG_MISC(trace, "action #{}/{}: {} with {} streams", i, num_actions, action.DebugString(), |
699 | 291k | streams.size()); |
700 | 291k | switch (action.action_selector_case()) { |
701 | 109k | case test::common::http::Action::kNewStream: { |
702 | 109k | if (!http2) { |
703 | | // HTTP/1 codec needs to have existing streams complete, so make it |
704 | | // easier to achieve a successful multi-stream example by flushing. |
705 | 9.05k | if (!client_server_buf_drain().ok()) { |
706 | 10 | codec_error = true; |
707 | 10 | break; |
708 | 10 | } |
709 | | // HTTP/1 client codec can only have a single active stream. |
710 | 9.04k | if (!pending_streams.empty() || (!streams.empty() && streams.back()->active())) { |
711 | 69 | ENVOY_LOG_MISC(trace, "Skipping new stream as HTTP/1 and already have existing stream"); |
712 | 69 | continue; |
713 | 69 | } |
714 | 9.04k | } |
715 | 109k | HttpStreamPtr stream = std::make_unique<HttpStream>( |
716 | 109k | *client, |
717 | 109k | fromSanitizedHeaders<TestRequestHeaderMapImpl>(action.new_stream().request_headers()), |
718 | 109k | action.new_stream().end_stream(), |
719 | 109k | [&should_close_connection, http2]() { |
720 | | // HTTP/1 codec has stream reset implying connection close. |
721 | 25.7k | if (!http2) { |
722 | 629 | should_close_connection = true; |
723 | 629 | } |
724 | 25.7k | }, |
725 | 109k | connection_context); |
726 | 109k | LinkedList::moveIntoListBack(std::move(stream), pending_streams); |
727 | 109k | break; |
728 | 109k | } |
729 | 57.5k | case test::common::http::Action::kStreamAction: { |
730 | 57.5k | const auto& stream_action = action.stream_action(); |
731 | 57.5k | if (streams.empty()) { |
732 | 1.19k | break; |
733 | 1.19k | } |
734 | | // Index into list of created streams (not HTTP/2 level stream ID). |
735 | 56.3k | const uint32_t stream_id = stream_action.stream_id() % streams.size(); |
736 | 56.3k | ENVOY_LOG_MISC(trace, "action for stream index {}", stream_id); |
737 | 56.3k | (*std::next(streams.begin(), stream_id))->streamAction(stream_action); |
738 | 56.3k | break; |
739 | 57.5k | } |
740 | 35.6k | case test::common::http::Action::kMutate: { |
741 | 35.6k | const auto& mutate = action.mutate(); |
742 | 35.6k | ReorderBuffer& write_buf = mutate.server() ? server_write_buf : client_write_buf; |
743 | 35.6k | write_buf.mutate(mutate.buffer(), mutate.offset(), mutate.value()); |
744 | 35.6k | break; |
745 | 57.5k | } |
746 | 20.0k | case test::common::http::Action::kSwapBuffer: { |
747 | 20.0k | const auto& swap_buffer = action.swap_buffer(); |
748 | 20.0k | ReorderBuffer& write_buf = swap_buffer.server() ? server_write_buf : client_write_buf; |
749 | 20.0k | write_buf.swap(swap_buffer.buffer()); |
750 | 20.0k | break; |
751 | 57.5k | } |
752 | 34.5k | case test::common::http::Action::kClientDrain: { |
753 | 34.5k | if (!client_write_buf.drain().ok()) { |
754 | 1.70k | codec_error = true; |
755 | 1.70k | break; |
756 | 1.70k | } |
757 | 32.8k | break; |
758 | 34.5k | } |
759 | 32.8k | case test::common::http::Action::kServerDrain: { |
760 | 20.6k | if (!server_write_buf.drain().ok()) { |
761 | 329 | codec_error = true; |
762 | 329 | break; |
763 | 329 | } |
764 | 20.3k | break; |
765 | 20.6k | } |
766 | 20.3k | case test::common::http::Action::kQuiesceDrain: { |
767 | 13.9k | if (!client_server_buf_drain().ok()) { |
768 | 650 | codec_error = true; |
769 | 650 | break; |
770 | 650 | } |
771 | 13.2k | break; |
772 | 13.9k | } |
773 | 13.2k | default: |
774 | | // Maybe nothing is set? |
775 | 0 | break; |
776 | 291k | } |
777 | 291k | if (DebugMode && !should_close_connection && !codec_error) { |
778 | 0 | if (!client_server_buf_drain().ok()) { |
779 | 0 | codec_error = true; |
780 | 0 | break; |
781 | 0 | } |
782 | 0 | } |
783 | 291k | } |
784 | | // Drain all remaining buffers, unless the connection is effectively closed. |
785 | 24.4k | if (!should_close_connection && !codec_error) { |
786 | 21.4k | if (!client_server_buf_drain().ok()) { |
787 | 13.7k | codec_error = true; |
788 | 13.7k | } |
789 | 21.4k | } |
790 | 24.4k | if (!codec_error && http2) { |
791 | 4.70k | dynamic_cast<Http2::ClientConnectionImpl&>(*client).goAway(); |
792 | 4.70k | dynamic_cast<Http2::ServerConnectionImpl&>(*server).goAway(); |
793 | 4.70k | } |
794 | | |
795 | | // Run deletion as would happen on the dispatchers to avoid inversion of |
796 | | // lifetimes of dispatcher and connection. |
797 | 24.4k | server_connection.dispatcher_.to_delete_.clear(); |
798 | 24.4k | } |
799 | | |
800 | | #ifdef FUZZ_PROTOCOL_http1 |
801 | 6.49k | void codecFuzzHttp1(const test::common::http::CodecImplFuzzTestCase& input) { |
802 | 6.49k | codecFuzz(input, HttpVersion::Http1); |
803 | 6.49k | } |
804 | | #endif |
805 | | |
806 | | #ifdef FUZZ_PROTOCOL_http2 |
807 | 8.98k | void codecFuzzHttp2Nghttp2(const test::common::http::CodecImplFuzzTestCase& input) { |
808 | 8.98k | codecFuzz(input, HttpVersion::Http2Nghttp2); |
809 | 8.98k | } |
810 | | |
811 | 8.98k | void codecFuzzHttp2Oghttp2(const test::common::http::CodecImplFuzzTestCase& input) { |
812 | 8.98k | codecFuzz(input, HttpVersion::Http2Oghttp2); |
813 | 8.98k | } |
814 | | #endif |
815 | | |
816 | | } // namespace |
817 | | |
818 | | // Fuzz the H1/H2 codec implementations. |
819 | 15.5k | DEFINE_PROTO_FUZZER(const test::common::http::CodecImplFuzzTestCase& input) { |
820 | 15.5k | try { |
821 | | // Validate input early. |
822 | 15.5k | TestUtility::validate(input); |
823 | 15.5k | #ifdef FUZZ_PROTOCOL_http1 |
824 | 15.5k | codecFuzzHttp1(input); |
825 | 15.5k | #endif |
826 | | #ifdef FUZZ_PROTOCOL_http2 |
827 | | // We wrap the calls to *codecFuzz* through these functions in order for |
828 | | // the codec name to explicitly be in any stacktrace. |
829 | | codecFuzzHttp2Nghttp2(input); |
830 | | // Prevent oghttp2 from aborting the program. |
831 | | // If when disabling the FATAL log abort the fuzzer will create a test that reaches an |
832 | | // inconsistent state (and crashes/accesses inconsistent memory), then it will be a bug we'll |
833 | | // need to further evaluate. However, in fuzzing we allow oghttp2 reaching FATAL states that may |
834 | | // happen in production environments. |
835 | | quiche::setDFatalExitDisabled(true); |
836 | | codecFuzzHttp2Oghttp2(input); |
837 | | #endif |
838 | 15.5k | } catch (const EnvoyException& e) { |
839 | 35 | ENVOY_LOG_MISC(debug, "EnvoyException: {}", e.what()); |
840 | 35 | } |
841 | 15.5k | } |
842 | | |
843 | | } // namespace Http |
844 | | } // namespace Envoy |