Skip to content

Commit 42c5811

Browse files
laramielcopybara-github
authored andcommitted
Update FuzzingBitGen to reduce possible infinite loops.
The old FuzzingBitGen algorithm would pull a byte from the head of the data stream to determine whether to return a min or a max, and then it would attempt to pull variate data from the tail of the stream. Once the data stream was expired it would return minimum values from the distributions, which could lead to infinite loops in rejection sampling algorithms. The updated FuzzingBitGen now takes the following. 1. Separate control and a data streams (as absl::Span<uint8_t>). The control stream indicates whether the distribution functions will return boundary values (min, max, mean) or a value derived from the data stream. The data stream provides the actual byte data for generating random values. 2. A seed for an internal LCG PRNG (as uint64_t). When the data stream is exhausted, FuzzingBitGen uses the internal LCG to generate random variates. While the old version had an internal LCG PRNG, those values were not used by the distribution functions. The basic flow of each variate generation is: 1. Read a byte from the control stream (in fuzzing_bit_gen). 2. Depending on the byte, return a min/max/mean/variate, etc. 3. Once the data stream is expired, use an internal LCG to generate variates. This update also calls c++ distribution functions in more cases, so that outputs are more aligned with actual distribution behavior. Adds a test to demonstrate that std::shuffle() is properly manipulated by the fuzzing framework. Also add additional tests to FuzzingBitGen for the distribution functions. NOTE: This will change the variates generated by FuzzingBitGen from prior versions. PiperOrigin-RevId: 873431571
1 parent 41e1606 commit 42c5811

19 files changed

+994
-343
lines changed

domain_tests/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ cc_test(
106106
"@abseil-cpp//absl/random",
107107
"@abseil-cpp//absl/random:bit_gen_ref",
108108
"@com_google_fuzztest//fuzztest:domain_core",
109+
"@com_google_fuzztest//fuzztest/internal:printer",
109110
"@googletest//:gtest_main",
110111
],
111112
)

domain_tests/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,21 @@ fuzztest_cc_test(
8080
GTest::gmock_main
8181
)
8282

83+
fuzztest_cc_test(
84+
NAME
85+
bitgen_ref_domain_test
86+
SRCS
87+
"bitgen_ref_domain_test.cc"
88+
DEPS
89+
fuzztest::domain_testing
90+
absl::random_bit_gen_ref
91+
absl::random_random
92+
fuzztest::domain_core
93+
fuzztest::meta
94+
fuzztest::printer
95+
GTest::gmock_main
96+
)
97+
8398
fuzztest_cc_test(
8499
NAME
85100
container_combinators_test

domain_tests/bitgen_ref_domain_test.cc

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
#include <algorithm>
16+
#include <cstddef>
17+
#include <string>
1518
#include <vector>
1619

20+
#include "gmock/gmock.h"
1721
#include "gtest/gtest.h"
1822
#include "absl/random/bit_gen_ref.h"
1923
#include "absl/random/random.h"
2024
#include "./fuzztest/domain_core.h"
2125
#include "./domain_tests/domain_testing.h"
26+
#include "./fuzztest/internal/printer.h"
2227

2328
namespace fuzztest {
2429
namespace {
@@ -30,33 +35,77 @@ TEST(BitGenRefDomainTest, DistinctVariatesGeneratedByCallOperator) {
3035
Value v0(domain, bitgen_for_seeding);
3136
Value v1(domain, bitgen_for_seeding);
3237

33-
// Discard the first value, which may be from the data stream.
34-
// If the implementation of BitGenRefDomain changes this may break.
35-
v0.user_value();
36-
v1.user_value();
37-
3838
std::vector<absl::BitGenRef::result_type> a, b;
3939
for (int i = 0; i < 10; ++i) {
4040
a.push_back(v0.user_value());
4141
b.push_back(v1.user_value());
4242
}
43+
44+
// These streams should be different, except in very rare cases.
4345
EXPECT_NE(a, b);
4446
}
4547

46-
TEST(BitGenRefDomainTest, AbseilUniformReturnsLowerBoundWhenExhausted) {
48+
TEST(BitGenRefDomainTest, AbseilUniformIsFunctionalWhenExhausted) {
4749
absl::BitGen bitgen_for_seeding;
4850

4951
Domain<absl::BitGenRef> domain = Arbitrary<absl::BitGenRef>();
5052
Value v0(domain, bitgen_for_seeding);
5153

52-
// Discard the first value, which may be from the data stream.
53-
// If the implementation of BitGenRefDomain changes this may break.
54-
v0.user_value();
54+
// When the same domain is used to generate multiple values, the generated
55+
// data sequence should be identical.
56+
std::vector<int> values;
57+
for (int i = 0; i < 20; ++i) {
58+
values.push_back(absl::Uniform<int>(v0.user_value, 0, 100));
59+
}
5560

56-
for (int i = 0; i < 10; ++i) {
57-
EXPECT_EQ(absl::Uniform<int>(v0.user_value, 0, 100), 0);
61+
// Verify repeatability
62+
Value v1(v0, domain);
63+
for (int i = 0; i < 20; ++i) {
64+
EXPECT_EQ(absl::Uniform<int>(v1.user_value, 0, 100), values[i]);
5865
}
5966
}
6067

68+
TEST(BitGenRefDomainTest, IsPrintable) {
69+
absl::BitGen bitgen_for_seeding;
70+
71+
Domain<absl::BitGenRef> domain = Arbitrary<absl::BitGenRef>();
72+
Value v0(domain, bitgen_for_seeding);
73+
74+
// Print corpus value
75+
std::string s;
76+
domain.GetPrinter().PrintCorpusValue(
77+
v0.corpus_value, &s, domain_implementor::PrintMode::kHumanReadable);
78+
EXPECT_THAT(s, testing::StartsWith("FuzzingBitGen"));
79+
}
80+
81+
TEST(BitGenRefDomainTest, MutateUntilSorted) {
82+
absl::BitGen bitgen_for_seeding;
83+
std::vector<int> vec;
84+
85+
// There are 5! possible permutations (120) of the 5-element vector. If
86+
// FuzzingBitGen generated completely random values, then the likelihood
87+
// that looping 10k times would not find a sorted permutation is
88+
// approximately e^(-10000/120).
89+
// However the `Mutate` operation does not generate an entirely new prng
90+
// sequence, so we reset the value every 10 iterations instead.
91+
int count = 0;
92+
[&]() {
93+
while (count < 10000) {
94+
auto domain = Arbitrary<absl::BitGenRef>();
95+
Value fuzz_rng(domain, bitgen_for_seeding);
96+
vec = {5, 4, 1, 3, 2};
97+
98+
for (size_t j = 0; j < 10; ++j) {
99+
count++;
100+
std::shuffle(vec.begin(), vec.end(), fuzz_rng.user_value);
101+
if (std::is_sorted(vec.begin(), vec.end())) return;
102+
fuzz_rng.Mutate(domain, bitgen_for_seeding, {}, false);
103+
}
104+
}
105+
}();
106+
107+
EXPECT_TRUE(std::is_sorted(vec.begin(), vec.end()));
108+
}
109+
61110
} // namespace
62111
} // namespace fuzztest

e2e_tests/functional_test.cc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1703,7 +1703,16 @@ TEST_P(FuzzingModeCrashFindingTest, VectorValueTestFindsAbortInFuzzingMode) {
17031703

17041704
TEST_P(FuzzingModeCrashFindingTest, BitGenRefTestFindsAbortInFuzzingMode) {
17051705
auto [status, std_out, std_err] = Run("MySuite.BitGenRef");
1706-
EXPECT_THAT_LOG(std_err, HasSubstr("argument 0: absl::BitGenRef{}"));
1706+
EXPECT_THAT_LOG(std_err, AnyOf(HasSubstr("argument 0: absl::BitGenRef{}"),
1707+
HasSubstr("argument 0: FuzzingBitGen")));
1708+
ExpectTargetAbort(status, std_err);
1709+
}
1710+
1711+
TEST_P(FuzzingModeCrashFindingTest,
1712+
BitGenRefShuffleTestFindsAbortInFuzzingMode) {
1713+
auto [status, std_out, std_err] = Run("MySuite.BitGenRefShuffle");
1714+
EXPECT_THAT_LOG(std_err, AnyOf(HasSubstr("argument 0: absl::BitGenRef{}"),
1715+
HasSubstr("argument 0: FuzzingBitGen")));
17071716
ExpectTargetAbort(status, std_err);
17081717
}
17091718

e2e_tests/testdata/fuzz_tests_for_microbenchmarking.cc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
// i.e., to check that the fuzzer behaves as expected and outputs the expected
2525
// results. E.g., the fuzzer finds the abort() or bug.
2626

27+
#include <algorithm>
2728
#include <array>
2829
#include <cmath>
2930
#include <cstdint>
@@ -33,6 +34,7 @@
3334
#include <string>
3435
#include <string_view>
3536
#include <tuple>
37+
#include <type_traits>
3638
#include <utility>
3739
#include <vector>
3840

@@ -240,15 +242,26 @@ FUZZ_TEST(MySuite, FixedSizeVectorValue)
240242
.WithDomains(fuzztest::VectorOf(fuzztest::Arbitrary<char>()).WithSize(4));
241243

242244
__attribute__((optnone)) void BitGenRef(absl::BitGenRef bitgen) {
245+
// This uses FuzzingBitGen's mocking support for absl::Uniform().
243246
if (absl::Uniform(bitgen, 0, 256) == 'F' &&
244247
absl::Uniform(bitgen, 0, 256) == 'U' &&
245-
absl::Uniform(bitgen, 0, 256) == 'Z' &&
246-
absl::Uniform(bitgen, 0, 256) == 'Z') {
248+
absl::Uniform(bitgen, 32, 128) == 'Z' &&
249+
absl::Uniform(bitgen, 32, 128) == 'Z') {
247250
std::abort(); // Bug!
248251
}
249252
}
250253
FUZZ_TEST(MySuite, BitGenRef);
251254

255+
__attribute__((optnone)) void BitGenRefShuffle(absl::BitGenRef bitgen) {
256+
// This uses FuzzingBitGen's operator().
257+
std::vector<int> v = {4, 1, 3, 2, 5};
258+
std::shuffle(v.begin(), v.end(), bitgen);
259+
if (std::is_sorted(v.begin(), v.end())) {
260+
std::abort(); // Bug!
261+
}
262+
}
263+
FUZZ_TEST(MySuite, BitGenRefShuffle);
264+
252265
__attribute__((optnone)) void WithDomainClass(uint8_t a, double d) {
253266
// This will only crash with a=10, to make it easier to check the results.
254267
// d can have any value.

fuzztest/BUILD

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,18 +219,31 @@ cc_library(
219219
],
220220
)
221221

222+
cc_test(
223+
name = "fuzzing_bit_gen_test",
224+
srcs = ["fuzzing_bit_gen_test.cc"],
225+
deps = [
226+
":fuzzing_bit_gen",
227+
"@abseil-cpp//absl/random",
228+
"@abseil-cpp//absl/random:bit_gen_ref",
229+
"@googletest//:gtest_main",
230+
],
231+
)
232+
222233
cc_library(
223234
name = "fuzzing_bit_gen",
224235
srcs = ["fuzzing_bit_gen.cc"],
225236
hdrs = ["fuzzing_bit_gen.h"],
226237
deps = [
238+
"@abseil-cpp//absl/base:core_headers",
227239
"@abseil-cpp//absl/base:fast_type_id",
228240
"@abseil-cpp//absl/base:no_destructor",
229241
"@abseil-cpp//absl/container:flat_hash_map",
230242
"@abseil-cpp//absl/numeric:bits",
231243
"@abseil-cpp//absl/numeric:int128",
232244
"@abseil-cpp//absl/random:bit_gen_ref",
233245
"@abseil-cpp//absl/types:span",
246+
"@com_google_fuzztest//fuzztest/internal:fuzzing_mock_stream",
234247
"@com_google_fuzztest//fuzztest/internal:register_fuzzing_mocks",
235248
],
236249
)

fuzztest/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,30 @@ fuzztest_cc_library(
6060
SRCS
6161
"fuzzing_bit_gen.cc"
6262
DEPS
63+
absl::core_headers
6364
absl::fast_type_id
6465
absl::no_destructor
6566
absl::flat_hash_map
6667
absl::bits
6768
absl::int128
6869
absl::random_bit_gen_ref
6970
absl::span
71+
fuzztest::fuzzing_mock_stream
7072
fuzztest::register_fuzzing_mocks
7173
)
7274

75+
fuzztest_cc_test(
76+
NAME
77+
fuzzing_bit_gen_test
78+
SRCS
79+
"fuzzing_bit_gen_test.cc"
80+
DEPS
81+
fuzztest::fuzzing_bit_gen
82+
absl::random_random
83+
absl::random_bit_gen_ref
84+
GTest::gmock_main
85+
)
86+
7387
fuzztest_cc_library(
7488
NAME
7589
fuzztest

0 commit comments

Comments
 (0)