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