PR #2144: validate log_uniform_int_distribution stream input and harden preconditions Imported from GitHub PR https://github.com/abseil/abseil-cpp/pull/2144 log_uniform_int_distribution's param_type constructor computes log_range_ as static_cast<int>(ceil((1/log(base)) * log(range))). A base of 0 or 1, or a negative base for a signed IntType, violates the base > 1 precondition and makes that math non-finite: 1/log(1) divides by zero, and the final cast of inf/NaN to int is undefined behavior (UBSan: "inf is outside the range of representable values of type 'int'"). It was also reachable through operator>>, which handed whatever it parsed straight to param_type. Changes: - Check the param_type preconditions with ABSL_HARDENING_ASSERT. - Only take the log() branch for base_ > 2, so an out-of-contract base never reaches the floating-point math. - Have operator>> validate max >= min and base > 1 and set failbit on bad input instead of constructing an invalid param_type, with a test covering that rejection. Merge b15918e1696ac26b2f283a26c03671a29f9b61a6 into 8bd6e5097d96974f5c85031759c545a8e7426b4a Merging this change closes #2144 PiperOrigin-RevId: 984157679 Change-Id: I003a3dd5536fdac5731850017095915e73ab05bb
diff --git a/absl/random/log_uniform_int_distribution.h b/absl/random/log_uniform_int_distribution.h index cbd5e0c..99abe05 100644 --- a/absl/random/log_uniform_int_distribution.h +++ b/absl/random/log_uniform_int_distribution.h
@@ -16,13 +16,14 @@ #define ABSL_RANDOM_LOG_UNIFORM_INT_DISTRIBUTION_H_ #include <algorithm> -#include <cassert> #include <cmath> +#include <ios> #include <istream> #include <limits> #include <ostream> #include "absl/base/config.h" +#include "absl/base/macros.h" #include "absl/random/internal/iostream_state_saver.h" #include "absl/random/internal/traits.h" #include "absl/random/uniform_int_distribution.h" @@ -60,15 +61,18 @@ range_(static_cast<unsigned_type>(max_) - static_cast<unsigned_type>(min_)), log_range_(0) { - assert(max_ >= min_); - assert(base_ > 1); + ABSL_HARDENING_ASSERT(max_ >= min_); + ABSL_HARDENING_ASSERT(base_ > 1); if (base_ == 2) { // Determine where the first set bit is on range(), giving a log2(range) // value which can be used to construct bounds. log_range_ = (std::min)(random_internal::BitWidth(range()), std::numeric_limits<unsigned_type>::digits); - } else { + } else if (base_ > 2) { + // An out-of-contract base_ (<= 1) skips this branch entirely so that + // no floating-point undefined behavior is reached. + // // NOTE: Computing the logN(x) introduces error from 2 sources: // 1. Conversion of int to double loses precision for values >= // 2^53, which may cause some log() computations to operate on @@ -240,9 +244,16 @@ auto saver = random_internal::make_istream_state_saver(is); is >> min >> max >> base; if (!is.fail()) { - x.param(param_type(static_cast<result_type>(min), - static_cast<result_type>(max), - static_cast<result_type>(base))); + const result_type min_val = static_cast<result_type>(min); + const result_type max_val = static_cast<result_type>(max); + const result_type base_val = static_cast<result_type>(base); + if (max_val < min_val || base_val <= 1) { + // The input violates the param_type preconditions; signal failure by + // setting the failbit instead of constructing an invalid param_type. + is.setstate(is.rdstate() | std::ios_base::failbit); + } else { + x.param(param_type(min_val, max_val, base_val)); + } } return is; }
diff --git a/absl/random/log_uniform_int_distribution_test.cc b/absl/random/log_uniform_int_distribution_test.cc index 591b5b3..4404111 100644 --- a/absl/random/log_uniform_int_distribution_test.cc +++ b/absl/random/log_uniform_int_distribution_test.cc
@@ -116,6 +116,26 @@ } } +// operator>> must reject input that violates the param_type preconditions +// (max >= min and base > 1) by setting failbit and leaving the distribution +// unchanged, rather than constructing an out-of-contract param_type. +TYPED_TEST(LogUniformIntDistributionTypeTest, DeserializeRejectsInvalidParams) { + for (const char* input : { + "0 100 1", // base == 1 + "0 100 0", // base == 0 + "100 0 2", // max < min + }) { + absl::log_uniform_int_distribution<TypeParam> dist(3, 6, 17); + const auto before = dist.param(); + + std::istringstream is(input); + is >> dist; + + EXPECT_TRUE(is.fail()) << input; + EXPECT_EQ(dist.param(), before) << input; + } +} + using log_uniform_i32 = absl::log_uniform_int_distribution<int32_t>; class LogUniformIntChiSquaredTest