Coverage Report

Created: 2023-11-12 09:30

/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