Skip to content

Commit 792d892

Browse files
fix: out of range string detected by the fuzzer (#905)
about an out of range string conversion not being caught properly. This commit changes the logic from an exception to using errno and a non-throwing alternative. Issue detected in https://github.com/CLIUtils/CLI11/actions/runs/5500247554/jobs/10023032108 The problem string was set up as a test. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent ccf141c commit 792d892

File tree

6 files changed

+60
-34
lines changed

6 files changed

+60
-34
lines changed

include/CLI/TypeTools.hpp

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -861,7 +861,7 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
861861
if(input.empty() || input.front() == '-') {
862862
return false;
863863
}
864-
char *val = nullptr;
864+
char *val{nullptr};
865865
errno = 0;
866866
std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
867867
if(errno == ERANGE) {
@@ -904,8 +904,8 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
904904
return false;
905905
}
906906

907-
/// Convert a flag into an integer value typically binary flags
908-
inline std::int64_t to_flag_value(std::string val) {
907+
/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed
908+
inline std::int64_t to_flag_value(std::string val) noexcept {
909909
static const std::string trueString("true");
910910
static const std::string falseString("false");
911911
if(val == trueString) {
@@ -933,7 +933,8 @@ inline std::int64_t to_flag_value(std::string val) {
933933
ret = 1;
934934
break;
935935
default:
936-
throw std::invalid_argument("unrecognized character");
936+
errno = EINVAL;
937+
return -1;
937938
}
938939
return ret;
939940
}
@@ -942,7 +943,11 @@ inline std::int64_t to_flag_value(std::string val) {
942943
} else if(val == falseString || val == "off" || val == "no" || val == "disable") {
943944
ret = -1;
944945
} else {
945-
ret = std::stoll(val);
946+
char *loc_ptr{nullptr};
947+
ret = std::strtoll(val.c_str(), &loc_ptr, 0);
948+
if(loc_ptr != (val.c_str() + val.size()) && errno == 0) {
949+
errno = EINVAL;
950+
}
946951
}
947952
return ret;
948953
}
@@ -971,18 +976,16 @@ bool lexical_cast(const std::string &input, T &output) {
971976
template <typename T,
972977
enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy>
973978
bool lexical_cast(const std::string &input, T &output) {
974-
try {
975-
auto out = to_flag_value(input);
979+
errno = 0;
980+
auto out = to_flag_value(input);
981+
if(errno == 0) {
976982
output = (out > 0);
977-
return true;
978-
} catch(const std::invalid_argument &) {
979-
return false;
980-
} catch(const std::out_of_range &) {
981-
// if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still
982-
// valid all we care about the sign
983+
} else if(errno == ERANGE) {
983984
output = (input[0] != '-');
984-
return true;
985+
} else {
986+
return false;
985987
}
988+
return true;
986989
}
987990

988991
/// Floats
@@ -1638,12 +1641,13 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) {
16381641
double tv{0.0};
16391642
auto comp = lexical_cast(arg, tv);
16401643
if(!comp) {
1641-
try {
1642-
tv = static_cast<double>(detail::to_flag_value(arg));
1643-
} catch(const std::exception &) {
1644-
fail = true;
1644+
errno = 0;
1645+
auto fv = detail::to_flag_value(arg);
1646+
fail = (errno != 0);
1647+
if(fail) {
16451648
break;
16461649
}
1650+
tv = static_cast<double>(fv);
16471651
}
16481652
val += tv;
16491653
}

include/CLI/impl/App_inl.hpp

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,18 +1431,15 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t
14311431
auto res = config_formatter_->to_flag(item);
14321432
bool converted{false};
14331433
if(op->get_disable_flag_override()) {
1434-
1435-
try {
1436-
auto val = detail::to_flag_value(res);
1437-
if(val == 1) {
1438-
res = op->get_flag_value(item.name, "{}");
1439-
converted = true;
1440-
}
1441-
} catch(...) {
1434+
auto val = detail::to_flag_value(res);
1435+
if(val == 1) {
1436+
res = op->get_flag_value(item.name, "{}");
1437+
converted = true;
14421438
}
14431439
}
14441440

14451441
if(!converted) {
1442+
errno = 0;
14461443
res = op->get_flag_value(item.name, res);
14471444
}
14481445

include/CLI/impl/Option_inl.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -389,15 +389,15 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::strin
389389
return input_value;
390390
}
391391
if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
392-
try {
393-
auto val = detail::to_flag_value(input_value);
394-
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
395-
} catch(const std::invalid_argument &) {
392+
errno = 0;
393+
auto val = detail::to_flag_value(input_value);
394+
if(errno != 0) {
395+
errno = 0;
396396
return input_value;
397397
}
398-
} else {
399-
return input_value;
398+
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
400399
}
400+
return input_value;
401401
}
402402

403403
CLI11_INLINE Option *Option::add_result(std::string s) {

tests/FuzzFailTest.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,16 @@ TEST_CASE("app_fail") {
4545
} catch(const CLI::ConstructionError & /*e*/) {
4646
}
4747
}
48+
49+
TEST_CASE("file_fail") {
50+
CLI::FuzzApp fuzzdata;
51+
auto app = fuzzdata.generateApp();
52+
53+
int index = GENERATE(range(1, 2));
54+
auto parseData = loadFailureFile("fuzz_file_fail", index);
55+
std::stringstream out(parseData);
56+
try {
57+
app->parse_from_stream(out);
58+
} catch(const CLI::ParseError & /*e*/) {
59+
}
60+
}

tests/HelpersTest.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,26 @@ TEST_CASE("StringTools: Modify3", "[helpers]") {
201201
}
202202

203203
TEST_CASE("StringTools: flagValues", "[helpers]") {
204+
errno = 0;
204205
CHECK(-1 == CLI::detail::to_flag_value("0"));
206+
CHECK(errno == 0);
205207
CHECK(1 == CLI::detail::to_flag_value("t"));
206208
CHECK(1 == CLI::detail::to_flag_value("1"));
207209
CHECK(6 == CLI::detail::to_flag_value("6"));
208210
CHECK(-6 == CLI::detail::to_flag_value("-6"));
209211
CHECK(-1 == CLI::detail::to_flag_value("false"));
210212
CHECK(1 == CLI::detail::to_flag_value("YES"));
211-
CHECK_THROWS_AS(CLI::detail::to_flag_value("frog"), std::invalid_argument);
212-
CHECK_THROWS_AS(CLI::detail::to_flag_value("q"), std::invalid_argument);
213+
errno = 0;
214+
CLI::detail::to_flag_value("frog");
215+
CHECK(errno == EINVAL);
216+
errno = 0;
217+
CLI::detail::to_flag_value("q");
218+
CHECK(errno == EINVAL);
219+
errno = 0;
220+
CLI::detail::to_flag_value(
221+
"77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777");
222+
CHECK(errno == ERANGE);
223+
errno = 0;
213224
CHECK(-1 == CLI::detail::to_flag_value("NO"));
214225
CHECK(475555233 == CLI::detail::to_flag_value("475555233"));
215226
}

tests/fuzzFail/fuzz_file_fail1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nflag2=555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555"="

0 commit comments

Comments
 (0)