mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-05-03 05:53:52 +00:00
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>
This commit is contained in:
parent
ccf141c49d
commit
792d892867
@ -861,7 +861,7 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
|
|||||||
if(input.empty() || input.front() == '-') {
|
if(input.empty() || input.front() == '-') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
char *val = nullptr;
|
char *val{nullptr};
|
||||||
errno = 0;
|
errno = 0;
|
||||||
std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
|
std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
|
||||||
if(errno == ERANGE) {
|
if(errno == ERANGE) {
|
||||||
@ -904,8 +904,8 @@ bool integral_conversion(const std::string &input, T &output) noexcept {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a flag into an integer value typically binary flags
|
/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed
|
||||||
inline std::int64_t to_flag_value(std::string val) {
|
inline std::int64_t to_flag_value(std::string val) noexcept {
|
||||||
static const std::string trueString("true");
|
static const std::string trueString("true");
|
||||||
static const std::string falseString("false");
|
static const std::string falseString("false");
|
||||||
if(val == trueString) {
|
if(val == trueString) {
|
||||||
@ -933,7 +933,8 @@ inline std::int64_t to_flag_value(std::string val) {
|
|||||||
ret = 1;
|
ret = 1;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw std::invalid_argument("unrecognized character");
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -942,7 +943,11 @@ inline std::int64_t to_flag_value(std::string val) {
|
|||||||
} else if(val == falseString || val == "off" || val == "no" || val == "disable") {
|
} else if(val == falseString || val == "off" || val == "no" || val == "disable") {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
} else {
|
} else {
|
||||||
ret = std::stoll(val);
|
char *loc_ptr{nullptr};
|
||||||
|
ret = std::strtoll(val.c_str(), &loc_ptr, 0);
|
||||||
|
if(loc_ptr != (val.c_str() + val.size()) && errno == 0) {
|
||||||
|
errno = EINVAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -971,18 +976,16 @@ bool lexical_cast(const std::string &input, T &output) {
|
|||||||
template <typename T,
|
template <typename T,
|
||||||
enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy>
|
enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy>
|
||||||
bool lexical_cast(const std::string &input, T &output) {
|
bool lexical_cast(const std::string &input, T &output) {
|
||||||
try {
|
errno = 0;
|
||||||
auto out = to_flag_value(input);
|
auto out = to_flag_value(input);
|
||||||
|
if(errno == 0) {
|
||||||
output = (out > 0);
|
output = (out > 0);
|
||||||
return true;
|
} else if(errno == ERANGE) {
|
||||||
} catch(const std::invalid_argument &) {
|
|
||||||
return false;
|
|
||||||
} catch(const std::out_of_range &) {
|
|
||||||
// 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
|
|
||||||
// valid all we care about the sign
|
|
||||||
output = (input[0] != '-');
|
output = (input[0] != '-');
|
||||||
return true;
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Floats
|
/// Floats
|
||||||
@ -1638,12 +1641,13 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) {
|
|||||||
double tv{0.0};
|
double tv{0.0};
|
||||||
auto comp = lexical_cast(arg, tv);
|
auto comp = lexical_cast(arg, tv);
|
||||||
if(!comp) {
|
if(!comp) {
|
||||||
try {
|
errno = 0;
|
||||||
tv = static_cast<double>(detail::to_flag_value(arg));
|
auto fv = detail::to_flag_value(arg);
|
||||||
} catch(const std::exception &) {
|
fail = (errno != 0);
|
||||||
fail = true;
|
if(fail) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
tv = static_cast<double>(fv);
|
||||||
}
|
}
|
||||||
val += tv;
|
val += tv;
|
||||||
}
|
}
|
||||||
|
@ -1431,18 +1431,15 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t
|
|||||||
auto res = config_formatter_->to_flag(item);
|
auto res = config_formatter_->to_flag(item);
|
||||||
bool converted{false};
|
bool converted{false};
|
||||||
if(op->get_disable_flag_override()) {
|
if(op->get_disable_flag_override()) {
|
||||||
|
auto val = detail::to_flag_value(res);
|
||||||
try {
|
if(val == 1) {
|
||||||
auto val = detail::to_flag_value(res);
|
res = op->get_flag_value(item.name, "{}");
|
||||||
if(val == 1) {
|
converted = true;
|
||||||
res = op->get_flag_value(item.name, "{}");
|
|
||||||
converted = true;
|
|
||||||
}
|
|
||||||
} catch(...) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!converted) {
|
if(!converted) {
|
||||||
|
errno = 0;
|
||||||
res = op->get_flag_value(item.name, res);
|
res = op->get_flag_value(item.name, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,15 +389,15 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::strin
|
|||||||
return input_value;
|
return input_value;
|
||||||
}
|
}
|
||||||
if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
|
if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
|
||||||
try {
|
errno = 0;
|
||||||
auto val = detail::to_flag_value(input_value);
|
auto val = detail::to_flag_value(input_value);
|
||||||
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
|
if(errno != 0) {
|
||||||
} catch(const std::invalid_argument &) {
|
errno = 0;
|
||||||
return input_value;
|
return input_value;
|
||||||
}
|
}
|
||||||
} else {
|
return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
|
||||||
return input_value;
|
|
||||||
}
|
}
|
||||||
|
return input_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
CLI11_INLINE Option *Option::add_result(std::string s) {
|
CLI11_INLINE Option *Option::add_result(std::string s) {
|
||||||
|
@ -45,3 +45,16 @@ TEST_CASE("app_fail") {
|
|||||||
} catch(const CLI::ConstructionError & /*e*/) {
|
} catch(const CLI::ConstructionError & /*e*/) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("file_fail") {
|
||||||
|
CLI::FuzzApp fuzzdata;
|
||||||
|
auto app = fuzzdata.generateApp();
|
||||||
|
|
||||||
|
int index = GENERATE(range(1, 2));
|
||||||
|
auto parseData = loadFailureFile("fuzz_file_fail", index);
|
||||||
|
std::stringstream out(parseData);
|
||||||
|
try {
|
||||||
|
app->parse_from_stream(out);
|
||||||
|
} catch(const CLI::ParseError & /*e*/) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -201,15 +201,26 @@ TEST_CASE("StringTools: Modify3", "[helpers]") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("StringTools: flagValues", "[helpers]") {
|
TEST_CASE("StringTools: flagValues", "[helpers]") {
|
||||||
|
errno = 0;
|
||||||
CHECK(-1 == CLI::detail::to_flag_value("0"));
|
CHECK(-1 == CLI::detail::to_flag_value("0"));
|
||||||
|
CHECK(errno == 0);
|
||||||
CHECK(1 == CLI::detail::to_flag_value("t"));
|
CHECK(1 == CLI::detail::to_flag_value("t"));
|
||||||
CHECK(1 == CLI::detail::to_flag_value("1"));
|
CHECK(1 == CLI::detail::to_flag_value("1"));
|
||||||
CHECK(6 == CLI::detail::to_flag_value("6"));
|
CHECK(6 == CLI::detail::to_flag_value("6"));
|
||||||
CHECK(-6 == CLI::detail::to_flag_value("-6"));
|
CHECK(-6 == CLI::detail::to_flag_value("-6"));
|
||||||
CHECK(-1 == CLI::detail::to_flag_value("false"));
|
CHECK(-1 == CLI::detail::to_flag_value("false"));
|
||||||
CHECK(1 == CLI::detail::to_flag_value("YES"));
|
CHECK(1 == CLI::detail::to_flag_value("YES"));
|
||||||
CHECK_THROWS_AS(CLI::detail::to_flag_value("frog"), std::invalid_argument);
|
errno = 0;
|
||||||
CHECK_THROWS_AS(CLI::detail::to_flag_value("q"), std::invalid_argument);
|
CLI::detail::to_flag_value("frog");
|
||||||
|
CHECK(errno == EINVAL);
|
||||||
|
errno = 0;
|
||||||
|
CLI::detail::to_flag_value("q");
|
||||||
|
CHECK(errno == EINVAL);
|
||||||
|
errno = 0;
|
||||||
|
CLI::detail::to_flag_value(
|
||||||
|
"77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777");
|
||||||
|
CHECK(errno == ERANGE);
|
||||||
|
errno = 0;
|
||||||
CHECK(-1 == CLI::detail::to_flag_value("NO"));
|
CHECK(-1 == CLI::detail::to_flag_value("NO"));
|
||||||
CHECK(475555233 == CLI::detail::to_flag_value("475555233"));
|
CHECK(475555233 == CLI::detail::to_flag_value("475555233"));
|
||||||
}
|
}
|
||||||
|
1
tests/fuzzFail/fuzz_file_fail1
Normal file
1
tests/fuzzFail/fuzz_file_fail1
Normal file
@ -0,0 +1 @@
|
|||||||
|
nflag2=555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555"="
|
Loading…
x
Reference in New Issue
Block a user