1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00
fix failing fuzz case involving binary string with a '\x' in it.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Philip Top 2024-11-30 07:13:49 -08:00 committed by GitHub
parent 8260578fd2
commit 063b2c911c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 98 additions and 7 deletions

View File

@ -29,7 +29,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
try {
if(pstring_start > 0) {
app->parse(parseString.substr(pstring_start, std::string::npos));
app->parse(parseString.substr(pstring_start));
} else {
app->parse(parseString);
}

View File

@ -542,8 +542,9 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
continue;
}
std::string value = detail::ini_join(
opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
auto results = opt->reduced_results();
std::string value =
detail::ini_join(results, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
bool isDefault = false;
if(value.empty() && default_also) {
@ -560,7 +561,16 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
}
if(!value.empty()) {
if(opt->get_expected_max() > 1 && detail::is_binary_escaped_string(value) && results.size() == 1 &&
!results[0].empty()) {
if(results[0].front() == '[' && results[0].back() == ']') {
// this is a condition which could be misinterpreted
results[0].insert(0, 1, results[0].front());
results[0].push_back(results[0].back());
value = detail::ini_join(
results, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote);
}
}
if(!opt->get_fnames().empty()) {
try {
value = opt->get_flag_value(single_name, value);

View File

@ -665,14 +665,21 @@ CLI11_INLINE int Option::_add_result(std::string &&result, std::vector<std::stri
if((allow_extra_args_ || get_expected_max() > 1) && !result.empty() && result.front() == '[' &&
result.back() == ']') { // this is now a vector string likely from the default or user entry
result.pop_back();
for(auto &var : CLI::detail::split_up(result.substr(1), ',')) {
result.erase(result.begin());
bool skipSection{false};
for(auto &var : CLI::detail::split_up(result, ',')) {
if(var == result) {
skipSection = true;
break;
}
if(!var.empty()) {
result_count += _add_result(std::move(var), res);
}
}
if(!skipSection) {
return result_count;
}
}
if(delimiter_ == '\0') {
res.push_back(std::move(result));
++result_count;

View File

@ -433,6 +433,13 @@ CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escap
stream << std::hex << static_cast<unsigned int>(static_cast<unsigned char>(c));
std::string code = stream.str();
escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code;
} else if(c == 'x' || c == 'X') {
// need to check for inadvertent binary sequences
if(!escaped_string.empty() && escaped_string.back() == '\\') {
escaped_string += std::string("\\x") + (c == 'x' ? "78" : "58");
} else {
escaped_string.push_back(c);
}
} else {
escaped_string.push_back(c);

View File

@ -260,3 +260,33 @@ TEST_CASE("fuzz_config_test1") {
CHECK(app->get_option_no_throw("--new_flag") != nullptr);
CHECK(app->get_option_no_throw("--new_vector") != nullptr);
}
// this test uses the same tests as above just with a full roundtrip test
TEST_CASE("app_roundtrip_custom") {
CLI::FuzzApp fuzzdata;
CLI::FuzzApp fuzzdata2;
auto app = fuzzdata.generateApp();
auto app2 = fuzzdata2.generateApp();
int index = GENERATE(range(1, 3));
std::string optionString, flagString;
auto parseData = loadFailureFile("round_trip_custom", index);
std::size_t pstring_start{0};
pstring_start = fuzzdata.add_custom_options(app.get(), parseData);
if(pstring_start > 0) {
app->parse(parseData.substr(pstring_start));
} else {
app->parse(parseData);
}
// should be able to write the config to a file and read from it again
std::string configOut = app->config_to_str();
app->clear();
std::stringstream out(configOut);
if(pstring_start > 0) {
fuzzdata2.add_custom_options(app2.get(), parseData);
}
app2->parse_from_stream(out);
auto result = fuzzdata2.compare(fuzzdata);
CHECK(result);
}

View File

@ -301,6 +301,42 @@ TEST_CASE("StringTools: binaryEscapseConversion2", "[helpers]") {
CHECK(rstring == testString);
}
TEST_CASE("StringTools: binaryEscapseConversion_withX", "[helpers]") {
std::string testString("hippy\\x35mm\\XF3_helpX26fox19");
testString.push_back(0);
testString.push_back(0);
testString.push_back(0);
testString.push_back(56);
testString.push_back(-112);
testString.push_back(-112);
testString.push_back(39);
testString.push_back(97);
std::string estring = CLI::detail::binary_escape_string(testString);
CHECK(CLI::detail::is_binary_escaped_string(estring));
std::string rstring = CLI::detail::extract_binary_string(estring);
CHECK(rstring == testString);
}
TEST_CASE("StringTools: binaryEscapseConversion_withBrackets", "[helpers]") {
std::string vstr = R"raw('B"([\xb0\x0a\xb0/\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0])"')raw";
std::string testString("[");
testString.push_back(-80);
testString.push_back('\n');
testString.push_back(-80);
testString.push_back('/');
for(int ii = 0; ii < 13; ++ii) {
testString.push_back(-80);
}
testString.push_back(']');
std::string estring = CLI::detail::binary_escape_string(testString);
CHECK(CLI::detail::is_binary_escaped_string(estring));
CHECK(estring == vstr);
std::string rstring = CLI::detail::extract_binary_string(estring);
CHECK(rstring == testString);
}
TEST_CASE("StringTools: binaryStrings", "[helpers]") {
std::string rstring = "B\"()\"";
CHECK(CLI::detail::extract_binary_string(rstring).empty());

Binary file not shown.

View File

@ -0,0 +1 @@
--vM=[ー ー/ーーーーーーーーーーーーー]