From de2f1ea12457780bfa4dac0f2d1ed2c8f02d591e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 6 Jan 2017 11:33:18 +0100 Subject: py_getentropy() now supports ENOSYS, EPERM & EINTR Issue #29157. --- Python/random.c | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Python/random.c b/Python/random.c index ad2c389..bc96226 100644 --- a/Python/random.c +++ b/Python/random.c @@ -183,14 +183,31 @@ py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise) #elif defined(HAVE_GETENTROPY) #define PY_GETENTROPY 1 -/* Fill buffer with size pseudo-random bytes generated by getentropy(). - Return 1 on success, or raise an exception and return -1 on error. +/* Fill buffer with size pseudo-random bytes generated by getentropy(): - If raise is zero, don't raise an exception on error. */ + - Return 1 on success + - Return 0 if getentropy() syscall is not available (failed with ENOSYS or + EPERM). + - Raise an exception (if raise is non-zero) and return -1 on error: + if getentropy() failed with EINTR, raise is non-zero and the Python signal + handler raised an exception, or if getentropy() failed with a different + error. + + getentropy() is retried if it failed with EINTR: interrupted by a signal. */ static int py_getentropy(char *buffer, Py_ssize_t size, int raise) { + /* Is getentropy() supported by the running kernel? Set to 0 if + getentropy() failed with ENOSYS or EPERM. */ + static int getentropy_works = 1; + + if (!getentropy_works) { + return 0; + } + while (size > 0) { + /* getentropy() is limited to returning up to 256 bytes. Call it + multiple times if more bytes are requested. */ Py_ssize_t len = Py_MIN(size, 256); int res; @@ -204,6 +221,25 @@ py_getentropy(char *buffer, Py_ssize_t size, int raise) } if (res < 0) { + /* ENOSYS: the syscall is not supported by the running kernel. + EPERM: the syscall is blocked by a security policy (ex: SECCOMP) + or something else. */ + if (errno == ENOSYS || errno == EPERM) { + getentropy_works = 0; + return 0; + } + + if (errno == EINTR) { + if (raise) { + if (PyErr_CheckSignals()) { + return -1; + } + } + + /* retry getentropy() if it was interrupted by a signal */ + continue; + } + if (raise) { PyErr_SetFromErrno(PyExc_OSError); } -- cgit v0.12