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: Tim Düsterhus <timwolla@php.net> |
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
26 #include "php.h"
27
28 #include "Zend/zend_exceptions.h"
29
30 #include "php_random.h"
31
32 #if HAVE_UNISTD_H
33 # include <unistd.h>
34 #endif
35
36 #ifdef PHP_WIN32
37 # include "win32/time.h"
38 # include "win32/winutil.h"
39 # include <process.h>
40 #endif
41
42 #ifdef __linux__
43 # include <sys/syscall.h>
44 #endif
45
46 #if HAVE_SYS_PARAM_H
47 # include <sys/param.h>
48 # if (__FreeBSD__ && __FreeBSD_version > 1200000) || (__DragonFly__ && __DragonFly_version >= 500700) || \
49 (defined(__sun) && defined(HAVE_GETRANDOM)) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000)
50 # include <sys/random.h>
51 # endif
52 #endif
53
54 #if HAVE_COMMONCRYPTO_COMMONRANDOM_H
55 # include <CommonCrypto/CommonCryptoError.h>
56 # include <CommonCrypto/CommonRandom.h>
57 #endif
58
59 #if __has_feature(memory_sanitizer)
60 # include <sanitizer/msan_interface.h>
61 #endif
62
php_random_bytes(void * bytes,size_t size,bool should_throw)63 PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw)
64 {
65 #ifdef PHP_WIN32
66 /* Defer to CryptGenRandom on Windows */
67 if (php_win32_get_random_bytes(bytes, size) == FAILURE) {
68 if (should_throw) {
69 zend_throw_exception(random_ce_Random_RandomException, "Failed to retrieve randomness from the operating system (BCryptGenRandom)", 0);
70 }
71 return FAILURE;
72 }
73 #elif HAVE_COMMONCRYPTO_COMMONRANDOM_H
74 /*
75 * Purposely prioritized upon arc4random_buf for modern macOs releases
76 * arc4random api on this platform uses `ccrng_generate` which returns
77 * a status but silented to respect the "no fail" arc4random api interface
78 * the vast majority of the time, it works fine ; but better make sure we catch failures
79 */
80 if (CCRandomGenerateBytes(bytes, size) != kCCSuccess) {
81 if (should_throw) {
82 zend_throw_exception(random_ce_Random_RandomException, "Failed to retrieve randomness from the operating system (CCRandomGenerateBytes)", 0);
83 }
84 return FAILURE;
85 }
86 #elif HAVE_DECL_ARC4RANDOM_BUF && ((defined(__OpenBSD__) && OpenBSD >= 201405) || (defined(__NetBSD__) && __NetBSD_Version__ >= 700000001 && __NetBSD_Version__ < 1000000000) || \
87 defined(__APPLE__))
88 /*
89 * OpenBSD until there is a valid equivalent
90 * or NetBSD before the 10.x release
91 * falls back to arc4random_buf
92 * giving a decent output, the main benefit
93 * is being (relatively) failsafe.
94 * Older macOs releases fall also into this
95 * category for reasons explained above.
96 */
97 arc4random_buf(bytes, size);
98 #else
99 size_t read_bytes = 0;
100 # if (defined(__linux__) && defined(SYS_getrandom)) || (defined(__FreeBSD__) && __FreeBSD_version >= 1200000) || (defined(__DragonFly__) && __DragonFly_version >= 500700) || \
101 (defined(__sun) && defined(HAVE_GETRANDOM)) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000)
102 /* Linux getrandom(2) syscall or FreeBSD/DragonFlyBSD/NetBSD getrandom(2) function
103 * Being a syscall, implemented in the kernel, getrandom offers higher quality output
104 * compared to the arc4random api albeit a fallback to /dev/urandom is considered.
105 */
106 while (read_bytes < size) {
107 /* Below, (bytes + read_bytes) is pointer arithmetic.
108
109 bytes read_bytes size
110 | | |
111 [#######=============] (we're going to write over the = region)
112 \\\\\\\\\\\\\
113 amount_to_read
114 */
115 size_t amount_to_read = size - read_bytes;
116 ssize_t n;
117
118 errno = 0;
119 # if defined(__linux__)
120 n = syscall(SYS_getrandom, bytes + read_bytes, amount_to_read, 0);
121 # else
122 n = getrandom(bytes + read_bytes, amount_to_read, 0);
123 # endif
124
125 if (n == -1) {
126 if (errno == ENOSYS) {
127 /* This can happen if PHP was compiled against a newer kernel where getrandom()
128 * is available, but then runs on an older kernel without getrandom(). If this
129 * happens we simply fall back to reading from /dev/urandom. */
130 ZEND_ASSERT(read_bytes == 0);
131 break;
132 } else if (errno == EINTR || errno == EAGAIN) {
133 /* Try again */
134 continue;
135 } else {
136 /* If the syscall fails, fall back to reading from /dev/urandom */
137 break;
138 }
139 }
140
141 # if __has_feature(memory_sanitizer)
142 /* MSan does not instrument manual syscall invocations. */
143 __msan_unpoison(bytes + read_bytes, n);
144 # endif
145 read_bytes += (size_t) n;
146 }
147 # endif
148 if (read_bytes < size) {
149 int fd = RANDOM_G(random_fd);
150 struct stat st;
151
152 if (fd < 0) {
153 errno = 0;
154 fd = open("/dev/urandom", O_RDONLY);
155 if (fd < 0) {
156 if (should_throw) {
157 if (errno != 0) {
158 zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Cannot open /dev/urandom: %s", strerror(errno));
159 } else {
160 zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Cannot open /dev/urandom");
161 }
162 }
163 return FAILURE;
164 }
165
166 errno = 0;
167 /* Does the file exist and is it a character device? */
168 if (fstat(fd, &st) != 0 ||
169 # ifdef S_ISNAM
170 !(S_ISNAM(st.st_mode) || S_ISCHR(st.st_mode))
171 # else
172 !S_ISCHR(st.st_mode)
173 # endif
174 ) {
175 close(fd);
176 if (should_throw) {
177 if (errno != 0) {
178 zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Error reading from /dev/urandom: %s", strerror(errno));
179 } else {
180 zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Error reading from /dev/urandom");
181 }
182 }
183 return FAILURE;
184 }
185 RANDOM_G(random_fd) = fd;
186 }
187
188 read_bytes = 0;
189 while (read_bytes < size) {
190 errno = 0;
191 ssize_t n = read(fd, bytes + read_bytes, size - read_bytes);
192
193 if (n <= 0) {
194 if (should_throw) {
195 if (errno != 0) {
196 zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Could not gather sufficient random data: %s", strerror(errno));
197 } else {
198 zend_throw_exception_ex(random_ce_Random_RandomException, 0, "Could not gather sufficient random data");
199 }
200 }
201 return FAILURE;
202 }
203
204 read_bytes += (size_t) n;
205 }
206 }
207 #endif
208
209 return SUCCESS;
210 }
211
php_random_int(zend_long min,zend_long max,zend_long * result,bool should_throw)212 PHPAPI zend_result php_random_int(zend_long min, zend_long max, zend_long *result, bool should_throw)
213 {
214 zend_ulong umax;
215 zend_ulong trial;
216
217 if (min == max) {
218 *result = min;
219 return SUCCESS;
220 }
221
222 umax = (zend_ulong) max - (zend_ulong) min;
223
224 if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
225 return FAILURE;
226 }
227
228 /* Special case where no modulus is required */
229 if (umax == ZEND_ULONG_MAX) {
230 *result = (zend_long)trial;
231 return SUCCESS;
232 }
233
234 /* Increment the max so the range is inclusive of max */
235 umax++;
236
237 /* Powers of two are not biased */
238 if ((umax & (umax - 1)) != 0) {
239 /* Ceiling under which ZEND_LONG_MAX % max == 0 */
240 zend_ulong limit = ZEND_ULONG_MAX - (ZEND_ULONG_MAX % umax) - 1;
241
242 /* Discard numbers over the limit to avoid modulo bias */
243 while (trial > limit) {
244 if (php_random_bytes(&trial, sizeof(trial), should_throw) == FAILURE) {
245 return FAILURE;
246 }
247 }
248 }
249
250 *result = (zend_long)((trial % umax) + min);
251 return SUCCESS;
252 }
253