24
24
#include <math.h>
25
25
26
26
#include "php.h"
27
+ #include "php_random.h"
27
28
28
29
#if PHP_WIN32
29
30
# include "win32/winutil.h"
30
31
#endif
31
32
32
- // Big thanks to @ircmaxell for the help on this bit
33
- union rand_long_buffer {
34
- char buffer [8 ];
35
- long number ;
36
- };
33
+ #ifdef ZTS
34
+ int random_globals_id ;
35
+ #else
36
+ php_random_globals random_globals ;
37
+ #endif
38
+
39
+ static void random_globals_ctor (php_random_globals * random_globals_p )
40
+ {
41
+ random_globals_p -> fd = -1 ;
42
+ }
43
+
44
+ static void random_globals_dtor (php_random_globals * random_globals_p )
45
+ {
46
+ if (random_globals_p -> fd > 0 ) {
47
+ close (random_globals_p -> fd );
48
+ random_globals_p -> fd = -1 ;
49
+ }
50
+ }
51
+
52
+ /* {{{ */
53
+ PHP_MINIT_FUNCTION (random )
54
+ {
55
+ #ifdef ZTS
56
+ ts_allocate_id (& random_globals_id , sizeof (php_random_globals ), (ts_allocate_ctor )random_globals_ctor , (ts_allocate_dtor )random_globals_dtor );
57
+ #else
58
+ random_globals_ctor (& random_globals );
59
+ #endif
60
+
61
+ return SUCCESS ;
62
+ }
63
+ /* }}} */
37
64
38
- // Copy/pasted from mcrypt.c
39
- static int php_random_bytes ( char * bytes , zend_long size )
65
+ /* {{{ */
66
+ PHP_MSHUTDOWN_FUNCTION ( random )
40
67
{
41
- int n = 0 ;
68
+ #ifndef ZTS
69
+ random_globals_dtor (& random_globals );
70
+ #endif
71
+ }
72
+ /* }}} */
42
73
74
+ /* {{{ */
75
+ static int php_random_bytes (void * bytes , size_t size )
76
+ {
43
77
#if PHP_WIN32
44
- /* random/urandom equivalent on Windows */
45
- BYTE * win_bytes = (BYTE * ) bytes ;
46
- if (php_win32_get_random_bytes (win_bytes , (size_t ) size ) == FAILURE ) {
78
+ /* Defer to CryptGenRandom on Windows */
79
+ if (php_win32_get_random_bytes (bytes , size ) == FAILURE ) {
47
80
php_error_docref (NULL , E_WARNING , "Could not gather sufficient random data" );
48
81
return FAILURE ;
49
82
}
50
- n = (int )size ;
51
83
#else
52
- // @todo Need to cache the fd for random_int() call within loop
53
- int fd ;
84
+ #if HAVE_DECL_ARC4RANDOM_BUF
85
+ arc4random_buf (bytes , size );
86
+ #else
87
+ int fd = RANDOM_G (fd );
54
88
size_t read_bytes = 0 ;
55
89
56
- fd = open ("/dev/urandom" , O_RDONLY );
57
90
if (fd < 0 ) {
58
- php_error_docref (NULL , E_WARNING , "Cannot open source device" );
59
- return FAILURE ;
91
+ #if HAVE_DEV_ARANDOM
92
+ fd = open ("/dev/arandom" , O_RDONLY );
93
+ #else
94
+ #if HAVE_DEV_URANDOM
95
+ fd = open ("/dev/urandom" , O_RDONLY );
96
+ #endif // URANDOM
97
+ #endif // ARANDOM
98
+ if (fd < 0 ) {
99
+ php_error_docref (NULL , E_WARNING , "Cannot open source device" );
100
+ return FAILURE ;
101
+ }
102
+
103
+ RANDOM_G (fd ) = fd ;
60
104
}
105
+
61
106
while (read_bytes < size ) {
62
- n = read (fd , bytes + read_bytes , size - read_bytes );
107
+ ssize_t n = read (fd , bytes + read_bytes , size - read_bytes );
63
108
if (n < 0 ) {
64
109
break ;
65
110
}
66
111
read_bytes += n ;
67
112
}
68
- n = read_bytes ;
69
113
70
- close (fd );
71
- if (n < size ) {
114
+ if (read_bytes < size ) {
72
115
php_error_docref (NULL , E_WARNING , "Could not gather sufficient random data" );
73
116
return FAILURE ;
74
117
}
75
- #endif
76
-
77
- // @todo - Do we need to do this?
78
- bytes [size ] = '\0' ;
118
+ #endif // !ARC4RANDOM_BUF
119
+ #endif // !WIN32
79
120
80
121
return SUCCESS ;
81
122
}
123
+ /* }}} */
82
124
83
- /* {{{ proto string random_bytes(int bytes )
125
+ /* {{{ proto string random_bytes(int length )
84
126
Return an arbitrary length of pseudo-random bytes as binary string */
85
127
PHP_FUNCTION (random_bytes )
86
128
{
@@ -91,60 +133,71 @@ PHP_FUNCTION(random_bytes)
91
133
return ;
92
134
}
93
135
94
- if (size <= 0 || size >= INT_MAX ) {
95
- php_error_docref (NULL , E_WARNING , "Cannot genrate a random string with a size of less than 1 or greater than %d" , INT_MAX );
136
+ if (size < 1 ) {
137
+ php_error_docref (NULL , E_WARNING , "Length must be greater than 0" );
96
138
RETURN_FALSE ;
97
139
}
98
140
99
141
bytes = zend_string_alloc (size , 0 );
100
142
101
143
if (php_random_bytes (bytes -> val , size ) == FAILURE ) {
102
144
zend_string_release (bytes );
103
- return ;
145
+ RETURN_FALSE ;
104
146
}
105
147
148
+ bytes -> val [size ] = '\0' ;
149
+
106
150
RETURN_STR (bytes );
107
151
}
108
152
/* }}} */
109
153
110
- /* {{{ proto int random_int(int maximum )
154
+ /* {{{ proto int random_int(int min, int max )
111
155
Return an arbitrary pseudo-random integer */
112
156
PHP_FUNCTION (random_int )
113
157
{
114
- zend_long maximum ;
115
- zend_long size ;
116
- size_t i ;
158
+ zend_long min ;
159
+ zend_long max ;
160
+ zend_ulong limit ;
161
+ zend_ulong umax ;
162
+ zend_ulong result ;
117
163
118
- if (zend_parse_parameters (ZEND_NUM_ARGS (), "|l " , & maximum ) == FAILURE ) {
164
+ if (zend_parse_parameters (ZEND_NUM_ARGS (), "ll " , & min , & max ) == FAILURE ) {
119
165
return ;
120
166
}
121
167
122
- if (ZEND_NUM_ARGS () == 0 ) {
123
- maximum = INT_MAX ;
168
+ if (min >= max ) {
169
+ php_error_docref (NULL , E_WARNING , "Minimum value must be less than the maximum value" );
170
+ RETURN_FALSE ;
124
171
}
125
172
126
- if (maximum <= 0 || maximum > INT_MAX ) {
127
- php_error_docref (NULL , E_WARNING , "Cannot use maximum less than 1 or greater than %d" , INT_MAX );
173
+ umax = max - min ;
174
+
175
+ if (php_random_bytes (& result , sizeof (result )) == FAILURE ) {
128
176
RETURN_FALSE ;
129
177
}
130
178
131
- long range = (long ) maximum ; // @todo Support min?
132
-
133
- // Big thanks to @ircmaxell for the help on this bit
134
- union rand_long_buffer value ;
135
- long result ;
136
- int bits = (int ) (log ((double ) range ) / log (2.0 )) + 1 ;
137
- int bytes = MAX (ceil (bits / 8 ), 1 );
138
- long mask = (long ) pow (2.0 , (double ) bits ) - 1 ;
179
+ // Special case where no modulus is required
180
+ if (umax == ZEND_ULONG_MAX ) {
181
+ RETURN_LONG ((zend_long )result );
182
+ }
139
183
140
- do {
141
- if (php_random_bytes (& value .buffer , 8 ) == FAILURE ) {
142
- return ;
184
+ // Increment the max so the range is inclusive of max
185
+ umax ++ ;
186
+
187
+ // Powers of two are not biased
188
+ if ((umax & ~umax ) != umax ) {
189
+ // Ceiling under which ZEND_LONG_MAX % max == 0
190
+ limit = ZEND_ULONG_MAX - (ZEND_ULONG_MAX % umax ) - 1 ;
191
+
192
+ // Discard numbers over the limit to avoid modulo bias
193
+ while (result > limit ) {
194
+ if (php_random_bytes (& result , sizeof (result )) == FAILURE ) {
195
+ return ;
196
+ }
143
197
}
144
- result = value .number & mask ;
145
- } while (result > maximum );
198
+ }
146
199
147
- RETURN_LONG (result );
200
+ RETURN_LONG (( zend_long )(( result % umax ) + min ) );
148
201
}
149
202
/* }}} */
150
203
0 commit comments