php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
csprng.c
Go to the documentation of this file.
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
29#include "Zend/zend_atomic.h"
30
31#include "php_random.h"
32#include "php_random_csprng.h"
33
34#ifdef HAVE_UNISTD_H
35# include <unistd.h>
36#endif
37
38#ifdef PHP_WIN32
39# include "win32/time.h"
40# include "win32/winutil.h"
41# include <process.h>
42#endif
43
44#ifdef __linux__
45# include <sys/syscall.h>
46#endif
47
48#ifdef HAVE_SYS_PARAM_H
49# include <sys/param.h>
50# if (defined(__FreeBSD__) && __FreeBSD_version > 1200000) || (defined(__DragonFly__) && __DragonFly_version >= 500700) || \
51 (defined(__sun) && defined(HAVE_GETRANDOM)) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) || defined(__midipix__)
52# include <sys/random.h>
53# endif
54#endif
55
56#ifdef 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#ifndef PHP_WIN32
66static zend_atomic_int random_fd = ZEND_ATOMIC_INT_INITIALIZER(-1);
67#endif
68
69ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_random_bytes_ex(void *bytes, size_t size, char *errstr, size_t errstr_size)
70{
71#ifdef PHP_WIN32
72 /* Defer to CryptGenRandom on Windows */
74 snprintf(errstr, errstr_size, "Failed to retrieve randomness from the operating system (BCryptGenRandom)");
75 return FAILURE;
76 }
77#elif defined(HAVE_COMMONCRYPTO_COMMONRANDOM_H)
78 /*
79 * Purposely prioritized upon arc4random_buf for modern macOs releases
80 * arc4random api on this platform uses `ccrng_generate` which returns
81 * a status but silented to respect the "no fail" arc4random api interface
82 * the vast majority of the time, it works fine ; but better make sure we catch failures
83 */
84 if (CCRandomGenerateBytes(bytes, size) != kCCSuccess) {
85 snprintf(errstr, errstr_size, "Failed to retrieve randomness from the operating system (CCRandomGenerateBytes)");
86 return FAILURE;
87 }
88#elif defined(HAVE_ARC4RANDOM_BUF) && ((defined(__OpenBSD__) && OpenBSD >= 201405) || (defined(__NetBSD__) && __NetBSD_Version__ >= 700000001 && __NetBSD_Version__ < 1000000000) || \
89 defined(__APPLE__))
90 /*
91 * OpenBSD until there is a valid equivalent
92 * or NetBSD before the 10.x release
93 * falls back to arc4random_buf
94 * giving a decent output, the main benefit
95 * is being (relatively) failsafe.
96 * Older macOs releases fall also into this
97 * category for reasons explained above.
98 */
99 arc4random_buf(bytes, size);
100#else
101 size_t read_bytes = 0;
102# if (defined(__linux__) && defined(SYS_getrandom)) || (defined(__FreeBSD__) && __FreeBSD_version >= 1200000) || (defined(__DragonFly__) && __DragonFly_version >= 500700) || \
103 (defined(__sun) && defined(HAVE_GETRANDOM)) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) || defined(__midipix__)
104 /* Linux getrandom(2) syscall or FreeBSD/DragonFlyBSD/NetBSD getrandom(2) function
105 * Being a syscall, implemented in the kernel, getrandom offers higher quality output
106 * compared to the arc4random api albeit a fallback to /dev/urandom is considered.
107 */
108 while (read_bytes < size) {
109 /* Below, (bytes + read_bytes) is pointer arithmetic.
110
111 bytes read_bytes size
112 | | |
113 [#######=============] (we're going to write over the = region)
114 \\\\\\\\\\\\\
115 amount_to_read
116 */
117 size_t amount_to_read = size - read_bytes;
118 ssize_t n;
119
120 errno = 0;
121# if defined(__linux__)
122 n = syscall(SYS_getrandom, bytes + read_bytes, amount_to_read, 0);
123# else
124 n = getrandom(bytes + read_bytes, amount_to_read, 0);
125# endif
126
127 if (n == -1) {
128 if (errno == ENOSYS) {
129 /* This can happen if PHP was compiled against a newer kernel where getrandom()
130 * is available, but then runs on an older kernel without getrandom(). If this
131 * happens we simply fall back to reading from /dev/urandom. */
132 ZEND_ASSERT(read_bytes == 0);
133 break;
134 } else if (errno == EINTR || errno == EAGAIN) {
135 /* Try again */
136 continue;
137 } else {
138 /* If the syscall fails, fall back to reading from /dev/urandom */
139 break;
140 }
141 }
142
143# if __has_feature(memory_sanitizer)
144 /* MSan does not instrument manual syscall invocations. */
145 __msan_unpoison(bytes + read_bytes, n);
146# endif
147 read_bytes += (size_t) n;
148 }
149# endif
150 if (read_bytes < size) {
151 int fd = zend_atomic_int_load_ex(&random_fd);
152 struct stat st;
153
154 if (fd < 0) {
155 errno = 0;
156 fd = open("/dev/urandom", O_RDONLY);
157 if (fd < 0) {
158 if (errno != 0) {
159 snprintf(errstr, errstr_size, "Cannot open /dev/urandom: %s", strerror(errno));
160 } else {
161 snprintf(errstr, errstr_size, "Cannot open /dev/urandom");
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 (errno != 0) {
177 snprintf(errstr, errstr_size, "Error reading from /dev/urandom: %s", strerror(errno));
178 } else {
179 snprintf(errstr, errstr_size, "Error reading from /dev/urandom");
180 }
181 return FAILURE;
182 }
183 int expected = -1;
184 if (!zend_atomic_int_compare_exchange_ex(&random_fd, &expected, fd)) {
185 close(fd);
186 /* expected is now the actual value of random_fd */
187 fd = expected;
188 }
189 }
190
191 read_bytes = 0;
192 while (read_bytes < size) {
193 errno = 0;
194 ssize_t n = read(fd, bytes + read_bytes, size - read_bytes);
195
196 if (n <= 0) {
197 if (errno != 0) {
198 snprintf(errstr, errstr_size, "Could not gather sufficient random data: %s", strerror(errno));
199 } else {
200 snprintf(errstr, errstr_size, "Could not gather sufficient random data");
201 }
202 return FAILURE;
203 }
204
205 read_bytes += (size_t) n;
206 }
207 }
208#endif
209
210 return SUCCESS;
211}
212
213ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw)
214{
215 char errstr[128];
216 zend_result result = php_random_bytes_ex(bytes, size, errstr, sizeof(errstr));
217
218 if (result == FAILURE && should_throw) {
220 }
221
222 return result;
223}
224
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
268{
269#ifndef PHP_WIN32
270 int fd = zend_atomic_int_exchange(&random_fd, -1);
271 if (fd != -1) {
272 close(fd);
273 }
274#endif
275}
fstat($stream)
stat(string $filename)
ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_random_bytes_ex(void *bytes, size_t size, char *errstr, size_t errstr_size)
Definition csprng.c:69
PHPAPI void php_random_csprng_shutdown(void)
Definition csprng.c:267
ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_random_int(zend_long min, zend_long max, zend_long *result, bool should_throw)
Definition csprng.c:225
ZEND_ATTRIBUTE_NONNULL PHPAPI zend_result php_random_bytes(void *bytes, size_t size, bool should_throw)
Definition csprng.c:213
#define max(a, b)
Definition exif.c:60
zend_long n
Definition ffi.c:4979
new_type size
Definition ffi.c:4365
#define SUCCESS
Definition hash_sha3.c:261
#define PHPAPI
Definition php.h:71
#define min(a, b)
PHPAPI zend_class_entry * random_ce_Random_RandomException
int fd
Definition phpdbg.h:282
#define close(a)
#define errno
#define EAGAIN
PHP_WINUTIL_API int php_win32_get_random_bytes(unsigned char *buf, size_t size)
Definition winutil.c:103
#define ENOSYS
Definition winutil.h:42
ZEND_API int zend_atomic_int_exchange(zend_atomic_int *obj, int desired)
Definition zend_atomic.c:38
struct zend_atomic_int_s zend_atomic_int
#define ZEND_ATOMIC_INT_INITIALIZER(desired)
#define snprintf
ZEND_API ZEND_COLD zend_object * zend_throw_exception(zend_class_entry *exception_ce, const char *message, zend_long code)
int32_t zend_long
Definition zend_long.h:42
uint32_t zend_ulong
Definition zend_long.h:43
#define ZEND_ULONG_MAX
Definition zend_long.h:47
#define ZEND_ATTRIBUTE_NONNULL
#define ZEND_ASSERT(c)
@ FAILURE
Definition zend_types.h:61
ZEND_RESULT_CODE zend_result
Definition zend_types.h:64
bool result