Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/test/server/server_fuzz_test.cc
Line
Count
Source (jump to first uncovered line)
1
#include <fstream>
2
3
#include "envoy/config/bootstrap/v3/bootstrap.pb.validate.h"
4
#include "envoy/config/core/v3/address.pb.h"
5
6
#include "source/common/common/random_generator.h"
7
#include "source/common/network/address_impl.h"
8
#include "source/common/thread_local/thread_local_impl.h"
9
#include "source/server/instance_impl.h"
10
#include "source/server/listener_hooks.h"
11
12
#include "test/fuzz/fuzz_runner.h"
13
#include "test/integration/server.h"
14
#include "test/mocks/server/hot_restart.h"
15
#include "test/mocks/server/options.h"
16
#include "test/mocks/stats/mocks.h"
17
#include "test/test_common/environment.h"
18
#include "test/test_common/test_time.h"
19
20
namespace Envoy {
21
namespace Server {
22
namespace {
23
24
void makePortHermetic(Fuzz::PerTestEnvironment& test_env,
25
2.60k
                      envoy::config::core::v3::Address& address) {
26
2.60k
  if (address.has_socket_address()) {
27
156
    address.mutable_socket_address()->set_port_value(0);
28
2.44k
  } else if (address.has_pipe() || address.has_envoy_internal_address()) {
29
    // TODO(asraa): Remove this work-around to replace EnvoyInternalAddress when implemented and
30
    // remove condition at line 74.
31
2.44k
    address.mutable_pipe()->set_path("@" + test_env.testId() + address.pipe().path());
32
2.44k
  }
33
2.60k
}
34
35
envoy::config::bootstrap::v3::Bootstrap
36
makeHermeticPathsAndPorts(Fuzz::PerTestEnvironment& test_env,
37
3.01k
                          const envoy::config::bootstrap::v3::Bootstrap& input) {
38
3.01k
  envoy::config::bootstrap::v3::Bootstrap output(input);
39
  // This is not a complete list of places where we need to zero out ports or sanitize paths, so we
40
  // should adapt it as we go and encounter places that we need to stabilize server test flakes.
41
  // config_validation_fuzz_test doesn't need to do this sanitization, so should pickup the coverage
42
  // we lose here. If we don't sanitize here, we get flakes due to port bind conflicts, file
43
  // conflicts, etc.
44
3.01k
  output.clear_admin();
45
  // The header_prefix is a write-once then read-only singleton that persists across tests. We clear
46
  // this field so that fuzz tests don't fail over multiple iterations.
47
3.01k
  output.clear_header_prefix();
48
3.01k
  for (auto& listener : *output.mutable_static_resources()->mutable_listeners()) {
49
2.83k
    if (listener.has_address()) {
50
2.60k
      makePortHermetic(test_env, *listener.mutable_address());
51
2.60k
    }
52
2.83k
  }
53
3.01k
  for (auto& cluster : *output.mutable_static_resources()->mutable_clusters()) {
54
0
    for (auto& health_check : *cluster.mutable_health_checks()) {
55
      // TODO(asraa): QUIC is not enabled in production code yet, so remove references for HTTP3.
56
      // Tracked at https://github.com/envoyproxy/envoy/issues/9513.
57
0
      if (health_check.http_health_check().codec_client_type() ==
58
0
          envoy::type::v3::CodecClientType::HTTP3) {
59
0
        health_check.mutable_http_health_check()->clear_codec_client_type();
60
0
      }
61
0
    }
62
0
    for (int j = 0; j < cluster.load_assignment().endpoints_size(); ++j) {
63
0
      auto* locality_lb = cluster.mutable_load_assignment()->mutable_endpoints(j);
64
0
      for (int k = 0; k < locality_lb->lb_endpoints_size(); ++k) {
65
0
        auto* lb_endpoint = locality_lb->mutable_lb_endpoints(k);
66
0
        if (lb_endpoint->endpoint().address().has_socket_address() ||
67
0
            lb_endpoint->endpoint().address().has_envoy_internal_address()) {
68
0
          makePortHermetic(test_env, *lb_endpoint->mutable_endpoint()->mutable_address());
69
0
        }
70
0
      }
71
0
    }
72
0
  }
73
3.01k
  return output;
74
3.01k
}
75
76
// When single_host_per_subset is set to be true, only expect 1 subset selector and 1 key inside the
77
// selector. Reject the misconfiguration as the use of single_host_per_subset is well documented.
78
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-lbsubsetconfig-lbsubsetselector
79
3.03k
bool validateLbSubsetConfig(const envoy::config::bootstrap::v3::Bootstrap& input) {
80
3.03k
  for (auto& cluster : input.static_resources().clusters()) {
81
34
    bool use_single_host_per_subset = false;
82
34
    int subset_selectors = 0;
83
34
    for (auto& subset_selector : cluster.lb_subset_config().subset_selectors()) {
84
3
      subset_selectors++;
85
3
      if (subset_selector.single_host_per_subset()) {
86
3
        use_single_host_per_subset = true;
87
3
        const auto& keys = subset_selector.keys();
88
        // Only expect 1 key inside subset selector when use_single_host_per_subset is set to true.
89
3
        if (keys.size() != 1) {
90
1
          return false;
91
1
        }
92
        // Expect key to be non-empty when use_single_host_per_subset is set to true.
93
2
        if (keys[0].empty()) {
94
0
          return false;
95
0
        }
96
2
      }
97
      // Only expect 1 subset selector when use_single_host_per_subset is set to true.
98
2
      if (use_single_host_per_subset && subset_selectors != 1) {
99
0
        return false;
100
0
      }
101
2
    }
102
34
  }
103
3.03k
  return true;
104
3.03k
}
105
106
3.03k
bool validateUpstreamConfig(const envoy::config::bootstrap::v3::Bootstrap& input) {
107
3.03k
  for (auto const& cluster : input.static_resources().clusters()) {
108
22
    if (Envoy::Config::Utility::getFactory<Envoy::Router::GenericConnPoolFactory>(
109
22
            cluster.upstream_config()) == nullptr) {
110
22
      ENVOY_LOG_MISC(debug, "upstream_config: typed config {} invalid",
111
22
                     cluster.upstream_config().DebugString());
112
22
      return false;
113
22
    }
114
22
  }
115
3.01k
  return true;
116
3.03k
}
117
118
3.07k
DEFINE_PROTO_FUZZER(const envoy::config::bootstrap::v3::Bootstrap& input) {
119
3.07k
  try {
120
3.07k
    TestUtility::validate(input);
121
3.07k
  } catch (const ProtoValidationException& e) {
122
43
    ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what());
123
43
    return;
124
43
  }
125
126
3.03k
  if (!validateLbSubsetConfig(input)) {
127
1
    return;
128
1
  }
129
3.03k
  if (!validateUpstreamConfig(input)) {
130
22
    return;
131
22
  }
132
133
3.01k
  testing::NiceMock<MockOptions> options;
134
3.01k
  DefaultListenerHooks hooks;
135
3.01k
  testing::NiceMock<MockHotRestart> restart;
136
3.01k
  Stats::TestIsolatedStoreImpl stats_store;
137
3.01k
  Thread::MutexBasicLockable fakelock;
138
3.01k
  TestComponentFactory component_factory;
139
3.01k
  ThreadLocal::InstanceImpl thread_local_instance;
140
3.01k
  DangerousDeprecatedTestTime test_time;
141
3.01k
  Fuzz::PerTestEnvironment test_env;
142
3.01k
  Init::ManagerImpl init_manager{"Server"};
143
144
3.01k
  {
145
3.01k
    const std::string bootstrap_path = test_env.temporaryPath("bootstrap.pb_text");
146
3.01k
    std::ofstream bootstrap_file(bootstrap_path);
147
3.01k
    bootstrap_file << makeHermeticPathsAndPorts(test_env, input).DebugString();
148
3.01k
    options.config_path_ = bootstrap_path;
149
3.01k
    options.log_level_ = Fuzz::Runner::logLevel();
150
3.01k
  }
151
152
3.01k
  std::unique_ptr<InstanceImpl> server;
153
3.01k
  try {
154
3.01k
    server = std::make_unique<InstanceImpl>(
155
3.01k
        init_manager, options, test_time.timeSystem(), hooks, restart, stats_store, fakelock,
156
3.01k
        std::make_unique<Random::RandomGeneratorImpl>(), thread_local_instance,
157
3.01k
        Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), nullptr);
158
3.01k
    server->initialize(std::make_shared<Network::Address::Ipv4Instance>("127.0.0.1"),
159
3.01k
                       component_factory);
160
3.01k
  } catch (const EnvoyException& ex) {
161
1.94k
    ENVOY_LOG_MISC(debug, "Controlled EnvoyException exit: {}", ex.what());
162
1.94k
    return;
163
1.94k
  }
164
  // Ensure the event loop gets at least one event to end the test.
165
1.06k
  auto end_timer =
166
1.06k
      server->dispatcher().createTimer([]() { ENVOY_LOG_MISC(trace, "server timer fired"); });
167
1.06k
  end_timer->enableTimer(std::chrono::milliseconds(5000));
168
  // If we were successful, run any pending events on the main thread's dispatcher loop. These might
169
  // be, for example, pending DNS resolution callbacks. If they generate exceptions, we want to
170
  // explode and fail the test, hence we do this outside of the try-catch above.
171
1.06k
  server->dispatcher().run(Event::Dispatcher::RunType::NonBlock);
172
1.06k
}
173
174
} // namespace
175
} // namespace Server
176
} // namespace Envoy