/proc/self/cwd/test/common/http/conn_manager_impl_fuzz_test.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // This fuzzer explores the behavior of HCM with replay of trace actions that describe the behavior |
2 | | // of a mocked codec and decoder/encoder filters. It is only partially complete (~60% test coverage |
3 | | // with supplied corpus), since HCM has a lot of behavior to model, requiring investment in building |
4 | | // out modeling actions and a corpus, which is time consuming and may not have significant security |
5 | | // of functional correctness payoff beyond existing tests. Places where we could increase fuzz |
6 | | // coverage include: |
7 | | // * Watermarks |
8 | | // * WebSocket upgrades |
9 | | // * Tracing and stats. |
10 | | // * Encode filter actions (e.g. modeling stop/continue, only done for decoder today). |
11 | | // * SSL |
12 | | // * Idle/drain timeouts. |
13 | | // * HTTP 1.0 special cases |
14 | | // * Fuzz config settings |
15 | | #include <chrono> |
16 | | |
17 | | #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" |
18 | | |
19 | | #include "source/common/common/empty_string.h" |
20 | | #include "source/common/http/conn_manager_impl.h" |
21 | | #include "source/common/http/context_impl.h" |
22 | | #include "source/common/http/date_provider_impl.h" |
23 | | #include "source/common/http/exception.h" |
24 | | #include "source/common/http/header_utility.h" |
25 | | #include "source/common/network/address_impl.h" |
26 | | #include "source/common/network/utility.h" |
27 | | |
28 | | #include "test/common/http/conn_manager_impl_fuzz.pb.validate.h" |
29 | | #include "test/fuzz/fuzz_runner.h" |
30 | | #include "test/fuzz/utility.h" |
31 | | #include "test/mocks/access_log/mocks.h" |
32 | | #include "test/mocks/common.h" |
33 | | #include "test/mocks/http/mocks.h" |
34 | | #include "test/mocks/local_info/mocks.h" |
35 | | #include "test/mocks/network/mocks.h" |
36 | | #include "test/mocks/router/mocks.h" |
37 | | #include "test/mocks/runtime/mocks.h" |
38 | | #include "test/mocks/server/mocks.h" |
39 | | #include "test/mocks/ssl/mocks.h" |
40 | | #include "test/mocks/tracing/mocks.h" |
41 | | #include "test/mocks/upstream/cluster_manager.h" |
42 | | #include "test/test_common/simulated_time_system.h" |
43 | | |
44 | | #include "gmock/gmock.h" |
45 | | |
46 | | using testing::InvokeWithoutArgs; |
47 | | using testing::Return; |
48 | | |
49 | | namespace Envoy { |
50 | | namespace Http { |
51 | | |
52 | | class FuzzConfig : public ConnectionManagerConfig { |
53 | | public: |
54 | | FuzzConfig(envoy::extensions::filters::network::http_connection_manager::v3:: |
55 | | HttpConnectionManager::ForwardClientCertDetails forward_client_cert) |
56 | | : stats_({ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), |
57 | | POOL_GAUGE(fake_stats_), |
58 | | POOL_HISTOGRAM(*fake_stats_.rootScope()))}, |
59 | | "", *fake_stats_.rootScope()), |
60 | | tracing_stats_{CONN_MAN_TRACING_STATS(POOL_COUNTER(fake_stats_))}, |
61 | | listener_stats_{CONN_MAN_LISTENER_STATS(POOL_COUNTER(fake_stats_))}, |
62 | 4.11k | local_reply_(LocalReply::Factory::createDefault()) { |
63 | 4.11k | ON_CALL(route_config_provider_, lastUpdated()).WillByDefault(Return(time_system_.systemTime())); |
64 | 4.11k | ON_CALL(scoped_route_config_provider_, lastUpdated()) |
65 | 4.11k | .WillByDefault(Return(time_system_.systemTime())); |
66 | 4.11k | access_logs_.emplace_back(std::make_shared<NiceMock<AccessLog::MockInstance>>()); |
67 | 4.11k | request_id_extension_ = Extensions::RequestId::UUIDRequestIDExtension::defaultInstance(random_); |
68 | 4.11k | forward_client_cert_ = fromClientCert(forward_client_cert); |
69 | 4.11k | } |
70 | | |
71 | 170k | void newStream() { |
72 | 170k | if (!codec_) { |
73 | 4.03k | codec_ = new NiceMock<MockServerConnection>(); |
74 | 4.03k | } |
75 | 170k | decoder_filter_ = new NiceMock<MockStreamDecoderFilter>(); |
76 | 170k | encoder_filter_ = new NiceMock<MockStreamEncoderFilter>(); |
77 | | |
78 | 170k | EXPECT_CALL(filter_factory_, createFilterChain(_)) |
79 | 170k | .WillOnce(Invoke([this](FilterChainManager& manager) -> bool { |
80 | 170k | FilterFactoryCb decoder_filter_factory = [this](FilterChainFactoryCallbacks& callbacks) { |
81 | 170k | callbacks.addStreamDecoderFilter(StreamDecoderFilterSharedPtr{decoder_filter_}); |
82 | 170k | }; |
83 | 170k | FilterFactoryCb encoder_filter_factory = [this](FilterChainFactoryCallbacks& callbacks) { |
84 | 170k | callbacks.addStreamEncoderFilter(StreamEncoderFilterSharedPtr{encoder_filter_}); |
85 | 170k | }; |
86 | | |
87 | 170k | manager.applyFilterFactoryCb({}, decoder_filter_factory); |
88 | 170k | manager.applyFilterFactoryCb({}, encoder_filter_factory); |
89 | 170k | return true; |
90 | 170k | })); |
91 | 170k | EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) |
92 | 170k | .WillOnce(Invoke([this](StreamDecoderFilterCallbacks& callbacks) -> void { |
93 | 170k | decoder_filter_->callbacks_ = &callbacks; |
94 | 170k | callbacks.streamInfo().setResponseCodeDetails(""); |
95 | 170k | })); |
96 | 170k | EXPECT_CALL(*encoder_filter_, setEncoderFilterCallbacks(_)); |
97 | 170k | EXPECT_CALL(filter_factory_, createUpgradeFilterChain("WebSocket", _, _)) |
98 | 170k | .WillRepeatedly(Invoke([&](absl::string_view, const Http::FilterChainFactory::UpgradeMap*, |
99 | 170k | FilterChainManager& manager) -> bool { |
100 | 163 | return filter_factory_.createFilterChain(manager); |
101 | 163 | })); |
102 | 170k | } |
103 | | |
104 | | Http::ForwardClientCertType |
105 | | fromClientCert(envoy::extensions::filters::network::http_connection_manager::v3:: |
106 | 4.11k | HttpConnectionManager::ForwardClientCertDetails forward_client_cert) { |
107 | 4.11k | switch (forward_client_cert) { |
108 | 3.96k | case envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: |
109 | 3.96k | SANITIZE: |
110 | 3.96k | return Http::ForwardClientCertType::Sanitize; |
111 | 21 | case envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: |
112 | 21 | FORWARD_ONLY: |
113 | 21 | return Http::ForwardClientCertType::ForwardOnly; |
114 | 82 | case envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: |
115 | 82 | APPEND_FORWARD: |
116 | 82 | return Http::ForwardClientCertType::AppendForward; |
117 | 20 | case envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: |
118 | 20 | SANITIZE_SET: |
119 | 20 | return Http::ForwardClientCertType::SanitizeSet; |
120 | 26 | case envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: |
121 | 26 | ALWAYS_FORWARD_ONLY: |
122 | 26 | return Http::ForwardClientCertType::AlwaysForwardOnly; |
123 | 0 | default: |
124 | 0 | return Http::ForwardClientCertType::Sanitize; |
125 | 4.11k | } |
126 | 4.11k | } |
127 | | |
128 | | // Http::ConnectionManagerConfig |
129 | 60.7k | const RequestIDExtensionSharedPtr& requestIDExtension() override { return request_id_extension_; } |
130 | 170k | const std::list<AccessLog::InstanceSharedPtr>& accessLogs() override { return access_logs_; } |
131 | 60.7k | bool flushAccessLogOnNewRequest() override { return flush_access_log_on_new_request_; } |
132 | 177 | bool flushAccessLogOnTunnelSuccessfullyEstablished() const override { |
133 | 177 | return flush_access_log_on_tunnel_successfully_established_; |
134 | 177 | } |
135 | 170k | const absl::optional<std::chrono::milliseconds>& accessLogFlushInterval() override { |
136 | 170k | return access_log_flush_interval_; |
137 | 170k | } |
138 | | ServerConnectionPtr createCodec(Network::Connection&, const Buffer::Instance&, |
139 | 4.03k | ServerConnectionCallbacks&, Server::OverloadManager&) override { |
140 | 4.03k | return ServerConnectionPtr{codec_}; |
141 | 4.03k | } |
142 | 111k | DateProvider& dateProvider() override { return date_provider_; } |
143 | 0 | std::chrono::milliseconds drainTimeout() const override { return std::chrono::milliseconds(100); } |
144 | 170k | FilterChainFactory& filterFactory() override { return filter_factory_; } |
145 | 60.7k | bool generateRequestId() const override { return true; } |
146 | 60.7k | bool preserveExternalRequestId() const override { return false; } |
147 | 111k | bool alwaysSetRequestIdInResponse() const override { return false; } |
148 | 0 | uint32_t maxRequestHeadersKb() const override { return max_request_headers_kb_; } |
149 | 0 | uint32_t maxRequestHeadersCount() const override { return max_request_headers_count_; } |
150 | 4.11k | absl::optional<std::chrono::milliseconds> idleTimeout() const override { return idle_timeout_; } |
151 | 682k | bool isRoutable() const override { return true; } |
152 | 4.11k | absl::optional<std::chrono::milliseconds> maxConnectionDuration() const override { |
153 | 4.11k | return max_connection_duration_; |
154 | 4.11k | } |
155 | 341k | absl::optional<std::chrono::milliseconds> maxStreamDuration() const override { |
156 | 341k | return max_stream_duration_; |
157 | 341k | } |
158 | 170k | std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } |
159 | 170k | std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } |
160 | 170k | std::chrono::milliseconds requestHeadersTimeout() const override { |
161 | 170k | return request_headers_timeout_; |
162 | 170k | } |
163 | 4.11k | std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } |
164 | 1.02M | Router::RouteConfigProvider* routeConfigProvider() override { |
165 | 1.02M | if (use_srds_) { |
166 | 0 | return nullptr; |
167 | 0 | } |
168 | 1.02M | return &route_config_provider_; |
169 | 1.02M | } |
170 | 341k | Config::ConfigProvider* scopedRouteConfigProvider() override { |
171 | 341k | if (use_srds_) { |
172 | 0 | return &scoped_route_config_provider_; |
173 | 0 | } |
174 | 341k | return nullptr; |
175 | 341k | } |
176 | 170k | OptRef<const Router::ScopeKeyBuilder> scopeKeyBuilder() override { |
177 | 170k | if (use_srds_) { |
178 | 0 | return scope_key_builder_; |
179 | 0 | } |
180 | 170k | return {}; |
181 | 170k | } |
182 | 115k | const std::string& serverName() const override { return server_name_; } |
183 | | HttpConnectionManagerProto::ServerHeaderTransformation |
184 | 111k | serverHeaderTransformation() const override { |
185 | 111k | return server_transformation_; |
186 | 111k | } |
187 | 60.7k | const absl::optional<std::string>& schemeToSet() const override { return scheme_; } |
188 | 4.11k | ConnectionManagerStats& stats() override { return stats_; } |
189 | 0 | ConnectionManagerTracingStats& tracingStats() override { return tracing_stats_; } |
190 | 121k | bool useRemoteAddress() const override { return use_remote_address_; } |
191 | 60.5k | const Http::InternalAddressConfig& internalAddressConfig() const override { |
192 | 60.5k | return internal_address_config_; |
193 | 60.5k | } |
194 | 60.7k | uint32_t xffNumTrustedHops() const override { return 0; } |
195 | 60.7k | bool skipXffAppend() const override { return false; } |
196 | 171k | const std::string& via() const override { return EMPTY_STRING; } |
197 | 121k | Http::ForwardClientCertType forwardClientCert() const override { return forward_client_cert_; } |
198 | 0 | const std::vector<Http::ClientCertDetailsType>& setCurrentClientCertDetails() const override { |
199 | 0 | return set_current_client_cert_details_; |
200 | 0 | } |
201 | 0 | const Network::Address::Instance& localAddress() override { return local_address_; } |
202 | 60.7k | const absl::optional<std::string>& userAgent() override { return user_agent_; } |
203 | 0 | Tracing::TracerSharedPtr tracer() override { return tracer_; } |
204 | 231k | const TracingConnectionManagerConfig* tracingConfig() override { return tracing_config_.get(); } |
205 | 4.11k | ConnectionManagerListenerStats& listenerStats() override { return listener_stats_; } |
206 | 341k | bool proxy100Continue() const override { return proxy_100_continue_; } |
207 | 0 | bool streamErrorOnInvalidHttpMessaging() const override { |
208 | 0 | return stream_error_on_invalid_http_messaging_; |
209 | 0 | } |
210 | 0 | const Http::Http1Settings& http1Settings() const override { return http1_settings_; } |
211 | 60.6k | bool shouldNormalizePath() const override { return false; } |
212 | 60.6k | bool shouldMergeSlashes() const override { return false; } |
213 | 60.7k | bool shouldStripTrailingHostDot() const override { return false; } |
214 | 121k | Http::StripPortType stripPortType() const override { return Http::StripPortType::None; } |
215 | | envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction |
216 | 0 | headersWithUnderscoresAction() const override { |
217 | 0 | return envoy::config::core::v3::HttpProtocolOptions::ALLOW; |
218 | 0 | } |
219 | 170k | const LocalReply::LocalReply& localReply() const override { return *local_reply_; } |
220 | | envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: |
221 | | PathWithEscapedSlashesAction |
222 | 60.6k | pathWithEscapedSlashesAction() const override { |
223 | 60.6k | return envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager:: |
224 | 60.6k | KEEP_UNCHANGED; |
225 | 60.6k | } |
226 | | const std::vector<Http::OriginalIPDetectionSharedPtr>& |
227 | 0 | originalIpDetectionExtensions() const override { |
228 | 0 | return ip_detection_extensions_; |
229 | 0 | } |
230 | 60.7k | const std::vector<Http::EarlyHeaderMutationPtr>& earlyHeaderMutationExtensions() const override { |
231 | 60.7k | return early_header_mutations_; |
232 | 60.7k | } |
233 | 170k | uint64_t maxRequestsPerConnection() const override { return 0; } |
234 | 115k | const HttpConnectionManagerProto::ProxyStatusConfig* proxyStatusConfig() const override { |
235 | 115k | return proxy_status_config_.get(); |
236 | 115k | } |
237 | 170k | Http::ServerHeaderValidatorPtr makeHeaderValidator(Protocol) override { |
238 | | // TODO(yanavlasov): fuzz test interface should use the default validator, although this could |
239 | | // be changed too |
240 | 170k | return nullptr; |
241 | 170k | } |
242 | 121k | bool appendXForwardedPort() const override { return false; } |
243 | 4.11k | bool addProxyProtocolConnectionState() const override { return true; } |
244 | | |
245 | | const envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager |
246 | | config_; |
247 | | NiceMock<Random::MockRandomGenerator> random_; |
248 | | RequestIDExtensionSharedPtr request_id_extension_; |
249 | | std::list<AccessLog::InstanceSharedPtr> access_logs_; |
250 | | bool flush_access_log_on_new_request_ = false; |
251 | | bool flush_access_log_on_tunnel_successfully_established_ = false; |
252 | | absl::optional<std::chrono::milliseconds> access_log_flush_interval_; |
253 | | MockServerConnection* codec_{}; |
254 | | MockStreamDecoderFilter* decoder_filter_{}; |
255 | | MockStreamEncoderFilter* encoder_filter_{}; |
256 | | NiceMock<MockFilterChainFactory> filter_factory_; |
257 | | Event::SimulatedTimeSystem time_system_; |
258 | | SlowDateProviderImpl date_provider_{time_system_}; |
259 | | bool use_srds_{}; |
260 | | Router::MockRouteConfigProvider route_config_provider_; |
261 | | Router::MockScopedRouteConfigProvider scoped_route_config_provider_; |
262 | | Router::MockScopeKeyBuilder scope_key_builder_; |
263 | | std::string server_name_; |
264 | | HttpConnectionManagerProto::ServerHeaderTransformation server_transformation_{ |
265 | | HttpConnectionManagerProto::OVERWRITE}; |
266 | | absl::optional<std::string> scheme_; |
267 | | Stats::IsolatedStoreImpl fake_stats_; |
268 | | ConnectionManagerStats stats_; |
269 | | ConnectionManagerTracingStats tracing_stats_; |
270 | | ConnectionManagerListenerStats listener_stats_; |
271 | | uint32_t max_request_headers_kb_{Http::DEFAULT_MAX_REQUEST_HEADERS_KB}; |
272 | | uint32_t max_request_headers_count_{Http::DEFAULT_MAX_HEADERS_COUNT}; |
273 | | absl::optional<std::chrono::milliseconds> idle_timeout_; |
274 | | absl::optional<std::chrono::milliseconds> max_connection_duration_; |
275 | | absl::optional<std::chrono::milliseconds> max_stream_duration_; |
276 | | std::chrono::milliseconds stream_idle_timeout_{}; |
277 | | std::chrono::milliseconds request_timeout_{}; |
278 | | std::chrono::milliseconds request_headers_timeout_{}; |
279 | | std::chrono::milliseconds delayed_close_timeout_{}; |
280 | | bool use_remote_address_{true}; |
281 | | Http::ForwardClientCertType forward_client_cert_; |
282 | | std::vector<Http::ClientCertDetailsType> set_current_client_cert_details_; |
283 | | Network::Address::Ipv4Instance local_address_{"127.0.0.1"}; |
284 | | absl::optional<std::string> user_agent_; |
285 | | Tracing::TracerSharedPtr tracer_{std::make_shared<NiceMock<Tracing::MockTracer>>()}; |
286 | | TracingConnectionManagerConfigPtr tracing_config_; |
287 | | bool proxy_100_continue_{true}; |
288 | | bool stream_error_on_invalid_http_messaging_ = false; |
289 | | bool preserve_external_request_id_{false}; |
290 | | Http::Http1Settings http1_settings_; |
291 | | Http::DefaultInternalAddressConfig internal_address_config_; |
292 | | bool normalize_path_{true}; |
293 | | LocalReply::LocalReplyPtr local_reply_; |
294 | | std::vector<Http::OriginalIPDetectionSharedPtr> ip_detection_extensions_{}; |
295 | | std::vector<Http::EarlyHeaderMutationPtr> early_header_mutations_; |
296 | | std::unique_ptr<HttpConnectionManagerProto::ProxyStatusConfig> proxy_status_config_; |
297 | | }; |
298 | | |
299 | | // Internal representation of stream state. Encapsulates the stream state, mocks |
300 | | // and encoders for both the request/response. |
301 | | class FuzzStream { |
302 | | public: |
303 | | // We track stream state here to prevent illegal operations, e.g. applying an |
304 | | // encodeData() to the codec after encodeTrailers(). This is necessary to |
305 | | // maintain the preconditions for operations on the codec at the API level. Of |
306 | | // course, it's the codecs must be robust to wire-level violations. We |
307 | | // explore these violations via MutateAction and SwapAction at the connection |
308 | | // buffer level. |
309 | | enum class StreamState { |
310 | | PendingHeaders, |
311 | | PendingNonInformationalHeaders, |
312 | | PendingDataOrTrailers, |
313 | | Closed |
314 | | }; |
315 | | |
316 | | FuzzStream(ConnectionManagerImpl& conn_manager, FuzzConfig& config, |
317 | | const HeaderMap& request_headers, |
318 | | test::common::http::HeaderStatus decode_header_status, bool end_stream) |
319 | 170k | : conn_manager_(conn_manager), config_(config) { |
320 | 170k | config_.newStream(); |
321 | 170k | request_state_ = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; |
322 | 170k | response_state_ = StreamState::PendingHeaders; |
323 | 170k | decoder_filter_ = config.decoder_filter_; |
324 | 170k | encoder_filter_ = config.encoder_filter_; |
325 | 170k | EXPECT_CALL(*config_.codec_, dispatch(_)) |
326 | 170k | .WillOnce(InvokeWithoutArgs([this, &request_headers, end_stream] { |
327 | 170k | decoder_ = &conn_manager_.newStream(encoder_); |
328 | 170k | auto headers = std::make_unique<TestRequestHeaderMapImpl>(request_headers); |
329 | 170k | if (headers->Method() == nullptr) { |
330 | 170k | headers->setReferenceKey(Headers::get().Method, "GET"); |
331 | 170k | } |
332 | 170k | if (headers->Host() != nullptr && |
333 | 170k | !HeaderUtility::authorityIsValid(headers->getHostValue())) { |
334 | | // Sanitize host header so we don't fail at ASSERTs that verify header sanity checks |
335 | | // which should have been performed by the codec. |
336 | 151 | headers->setHost(Fuzz::replaceInvalidHostCharacters(headers->getHostValue())); |
337 | 151 | } |
338 | | // If sendLocalReply is called: |
339 | 170k | ON_CALL(encoder_, encodeHeaders(_, true)) |
340 | 170k | .WillByDefault(Invoke([this](const ResponseHeaderMap&, bool end_stream) -> void { |
341 | 103k | response_state_ = |
342 | 103k | end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; |
343 | 103k | })); |
344 | 170k | decoder_->decodeHeaders(std::move(headers), end_stream); |
345 | 170k | return Http::okStatus(); |
346 | 170k | })); |
347 | 170k | ON_CALL(*decoder_filter_, decodeHeaders(_, _)) |
348 | 170k | .WillByDefault( |
349 | 170k | InvokeWithoutArgs([this, decode_header_status]() -> Http::FilterHeadersStatus { |
350 | 60.0k | header_status_ = fromHeaderStatus(decode_header_status); |
351 | 60.0k | return *header_status_; |
352 | 60.0k | })); |
353 | 170k | fakeOnData(); |
354 | 170k | FUZZ_ASSERT(testing::Mock::VerifyAndClearExpectations(config_.codec_)); |
355 | 170k | } |
356 | | |
357 | 176k | void fakeOnData() { |
358 | 176k | Buffer::OwnedImpl fake_input; |
359 | 176k | conn_manager_.onData(fake_input, false); |
360 | 176k | } |
361 | | |
362 | 60.0k | Http::FilterHeadersStatus fromHeaderStatus(test::common::http::HeaderStatus status) { |
363 | 60.0k | switch (status) { |
364 | 54.9k | case test::common::http::HeaderStatus::HEADER_CONTINUE: |
365 | 54.9k | return Http::FilterHeadersStatus::Continue; |
366 | 1.56k | case test::common::http::HeaderStatus::HEADER_STOP_ITERATION: |
367 | 1.56k | return Http::FilterHeadersStatus::StopIteration; |
368 | 684 | case test::common::http::HeaderStatus::HEADER_STOP_ALL_ITERATION_AND_BUFFER: |
369 | 684 | return Http::FilterHeadersStatus::StopAllIterationAndBuffer; |
370 | 2.85k | case test::common::http::HeaderStatus::HEADER_STOP_ALL_ITERATION_AND_WATERMARK: |
371 | 2.85k | return Http::FilterHeadersStatus::StopAllIterationAndWatermark; |
372 | 0 | default: |
373 | 0 | return Http::FilterHeadersStatus::Continue; |
374 | 60.0k | } |
375 | 60.0k | } |
376 | | |
377 | 4.29k | Http::FilterDataStatus fromDataStatus(test::common::http::DataStatus status) { |
378 | 4.29k | switch (status) { |
379 | 2.66k | case test::common::http::DataStatus::DATA_CONTINUE: |
380 | 2.66k | return Http::FilterDataStatus::Continue; |
381 | 956 | case test::common::http::DataStatus::DATA_STOP_ITERATION_AND_BUFFER: |
382 | 956 | return Http::FilterDataStatus::StopIterationAndBuffer; |
383 | 27 | case test::common::http::DataStatus::DATA_STOP_ITERATION_AND_WATERMARK: |
384 | 27 | return Http::FilterDataStatus::StopIterationAndWatermark; |
385 | 652 | case test::common::http::DataStatus::DATA_STOP_ITERATION_NO_BUFFER: |
386 | 652 | return Http::FilterDataStatus::StopIterationNoBuffer; |
387 | 0 | default: |
388 | 0 | return Http::FilterDataStatus::Continue; |
389 | 4.29k | } |
390 | 4.29k | } |
391 | | |
392 | 307 | Http::FilterTrailersStatus fromTrailerStatus(test::common::http::TrailerStatus status) { |
393 | 307 | switch (status) { |
394 | 256 | case test::common::http::TrailerStatus::TRAILER_CONTINUE: |
395 | 256 | return Http::FilterTrailersStatus::Continue; |
396 | 51 | case test::common::http::TrailerStatus::TRAILER_STOP_ITERATION: |
397 | 51 | return Http::FilterTrailersStatus::StopIteration; |
398 | 0 | default: |
399 | 0 | return Http::FilterTrailersStatus::Continue; |
400 | 307 | } |
401 | 307 | } |
402 | | |
403 | | void decoderFilterCallbackAction( |
404 | 1.00k | const test::common::http::DecoderFilterCallbackAction& decoder_filter_callback_action) { |
405 | 1.00k | switch (decoder_filter_callback_action.decoder_filter_callback_action_selector_case()) { |
406 | 880 | case test::common::http::DecoderFilterCallbackAction::kAddDecodedData: { |
407 | 880 | if (request_state_ == StreamState::PendingDataOrTrailers) { |
408 | 877 | Buffer::OwnedImpl buf(std::string( |
409 | 877 | decoder_filter_callback_action.add_decoded_data().size() % (1024 * 1024), 'a')); |
410 | 877 | decoder_filter_->callbacks_->addDecodedData( |
411 | 877 | buf, decoder_filter_callback_action.add_decoded_data().streaming()); |
412 | 877 | } |
413 | 880 | break; |
414 | 0 | } |
415 | 121 | default: |
416 | | // Maybe nothing is set? |
417 | 121 | break; |
418 | 1.00k | } |
419 | 1.00k | } |
420 | | |
421 | 13.5k | void requestAction(StreamState& state, const test::common::http::RequestAction& request_action) { |
422 | 13.5k | switch (request_action.request_action_selector_case()) { |
423 | 6.97k | case test::common::http::RequestAction::kData: { |
424 | 6.97k | if (state == StreamState::PendingDataOrTrailers) { |
425 | 5.83k | const auto& data_action = request_action.data(); |
426 | 5.83k | ON_CALL(*decoder_filter_, decodeData(_, _)) |
427 | 5.83k | .WillByDefault(InvokeWithoutArgs([this, &data_action]() -> Http::FilterDataStatus { |
428 | 4.29k | if (data_action.has_decoder_filter_callback_action()) { |
429 | 938 | decoderFilterCallbackAction(data_action.decoder_filter_callback_action()); |
430 | 938 | } |
431 | 4.29k | data_status_ = fromDataStatus(data_action.status()); |
432 | 4.29k | return *data_status_; |
433 | 4.29k | })); |
434 | 5.83k | EXPECT_CALL(*config_.codec_, dispatch(_)).WillOnce(InvokeWithoutArgs([this, &data_action] { |
435 | 5.83k | Buffer::OwnedImpl buf(std::string(data_action.size() % (1024 * 1024), 'a')); |
436 | 5.83k | decoder_->decodeData(buf, data_action.end_stream()); |
437 | 5.83k | return Http::okStatus(); |
438 | 5.83k | })); |
439 | 5.83k | fakeOnData(); |
440 | 5.83k | FUZZ_ASSERT(testing::Mock::VerifyAndClearExpectations(config_.codec_)); |
441 | 5.83k | state = data_action.end_stream() ? StreamState::Closed : StreamState::PendingDataOrTrailers; |
442 | 5.83k | decoding_done_ = false; |
443 | 5.83k | } |
444 | 6.97k | break; |
445 | 6.97k | } |
446 | 6.97k | case test::common::http::RequestAction::kTrailers: { |
447 | 851 | if (state == StreamState::PendingDataOrTrailers) { |
448 | 480 | const auto& trailers_action = request_action.trailers(); |
449 | 480 | ON_CALL(*decoder_filter_, decodeTrailers(_)) |
450 | 480 | .WillByDefault( |
451 | 480 | InvokeWithoutArgs([this, &trailers_action]() -> Http::FilterTrailersStatus { |
452 | 307 | if (trailers_action.has_decoder_filter_callback_action()) { |
453 | 63 | decoderFilterCallbackAction(trailers_action.decoder_filter_callback_action()); |
454 | 63 | } |
455 | 307 | trailers_status_ = fromTrailerStatus(trailers_action.status()); |
456 | 307 | return *trailers_status_; |
457 | 307 | })); |
458 | 480 | EXPECT_CALL(*config_.codec_, dispatch(_)) |
459 | 480 | .WillOnce(InvokeWithoutArgs([this, &trailers_action] { |
460 | 480 | decoder_->decodeTrailers(std::make_unique<TestRequestTrailerMapImpl>( |
461 | 480 | Fuzz::fromHeaders<TestRequestTrailerMapImpl>(trailers_action.headers()))); |
462 | 480 | return Http::okStatus(); |
463 | 480 | })); |
464 | 480 | fakeOnData(); |
465 | 480 | FUZZ_ASSERT(testing::Mock::VerifyAndClearExpectations(config_.codec_)); |
466 | 480 | state = StreamState::Closed; |
467 | 480 | decoding_done_ = false; |
468 | 480 | } |
469 | 851 | break; |
470 | 851 | } |
471 | 1.58k | case test::common::http::RequestAction::kContinueDecoding: { |
472 | 1.58k | if (!decoding_done_ && |
473 | 1.58k | (header_status_ == FilterHeadersStatus::StopAllIterationAndBuffer || |
474 | 1.56k | header_status_ == FilterHeadersStatus::StopAllIterationAndWatermark || |
475 | 1.56k | header_status_ == FilterHeadersStatus::StopIteration) && |
476 | 1.58k | (!data_status_.has_value() || data_status_ == FilterDataStatus::StopIterationAndBuffer || |
477 | 255 | data_status_ == FilterDataStatus::StopIterationAndWatermark || |
478 | 255 | data_status_ == FilterDataStatus::StopIterationNoBuffer) && |
479 | 1.58k | (!trailers_status_.has_value() || |
480 | 186 | trailers_status_ == FilterTrailersStatus::StopIteration)) { |
481 | 186 | decoder_filter_->callbacks_->continueDecoding(); |
482 | 186 | decoding_done_ = true; |
483 | 186 | } |
484 | 1.58k | break; |
485 | 851 | } |
486 | 340 | case test::common::http::RequestAction::kThrowDecoderException: |
487 | | // Dispatch no longer throws, execute subsequent kReturnDecoderError case. |
488 | 905 | case test::common::http::RequestAction::kReturnDecoderError: { |
489 | 905 | if (state == StreamState::PendingDataOrTrailers) { |
490 | 96 | EXPECT_CALL(*config_.codec_, dispatch(_)) |
491 | 96 | .WillOnce(testing::Return(codecProtocolError("blah"))); |
492 | 96 | fakeOnData(); |
493 | 96 | FUZZ_ASSERT(testing::Mock::VerifyAndClearExpectations(config_.codec_)); |
494 | 96 | state = StreamState::Closed; |
495 | 96 | decoding_done_ = true; |
496 | 96 | } |
497 | 905 | break; |
498 | 905 | } |
499 | 3.22k | default: |
500 | | // Maybe nothing is set or not a request action? |
501 | 3.22k | break; |
502 | 13.5k | } |
503 | 13.5k | } |
504 | | |
505 | | void responseAction(StreamState& state, |
506 | 7.23k | const test::common::http::ResponseAction& response_action) { |
507 | 7.23k | const bool end_stream = response_action.end_stream(); |
508 | 7.23k | switch (response_action.response_action_selector_case()) { |
509 | 76 | case test::common::http::ResponseAction::kContinueHeaders: { |
510 | 76 | if (state == StreamState::PendingHeaders) { |
511 | 55 | auto headers = std::make_unique<TestResponseHeaderMapImpl>( |
512 | 55 | Fuzz::fromHeaders<TestResponseHeaderMapImpl>(response_action.continue_headers())); |
513 | 55 | headers->setReferenceKey(Headers::get().Status, "100"); |
514 | 55 | decoder_filter_->callbacks_->encode1xxHeaders(std::move(headers)); |
515 | | // We don't allow multiple 100-continue headers in HCM, UpstreamRequest is responsible |
516 | | // for coalescing. |
517 | 55 | state = StreamState::PendingNonInformationalHeaders; |
518 | 55 | } |
519 | 76 | break; |
520 | 0 | } |
521 | 598 | case test::common::http::ResponseAction::kHeaders: { |
522 | 598 | if (state == StreamState::PendingHeaders || |
523 | 598 | state == StreamState::PendingNonInformationalHeaders) { |
524 | 512 | auto headers = std::make_unique<TestResponseHeaderMapImpl>( |
525 | 512 | Fuzz::fromHeaders<TestResponseHeaderMapImpl>(response_action.headers())); |
526 | | // The client codec will ensure we always have a valid :status. |
527 | | // Similarly, local replies should always contain this. |
528 | 512 | const auto status = Utility::getResponseStatusOrNullopt(*headers); |
529 | | // The only 1xx header that may be provided to encodeHeaders() is a 101 upgrade, |
530 | | // guaranteed by the codec parsers. See include/envoy/http/filter.h. |
531 | 512 | if (!status.has_value() || (CodeUtility::is1xx(status.value()) && |
532 | 300 | status.value() != enumToInt(Http::Code::SwitchingProtocols))) { |
533 | 213 | headers->setReferenceKey(Headers::get().Status, "200"); |
534 | 213 | } |
535 | 512 | decoder_filter_->callbacks_->encodeHeaders(std::move(headers), end_stream, "details"); |
536 | 512 | state = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; |
537 | 512 | } |
538 | 598 | break; |
539 | 0 | } |
540 | 1.27k | case test::common::http::ResponseAction::kData: { |
541 | 1.27k | if (state == StreamState::PendingDataOrTrailers) { |
542 | 423 | Buffer::OwnedImpl buf(std::string(response_action.data() % (1024 * 1024), 'a')); |
543 | 423 | decoder_filter_->callbacks_->encodeData(buf, end_stream); |
544 | 423 | state = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; |
545 | 423 | } |
546 | 1.27k | break; |
547 | 0 | } |
548 | 215 | case test::common::http::ResponseAction::kTrailers: { |
549 | 215 | if (state == StreamState::PendingDataOrTrailers) { |
550 | 29 | decoder_filter_->callbacks_->encodeTrailers(std::make_unique<TestResponseTrailerMapImpl>( |
551 | 29 | Fuzz::fromHeaders<TestResponseTrailerMapImpl>(response_action.trailers()))); |
552 | 29 | state = StreamState::Closed; |
553 | 29 | } |
554 | 215 | break; |
555 | 0 | } |
556 | 5.07k | default: |
557 | | // Maybe nothing is set? |
558 | 5.07k | break; |
559 | 7.23k | } |
560 | 7.23k | } |
561 | | |
562 | 87.1k | void streamAction(const test::common::http::StreamAction& stream_action) { |
563 | 87.1k | switch (stream_action.stream_action_selector_case()) { |
564 | 13.5k | case test::common::http::StreamAction::kRequest: { |
565 | 13.5k | requestAction(request_state_, stream_action.request()); |
566 | 13.5k | break; |
567 | 0 | } |
568 | 7.23k | case test::common::http::StreamAction::kResponse: { |
569 | 7.23k | responseAction(response_state_, stream_action.response()); |
570 | 7.23k | break; |
571 | 0 | } |
572 | 66.3k | default: |
573 | | // Maybe nothing is set? |
574 | 66.3k | break; |
575 | 87.1k | } |
576 | 87.1k | } |
577 | | |
578 | | ConnectionManagerImpl& conn_manager_; |
579 | | FuzzConfig& config_; |
580 | | RequestDecoder* decoder_{}; |
581 | | NiceMock<MockResponseEncoder> encoder_; |
582 | | MockStreamDecoderFilter* decoder_filter_{}; |
583 | | MockStreamEncoderFilter* encoder_filter_{}; |
584 | | StreamState request_state_; |
585 | | StreamState response_state_; |
586 | | absl::optional<Http::FilterHeadersStatus> header_status_; |
587 | | absl::optional<Http::FilterDataStatus> data_status_; |
588 | | absl::optional<Http::FilterTrailersStatus> trailers_status_; |
589 | | bool decoding_done_{}; |
590 | | }; |
591 | | |
592 | | using FuzzStreamPtr = std::unique_ptr<FuzzStream>; |
593 | | |
594 | 4.11k | DEFINE_PROTO_FUZZER(const test::common::http::ConnManagerImplTestCase& input) { |
595 | 4.11k | try { |
596 | 4.11k | TestUtility::validate(input); |
597 | 4.11k | } catch (const ProtoValidationException& e) { |
598 | 3 | ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what()); |
599 | 3 | return; |
600 | 3 | } catch (const Envoy::ProtobufMessage::DeprecatedProtoFieldException& e) { |
601 | 0 | ENVOY_LOG_MISC(debug, "DeprecatedProtoFieldException: {}", e.what()); |
602 | 0 | return; |
603 | 0 | } |
604 | | |
605 | 4.11k | FuzzConfig config(input.forward_client_cert()); |
606 | 4.11k | NiceMock<Network::MockDrainDecision> drain_close; |
607 | 4.11k | NiceMock<Random::MockRandomGenerator> random; |
608 | 4.11k | Stats::SymbolTableImpl symbol_table; |
609 | 4.11k | Http::ContextImpl http_context(symbol_table); |
610 | 4.11k | NiceMock<Runtime::MockLoader> runtime; |
611 | 4.11k | NiceMock<LocalInfo::MockLocalInfo> local_info; |
612 | 4.11k | NiceMock<Upstream::MockClusterManager> cluster_manager; |
613 | 4.11k | NiceMock<Network::MockReadFilterCallbacks> filter_callbacks; |
614 | 4.11k | NiceMock<Server::MockOverloadManager> overload_manager; |
615 | 4.11k | auto ssl_connection = std::make_shared<Ssl::MockConnectionInfo>(); |
616 | 4.11k | bool connection_alive = true; |
617 | | |
618 | 4.11k | ON_CALL(filter_callbacks.connection_, ssl()).WillByDefault(Return(ssl_connection)); |
619 | 4.11k | ON_CALL(Const(filter_callbacks.connection_), ssl()).WillByDefault(Return(ssl_connection)); |
620 | 4.11k | ON_CALL(filter_callbacks.connection_, close(_)) |
621 | 4.11k | .WillByDefault(InvokeWithoutArgs([&connection_alive] { connection_alive = false; })); |
622 | 4.11k | ON_CALL(filter_callbacks.connection_, close(_, _)) |
623 | 9.03k | .WillByDefault(InvokeWithoutArgs([&connection_alive] { connection_alive = false; })); |
624 | 4.11k | filter_callbacks.connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress( |
625 | 4.11k | std::make_shared<Network::Address::Ipv4Instance>("127.0.0.1")); |
626 | 4.11k | filter_callbacks.connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( |
627 | 4.11k | std::make_shared<Network::Address::Ipv4Instance>("0.0.0.0")); |
628 | | |
629 | 4.11k | ConnectionManagerImpl conn_manager(config, drain_close, random, http_context, runtime, local_info, |
630 | 4.11k | cluster_manager, overload_manager, config.time_system_); |
631 | 4.11k | conn_manager.initializeReadFilterCallbacks(filter_callbacks); |
632 | | |
633 | 4.11k | std::vector<FuzzStreamPtr> streams; |
634 | | |
635 | 499k | for (const auto& action : input.actions()) { |
636 | 499k | ENVOY_LOG_MISC(trace, "action {} with {} streams", action.DebugString(), streams.size()); |
637 | 499k | if (!connection_alive) { |
638 | 114 | ENVOY_LOG_MISC(trace, "skipping due to dead connection"); |
639 | 114 | break; |
640 | 114 | } |
641 | | |
642 | 499k | switch (action.action_selector_case()) { |
643 | 170k | case test::common::http::Action::kNewStream: { |
644 | 170k | streams.emplace_back(new FuzzStream( |
645 | 170k | conn_manager, config, |
646 | 170k | Fuzz::fromHeaders<TestRequestHeaderMapImpl>(action.new_stream().request_headers(), |
647 | 170k | /* ignore_headers =*/{}, {":authority"}), |
648 | 170k | action.new_stream().status(), action.new_stream().end_stream())); |
649 | 170k | break; |
650 | 0 | } |
651 | 87.2k | case test::common::http::Action::kStreamAction: { |
652 | 87.2k | const auto& stream_action = action.stream_action(); |
653 | 87.2k | if (streams.empty()) { |
654 | 119 | break; |
655 | 119 | } |
656 | 87.1k | (*std::next(streams.begin(), stream_action.stream_id() % streams.size())) |
657 | 87.1k | ->streamAction(stream_action); |
658 | 87.1k | break; |
659 | 87.2k | } |
660 | 241k | default: |
661 | | // Maybe nothing is set? |
662 | 241k | break; |
663 | 499k | } |
664 | 499k | } |
665 | | |
666 | 4.11k | filter_callbacks.connection_.raiseEvent(Network::ConnectionEvent::LocalClose); |
667 | 4.11k | filter_callbacks.connection_.dispatcher_.clearDeferredDeleteList(); |
668 | 4.11k | } |
669 | | |
670 | | } // namespace Http |
671 | | } // namespace Envoy |