Skip to content

Commit 3d413ad

Browse files
committed
Ensure random_int() uses a uniform distribution
1 parent 7a99db6 commit 3d413ad

File tree

1 file changed

+26
-31
lines changed

1 file changed

+26
-31
lines changed

ext/standard/random.c

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,6 @@
2929
# include "win32/winutil.h"
3030
#endif
3131

32-
// Big thanks to @ircmaxell for the help on this bit
33-
union rand_long_buffer {
34-
char buffer[8];
35-
long number;
36-
};
37-
38-
// Copy/pasted from mcrypt.c
3932
static int php_random_bytes(void *bytes, size_t size)
4033
{
4134
int n = 0;
@@ -79,7 +72,7 @@ static int php_random_bytes(void *bytes, size_t size)
7972
return SUCCESS;
8073
}
8174

82-
/* {{{ proto string random_bytes(int bytes)
75+
/* {{{ proto string random_bytes(int length)
8376
Return an arbitrary length of pseudo-random bytes as binary string */
8477
PHP_FUNCTION(random_bytes)
8578
{
@@ -90,8 +83,8 @@ PHP_FUNCTION(random_bytes)
9083
return;
9184
}
9285

93-
if (size <= 0 || size >= INT_MAX) {
94-
php_error_docref(NULL, E_WARNING, "Cannot genrate a random string with a size of less than 1 or greater than %d", INT_MAX);
86+
if (size < 1) {
87+
php_error_docref(NULL, E_WARNING, "Length must be greater than 0");
9588
RETURN_FALSE;
9689
}
9790

@@ -108,44 +101,46 @@ PHP_FUNCTION(random_bytes)
108101
}
109102
/* }}} */
110103

111-
/* {{{ proto int random_int(int maximum)
104+
/* {{{ proto int random_int(int max)
112105
Return an arbitrary pseudo-random integer */
113106
PHP_FUNCTION(random_int)
114107
{
115-
zend_long maximum;
116-
zend_long size;
117-
size_t i;
108+
zend_long max = ZEND_LONG_MAX;
109+
zend_long limit;
110+
zend_long result;
118111

119-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &maximum) == FAILURE) {
112+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &max) == FAILURE) {
120113
return;
121114
}
122115

123-
if (ZEND_NUM_ARGS() == 0) {
124-
maximum = INT_MAX;
116+
if (max <= 0 || max > ZEND_LONG_MAX) {
117+
php_error_docref(NULL, E_WARNING, "Cannot use maximum less than 1 or greater than %d", ZEND_LONG_MAX);
118+
RETURN_FALSE;
125119
}
126120

127-
if (maximum <= 0 || maximum > INT_MAX) {
128-
php_error_docref(NULL, E_WARNING, "Cannot use maximum less than 1 or greater than %d", INT_MAX);
129-
RETURN_FALSE;
121+
// Special case so we can return a range inclusive of the upper bound
122+
if (max == ZEND_LONG_MAX) {
123+
if (php_random_bytes(&result, sizeof(result)) == FAILURE) {
124+
return;
125+
}
126+
RETURN_LONG(result & ZEND_LONG_MAX);
130127
}
131128

132-
long range = (long) maximum; // @todo Support min?
129+
// Increment the max so the range is inclusive of max
130+
max++;
133131

134-
// Big thanks to @ircmaxell for the help on this bit
135-
union rand_long_buffer value;
136-
long result;
137-
int bits = (int) (log((double) range) / log(2.0)) + 1;
138-
int bytes = MAX(ceil(bits / 8), 1);
139-
long mask = (long) pow(2.0, (double) bits) - 1;
132+
// Ceiling under which ZEND_LONG_MAX % max == 0
133+
limit = ZEND_LONG_MAX - (ZEND_LONG_MAX % max) - 1;
140134

135+
// Discard numbers over the limit to avoid modulo bias
141136
do {
142-
if (php_random_bytes(&value.buffer, 8) == FAILURE) {
137+
if (php_random_bytes(&result, sizeof(result)) == FAILURE) {
143138
return;
144139
}
145-
result = value.number & mask;
146-
} while (result > maximum);
140+
result &= ZEND_LONG_MAX;
141+
} while (result > limit);
147142

148-
RETURN_LONG(result);
143+
RETURN_LONG(result % max);
149144
}
150145
/* }}} */
151146

0 commit comments

Comments
 (0)