Coverage Report

Created: 2024-09-19 09:45

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