xref: /PHP-8.2/ext/random/random.c (revision 5c9c2757)
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    |          Go Kudo <zeriyoshi@php.net>                                 |
15    +----------------------------------------------------------------------+
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
21 
22 #include <stdlib.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 #include <math.h>
26 
27 #include "php.h"
28 
29 #include "Zend/zend_exceptions.h"
30 
31 #include "php_random.h"
32 
33 #if HAVE_UNISTD_H
34 # include <unistd.h>
35 #endif
36 
37 #ifdef PHP_WIN32
38 # include "win32/time.h"
39 # include "win32/winutil.h"
40 # include <process.h>
41 #else
42 # include <sys/time.h>
43 #endif
44 
45 #ifdef __linux__
46 # include <sys/syscall.h>
47 #endif
48 
49 #if HAVE_SYS_PARAM_H
50 # include <sys/param.h>
51 # if (__FreeBSD__ && __FreeBSD_version > 1200000) || (__DragonFly__ && __DragonFly_version >= 500700) || (defined(__sun) && defined(HAVE_GETRANDOM))
52 #  include <sys/random.h>
53 # endif
54 #endif
55 
56 #if HAVE_COMMONCRYPTO_COMMONRANDOM_H
57 # include <CommonCrypto/CommonCryptoError.h>
58 # include <CommonCrypto/CommonRandom.h>
59 #endif
60 
61 #if __has_feature(memory_sanitizer)
62 # include <sanitizer/msan_interface.h>
63 #endif
64 
65 #include "random_arginfo.h"
66 
67 PHPAPI ZEND_DECLARE_MODULE_GLOBALS(random)
68 
69 PHPAPI zend_class_entry *random_ce_Random_Engine;
70 PHPAPI zend_class_entry *random_ce_Random_CryptoSafeEngine;
71 
72 PHPAPI zend_class_entry *random_ce_Random_Engine_Mt19937;
73 PHPAPI zend_class_entry *random_ce_Random_Engine_PcgOneseq128XslRr64;
74 PHPAPI zend_class_entry *random_ce_Random_Engine_Xoshiro256StarStar;
75 PHPAPI zend_class_entry *random_ce_Random_Engine_Secure;
76 
77 PHPAPI zend_class_entry *random_ce_Random_Randomizer;
78 
79 PHPAPI zend_class_entry *random_ce_Random_RandomError;
80 PHPAPI zend_class_entry *random_ce_Random_BrokenRandomEngineError;
81 PHPAPI zend_class_entry *random_ce_Random_RandomException;
82 
83 static zend_object_handlers random_engine_mt19937_object_handlers;
84 static zend_object_handlers random_engine_pcgoneseq128xslrr64_object_handlers;
85 static zend_object_handlers random_engine_xoshiro256starstar_object_handlers;
86 static zend_object_handlers random_engine_secure_object_handlers;
87 static zend_object_handlers random_randomizer_object_handlers;
88 
89 #define RANDOM_RANGE_ATTEMPTS (50)
90 
rand_range32(const php_random_algo * algo,php_random_status * status,uint32_t umax)91 static inline uint32_t rand_range32(const php_random_algo *algo, php_random_status *status, uint32_t umax)
92 {
93 	uint32_t result, limit;
94 	size_t total_size = 0;
95 	uint32_t count = 0;
96 
97 	result = 0;
98 	total_size = 0;
99 	do {
100 		uint32_t r = algo->generate(status);
101 		result = result | (r << (total_size * 8));
102 		total_size += status->last_generated_size;
103 		if (EG(exception)) {
104 			return 0;
105 		}
106 	} while (total_size < sizeof(uint32_t));
107 
108 	/* Special case where no modulus is required */
109 	if (UNEXPECTED(umax == UINT32_MAX)) {
110 		return result;
111 	}
112 
113 	/* Increment the max so range is inclusive of max */
114 	umax++;
115 
116 	/* Powers of two are not biased */
117 	if ((umax & (umax - 1)) == 0) {
118 		return result & (umax - 1);
119 	}
120 
121 	/* Ceiling under which UINT32_MAX % max == 0 */
122 	limit = UINT32_MAX - (UINT32_MAX % umax) - 1;
123 
124 	/* Discard numbers over the limit to avoid modulo bias */
125 	while (UNEXPECTED(result > limit)) {
126 		/* If the requirements cannot be met in a cycles, return fail */
127 		if (++count > RANDOM_RANGE_ATTEMPTS) {
128 			zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", RANDOM_RANGE_ATTEMPTS);
129 			return 0;
130 		}
131 
132 		result = 0;
133 		total_size = 0;
134 		do {
135 			uint32_t r = algo->generate(status);
136 			result = result | (r << (total_size * 8));
137 			total_size += status->last_generated_size;
138 			if (EG(exception)) {
139 				return 0;
140 			}
141 		} while (total_size < sizeof(uint32_t));
142 	}
143 
144 	return result % umax;
145 }
146 
rand_range64(const php_random_algo * algo,php_random_status * status,uint64_t umax)147 static inline uint64_t rand_range64(const php_random_algo *algo, php_random_status *status, uint64_t umax)
148 {
149 	uint64_t result, limit;
150 	size_t total_size = 0;
151 	uint32_t count = 0;
152 
153 	result = 0;
154 	total_size = 0;
155 	do {
156 		uint64_t r = algo->generate(status);
157 		result = result | (r << (total_size * 8));
158 		total_size += status->last_generated_size;
159 		if (EG(exception)) {
160 			return 0;
161 		}
162 	} while (total_size < sizeof(uint64_t));
163 
164 	/* Special case where no modulus is required */
165 	if (UNEXPECTED(umax == UINT64_MAX)) {
166 		return result;
167 	}
168 
169 	/* Increment the max so range is inclusive of max */
170 	umax++;
171 
172 	/* Powers of two are not biased */
173 	if ((umax & (umax - 1)) == 0) {
174 		return result & (umax - 1);
175 	}
176 
177 	/* Ceiling under which UINT64_MAX % max == 0 */
178 	limit = UINT64_MAX - (UINT64_MAX % umax) - 1;
179 
180 	/* Discard numbers over the limit to avoid modulo bias */
181 	while (UNEXPECTED(result > limit)) {
182 		/* If the requirements cannot be met in a cycles, return fail */
183 		if (++count > RANDOM_RANGE_ATTEMPTS) {
184 			zend_throw_error(random_ce_Random_BrokenRandomEngineError, "Failed to generate an acceptable random number in %d attempts", RANDOM_RANGE_ATTEMPTS);
185 			return 0;
186 		}
187 
188 		result = 0;
189 		total_size = 0;
190 		do {
191 			uint64_t r = algo->generate(status);
192 			result = result | (r << (total_size * 8));
193 			total_size += status->last_generated_size;
194 			if (EG(exception)) {
195 				return 0;
196 			}
197 		} while (total_size < sizeof(uint64_t));
198 	}
199 
200 	return result % umax;
201 }
202 
php_random_engine_mt19937_new(zend_class_entry * ce)203 static zend_object *php_random_engine_mt19937_new(zend_class_entry *ce)
204 {
205 	return &php_random_engine_common_init(ce, &random_engine_mt19937_object_handlers, &php_random_algo_mt19937)->std;
206 }
207 
php_random_engine_pcgoneseq128xslrr64_new(zend_class_entry * ce)208 static zend_object *php_random_engine_pcgoneseq128xslrr64_new(zend_class_entry *ce)
209 {
210 	return &php_random_engine_common_init(ce, &random_engine_pcgoneseq128xslrr64_object_handlers, &php_random_algo_pcgoneseq128xslrr64)->std;
211 }
212 
php_random_engine_xoshiro256starstar_new(zend_class_entry * ce)213 static zend_object *php_random_engine_xoshiro256starstar_new(zend_class_entry *ce)
214 {
215 	return &php_random_engine_common_init(ce, &random_engine_xoshiro256starstar_object_handlers, &php_random_algo_xoshiro256starstar)->std;
216 }
217 
php_random_engine_secure_new(zend_class_entry * ce)218 static zend_object *php_random_engine_secure_new(zend_class_entry *ce)
219 {
220 	return &php_random_engine_common_init(ce, &random_engine_secure_object_handlers, &php_random_algo_secure)->std;
221 }
222 
php_random_randomizer_new(zend_class_entry * ce)223 static zend_object *php_random_randomizer_new(zend_class_entry *ce)
224 {
225 	php_random_randomizer *randomizer = zend_object_alloc(sizeof(php_random_randomizer), ce);
226 
227 	zend_object_std_init(&randomizer->std, ce);
228 	object_properties_init(&randomizer->std, ce);
229 
230 	randomizer->std.handlers = &random_randomizer_object_handlers;
231 
232 	return &randomizer->std;
233 }
234 
randomizer_free_obj(zend_object * object)235 static void randomizer_free_obj(zend_object *object) {
236 	php_random_randomizer *randomizer = php_random_randomizer_from_obj(object);
237 
238 	if (randomizer->is_userland_algo && randomizer->status) {
239 		php_random_status_free(randomizer->status, false);
240 	}
241 
242 	zend_object_std_dtor(&randomizer->std);
243 }
244 
php_random_status_alloc(const php_random_algo * algo,const bool persistent)245 PHPAPI php_random_status *php_random_status_alloc(const php_random_algo *algo, const bool persistent)
246 {
247 	php_random_status *status = pecalloc(1, sizeof(php_random_status), persistent);
248 
249 	status->last_generated_size = algo->generate_size;
250 	status->state = algo->state_size > 0 ? pecalloc(1, algo->state_size, persistent) : NULL;
251 
252 	return status;
253 }
254 
php_random_status_copy(const php_random_algo * algo,php_random_status * old_status,php_random_status * new_status)255 PHPAPI php_random_status *php_random_status_copy(const php_random_algo *algo, php_random_status *old_status, php_random_status *new_status)
256 {
257 	new_status->last_generated_size = old_status->last_generated_size;
258 	new_status->state = memcpy(new_status->state, old_status->state, algo->state_size);
259 
260 	return new_status;
261 }
262 
php_random_status_free(php_random_status * status,const bool persistent)263 PHPAPI void php_random_status_free(php_random_status *status, const bool persistent)
264 {
265 	if (status->state) {
266 		pefree(status->state, persistent);
267 	}
268 	pefree(status, persistent);
269 }
270 
php_random_engine_common_init(zend_class_entry * ce,zend_object_handlers * handlers,const php_random_algo * algo)271 PHPAPI php_random_engine *php_random_engine_common_init(zend_class_entry *ce, zend_object_handlers *handlers, const php_random_algo *algo)
272 {
273 	php_random_engine *engine = zend_object_alloc(sizeof(php_random_engine), ce);
274 
275 	zend_object_std_init(&engine->std, ce);
276 	object_properties_init(&engine->std, ce);
277 
278 	engine->algo = algo;
279 	engine->status = php_random_status_alloc(engine->algo, false);
280 	engine->std.handlers = handlers;
281 
282 	return engine;
283 }
284 
php_random_engine_common_free_object(zend_object * object)285 PHPAPI void php_random_engine_common_free_object(zend_object *object)
286 {
287 	php_random_engine *engine = php_random_engine_from_obj(object);
288 
289 	if (engine->status) {
290 		php_random_status_free(engine->status, false);
291 	}
292 
293 	zend_object_std_dtor(object);
294 }
295 
php_random_engine_common_clone_object(zend_object * object)296 PHPAPI zend_object *php_random_engine_common_clone_object(zend_object *object)
297 {
298 	php_random_engine *old_engine = php_random_engine_from_obj(object);
299 	php_random_engine *new_engine = php_random_engine_from_obj(old_engine->std.ce->create_object(old_engine->std.ce));
300 
301 	new_engine->algo = old_engine->algo;
302 	if (old_engine->status) {
303 		new_engine->status = php_random_status_copy(old_engine->algo, old_engine->status, new_engine->status);
304 	}
305 
306 	zend_objects_clone_members(&new_engine->std, &old_engine->std);
307 
308 	return &new_engine->std;
309 }
310 
311 /* {{{ php_random_range */
php_random_range(const php_random_algo * algo,php_random_status * status,zend_long min,zend_long max)312 PHPAPI zend_long php_random_range(const php_random_algo *algo, php_random_status *status, zend_long min, zend_long max)
313 {
314 	zend_ulong umax = (zend_ulong) max - (zend_ulong) min;
315 
316 	if (umax > UINT32_MAX) {
317 		return (zend_long) (rand_range64(algo, status, umax) + min);
318 	}
319 
320 	return (zend_long) (rand_range32(algo, status, umax) + min);
321 }
322 /* }}} */
323 
324 /* {{{ php_random_default_algo */
php_random_default_algo(void)325 PHPAPI const php_random_algo *php_random_default_algo(void)
326 {
327 	return &php_random_algo_mt19937;
328 }
329 /* }}} */
330 
331 /* {{{ php_random_default_status */
php_random_default_status(void)332 PHPAPI php_random_status *php_random_default_status(void)
333 {
334 	php_random_status *status = RANDOM_G(mt19937);
335 
336 	if (!RANDOM_G(mt19937_seeded)) {
337 		((php_random_status_state_mt19937 *)status->state)->mode = MT_RAND_MT19937;
338 		php_random_mt19937_seed_default(status->state);
339 		RANDOM_G(mt19937_seeded) = true;
340 	}
341 
342 	return status;
343 }
344 /* }}} */
345 
346 /* this is read-only, so it's ok */
347 ZEND_SET_ALIGNED(16, static const char hexconvtab[]) = "0123456789abcdef";
348 
349 /* {{{ php_random_bin2hex_le */
350 /* stolen from standard/string.c */
php_random_bin2hex_le(const void * ptr,const size_t len)351 PHPAPI zend_string *php_random_bin2hex_le(const void *ptr, const size_t len)
352 {
353 	zend_string *str;
354 	size_t i;
355 
356 	str = zend_string_safe_alloc(len, 2 * sizeof(char), 0, 0);
357 
358 	i = 0;
359 #ifdef WORDS_BIGENDIAN
360 	/* force little endian */
361 	for (zend_long j = (len - 1); 0 <= j; j--) {
362 #else
363 	for (zend_long j = 0; j < len; j++) {
364 #endif
365 		ZSTR_VAL(str)[i++] = hexconvtab[((unsigned char *) ptr)[j] >> 4];
366 		ZSTR_VAL(str)[i++] = hexconvtab[((unsigned char *) ptr)[j] & 15];
367 	}
368 	ZSTR_VAL(str)[i] = '\0';
369 
370 	return str;
371 }
372 /* }}} */
373 
374 /* {{{ php_random_hex2bin_le */
375 /* stolen from standard/string.c */
376 PHPAPI bool php_random_hex2bin_le(zend_string *hexstr, void *dest)
377 {
378 	size_t len = hexstr->len >> 1;
379 	unsigned char *str = (unsigned char *) hexstr->val, c, l, d;
380 	unsigned char *ptr = (unsigned char *) dest;
381 	int is_letter, i = 0;
382 
383 #ifdef WORDS_BIGENDIAN
384 	/* force little endian */
385 	for (zend_long j = (len - 1); 0 <= j; j--) {
386 #else
387 	for (zend_long j = 0; j < len; j++) {
388 #endif
389 		c = str[i++];
390 		l = c & ~0x20;
391 		is_letter = ((uint32_t) ((l - 'A') ^ (l - 'F' - 1))) >> (8 * sizeof(uint32_t) - 1);
392 
393 		/* basically (c >= '0' && c <= '9') || (l >= 'A' && l <= 'F') */
394 		if (EXPECTED((((c ^ '0') - 10) >> (8 * sizeof(uint32_t) - 1)) | is_letter)) {
395 			d = (l - 0x10 - 0x27 * is_letter) << 4;
396 		} else {
397 			return false;
398 		}
399 		c = str[i++];
400 		l = c & ~0x20;
401 		is_letter = ((uint32_t) ((l - 'A') ^ (l - 'F' - 1))) >> (8 * sizeof(uint32_t) - 1);
402 		if (EXPECTED((((c ^ '0') - 10) >> (8 * sizeof(uint32_t) - 1)) | is_letter)) {
403 			d |= l - 0x10 - 0x27 * is_letter;
404 		} else {
405 			return false;
406 		}
407 		ptr[j] = d;
408 	}
409 	return true;
410 }
411 /* }}} */
412 
413 /* {{{ php_combined_lcg */
414 PHPAPI double php_combined_lcg(void)
415 {
416 	php_random_status *status = RANDOM_G(combined_lcg);
417 
418 	if (!RANDOM_G(combined_lcg_seeded)) {
419 		php_random_combinedlcg_seed_default(status->state);
420 		RANDOM_G(combined_lcg_seeded) = true;
421 	}
422 
423 	return php_random_algo_combinedlcg.generate(status) * 4.656613e-10;
424 }
425 /* }}} */
426 
427 /* {{{ php_mt_srand */
428 PHPAPI void php_mt_srand(uint32_t seed)
429 {
430 	/* Seed the generator with a simple uint32 */
431 	php_random_algo_mt19937.seed(php_random_default_status(), (zend_long) seed);
432 }
433 /* }}} */
434 
435 /* {{{ php_mt_rand */
436 PHPAPI uint32_t php_mt_rand(void)
437 {
438 	return (uint32_t) php_random_algo_mt19937.generate(php_random_default_status());
439 }
440 /* }}} */
441 
442 /* {{{ php_mt_rand_range */
443 PHPAPI zend_long php_mt_rand_range(zend_long min, zend_long max)
444 {
445 	return php_random_algo_mt19937.range(php_random_default_status(), min, max);
446 }
447 /* }}} */
448 
449 /* {{{ php_mt_rand_common
450  * rand() allows min > max, mt_rand does not */
451 PHPAPI zend_long php_mt_rand_common(zend_long min, zend_long max)
452 {
453 	php_random_status *status = php_random_default_status();
454 	php_random_status_state_mt19937 *s = status->state;
455 
456 	if (s->mode == MT_RAND_MT19937) {
457 		return php_mt_rand_range(min, max);
458 	}
459 
460 	uint64_t r = php_random_algo_mt19937.generate(php_random_default_status()) >> 1;
461 
462 	/* This is an inlined version of the RAND_RANGE_BADSCALING macro that does not invoke UB when encountering
463 	 * (max - min) > ZEND_LONG_MAX.
464 	 */
465 	zend_ulong offset = (double) ( (double) max - min + 1.0) * (r / (PHP_MT_RAND_MAX + 1.0));
466 
467 	return (zend_long) (offset + min);
468 }
469 /* }}} */
470 
471 /* {{{ php_srand */
472 PHPAPI void php_srand(zend_long seed)
473 {
474 	php_mt_srand((uint32_t) seed);
475 }
476 /* }}} */
477 
478 /* {{{ php_rand */
479 PHPAPI zend_long php_rand(void)
480 {
481 	return php_mt_rand();
482 }
483 /* }}} */
484 
485 /* {{{ php_random_bytes */
486 PHPAPI int php_random_bytes(void *bytes, size_t size, bool should_throw)
487 {
488 #ifdef PHP_WIN32
489 	/* Defer to CryptGenRandom on Windows */
490 	if (php_win32_get_random_bytes(bytes, size) == FAILURE) {
491 		if (should_throw) {
492 			zend_throw_exception(random_ce_Random_RandomException, "Failed to retrieve randomness from the operating system (BCryptGenRandom)", 0);
493 		}
494 		return FAILURE;
495 	}
496 #elif HAVE_COMMONCRYPTO_COMMONRANDOM_H
497 	/*
498 	 * Purposely prioritized upon arc4random_buf for modern macOs releases
499 	 * arc4random api on this platform uses `ccrng_generate` which returns
500 	 * a status but silented to respect the "no fail" arc4random api interface
501 	 * the vast majority of the time, it works fine ; but better make sure we catch failures
502 	 */
503 	if (CCRandomGenerateBytes(bytes, size) != kCCSuccess) {
504 		if (should_throw) {
505 			zend_throw_exception(random_ce_Random_RandomException, "Failed to retrieve randomness from the operating system (CCRandomGenerateBytes)", 0);
506 		}
507 		return FAILURE;
508 	}
509 #elif HAVE_DECL_ARC4RANDOM_BUF && ((defined(__OpenBSD__) && OpenBSD >= 201405) || (defined(__NetBSD__) && __NetBSD_Version__ >= 700000001) || defined(__APPLE__))
510 	arc4random_buf(bytes, size);
511 #else
512 	size_t read_bytes = 0;
513 	ssize_t n;
514 # if (defined(__linux__) && defined(SYS_getrandom)) || (defined(__FreeBSD__) && __FreeBSD_version >= 1200000) || (defined(__DragonFly__) && __DragonFly_version >= 500700) || (defined(__sun) && defined(HAVE_GETRANDOM))
515 	/* Linux getrandom(2) syscall or FreeBSD/DragonFlyBSD getrandom(2) function*/
516 	/* Keep reading until we get enough entropy */
517 	while (read_bytes < size) {
518 		errno = 0;
519 
520 		/* Below, (bytes + read_bytes)  is pointer arithmetic.
521 
522 		   bytes   read_bytes  size
523 		     |      |           |
524 		    [#######=============] (we're going to write over the = region)
525 		             \\\\\\\\\\\\\
526 		              amount_to_read
527 		*/
528 		size_t amount_to_read = size - read_bytes;
529 #  if defined(__linux__)
530 		n = syscall(SYS_getrandom, bytes + read_bytes, amount_to_read, 0);
531 #  else
532 		n = getrandom(bytes + read_bytes, amount_to_read, 0);
533 #  endif
534 
535 		if (n == -1) {
536 			if (errno == ENOSYS) {
537 				/* This can happen if PHP was compiled against a newer kernel where getrandom()
538 				 * is available, but then runs on an older kernel without getrandom(). If this
539 				 * happens we simply fall back to reading from /dev/urandom. */
540 				ZEND_ASSERT(read_bytes == 0);
541 				break;
542 			} else if (errno == EINTR || errno == EAGAIN) {
543 				/* Try again */
544 				continue;
545 			} else {
546 				/* If the syscall fails, fall back to reading from /dev/urandom */
547 				break;
548 			}
549 		}
550 
551 #  if __has_feature(memory_sanitizer)
552 		/* MSan does not instrument manual syscall invocations. */
553 		__msan_unpoison(bytes + read_bytes, n);
554 #  endif
555 		read_bytes += (size_t) n;
556 	}
557 # endif
558 	if (read_bytes < size) {
559 		int    fd = RANDOM_G(random_fd);
560 		struct stat st;
561 
562 		if (fd < 0) {
563 			errno = 0;
564 # if HAVE_DEV_URANDOM
565 			fd = open("/dev/urandom", O_RDONLY);
566 # endif
567 			if (fd < 0) {
568 				if (should_throw) {
569 					if (errno != 0) {
570 						zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Cannot open /dev/urandom: %s", strerror(errno));
571 					} else {
572 						zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Cannot open /dev/urandom");
573 					}
574 				}
575 				return FAILURE;
576 			}
577 
578 			errno = 0;
579 			/* Does the file exist and is it a character device? */
580 			if (fstat(fd, &st) != 0 ||
581 # ifdef S_ISNAM
582 					!(S_ISNAM(st.st_mode) || S_ISCHR(st.st_mode))
583 # else
584 					!S_ISCHR(st.st_mode)
585 # endif
586 			) {
587 				close(fd);
588 				if (should_throw) {
589 					if (errno != 0) {
590 						zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Error reading from /dev/urandom: %s", strerror(errno));
591 					} else {
592 						zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Error reading from /dev/urandom");
593 					}
594 				}
595 				return FAILURE;
596 			}
597 			RANDOM_G(random_fd) = fd;
598 		}
599 
600 		for (read_bytes = 0; read_bytes < size; read_bytes += (size_t) n) {
601 			errno = 0;
602 			n = read(fd, bytes + read_bytes, size - read_bytes);
603 			if (n <= 0) {
604 				break;
605 			}
606 		}
607 
608 		if (read_bytes < size) {
609 			if (should_throw) {
610 				if (errno != 0) {
611 					zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Could not gather sufficient random data: %s", strerror(errno));
612 				} else {
613 					zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Could not gather sufficient random data");
614 				}
615 			}
616 			return FAILURE;
617 		}
618 	}
619 #endif
620 
621 	return SUCCESS;
622 }
623 /* }}} */
624 
625 /* {{{ php_random_int */
626 PHPAPI int php_random_int(zend_long min, zend_long max, zend_long *result, bool should_throw)
627 {
628 	zend_ulong umax;
629 	zend_ulong trial;
630 
631 	if (min == max) {
632 		*result = min;
633 		return SUCCESS;
634 	}
635 
636 	umax = (zend_ulong) max - (zend_ulong) min;
637 
638 	if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
639 		return FAILURE;
640 	}
641 
642 	/* Special case where no modulus is required */
643 	if (umax == ZEND_ULONG_MAX) {
644 		*result = (zend_long)trial;
645 		return SUCCESS;
646 	}
647 
648 	/* Increment the max so the range is inclusive of max */
649 	umax++;
650 
651 	/* Powers of two are not biased */
652 	if ((umax & (umax - 1)) != 0) {
653 		/* Ceiling under which ZEND_LONG_MAX % max == 0 */
654 		zend_ulong limit = ZEND_ULONG_MAX - (ZEND_ULONG_MAX % umax) - 1;
655 
656 		/* Discard numbers over the limit to avoid modulo bias */
657 		while (trial > limit) {
658 			if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
659 				return FAILURE;
660 			}
661 		}
662 	}
663 
664 	*result = (zend_long)((trial % umax) + min);
665 	return SUCCESS;
666 }
667 /* }}} */
668 
669 /* {{{ Returns a value from the combined linear congruential generator */
670 PHP_FUNCTION(lcg_value)
671 {
672 	ZEND_PARSE_PARAMETERS_NONE();
673 
674 	RETURN_DOUBLE(php_combined_lcg());
675 }
676 /* }}} */
677 
678 /* {{{ Seeds Mersenne Twister random number generator */
679 PHP_FUNCTION(mt_srand)
680 {
681 	zend_long seed = 0;
682 	zend_long mode = MT_RAND_MT19937;
683 	php_random_status *status = RANDOM_G(mt19937);
684 	php_random_status_state_mt19937 *state = status->state;
685 
686 	ZEND_PARSE_PARAMETERS_START(0, 2)
687 		Z_PARAM_OPTIONAL
688 		Z_PARAM_LONG(seed)
689 		Z_PARAM_LONG(mode)
690 	ZEND_PARSE_PARAMETERS_END();
691 
692 	switch (mode) {
693 	case MT_RAND_PHP:
694 		state->mode = MT_RAND_PHP;
695 		break;
696 	default:
697 		state->mode = MT_RAND_MT19937;
698 	}
699 
700 	if (ZEND_NUM_ARGS() == 0) {
701 		php_random_mt19937_seed_default(status->state);
702 	} else {
703 		php_random_algo_mt19937.seed(status, (uint64_t) seed);
704 	}
705 	RANDOM_G(mt19937_seeded) = true;
706 }
707 /* }}} */
708 
709 /* {{{ Returns a random number from Mersenne Twister */
710 PHP_FUNCTION(mt_rand)
711 {
712 	zend_long min, max;
713 	int argc = ZEND_NUM_ARGS();
714 
715 	if (argc == 0) {
716 		/* genrand_int31 in mt19937ar.c performs a right shift */
717 		RETURN_LONG(php_mt_rand() >> 1);
718 	}
719 
720 	ZEND_PARSE_PARAMETERS_START(2, 2)
721 		Z_PARAM_LONG(min)
722 		Z_PARAM_LONG(max)
723 	ZEND_PARSE_PARAMETERS_END();
724 
725 	if (UNEXPECTED(max < min)) {
726 		zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
727 		RETURN_THROWS();
728 	}
729 
730 	RETURN_LONG(php_mt_rand_common(min, max));
731 }
732 /* }}} */
733 
734 /* {{{ Returns the maximum value a random number from Mersenne Twister can have */
735 PHP_FUNCTION(mt_getrandmax)
736 {
737 	ZEND_PARSE_PARAMETERS_NONE();
738 
739 	/*
740 	 * Melo: it could be 2^^32 but we only use 2^^31 to maintain
741 	 * compatibility with the previous php_rand
742 	 */
743 	RETURN_LONG(PHP_MT_RAND_MAX); /* 2^^31 */
744 }
745 /* }}} */
746 
747 /* {{{ Returns a random number from Mersenne Twister */
748 PHP_FUNCTION(rand)
749 {
750 	zend_long min, max;
751 	int argc = ZEND_NUM_ARGS();
752 
753 	if (argc == 0) {
754 		/* genrand_int31 in mt19937ar.c performs a right shift */
755 		RETURN_LONG(php_mt_rand() >> 1);
756 	}
757 
758 	ZEND_PARSE_PARAMETERS_START(2, 2)
759 		Z_PARAM_LONG(min)
760 		Z_PARAM_LONG(max)
761 	ZEND_PARSE_PARAMETERS_END();
762 
763 	if (max < min) {
764 		RETURN_LONG(php_mt_rand_common(max, min));
765 	}
766 
767 	RETURN_LONG(php_mt_rand_common(min, max));
768 }
769 /* }}} */
770 
771 /* {{{ Return an arbitrary length of pseudo-random bytes as binary string */
772 PHP_FUNCTION(random_bytes)
773 {
774 	zend_long size;
775 	zend_string *bytes;
776 
777 	ZEND_PARSE_PARAMETERS_START(1, 1)
778 		Z_PARAM_LONG(size)
779 	ZEND_PARSE_PARAMETERS_END();
780 
781 	if (size < 1) {
782 		zend_argument_value_error(1, "must be greater than 0");
783 		RETURN_THROWS();
784 	}
785 
786 	bytes = zend_string_alloc(size, 0);
787 
788 	if (php_random_bytes_throw(ZSTR_VAL(bytes), size) == FAILURE) {
789 		zend_string_release_ex(bytes, 0);
790 		RETURN_THROWS();
791 	}
792 
793 	ZSTR_VAL(bytes)[size] = '\0';
794 
795 	RETURN_STR(bytes);
796 }
797 /* }}} */
798 
799 /* {{{ Return an arbitrary pseudo-random integer */
800 PHP_FUNCTION(random_int)
801 {
802 	zend_long min, max, result;
803 
804 	ZEND_PARSE_PARAMETERS_START(2, 2)
805 		Z_PARAM_LONG(min)
806 		Z_PARAM_LONG(max)
807 	ZEND_PARSE_PARAMETERS_END();
808 
809 	if (min > max) {
810 		zend_argument_value_error(1, "must be less than or equal to argument #2 ($max)");
811 		RETURN_THROWS();
812 	}
813 
814 	if (php_random_int_throw(min, max, &result) == FAILURE) {
815 		RETURN_THROWS();
816 	}
817 
818 	RETURN_LONG(result);
819 }
820 /* }}} */
821 
822 /* {{{ PHP_GINIT_FUNCTION */
823 static PHP_GINIT_FUNCTION(random)
824 {
825 	random_globals->random_fd = -1;
826 
827 	random_globals->combined_lcg = php_random_status_alloc(&php_random_algo_combinedlcg, true);
828 	random_globals->combined_lcg_seeded = false;
829 
830 	random_globals->mt19937 = php_random_status_alloc(&php_random_algo_mt19937, true);
831 	random_globals->mt19937_seeded = false;
832 }
833 /* }}} */
834 
835 /* {{{ PHP_GSHUTDOWN_FUNCTION */
836 static PHP_GSHUTDOWN_FUNCTION(random)
837 {
838 	if (random_globals->random_fd >= 0) {
839 		close(random_globals->random_fd);
840 		random_globals->random_fd = -1;
841 	}
842 
843 	php_random_status_free(random_globals->combined_lcg, true);
844 	random_globals->combined_lcg = NULL;
845 
846 	php_random_status_free(random_globals->mt19937, true);
847 	random_globals->mt19937 = NULL;
848 }
849 /* }}} */
850 
851 /* {{{ PHP_MINIT_FUNCTION */
852 PHP_MINIT_FUNCTION(random)
853 {
854 	/* Random\Engine */
855 	random_ce_Random_Engine = register_class_Random_Engine();
856 
857 	/* Random\CryptoSafeEngine */
858 	random_ce_Random_CryptoSafeEngine = register_class_Random_CryptoSafeEngine(random_ce_Random_Engine);
859 
860 	/* Random\RandomError */
861 	random_ce_Random_RandomError = register_class_Random_RandomError(zend_ce_error);
862 
863 	/* Random\BrokenRandomEngineError */
864 	random_ce_Random_BrokenRandomEngineError = register_class_Random_BrokenRandomEngineError(random_ce_Random_RandomError);
865 
866 	/* Random\RandomException */
867 	random_ce_Random_RandomException = register_class_Random_RandomException(zend_ce_exception);
868 
869 	/* Random\Engine\Mt19937 */
870 	random_ce_Random_Engine_Mt19937 = register_class_Random_Engine_Mt19937(random_ce_Random_Engine);
871 	random_ce_Random_Engine_Mt19937->create_object = php_random_engine_mt19937_new;
872 	memcpy(&random_engine_mt19937_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
873 	random_engine_mt19937_object_handlers.offset = XtOffsetOf(php_random_engine, std);
874 	random_engine_mt19937_object_handlers.free_obj = php_random_engine_common_free_object;
875 	random_engine_mt19937_object_handlers.clone_obj = php_random_engine_common_clone_object;
876 
877 	/* Random\Engine\PcgOnseq128XslRr64 */
878 	random_ce_Random_Engine_PcgOneseq128XslRr64 = register_class_Random_Engine_PcgOneseq128XslRr64(random_ce_Random_Engine);
879 	random_ce_Random_Engine_PcgOneseq128XslRr64->create_object = php_random_engine_pcgoneseq128xslrr64_new;
880 	memcpy(&random_engine_pcgoneseq128xslrr64_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
881 	random_engine_pcgoneseq128xslrr64_object_handlers.offset = XtOffsetOf(php_random_engine, std);
882 	random_engine_pcgoneseq128xslrr64_object_handlers.free_obj = php_random_engine_common_free_object;
883 	random_engine_pcgoneseq128xslrr64_object_handlers.clone_obj = php_random_engine_common_clone_object;
884 
885 	/* Random\Engine\Xoshiro256StarStar */
886 	random_ce_Random_Engine_Xoshiro256StarStar = register_class_Random_Engine_Xoshiro256StarStar(random_ce_Random_Engine);
887 	random_ce_Random_Engine_Xoshiro256StarStar->create_object = php_random_engine_xoshiro256starstar_new;
888 	memcpy(&random_engine_xoshiro256starstar_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
889 	random_engine_xoshiro256starstar_object_handlers.offset = XtOffsetOf(php_random_engine, std);
890 	random_engine_xoshiro256starstar_object_handlers.free_obj = php_random_engine_common_free_object;
891 	random_engine_xoshiro256starstar_object_handlers.clone_obj = php_random_engine_common_clone_object;
892 
893 	/* Random\Engine\Secure */
894 	random_ce_Random_Engine_Secure = register_class_Random_Engine_Secure(random_ce_Random_CryptoSafeEngine);
895 	random_ce_Random_Engine_Secure->create_object = php_random_engine_secure_new;
896 	memcpy(&random_engine_secure_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
897 	random_engine_secure_object_handlers.offset = XtOffsetOf(php_random_engine, std);
898 	random_engine_secure_object_handlers.free_obj = php_random_engine_common_free_object;
899 	random_engine_secure_object_handlers.clone_obj = NULL;
900 
901 	/* Random\Randomizer */
902 	random_ce_Random_Randomizer = register_class_Random_Randomizer();
903 	random_ce_Random_Randomizer->create_object = php_random_randomizer_new;
904 	memcpy(&random_randomizer_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
905 	random_randomizer_object_handlers.offset = XtOffsetOf(php_random_randomizer, std);
906 	random_randomizer_object_handlers.free_obj = randomizer_free_obj;
907 	random_randomizer_object_handlers.clone_obj = NULL;
908 
909 	register_random_symbols(module_number);
910 
911 	return SUCCESS;
912 }
913 /* }}} */
914 
915 /* {{{ PHP_RINIT_FUNCTION */
916 PHP_RINIT_FUNCTION(random)
917 {
918 	RANDOM_G(combined_lcg_seeded) = false;
919 	RANDOM_G(mt19937_seeded) = false;
920 
921 	return SUCCESS;
922 }
923 /* }}} */
924 
925 /* {{{ random_module_entry */
926 zend_module_entry random_module_entry = {
927 	STANDARD_MODULE_HEADER,
928 	"random",					/* Extension name */
929 	ext_functions,				/* zend_function_entry */
930 	PHP_MINIT(random),			/* PHP_MINIT - Module initialization */
931 	NULL,						/* PHP_MSHUTDOWN - Module shutdown */
932 	PHP_RINIT(random),			/* PHP_RINIT - Request initialization */
933 	NULL,						/* PHP_RSHUTDOWN - Request shutdown */
934 	NULL,						/* PHP_MINFO - Module info */
935 	PHP_VERSION,				/* Version */
936 	PHP_MODULE_GLOBALS(random),	/* ZTS Module globals */
937 	PHP_GINIT(random),			/* PHP_GINIT - Global initialization */
938 	PHP_GSHUTDOWN(random),		/* PHP_GSHUTDOWN - Global shutdown */
939 	NULL,						/* Post deactivate */
940 	STANDARD_MODULE_PROPERTIES_EX
941 };
942 /* }}} */
943