Skip to content

Commit cc668d0

Browse files
tests: Add fuzzing harness for strprintf(...)
1 parent ccc3c76 commit cc668d0

File tree

3 files changed

+155
-1
lines changed

3 files changed

+155
-1
lines changed

src/Makefile.test.include

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ FUZZ_TARGETS = \
5454
test/fuzz/script_flags \
5555
test/fuzz/service_deserialize \
5656
test/fuzz/spanparsing \
57+
test/fuzz/strprintf \
5758
test/fuzz/sub_net_deserialize \
5859
test/fuzz/transaction \
5960
test/fuzz/tx_in \
@@ -535,6 +536,12 @@ test_fuzz_spanparsing_LDADD = $(FUZZ_SUITE_LD_COMMON)
535536
test_fuzz_spanparsing_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
536537
test_fuzz_spanparsing_SOURCES = $(FUZZ_SUITE) test/fuzz/spanparsing.cpp
537538

539+
test_fuzz_strprintf_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
540+
test_fuzz_strprintf_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
541+
test_fuzz_strprintf_LDADD = $(FUZZ_SUITE_LD_COMMON)
542+
test_fuzz_strprintf_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
543+
test_fuzz_strprintf_SOURCES = $(FUZZ_SUITE) test/fuzz/strprintf.cpp
544+
538545
test_fuzz_sub_net_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DSUB_NET_DESERIALIZE=1
539546
test_fuzz_sub_net_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
540547
test_fuzz_sub_net_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)

src/test/fuzz/strprintf.cpp

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright (c) 2020 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <test/fuzz/FuzzedDataProvider.h>
6+
#include <test/fuzz/fuzz.h>
7+
#include <tinyformat.h>
8+
#include <util/strencodings.h>
9+
10+
#include <algorithm>
11+
#include <cassert>
12+
#include <cstdint>
13+
#include <string>
14+
#include <vector>
15+
16+
void test_one_input(const std::vector<uint8_t>& buffer)
17+
{
18+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
19+
const std::string format_string = fuzzed_data_provider.ConsumeRandomLengthString(64);
20+
21+
const int digits_in_format_specifier = std::count_if(format_string.begin(), format_string.end(), IsDigit);
22+
23+
// Avoid triggering the following crash bug:
24+
// * strprintf("%987654321000000:", 1);
25+
//
26+
// Avoid triggering the following OOM bug:
27+
// * strprintf("%.222222200000000$", 1.1);
28+
//
29+
// Upstream bug report: https://github.com/c42f/tinyformat/issues/70
30+
if (format_string.find("%") != std::string::npos && digits_in_format_specifier >= 7) {
31+
return;
32+
}
33+
34+
// Avoid triggering the following crash bug:
35+
// * strprintf("%1$*1$*", -11111111);
36+
//
37+
// Upstream bug report: https://github.com/c42f/tinyformat/issues/70
38+
if (format_string.find("%") != std::string::npos && format_string.find("$") != std::string::npos && format_string.find("*") != std::string::npos && digits_in_format_specifier > 0) {
39+
return;
40+
}
41+
42+
// Avoid triggering the following crash bug:
43+
// * strprintf("%.1s", (char*)nullptr);
44+
//
45+
// (void)strprintf(format_string, (char*)nullptr);
46+
//
47+
// Upstream bug report: https://github.com/c42f/tinyformat/issues/70
48+
49+
try {
50+
(void)strprintf(format_string, (signed char*)nullptr);
51+
} catch (const tinyformat::format_error&) {
52+
}
53+
try {
54+
(void)strprintf(format_string, (unsigned char*)nullptr);
55+
} catch (const tinyformat::format_error&) {
56+
}
57+
try {
58+
(void)strprintf(format_string, (void*)nullptr);
59+
} catch (const tinyformat::format_error&) {
60+
}
61+
try {
62+
(void)strprintf(format_string, (bool*)nullptr);
63+
} catch (const tinyformat::format_error&) {
64+
}
65+
try {
66+
(void)strprintf(format_string, (float*)nullptr);
67+
} catch (const tinyformat::format_error&) {
68+
}
69+
try {
70+
(void)strprintf(format_string, (double*)nullptr);
71+
} catch (const tinyformat::format_error&) {
72+
}
73+
try {
74+
(void)strprintf(format_string, (int16_t*)nullptr);
75+
} catch (const tinyformat::format_error&) {
76+
}
77+
try {
78+
(void)strprintf(format_string, (uint16_t*)nullptr);
79+
} catch (const tinyformat::format_error&) {
80+
}
81+
try {
82+
(void)strprintf(format_string, (int32_t*)nullptr);
83+
} catch (const tinyformat::format_error&) {
84+
}
85+
try {
86+
(void)strprintf(format_string, (uint32_t*)nullptr);
87+
} catch (const tinyformat::format_error&) {
88+
}
89+
try {
90+
(void)strprintf(format_string, (int64_t*)nullptr);
91+
} catch (const tinyformat::format_error&) {
92+
}
93+
try {
94+
(void)strprintf(format_string, (uint64_t*)nullptr);
95+
} catch (const tinyformat::format_error&) {
96+
}
97+
98+
try {
99+
switch (fuzzed_data_provider.ConsumeIntegralInRange(0, 13)) {
100+
case 0:
101+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeRandomLengthString(32));
102+
break;
103+
case 1:
104+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeRandomLengthString(32).c_str());
105+
break;
106+
case 2:
107+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<signed char>());
108+
break;
109+
case 3:
110+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<unsigned char>());
111+
break;
112+
case 4:
113+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<char>());
114+
break;
115+
case 5:
116+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeBool());
117+
break;
118+
case 6:
119+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeFloatingPoint<float>());
120+
break;
121+
case 7:
122+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeFloatingPoint<double>());
123+
break;
124+
case 8:
125+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<int16_t>());
126+
break;
127+
case 9:
128+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<uint16_t>());
129+
break;
130+
case 10:
131+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<int32_t>());
132+
break;
133+
case 11:
134+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<uint32_t>());
135+
break;
136+
case 12:
137+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<int64_t>());
138+
break;
139+
case 13:
140+
(void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral<uint64_t>());
141+
break;
142+
default:
143+
assert(false);
144+
}
145+
} catch (const tinyformat::format_error&) {
146+
}
147+
}

test/lint/lint-format-strings.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ if ! python3 -m doctest test/lint/lint-format-strings.py; then
3434
fi
3535
for S in "${FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS[@]}"; do
3636
IFS="," read -r FUNCTION_NAME SKIP_ARGUMENTS <<< "${S}"
37-
for MATCHING_FILE in $(git grep --full-name -l "${FUNCTION_NAME}" -- "*.c" "*.cpp" "*.h" | sort | grep -vE "^src/(leveldb|secp256k1|tinyformat|univalue)"); do
37+
for MATCHING_FILE in $(git grep --full-name -l "${FUNCTION_NAME}" -- "*.c" "*.cpp" "*.h" | sort | grep -vE "^src/(leveldb|secp256k1|tinyformat|univalue|test/fuzz/strprintf.cpp)"); do
3838
MATCHING_FILES+=("${MATCHING_FILE}")
3939
done
4040
if ! test/lint/lint-format-strings.py --skip-arguments "${SKIP_ARGUMENTS}" "${FUNCTION_NAME}" "${MATCHING_FILES[@]}"; then

0 commit comments

Comments
 (0)