mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
fp_uscale.c: fix shift and clz UB in tiny-float formatting
Two UBSan issues exposed by sprintf("%f", 1e-7) and similar:
1. uscale() shifted hi by c.s without bounding c.s, hitting UB
when c.s >= 64. The mask line had `c.s & 63`, but the actual
`hi >> c.s` line did not, so the partial guard was incomplete.
On x86 the hardware silently masks the shift, producing wrong
output ("1844674407370.955078" for 1e-7) instead of crashing.
When c.s >= 64 the value rounds to 0 with sticky=1, so we can
bail early.
2. count_digits(0) called bits_len64(0) -> clz64(0), which is UB.
The only other bits_len64 caller already guards d == 0; align
count_digits with that pattern. Returning 1 (since "0" is one
digit) preserves output formatting.
Reported by OSS-Fuzz (clusterfuzz testcase 5210395240628224).
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
+9
-1
@@ -818,7 +818,14 @@ static unrounded uscale(uint64_t x, scaler c)
|
||||
mul64(x, c.pm.hi, &hi, &mid);
|
||||
|
||||
uint64_t sticky = 1;
|
||||
uint64_t mask = (1ULL << (c.s & 63)) - 1;
|
||||
|
||||
if (c.s >= 64) {
|
||||
/* x * 10^p < 2^(c.s-64), i.e. < 1 in the unrounded "1.0 = 4" encoding;
|
||||
rounds to 0 with sticky=1 */
|
||||
return sticky;
|
||||
}
|
||||
|
||||
uint64_t mask = (1ULL << c.s) - 1;
|
||||
|
||||
if ((hi & mask) == 0) {
|
||||
mul64(x, c.pm.lo, &mid2, &lo_unused);
|
||||
@@ -886,6 +893,7 @@ static const uint64_t uint64_pow10[20] = {
|
||||
|
||||
static int count_digits(uint64_t d)
|
||||
{
|
||||
if (d == 0) return 1; /* clz64(0) is UB; "0" is one digit */
|
||||
int nd = log10_pow2(bits_len64(d));
|
||||
return nd + (d >= uint64_pow10[nd] ? 1 : 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user