Skip to content

Commit ff558f5

Browse files
committed
Issue #29157: Prefer getrandom() over getentropy()
* dev_urandom() now calls py_getentropy(). Prepare the fallback to support getentropy() failure and falls back on reading from /dev/urandom. * Simplify dev_urandom(). pyurandom() is now responsible to call getentropy() or getrandom(). Enhance also dev_urandom() and pyurandom() documentation. * getrandom() is now preferred over getentropy(). The glibc 2.24 now implements getentropy() on Linux using the getrandom() syscall. But getentropy() doesn't support non-blocking mode. Since getrandom() is tried first, it's not more needed to explicitly exclude getentropy() on Solaris. Replace: "if defined(HAVE_GETENTROPY) && !defined(sun)" with "if defined(HAVE_GETENTROPY)" * Enhance py_getrandom() documentation. py_getentropy() now supports ENOSYS, EPERM & EINTR
1 parent 84b6fb0 commit ff558f5

1 file changed

Lines changed: 187 additions & 87 deletions

File tree

Python/random.c

Lines changed: 187 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -77,57 +77,23 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
7777
return 0;
7878
}
7979

80-
/* Issue #25003: Don't use getentropy() on Solaris (available since
81-
* Solaris 11.3), it is blocking whereas os.urandom() should not block. */
82-
#elif defined(HAVE_GETENTROPY) && !defined(sun)
83-
#define PY_GETENTROPY 1
84-
85-
/* Fill buffer with size pseudo-random bytes generated by getentropy().
86-
Return 0 on success, or raise an exception and return -1 on error.
87-
88-
If raise is zero, don't raise an exception on error. */
89-
static int
90-
py_getentropy(char *buffer, Py_ssize_t size, int raise)
91-
{
92-
while (size > 0) {
93-
Py_ssize_t len = Py_MIN(size, 256);
94-
int res;
95-
96-
if (raise) {
97-
Py_BEGIN_ALLOW_THREADS
98-
res = getentropy(buffer, len);
99-
Py_END_ALLOW_THREADS
100-
}
101-
else {
102-
res = getentropy(buffer, len);
103-
}
104-
105-
if (res < 0) {
106-
if (raise) {
107-
PyErr_SetFromErrno(PyExc_OSError);
108-
}
109-
return -1;
110-
}
111-
112-
buffer += len;
113-
size -= len;
114-
}
115-
return 0;
116-
}
117-
118-
#else
80+
#else /* !MS_WINDOWS */
11981

12082
#if defined(HAVE_GETRANDOM) || defined(HAVE_GETRANDOM_SYSCALL)
12183
#define PY_GETRANDOM 1
12284

123-
/* Call getrandom()
85+
/* Call getrandom() to get random bytes:
86+
12487
- Return 1 on success
125-
- Return 0 if getrandom() syscall is not available (failed with ENOSYS or
126-
EPERM) or if getrandom(GRND_NONBLOCK) failed with EAGAIN (system urandom
127-
not initialized yet) and raise=0.
88+
- Return 0 if getrandom() is not available (failed with ENOSYS or EPERM),
89+
or if getrandom(GRND_NONBLOCK) failed with EAGAIN (system urandom not
90+
initialized yet) and raise=0.
12891
- Raise an exception (if raise is non-zero) and return -1 on error:
129-
getrandom() failed with EINTR and the Python signal handler raised an
130-
exception, or getrandom() failed with a different error. */
92+
if getrandom() failed with EINTR, raise is non-zero and the Python signal
93+
handler raised an exception, or if getrandom() failed with a different
94+
error.
95+
96+
getrandom() is retried if it failed with EINTR: interrupted by a signal. */
13197
static int
13298
py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
13399
{
@@ -148,7 +114,8 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
148114
while (0 < size) {
149115
#ifdef sun
150116
/* Issue #26735: On Solaris, getrandom() is limited to returning up
151-
to 1024 bytes */
117+
to 1024 bytes. Call it multiple times if more bytes are
118+
requested. */
152119
n = Py_MIN(size, 1024);
153120
#else
154121
n = Py_MIN(size, LONG_MAX);
@@ -179,18 +146,19 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
179146
#endif
180147

181148
if (n < 0) {
182-
/* ENOSYS: getrandom() syscall not supported by the kernel (but
183-
* maybe supported by the host which built Python). EPERM:
184-
* getrandom() syscall blocked by SECCOMP or something else. */
149+
/* ENOSYS: the syscall is not supported by the kernel.
150+
EPERM: the syscall is blocked by a security policy (ex: SECCOMP)
151+
or something else. */
185152
if (errno == ENOSYS || errno == EPERM) {
186153
getrandom_works = 0;
187154
return 0;
188155
}
189156

190157
/* getrandom(GRND_NONBLOCK) fails with EAGAIN if the system urandom
191-
is not initialiazed yet. For _PyRandom_Init(), we ignore their
158+
is not initialiazed yet. For _PyRandom_Init(), we ignore the
192159
error and fall back on reading /dev/urandom which never blocks,
193-
even if the system urandom is not initialized yet. */
160+
even if the system urandom is not initialized yet:
161+
see the PEP 524. */
194162
if (errno == EAGAIN && !raise && !blocking) {
195163
return 0;
196164
}
@@ -217,43 +185,119 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise)
217185
}
218186
return 1;
219187
}
220-
#endif
188+
189+
#elif defined(HAVE_GETENTROPY)
190+
#define PY_GETENTROPY 1
191+
192+
/* Fill buffer with size pseudo-random bytes generated by getentropy():
193+
194+
- Return 1 on success
195+
- Return 0 if getentropy() syscall is not available (failed with ENOSYS or
196+
EPERM).
197+
- Raise an exception (if raise is non-zero) and return -1 on error:
198+
if getentropy() failed with EINTR, raise is non-zero and the Python signal
199+
handler raised an exception, or if getentropy() failed with a different
200+
error.
201+
202+
getentropy() is retried if it failed with EINTR: interrupted by a signal. */
203+
static int
204+
py_getentropy(char *buffer, Py_ssize_t size, int raise)
205+
{
206+
/* Is getentropy() supported by the running kernel? Set to 0 if
207+
getentropy() failed with ENOSYS or EPERM. */
208+
static int getentropy_works = 1;
209+
210+
if (!getentropy_works) {
211+
return 0;
212+
}
213+
214+
while (size > 0) {
215+
/* getentropy() is limited to returning up to 256 bytes. Call it
216+
multiple times if more bytes are requested. */
217+
Py_ssize_t len = Py_MIN(size, 256);
218+
int res;
219+
220+
if (raise) {
221+
Py_BEGIN_ALLOW_THREADS
222+
res = getentropy(buffer, len);
223+
Py_END_ALLOW_THREADS
224+
}
225+
else {
226+
res = getentropy(buffer, len);
227+
}
228+
229+
if (res < 0) {
230+
/* ENOSYS: the syscall is not supported by the running kernel.
231+
EPERM: the syscall is blocked by a security policy (ex: SECCOMP)
232+
or something else. */
233+
if (errno == ENOSYS || errno == EPERM) {
234+
getentropy_works = 0;
235+
return 0;
236+
}
237+
238+
if (errno == EINTR) {
239+
if (raise) {
240+
if (PyErr_CheckSignals()) {
241+
return -1;
242+
}
243+
}
244+
245+
/* retry getentropy() if it was interrupted by a signal */
246+
continue;
247+
}
248+
249+
if (raise) {
250+
PyErr_SetFromErrno(PyExc_OSError);
251+
}
252+
return -1;
253+
}
254+
255+
buffer += len;
256+
size -= len;
257+
}
258+
return 1;
259+
}
260+
#endif /* defined(HAVE_GETENTROPY) && !defined(sun) */
261+
221262

222263
static struct {
223264
int fd;
224265
dev_t st_dev;
225266
ino_t st_ino;
226267
} urandom_cache = { -1 };
227268

269+
/* Read random bytes from the /dev/urandom device:
270+
271+
- Return 0 on success
272+
- Raise an exception (if raise is non-zero) and return -1 on error
273+
274+
Possible causes of errors:
275+
276+
- open() failed with ENOENT, ENXIO, ENODEV, EACCES: the /dev/urandom device
277+
was not found. For example, it was removed manually or not exposed in a
278+
chroot or container.
279+
- open() failed with a different error
280+
- fstat() failed
281+
- read() failed or returned 0
228282
229-
/* Read 'size' random bytes from py_getrandom(). Fall back on reading from
230-
/dev/urandom if getrandom() is not available.
283+
read() is retried if it failed with EINTR: interrupted by a signal.
231284
232-
Return 0 on success. Raise an exception (if raise is non-zero) and return -1
233-
on error. */
285+
The file descriptor of the device is kept open between calls to avoid using
286+
many file descriptors when run in parallel from multiple threads:
287+
see the issue #18756.
288+
289+
st_dev and st_ino fields of the file descriptor (from fstat()) are cached to
290+
check if the file descriptor was replaced by a different file (which is
291+
likely a bug in the application): see the issue #21207.
292+
293+
If the file descriptor was closed or replaced, open a new file descriptor
294+
but don't close the old file descriptor: it probably points to something
295+
important for some third-party code. */
234296
static int
235-
dev_urandom(char *buffer, Py_ssize_t size, int blocking, int raise)
297+
dev_urandom(char *buffer, Py_ssize_t size, int raise)
236298
{
237299
int fd;
238300
Py_ssize_t n;
239-
#ifdef PY_GETRANDOM
240-
int res;
241-
#endif
242-
243-
assert(size > 0);
244-
245-
#ifdef PY_GETRANDOM
246-
res = py_getrandom(buffer, size, blocking, raise);
247-
if (res < 0) {
248-
return -1;
249-
}
250-
if (res == 1) {
251-
return 0;
252-
}
253-
/* getrandom() failed with ENOSYS or EPERM,
254-
fall back on reading /dev/urandom */
255-
#endif
256-
257301

258302
if (raise) {
259303
struct _Py_stat_struct st;
@@ -275,9 +319,10 @@ dev_urandom(char *buffer, Py_ssize_t size, int blocking, int raise)
275319
fd = _Py_open("/dev/urandom", O_RDONLY);
276320
if (fd < 0) {
277321
if (errno == ENOENT || errno == ENXIO ||
278-
errno == ENODEV || errno == EACCES)
322+
errno == ENODEV || errno == EACCES) {
279323
PyErr_SetString(PyExc_NotImplementedError,
280324
"/dev/urandom (or equivalent) not found");
325+
}
281326
/* otherwise, keep the OSError exception raised by _Py_open() */
282327
return -1;
283328
}
@@ -349,8 +394,8 @@ dev_urandom_close(void)
349394
urandom_cache.fd = -1;
350395
}
351396
}
397+
#endif /* !MS_WINDOWS */
352398

353-
#endif
354399

355400
/* Fill buffer with pseudo-random bytes generated by a linear congruent
356401
generator (LCG):
@@ -373,14 +418,56 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size)
373418
}
374419
}
375420

376-
/* If raise is zero:
377-
- Don't raise exceptions on error
378-
- Don't call PyErr_CheckSignals() on EINTR (retry directly the interrupted
379-
syscall)
380-
- Don't release the GIL to call syscalls. */
421+
/* Read random bytes:
422+
423+
- Return 0 on success
424+
- Raise an exception (if raise is non-zero) and return -1 on error
425+
426+
Used sources of entropy ordered by preference, preferred source first:
427+
428+
- CryptGenRandom() on Windows
429+
- getrandom() function (ex: Linux and Solaris): call py_getrandom()
430+
- getentropy() function (ex: OpenBSD): call py_getentropy()
431+
- /dev/urandom device
432+
433+
Read from the /dev/urandom device if getrandom() or getentropy() function
434+
is not available or does not work.
435+
436+
Prefer getrandom() over getentropy() because getrandom() supports blocking
437+
and non-blocking mode: see the PEP 524. Python requires non-blocking RNG at
438+
startup to initialize its hash secret, but os.urandom() must block until the
439+
system urandom is initialized (at least on Linux 3.17 and newer).
440+
441+
Prefer getrandom() and getentropy() over reading directly /dev/urandom
442+
because these functions don't need file descriptors and so avoid ENFILE or
443+
EMFILE errors (too many open files): see the issue #18756.
444+
445+
Only the getrandom() function supports non-blocking mode.
446+
447+
Only use RNG running in the kernel. They are more secure because it is
448+
harder to get the internal state of a RNG running in the kernel land than a
449+
RNG running in the user land. The kernel has a direct access to the hardware
450+
and has access to hardware RNG, they are used as entropy sources.
451+
452+
Note: the OpenSSL RAND_pseudo_bytes() function does not automatically reseed
453+
its RNG on fork(), two child processes (with the same pid) generate the same
454+
random numbers: see issue #18747. Kernel RNGs don't have this issue,
455+
they have access to good quality entropy sources.
456+
457+
If raise is zero:
458+
459+
- Don't raise an exception on error
460+
- Don't call the Python signal handler (don't call PyErr_CheckSignals()) if
461+
a function fails with EINTR: retry directly the interrupted function
462+
- Don't release the GIL to call functions.
463+
*/
381464
static int
382465
pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise)
383466
{
467+
#if defined(PY_GETRANDOM) || defined(PY_GETENTROPY)
468+
int res;
469+
#endif
470+
384471
if (size < 0) {
385472
if (raise) {
386473
PyErr_Format(PyExc_ValueError,
@@ -395,10 +482,25 @@ pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise)
395482

396483
#ifdef MS_WINDOWS
397484
return win32_urandom((unsigned char *)buffer, size, raise);
398-
#elif defined(PY_GETENTROPY)
399-
return py_getentropy(buffer, size, raise);
400485
#else
401-
return dev_urandom(buffer, size, blocking, raise);
486+
487+
#if defined(PY_GETRANDOM) || defined(PY_GETENTROPY)
488+
#ifdef PY_GETRANDOM
489+
res = py_getrandom(buffer, size, blocking, raise);
490+
#else
491+
res = py_getentropy(buffer, size, raise);
492+
#endif
493+
if (res < 0) {
494+
return -1;
495+
}
496+
if (res == 1) {
497+
return 0;
498+
}
499+
/* getrandom() or getentropy() function is not available: failed with
500+
ENOSYS or EPERM. Fall back on reading from /dev/urandom. */
501+
#endif
502+
503+
return dev_urandom(buffer, size, raise);
402504
#endif
403505
}
404506

@@ -491,8 +593,6 @@ _PyRandom_Fini(void)
491593
CryptReleaseContext(hCryptProv, 0);
492594
hCryptProv = 0;
493595
}
494-
#elif defined(PY_GETENTROPY)
495-
/* nothing to clean */
496596
#else
497597
dev_urandom_close();
498598
#endif

0 commit comments

Comments
 (0)