Coverage Report

Created: 2023-11-12 09:30

/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
3.25k
                      envoy::config::core::v3::Address& address) {
26
3.25k
  if (address.has_socket_address()) {
27
159
    address.mutable_socket_address()->set_port_value(0);
28
3.09k
  } 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
3.09k
    address.mutable_pipe()->set_path("@" + test_env.testId() + address.pipe().path());
32
3.09k
  }
33
3.25k
}
34
35
envoy::config::bootstrap::v3::Bootstrap
36
makeHermeticPathsAndPorts(Fuzz::PerTestEnvironment& test_env,
37
2.84k
                          const envoy::config::bootstrap::v3::Bootstrap& input) {
38
2.84k
  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
2.84k
  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
2.84k
  output.clear_header_prefix();
48
3.40k
  for (auto& listener : *output.mutable_static_resources()->mutable_listeners()) {
49
3.40k
    if (listener.has_address()) {
50
3.25k
      makePortHermetic(test_env, *listener.mutable_address());
51
3.25k
    }
52
3.40k
  }
53
2.84k
  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
2.84k
  return output;
74
2.84k
}
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
2.86k
bool validateLbSubsetConfig(const envoy::config::bootstrap::v3::Bootstrap& input) {
80
2.86k
  for (auto& cluster : input.static_resources().clusters()) {
81
43
    bool use_single_host_per_subset = false;
82
43
    int subset_selectors = 0;
83
43
    for (auto& subset_selector : cluster.lb_subset_config().subset_selectors()) {
84
9
      subset_selectors++;
85
9
      if (subset_selector.single_host_per_subset()) {
86
2
        use_single_host_per_subset = true;
87
2
        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
2
        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
1
        if (keys[0].empty()) {
94
0
          return false;
95
0
        }
96
1
      }
97
      // Only expect 1 subset selector when use_single_host_per_subset is set to true.
98
8
      if (use_single_host_per_subset && subset_selectors != 1) {
99
0
        return false;
100
0
      }
101
8
    }
102
43
  }
103
2.86k
  return true;
104
2.86k
}
105
106
2.86k
bool validateUpstreamConfig(const envoy::config::bootstrap::v3::Bootstrap& input) {
107
2.86k
  for (auto const& cluster : input.static_resources().clusters()) {
108
25
    if (Envoy::Config::Utility::getFactory<Envoy::Router::GenericConnPoolFactory>(
109
25
            cluster.upstream_config()) == nullptr) {
110
25
      ENVOY_LOG_MISC(debug, "upstream_config: typed config {} invalid",
111
25
                     cluster.upstream_config().DebugString());
112
25
      return false;
113
25
    }
114
25
  }
115
2.84k
  return true;
116
2.86k
}
117
118
2.89k
DEFINE_PROTO_FUZZER(const envoy::config::bootstrap::v3::Bootstrap& input) {
119
2.89k
  try {
120
2.89k
    TestUtility::validate(input);
121
2.89k
  } catch (const ProtoValidationException& e) {
122
27
    ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what());
123
27
    return;
124
27
  }
125
126
2.86k
  if (!validateLbSubsetConfig(input)) {
127
1
    return;
128
1
  }
129
2.86k
  if (!validateUpstreamConfig(input)) {
130
25
    return;
131
25
  }
132
133
2.84k
  testing::NiceMock<MockOptions> options;
134
2.84k
  DefaultListenerHooks hooks;
135
2.84k
  testing::NiceMock<MockHotRestart> restart;
136
2.84k
  Stats::TestIsolatedStoreImpl stats_store;
137
2.84k
  Thread::MutexBasicLockable fakelock;
138
2.84k
  TestComponentFactory component_factory;
139
2.84k
  ThreadLocal::InstanceImpl thread_local_instance;
140
2.84k
  DangerousDeprecatedTestTime test_time;
141
2.84k
  Fuzz::PerTestEnvironment test_env;
142
2.84k
  Init::ManagerImpl init_manager{"Server"};
143
144
2.84k
  {
145
2.84k
    const std::string bootstrap_path = test_env.temporaryPath("bootstrap.pb_text");
146
2.84k
    std::ofstream bootstrap_file(bootstrap_path);
147
2.84k
    bootstrap_file << makeHermeticPathsAndPorts(test_env, input).DebugString();
148
2.84k
    options.config_path_ = bootstrap_path;
149
2.84k
    options.log_level_ = Fuzz::Runner::logLevel();
150
2.84k
  }
151
152
2.84k
  std::unique_ptr<InstanceImpl> server;
153
2.84k
  try {
154
2.84k
    server = std::make_unique<InstanceImpl>(
155
2.84k
        init_manager, options, test_time.timeSystem(), hooks, restart, stats_store, fakelock,
156
2.84k
        std::make_unique<Random::RandomGeneratorImpl>(), thread_local_instance,
157
2.84k
        Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), nullptr);
158
2.84k
    server->initialize(std::make_shared<Network::Address::Ipv4Instance>("127.0.0.1"),
159
2.84k
                       component_factory);
160
2.84k
  } catch (const EnvoyException& ex) {
161
1.67k
    ENVOY_LOG_MISC(debug, "Controlled EnvoyException exit: {}", ex.what());
162
1.67k
    return;
163
1.67k
  }
164
  // Ensure the event loop gets at least one event to end the test.
165
1.16k
  auto end_timer =
166
1.16k
      server->dispatcher().createTimer([]() { ENVOY_LOG_MISC(trace, "server timer fired"); });
167
1.16k
  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.16k
  server->dispatcher().run(Event::Dispatcher::RunType::NonBlock);
172
1.16k
}
173
174
} // namespace
175
} // namespace Server
176
} // namespace Envoy