1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2018 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Sammy Kaye Powers <me@sammyk.me> |
16 +----------------------------------------------------------------------+
17 */
18
19 #include <stdlib.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #include <math.h>
23
24 #include "php.h"
25 #include "zend_exceptions.h"
26 #include "php_random.h"
27
28 #ifdef PHP_WIN32
29 # include "win32/winutil.h"
30 #endif
31 #ifdef __linux__
32 # include <sys/syscall.h>
33 #endif
34 #if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__)
35 # include <sys/param.h>
36 # if __FreeBSD__ && __FreeBSD_version > 1200000
37 # include <sys/random.h>
38 # endif
39 #endif
40
41 #ifdef ZTS
42 int random_globals_id;
43 #else
44 php_random_globals random_globals;
45 #endif
46
random_globals_ctor(php_random_globals * random_globals_p)47 static void random_globals_ctor(php_random_globals *random_globals_p)
48 {
49 random_globals_p->fd = -1;
50 }
51
random_globals_dtor(php_random_globals * random_globals_p)52 static void random_globals_dtor(php_random_globals *random_globals_p)
53 {
54 if (random_globals_p->fd > 0) {
55 close(random_globals_p->fd);
56 random_globals_p->fd = -1;
57 }
58 }
59
60 /* {{{ */
PHP_MINIT_FUNCTION(random)61 PHP_MINIT_FUNCTION(random)
62 {
63 #ifdef ZTS
64 ts_allocate_id(&random_globals_id, sizeof(php_random_globals), (ts_allocate_ctor)random_globals_ctor, (ts_allocate_dtor)random_globals_dtor);
65 #else
66 random_globals_ctor(&random_globals);
67 #endif
68
69 return SUCCESS;
70 }
71 /* }}} */
72
73 /* {{{ */
PHP_MSHUTDOWN_FUNCTION(random)74 PHP_MSHUTDOWN_FUNCTION(random)
75 {
76 #ifndef ZTS
77 random_globals_dtor(&random_globals);
78 #endif
79
80 return SUCCESS;
81 }
82 /* }}} */
83
84 /* {{{ php_random_bytes */
php_random_bytes(void * bytes,size_t size,zend_bool should_throw)85 PHPAPI int php_random_bytes(void *bytes, size_t size, zend_bool should_throw)
86 {
87 #ifdef PHP_WIN32
88 /* Defer to CryptGenRandom on Windows */
89 if (php_win32_get_random_bytes(bytes, size) == FAILURE) {
90 if (should_throw) {
91 zend_throw_exception(zend_ce_exception, "Could not gather sufficient random data", 0);
92 }
93 return FAILURE;
94 }
95 #elif HAVE_DECL_ARC4RANDOM_BUF && ((defined(__OpenBSD__) && OpenBSD >= 201405) || (defined(__NetBSD__) && __NetBSD_Version__ >= 700000001))
96 arc4random_buf(bytes, size);
97 #else
98 size_t read_bytes = 0;
99 ssize_t n;
100 #if (defined(__linux__) && defined(SYS_getrandom)) || (defined(__FreeBSD__) && __FreeBSD_version >= 1200000)
101 /* Linux getrandom(2) syscall or FreeBSD getrandom(2) function*/
102 /* Keep reading until we get enough entropy */
103 while (read_bytes < size) {
104 /* Below, (bytes + read_bytes) is pointer arithmetic.
105
106 bytes read_bytes size
107 | | |
108 [#######=============] (we're going to write over the = region)
109 \\\\\\\\\\\\\
110 amount_to_read
111
112 */
113 size_t amount_to_read = size - read_bytes;
114 #if defined(__linux__)
115 n = syscall(SYS_getrandom, bytes + read_bytes, amount_to_read, 0);
116 #else
117 n = getrandom(bytes + read_bytes, amount_to_read, 0);
118 #endif
119
120 if (n == -1) {
121 if (errno == ENOSYS) {
122 /* This can happen if PHP was compiled against a newer kernel where getrandom()
123 * is available, but then runs on an older kernel without getrandom(). If this
124 * happens we simply fall back to reading from /dev/urandom. */
125 ZEND_ASSERT(read_bytes == 0);
126 break;
127 } else if (errno == EINTR || errno == EAGAIN) {
128 /* Try again */
129 continue;
130 } else {
131 /* If the syscall fails, fall back to reading from /dev/urandom */
132 break;
133 }
134 }
135
136 read_bytes += (size_t) n;
137 }
138 #endif
139 if (read_bytes < size) {
140 int fd = RANDOM_G(fd);
141 struct stat st;
142
143 if (fd < 0) {
144 #if HAVE_DEV_URANDOM
145 fd = open("/dev/urandom", O_RDONLY);
146 #endif
147 if (fd < 0) {
148 if (should_throw) {
149 zend_throw_exception(zend_ce_exception, "Cannot open source device", 0);
150 }
151 return FAILURE;
152 }
153 /* Does the file exist and is it a character device? */
154 if (fstat(fd, &st) != 0 ||
155 # ifdef S_ISNAM
156 !(S_ISNAM(st.st_mode) || S_ISCHR(st.st_mode))
157 # else
158 !S_ISCHR(st.st_mode)
159 # endif
160 ) {
161 close(fd);
162 if (should_throw) {
163 zend_throw_exception(zend_ce_exception, "Error reading from source device", 0);
164 }
165 return FAILURE;
166 }
167 RANDOM_G(fd) = fd;
168 }
169
170 for (read_bytes = 0; read_bytes < size; read_bytes += (size_t) n) {
171 n = read(fd, bytes + read_bytes, size - read_bytes);
172 if (n <= 0) {
173 break;
174 }
175 }
176
177 if (read_bytes < size) {
178 if (should_throw) {
179 zend_throw_exception(zend_ce_exception, "Could not gather sufficient random data", 0);
180 }
181 return FAILURE;
182 }
183 }
184 #endif
185
186 return SUCCESS;
187 }
188 /* }}} */
189
190 /* {{{ proto string random_bytes(int length)
191 Return an arbitrary length of pseudo-random bytes as binary string */
PHP_FUNCTION(random_bytes)192 PHP_FUNCTION(random_bytes)
193 {
194 zend_long size;
195 zend_string *bytes;
196
197 ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1)
198 Z_PARAM_LONG(size)
199 ZEND_PARSE_PARAMETERS_END();
200
201 if (size < 1) {
202 zend_throw_exception(zend_ce_error, "Length must be greater than 0", 0);
203 return;
204 }
205
206 bytes = zend_string_alloc(size, 0);
207
208 if (php_random_bytes_throw(ZSTR_VAL(bytes), size) == FAILURE) {
209 zend_string_release_ex(bytes, 0);
210 return;
211 }
212
213 ZSTR_VAL(bytes)[size] = '\0';
214
215 RETURN_STR(bytes);
216 }
217 /* }}} */
218
219 /* {{{ */
php_random_int(zend_long min,zend_long max,zend_long * result,zend_bool should_throw)220 PHPAPI int php_random_int(zend_long min, zend_long max, zend_long *result, zend_bool should_throw)
221 {
222 zend_ulong umax;
223 zend_ulong trial;
224
225 if (min == max) {
226 *result = min;
227 return SUCCESS;
228 }
229
230 umax = max - min;
231
232 if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
233 return FAILURE;
234 }
235
236 /* Special case where no modulus is required */
237 if (umax == ZEND_ULONG_MAX) {
238 *result = (zend_long)trial;
239 return SUCCESS;
240 }
241
242 /* Increment the max so the range is inclusive of max */
243 umax++;
244
245 /* Powers of two are not biased */
246 if ((umax & (umax - 1)) != 0) {
247 /* Ceiling under which ZEND_LONG_MAX % max == 0 */
248 zend_ulong limit = ZEND_ULONG_MAX - (ZEND_ULONG_MAX % umax) - 1;
249
250 /* Discard numbers over the limit to avoid modulo bias */
251 while (trial > limit) {
252 if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
253 return FAILURE;
254 }
255 }
256 }
257
258 *result = (zend_long)((trial % umax) + min);
259 return SUCCESS;
260 }
261 /* }}} */
262
263 /* {{{ proto int random_int(int min, int max)
264 Return an arbitrary pseudo-random integer */
PHP_FUNCTION(random_int)265 PHP_FUNCTION(random_int)
266 {
267 zend_long min;
268 zend_long max;
269 zend_long result;
270
271 ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2)
272 Z_PARAM_LONG(min)
273 Z_PARAM_LONG(max)
274 ZEND_PARSE_PARAMETERS_END();
275
276 if (min > max) {
277 zend_throw_exception(zend_ce_error, "Minimum value must be less than or equal to the maximum value", 0);
278 return;
279 }
280
281 if (php_random_int_throw(min, max, &result) == FAILURE) {
282 return;
283 }
284
285 RETURN_LONG(result);
286 }
287 /* }}} */
288
289 /*
290 * Local variables:
291 * tab-width: 4
292 * c-basic-offset: 4
293 * End:
294 * vim600: sw=4 ts=4 fdm=marker
295 * vim<600: sw=4 ts=4
296 */
297