Skip to content

Commit 50591fb

Browse files
Fuzz fail new (#1164)
Additional fuzz failures from longer runs of fuzzer. Ran the fuzzer for a couple hours. Picked up a few interesting bug particularly in the config out and return. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 2c787a5 commit 50591fb

19 files changed

+455
-104
lines changed

fuzz/cli11_app_fuzz.cpp

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
1717
std::string parseString(reinterpret_cast<const char *>(Data), Size);
1818

1919
CLI::FuzzApp fuzzdata;
20-
CLI::FuzzApp fuzzdata2;
20+
2121
auto app = fuzzdata.generateApp();
22-
auto app2 = fuzzdata2.generateApp();
2322
std::size_t pstring_start{0};
2423
try {
2524
pstring_start = fuzzdata.add_custom_options(app.get(), parseString);
@@ -40,17 +39,21 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
4039
// this just indicates we caught an error known by CLI
4140
return 0; // Non-zero return values are reserved for future use.
4241
}
43-
// should be able to write the config to a file and read from it again
44-
std::string configOut = app->config_to_str();
45-
app->clear();
46-
std::stringstream out(configOut);
47-
if(pstring_start > 0) {
48-
fuzzdata2.add_custom_options(app2.get(), parseString);
49-
}
50-
app2->parse_from_stream(out);
51-
auto result = fuzzdata2.compare(fuzzdata);
52-
if(!result) {
53-
throw CLI::ValidationError("fuzzer", "file input results don't match parse results");
42+
if(fuzzdata.support_config_file_only()) {
43+
CLI::FuzzApp fuzzdata2;
44+
auto app2 = fuzzdata2.generateApp();
45+
// should be able to write the config to a file and read from it again
46+
std::string configOut = app->config_to_str();
47+
std::stringstream out(configOut);
48+
if(pstring_start > 0) {
49+
fuzzdata2.add_custom_options(app2.get(), parseString);
50+
}
51+
app2->parse_from_stream(out);
52+
auto result = fuzzdata2.compare(fuzzdata);
53+
if(!result) {
54+
throw CLI::ValidationError("fuzzer", "file input results don't match parse results");
55+
}
5456
}
57+
5558
return 0;
5659
}

fuzz/fuzzApp.cpp

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "fuzzApp.hpp"
88
#include <algorithm>
9+
#include <iostream>
910

1011
namespace CLI {
1112
/*
@@ -151,7 +152,27 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() {
151152
return fApp;
152153
}
153154

154-
bool FuzzApp::compare(const FuzzApp &other) const {
155+
static void print_string_comparison(const std::string &s1,
156+
const std::string &s2,
157+
const std::string &prefix,
158+
const std::string &s1name,
159+
const std::string &s2name) {
160+
for(size_t jj = 0; jj < (std::max)(s1.size(), s2.size()); ++jj) {
161+
if(jj >= s1.size()) {
162+
std::cout << prefix << ":" << s1name << "[" << jj << "] = [empty], " << s2name << "[" << jj
163+
<< "]=" << static_cast<int>(s2[jj]) << '\n';
164+
} else if(jj >= s2.size()) {
165+
std::cout << prefix << ":" << s1name << "[" << jj << "]=" << static_cast<int>(s1[jj]) << ", " << s2name
166+
<< "[" << jj << "]=[empty] \n";
167+
} else if(s1[jj] != s2[jj]) {
168+
std::cout << "-->" << prefix << ":" << s1name << "[" << jj << "]=" << static_cast<int>(s1[jj]) << ", "
169+
<< s2name << "[" << jj << "]=" << static_cast<int>(s2[jj]) << '\n';
170+
} else {
171+
std::cout << prefix << ":" << s1name << "[" << jj << "]=" << static_cast<int>(s1[jj]) << '\n';
172+
}
173+
}
174+
}
175+
bool FuzzApp::compare(const FuzzApp &other, bool print_error) const {
155176
if(val32 != other.val32) {
156177
return false;
157178
}
@@ -291,6 +312,20 @@ bool FuzzApp::compare(const FuzzApp &other) const {
291312
std::vector<std::string> res = vstrD;
292313
std::reverse(res.begin(), res.end());
293314
if(res != other.vstrD) {
315+
if(print_error) {
316+
if(res.size() != other.vstrD.size()) {
317+
std::cout << "size is different vstrD.size()=" << res.size()
318+
<< " other.vstrD.size=" << other.vstrD.size() << '\n';
319+
} else {
320+
for(size_t ii = 0; ii < res.size(); ++ii) {
321+
print_string_comparison(res[ii],
322+
other.vstrD[ii],
323+
std::string("string[") + std::to_string(ii) + ']',
324+
"vstrD",
325+
"other.vstrD");
326+
}
327+
}
328+
}
294329
return false;
295330
}
296331
}
@@ -311,17 +346,28 @@ bool FuzzApp::compare(const FuzzApp &other) const {
311346
return false;
312347
}
313348
for(std::size_t ii = 0; ii < custom_string_options.size(); ++ii) {
314-
if(*custom_string_options[ii] != *other.custom_string_options[ii]) {
315-
return false;
349+
if(custom_string_options[ii]->first != other.custom_string_options[ii]->first) {
350+
if(custom_string_options[ii]->second) {
351+
if(print_error) {
352+
print_string_comparison(custom_string_options[ii]->first,
353+
other.custom_string_options[ii]->first,
354+
std::string("custom_string[") + std::to_string(ii) + ']',
355+
"c1",
356+
"other.c1");
357+
}
358+
return false;
359+
}
316360
}
317361
}
318362
// now test custom vector_options
319363
if(custom_vector_options.size() != other.custom_vector_options.size()) {
320364
return false;
321365
}
322366
for(std::size_t ii = 0; ii < custom_vector_options.size(); ++ii) {
323-
if(*custom_vector_options[ii] != *other.custom_vector_options[ii]) {
324-
return false;
367+
if(custom_vector_options[ii]->first != other.custom_vector_options[ii]->first) {
368+
if(custom_vector_options[ii]->second) {
369+
return false;
370+
}
325371
}
326372
}
327373
return true;
@@ -447,11 +493,17 @@ std::size_t FuzzApp::add_custom_options(CLI::App *app, const std::string &descri
447493
break;
448494
}
449495
std::string name = description_string.substr(header_close + 1, end_option - header_close - 1);
450-
custom_string_options.push_back(std::make_shared<std::string>());
451-
auto *opt = app->add_option(name, *(custom_string_options.back()));
496+
custom_string_options.push_back(std::make_shared<std::pair<std::string, bool>>("", true));
497+
auto *opt = app->add_option(name, custom_string_options.back()->first);
452498
if(header_close > current_index + 19) {
453499
std::string attributes = description_string.substr(current_index + 8, header_close - 8 - current_index);
454500
modify_option(opt, attributes);
501+
if(!opt->get_configurable()) {
502+
custom_string_options.back()->second = false;
503+
if(opt->get_required()) {
504+
non_config_required = true;
505+
}
506+
}
455507
}
456508

457509
current_index = end_option + 9;
@@ -465,12 +517,18 @@ std::size_t FuzzApp::add_custom_options(CLI::App *app, const std::string &descri
465517
break;
466518
}
467519
std::string name = description_string.substr(header_close + 1, end_option - header_close - 1);
468-
custom_string_options.push_back(std::make_shared<std::string>());
469-
auto *opt = app->add_option(name, *(custom_string_options.back()));
520+
custom_string_options.push_back(std::make_shared<std::pair<std::string, bool>>("", true));
521+
auto *opt = app->add_option(name, custom_string_options.back()->first);
470522

471523
if(header_close > current_index + 17) {
472524
std::string attributes = description_string.substr(current_index + 6, header_close - 6 - current_index);
473525
modify_option(opt, attributes);
526+
if(!opt->get_configurable()) {
527+
custom_string_options.back()->second = false;
528+
if(opt->get_required()) {
529+
non_config_required = true;
530+
}
531+
}
474532
}
475533
current_index = end_option + 7;
476534
} else if(description_string.compare(current_index, 7, "<vector") == 0) {
@@ -483,11 +541,18 @@ std::size_t FuzzApp::add_custom_options(CLI::App *app, const std::string &descri
483541
break;
484542
}
485543
std::string name = description_string.substr(header_close + 1, end_option - header_close - 1);
486-
custom_vector_options.push_back(std::make_shared<std::vector<std::string>>());
487-
auto *opt = app->add_option(name, *(custom_vector_options.back()));
544+
custom_vector_options.push_back(std::make_shared<std::pair<std::vector<std::string>, bool>>());
545+
custom_vector_options.back()->second = true;
546+
auto *opt = app->add_option(name, custom_vector_options.back()->first);
488547
if(header_close > current_index + 19) {
489548
std::string attributes = description_string.substr(current_index + 8, header_close - 8 - current_index);
490549
modify_option(opt, attributes);
550+
if(!opt->get_configurable()) {
551+
custom_vector_options.back()->second = false;
552+
if(opt->get_required()) {
553+
non_config_required = true;
554+
}
555+
}
491556
}
492557
current_index = end_option + 9;
493558
} else {

fuzz/fuzzApp.hpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,13 @@ class FuzzApp {
5757
/** generate a fuzzing application with a bunch of different interfaces*/
5858
std::shared_ptr<CLI::App> generateApp();
5959
/** compare two fuzz apps for equality*/
60-
CLI11_NODISCARD bool compare(const FuzzApp &other) const;
60+
CLI11_NODISCARD bool compare(const FuzzApp &other, bool print_error = false) const;
6161
/** generate additional options based on a string config*/
6262
std::size_t add_custom_options(CLI::App *app, const std::string &description_string);
6363
/** modify an option based on string*/
64-
void modify_option(CLI::Option *opt, const std::string &modifier);
64+
static void modify_option(CLI::Option *opt, const std::string &modifier);
6565

66+
CLI11_NODISCARD bool support_config_file_only() const { return !non_config_required; }
6667
int32_t val32{0};
6768
int16_t val16{0};
6869
int8_t val8{0};
@@ -121,7 +122,10 @@ class FuzzApp {
121122
std::vector<std::string> vstrF{};
122123
std::string mergeBuffer{};
123124
std::vector<std::string> validator_strings{};
124-
std::vector<std::shared_ptr<std::string>> custom_string_options{};
125-
std::vector<std::shared_ptr<std::vector<std::string>>> custom_vector_options{};
125+
std::vector<std::shared_ptr<std::pair<std::string, bool>>> custom_string_options{};
126+
std::vector<std::shared_ptr<std::pair<std::vector<std::string>, bool>>> custom_vector_options{};
127+
128+
private:
129+
bool non_config_required{false};
126130
};
127131
} // namespace CLI

include/CLI/App.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,9 @@ class App {
13221322
/// Fill in a single config option
13231323
bool _parse_single_config(const ConfigItem &item, std::size_t level = 0);
13241324

1325+
/// @brief store the results for a flag like option
1326+
bool _add_flag_like_result(Option *op, const ConfigItem &item, const std::vector<std::string> &inputs);
1327+
13251328
/// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing
13261329
/// from main return false if the parse has failed and needs to return to parent
13271330
bool _parse_single(std::vector<std::string> &args, bool &positional_only);

include/CLI/StringTools.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ CLI11_INLINE std::string add_escaped_characters(const std::string &str);
251251
CLI11_INLINE std::string remove_escaped_characters(const std::string &str);
252252

253253
/// generate a string with all non printable characters escaped to hex codes
254-
CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape);
254+
CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force = false);
255255

256256
CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string);
257257

0 commit comments

Comments
 (0)