Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion absl/random/log_uniform_int_distribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,18 @@ class log_uniform_int_distribution {
// which can eliminate some values depending on where the bounds fall.
const double inv_log_base = 1.0 / std::log(static_cast<double>(base_));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this fix fails to handle base_ == 1, which will produce a divide by 0 here.

const double log_range = std::log(static_cast<double>(range()) + 0.5);
log_range_ = static_cast<int>(std::ceil(inv_log_base * log_range));
const double result = std::ceil(inv_log_base * log_range);
// A base_ of 0 or 1, or a negative base_, violates the base_ > 1
// precondition and leaves inv_log_base non-finite, so `result` can be
// inf or NaN. Casting such a value to int is undefined behavior; guard
// it so an out-of-contract base yields a defined (if meaningless)
// log_range_ instead. For a valid base_ (> 1), result is a small
// non-negative integer and this guard is a no-op.
log_range_ =
(result >= 0 &&
result < static_cast<double>((std::numeric_limits<int>::max)()))
? static_cast<int>(result)
: 0;
}
}

Expand Down
27 changes: 27 additions & 0 deletions absl/random/log_uniform_int_distribution_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,33 @@ TYPED_TEST(LogUniformIntDistributionTypeTest, SerializeTest) {
}
}

// A base of 1 (or, for signed types, a negative base) violates the base > 1
// precondition. In debug builds the constructor asserts; in opt builds it must
// still yield a defined object rather than invoking undefined behavior while
// computing log_range_, which casts 1/log(base) * log(range) to int -- a cast
// of a non-finite double (inf/NaN) to int is UB. The bad base can also reach
// the object through operator>> reading an untrusted stream.
TYPED_TEST(LogUniformIntDistributionTypeTest, InvalidBaseIsDefinedInOptMode) {
#if defined(NDEBUG)
absl::InsecureBitGen gen;

// Direct construction with an out-of-contract base must not invoke UB.
absl::log_uniform_int_distribution<TypeParam> dist(0, 100, 1);
auto sample = dist(gen);
EXPECT_GE(sample, dist.min());
EXPECT_LE(sample, dist.max());

// The same bad base arriving through deserialization must also stay defined.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proper fix for this is for operator>> to validate input and set std::ios_base::failbit. The wrong fix is for param_type to quietly sanitize bad input and leave the distribution in a silently failing state.

absl::log_uniform_int_distribution<TypeParam> after(3, 6, 17);
std::istringstream is("0 100 1");
is >> after;
EXPECT_EQ(after.base(), static_cast<TypeParam>(1));
sample = after(gen);
EXPECT_GE(sample, after.min());
EXPECT_LE(sample, after.max());
#endif // NDEBUG
}

using log_uniform_i32 = absl::log_uniform_int_distribution<int32_t>;

class LogUniformIntChiSquaredTest
Expand Down