Skip to content

Commit f89900f

Browse files
committed
Fix undefined behavior of MT_RAND_PHP if range exceeds ZEND_LONG_MAX
RAND_RANGE_BADSCALING() invokes undefined behavior when (max - min) > ZEND_LONG_MAX, because the intermediate `double` might not fit into `zend_long`. Fix this by inlining a fixed version of the macro into Mt19937's range() function. Fixing the macro itself cannot be done in the general case, because the types of the inputs are not known. Instead of replacing one possibly broken version with another possibly broken version, the macro is simply left as is and should be removed in a future version. The fix itself is simple: Instead of storing the "offset" in a `zend_long`, we use a `zend_ulong` which is capable of storing the resulting double by construction. With this fix the implementation of this broken scaling is effectively identical to the implementation of php_random_range from a data type perspective, making it easy to verify the correctness. It was further empirically verified that the broken macro and the fix return the same results for all possible values of `r` for several distinct pairs of (min, max). Fixes GH-9190 Fixes GH-9191
1 parent 9e2de4c commit f89900f

File tree

1 file changed

+9
-3
lines changed

1 file changed

+9
-3
lines changed

ext/random/engine_mt19937.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,17 @@ static zend_long range(php_random_status *status, zend_long min, zend_long max)
169169
return php_random_range(&php_random_algo_mt19937, status, min, max);
170170
}
171171

172-
uint64_t r = php_random_algo_mt19937.generate(status) >> 1;
173172
/* Legacy mode deliberately not inside php_mt_rand_range()
174173
* to prevent other functions being affected */
175-
RAND_RANGE_BADSCALING(r, min, max, PHP_MT_RAND_MAX);
176-
return (zend_long) r;
174+
175+
uint64_t r = php_random_algo_mt19937.generate(status) >> 1;
176+
177+
/* This is an inlined version of the RAND_RANGE_BADSCALING macro that does not invoke UB when encountering
178+
* (max - min) > ZEND_LONG_MAX.
179+
*/
180+
zend_ulong offset = (double) ( (double) max - min + 1.0) * (r / (PHP_MT_RAND_MAX + 1.0));
181+
182+
return (zend_long) (offset + min);
177183
}
178184

179185
static bool serialize(php_random_status *status, HashTable *data)

0 commit comments

Comments
 (0)