Coverage Report

Created: 2023-11-12 09:30

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