Skip to content

Commit 226a603

Browse files
authored
fuzz: H1 capture fuzz test performance improvements. (envoyproxy#10281)
The main contribution in this patch is a "persistent" mode for h1_capture_[direct_response_]fuzz_test. Based on profiling observations, we were spending 30-40% of time rebuilding the Envoy server on each run. This is avoided by having fuzzer variants that makes the integration test proxy static. There is a downside of this approach, since different fuzz invocations may interfere with each other. Ideally we would snapshot/fork for each fuzz invocation, but Envoy doesn't like forking once events/dispatchers are up. So, for now we have two builds of the fuzzer, where we trade fuzz engine efficacy for fuzz target performance. Some form of VM snapshotting would be ideal. The persistent mode takes the H1 replay tests to O(10 exec/s) from O(1 exec/s). This is still not great. Doing some perf analysis, it seems that we're spending the bulk of time in ASAN. Running the fuzzers without ASAN gives O(100 exec/s), which seems reasonable for a LPM-style integration test. It's future work why ASAN is so expensive, ASAN advertises itself as generally a 2x slowdown. There is also some secondary effect from the cost of mocks used in the integration test TCP client (mock watermark buffer), this speaks to our general mocking performance problem in fuzzing. In addition to the above, this patch has an optimization for the direct response fuzzer (don't initiate upstream connections) and a --config=plain-fuzz mode for peformance work without confounding ASAN. Risk level: Low Testing: Manual bazel runs of the fuzzers, observing exec/s. Signed-off-by: Harvey Tuch <[email protected]>
1 parent bd7c978 commit 226a603

7 files changed

+67
-17
lines changed

.bazelrc

+6
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,14 @@ build:asan-fuzzer --config=clang-asan
190190
build:asan-fuzzer --define=FUZZING_ENGINE=libfuzzer
191191
build:asan-fuzzer --copt=-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
192192
build:asan-fuzzer --copt=-fsanitize=fuzzer-no-link
193+
build:asan-fuzzer --copt=-fno-omit-frame-pointer
193194
# Remove UBSAN halt_on_error to avoid crashing on protobuf errors.
194195
build:asan-fuzzer --test_env=UBSAN_OPTIONS=print_stacktrace=1
195196

197+
# Fuzzing without ASAN. This is useful for profiling fuzzers without any ASAN artifacts.
198+
build:plain-fuzzer --define=FUZZING_ENGINE=libfuzzer
199+
build:plain-fuzzer --copt=-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
200+
build:plain-fuzzer --copt=-fsanitize=fuzzer-no-link
201+
196202
try-import %workspace%/clang.bazelrc
197203
try-import %workspace%/user.bazelrc

test/fuzz/fuzz_runner.h

+6
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv);
6262
// See https://llvm.org/docs/LibFuzzer.html#fuzz-target.
6363
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
6464

65+
#ifdef PERSISTENT_FUZZER
66+
#define PERSISTENT_FUZZ_VAR static
67+
#else
68+
#define PERSISTENT_FUZZ_VAR
69+
#endif
70+
6571
#define DEFINE_TEST_ONE_INPUT_IMPL \
6672
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { \
6773
EnvoyTestOneInput(data, size); \

test/integration/BUILD

+38-9
Original file line numberDiff line numberDiff line change
@@ -975,19 +975,29 @@ envoy_cc_test(
975975
],
976976
)
977977

978+
H1_FUZZ_LIB_DEPS = [
979+
":capture_fuzz_proto_cc_proto",
980+
":http_integration_lib",
981+
"//source/common/common:assert_lib",
982+
"//source/common/common:logger_lib",
983+
"//test/fuzz:fuzz_runner_lib",
984+
"//test/integration:integration_lib",
985+
"//test/test_common:environment_lib",
986+
]
987+
978988
envoy_cc_test_library(
979989
name = "h1_fuzz_lib",
980990
srcs = ["h1_fuzz.cc"],
981991
hdrs = ["h1_fuzz.h"],
982-
deps = [
983-
":capture_fuzz_proto_cc_proto",
984-
":http_integration_lib",
985-
"//source/common/common:assert_lib",
986-
"//source/common/common:logger_lib",
987-
"//test/fuzz:fuzz_runner_lib",
988-
"//test/integration:integration_lib",
989-
"//test/test_common:environment_lib",
990-
],
992+
deps = H1_FUZZ_LIB_DEPS,
993+
)
994+
995+
envoy_cc_test_library(
996+
name = "h1_fuzz_persistent_lib",
997+
srcs = ["h1_fuzz.cc"],
998+
hdrs = ["h1_fuzz.h"],
999+
copts = ["-DPERSISTENT_FUZZER"],
1000+
deps = H1_FUZZ_LIB_DEPS,
9911001
)
9921002

9931003
envoy_cc_fuzz_test(
@@ -997,6 +1007,14 @@ envoy_cc_fuzz_test(
9971007
deps = [":h1_fuzz_lib"],
9981008
)
9991009

1010+
envoy_cc_fuzz_test(
1011+
name = "h1_capture_persistent_fuzz_test",
1012+
srcs = ["h1_capture_fuzz_test.cc"],
1013+
copts = ["-DPERSISTENT_FUZZER"],
1014+
corpus = "h1_corpus",
1015+
deps = [":h1_fuzz_persistent_lib"],
1016+
)
1017+
10001018
envoy_cc_fuzz_test(
10011019
name = "h1_capture_direct_response_fuzz_test",
10021020
srcs = ["h1_capture_direct_response_fuzz_test.cc"],
@@ -1007,6 +1025,17 @@ envoy_cc_fuzz_test(
10071025
],
10081026
)
10091027

1028+
envoy_cc_fuzz_test(
1029+
name = "h1_capture_direct_response_persistent_fuzz_test",
1030+
srcs = ["h1_capture_direct_response_fuzz_test.cc"],
1031+
copts = ["-DPERSISTENT_FUZZER"],
1032+
corpus = "h1_corpus",
1033+
deps = [
1034+
":h1_fuzz_persistent_lib",
1035+
"@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto",
1036+
],
1037+
)
1038+
10101039
envoy_cc_test(
10111040
name = "scoped_rds_integration_test",
10121041
srcs = [

test/integration/h1_capture_direct_response_fuzz_test.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ void H1FuzzIntegrationTest::initialize() {
3131
DEFINE_PROTO_FUZZER(const test::integration::CaptureFuzzTestCase& input) {
3232
RELEASE_ASSERT(!TestEnvironment::getIpVersionsForTest().empty(), "");
3333
const auto ip_version = TestEnvironment::getIpVersionsForTest()[0];
34-
H1FuzzIntegrationTest h1_fuzz_integration_test(ip_version);
35-
h1_fuzz_integration_test.replay(input);
34+
PERSISTENT_FUZZ_VAR H1FuzzIntegrationTest h1_fuzz_integration_test(ip_version);
35+
h1_fuzz_integration_test.replay(input, true);
3636
}
3737

3838
} // namespace Envoy

test/integration/h1_capture_fuzz_test.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ DEFINE_PROTO_FUZZER(const test::integration::CaptureFuzzTestCase& input) {
77
// Pick an IP version to use for loopback, it doesn't matter which.
88
RELEASE_ASSERT(!TestEnvironment::getIpVersionsForTest().empty(), "");
99
const auto ip_version = TestEnvironment::getIpVersionsForTest()[0];
10-
H1FuzzIntegrationTest h1_fuzz_integration_test(ip_version);
11-
h1_fuzz_integration_test.replay(input);
10+
PERSISTENT_FUZZ_VAR H1FuzzIntegrationTest h1_fuzz_integration_test(ip_version);
11+
h1_fuzz_integration_test.replay(input, false);
1212
}
1313

1414
} // namespace Envoy

test/integration/h1_fuzz.cc

+11-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010

1111
namespace Envoy {
1212

13-
void H1FuzzIntegrationTest::replay(const test::integration::CaptureFuzzTestCase& input) {
14-
initialize();
15-
fake_upstreams_[0]->set_allow_unexpected_disconnects(true);
13+
void H1FuzzIntegrationTest::replay(const test::integration::CaptureFuzzTestCase& input,
14+
bool ignore_response) {
15+
PERSISTENT_FUZZ_VAR bool initialized = [this]() -> bool {
16+
initialize();
17+
fake_upstreams_[0]->set_allow_unexpected_disconnects(true);
18+
return true;
19+
}();
20+
UNREFERENCED_PARAMETER(initialized);
1621
IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("http"));
1722
FakeRawConnectionPtr fake_upstream_connection;
1823
for (int i = 0; i < input.events().size(); ++i) {
@@ -31,6 +36,9 @@ void H1FuzzIntegrationTest::replay(const test::integration::CaptureFuzzTestCase&
3136
// TODO(htuch): Should we wait for some data?
3237
break;
3338
case test::integration::Event::kUpstreamSendBytes:
39+
if (ignore_response) {
40+
break;
41+
}
3442
if (fake_upstream_connection == nullptr) {
3543
if (!fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection, max_wait_ms_)) {
3644
// If we timed out, we fail out.

test/integration/h1_fuzz.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class H1FuzzIntegrationTest : public HttpIntegrationTest {
1515
: HttpIntegrationTest(Http::CodecClient::Type::HTTP1, version) {}
1616

1717
void initialize() override;
18-
void replay(const test::integration::CaptureFuzzTestCase&);
18+
void replay(const test::integration::CaptureFuzzTestCase&, bool ignore_response);
1919
const std::chrono::milliseconds max_wait_ms_{10};
2020
};
21+
2122
} // namespace Envoy

0 commit comments

Comments
 (0)