diff --git a/ChangeLog b/ChangeLog index b6cd508a..b7571235 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2020-11-21 Jay Berkenbilt + + * Fix QIntC::range_check to handle negative numbers properly (fuzz + issue 26994). + 2020-11-11 Jay Berkenbilt * Treat a direct page object as a runtime error rather than a diff --git a/fuzz/qpdf_extra/26994.fuzz b/fuzz/qpdf_extra/26994.fuzz new file mode 100644 index 00000000..ce2860bb Binary files /dev/null and b/fuzz/qpdf_extra/26994.fuzz differ diff --git a/include/qpdf/QIntC.hh b/include/qpdf/QIntC.hh index 5f7f21bb..e3ea0a28 100644 --- a/include/qpdf/QIntC.hh +++ b/include/qpdf/QIntC.hh @@ -226,6 +226,11 @@ namespace QIntC // QIntC = qpdf Integer Conversion template void range_check(T const& cur, T const& delta) { + if ((delta > 0) != (cur > 0)) + { + return; + } + if ((delta > 0) && ((std::numeric_limits::max() - cur) < delta)) { @@ -235,6 +240,15 @@ namespace QIntC // QIntC = qpdf Integer Conversion << " would cause an integer overflow"; throw std::range_error(msg.str()); } + else if ((delta < 0) && + ((std::numeric_limits::min() - cur) > delta)) + { + std::ostringstream msg; + msg.imbue(std::locale::classic()); + msg << "adding " << delta << " to " << cur + << " would cause an integer underflow"; + throw std::range_error(msg.str()); + } } }; diff --git a/libtests/qintc.cc b/libtests/qintc.cc index 6b35e837..32c3713f 100644 --- a/libtests/qintc.cc +++ b/libtests/qintc.cc @@ -25,6 +25,29 @@ static void try_convert_real( std::cout << ((passed == exp_pass) ? " PASSED" : " FAILED") << std::endl; } +#define try_range_check(exp_pass, a, b) \ + try_range_check_real(#a " + " #b, exp_pass, a, b) + +template +static void try_range_check_real( + char const* description, bool exp_pass, + T const& a, T const& b) +{ + bool passed = false; + try + { + QIntC::range_check(a, b); + std::cout << description << ": okay"; + passed = true; + } + catch (std::range_error& e) + { + std::cout << description << ": " << e.what(); + passed = false; + } + std::cout << ((passed == exp_pass) ? " PASSED" : " FAILED") << std::endl; +} + int main() { uint32_t u1 = 3141592653U; // Too big for signed type @@ -56,5 +79,22 @@ int main() try_convert(true, QIntC::to_uchar, c2); try_convert(true, QIntC::to_char, c2); + auto constexpr max_ll = std::numeric_limits::max(); + auto constexpr max_ull = std::numeric_limits::max(); + auto constexpr min_ll = std::numeric_limits::min(); + auto constexpr max_sc = std::numeric_limits::max(); + try_range_check(true, 1, 2); + try_range_check(true, -1, 2); + try_range_check(true, -100, -200); + try_range_check(true, max_ll, 0LL); + try_range_check(false, max_ll, 1LL); + try_range_check(true, max_ll, 0LL); + try_range_check(false, max_ll, 1LL); + try_range_check(true, max_ull, 0ULL); + try_range_check(false, max_ull, 1ULL); + try_range_check(true, min_ll, 0LL); + try_range_check(false, min_ll, -1LL); + try_range_check(false, max_sc, max_sc); + try_range_check(true, '!', '#'); return 0; } diff --git a/libtests/qtest/qintc/qintc.out b/libtests/qtest/qintc/qintc.out index 2a2ff9f5..5520c635 100644 --- a/libtests/qtest/qintc/qintc.out +++ b/libtests/qtest/qintc/qintc.out @@ -13,3 +13,16 @@ QIntC::to_uchar(i2): 81 Q PASSED QIntC::to_uchar(c1): integer out of range converting ÷ from a 1-byte signed type to a 1-byte unsigned type PASSED QIntC::to_uchar(c2): W W PASSED QIntC::to_char(c2): W W PASSED +1 + 2: okay PASSED +-1 + 2: okay PASSED +-100 + -200: okay PASSED +max_ll + 0LL: okay PASSED +max_ll + 1LL: adding 1 to 9223372036854775807 would cause an integer overflow PASSED +max_ll + 0LL: okay PASSED +max_ll + 1LL: adding 1 to 9223372036854775807 would cause an integer overflow PASSED +max_ull + 0ULL: okay PASSED +max_ull + 1ULL: adding 1 to 18446744073709551615 would cause an integer overflow PASSED +min_ll + 0LL: okay PASSED +min_ll + -1LL: adding -1 to -9223372036854775808 would cause an integer underflow PASSED +max_sc + max_sc: adding  to  would cause an integer overflow PASSED +'!' + '#': okay PASSED