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