From dc47a19e576dcb358547608c5347a6bf78ced562 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 2 May 2014 22:06:44 +0200 Subject: Issue #21393: random.c: on Windows, close the hCryptProv handle at exit --- Python/random.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 2941ba16af..b04d205a46 100644 --- a/Python/random.c +++ b/Python/random.c @@ -15,8 +15,6 @@ static int _Py_HashSecret_Initialized = 0; #endif #ifdef MS_WINDOWS -/* This handle is never explicitly released. Instead, the operating - system will release it when the process terminates. */ static HCRYPTPROV hCryptProv = 0; static int @@ -298,7 +296,12 @@ _PyRandom_Init(void) void _PyRandom_Fini(void) { -#ifndef MS_WINDOWS +#ifdef MS_WINDOWS + if (hCryptProv) { + CloseHandle(hCryptProv); + hCryptProv = 0; + } +#else dev_urandom_close(); #endif } -- cgit v1.2.1 From 0bb52d78d45567397897b61a0032b8dd5eb059c3 Mon Sep 17 00:00:00 2001 From: Tim Golden Date: Tue, 6 May 2014 13:29:45 +0100 Subject: Issue21393 Use CryptReleaseContext to release Crypt handle on Windows --- Python/random.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index b04d205a46..a052b65725 100644 --- a/Python/random.c +++ b/Python/random.c @@ -298,7 +298,7 @@ _PyRandom_Fini(void) { #ifdef MS_WINDOWS if (hCryptProv) { - CloseHandle(hCryptProv); + CryptReleaseContext(hCryptProv, 0); hCryptProv = 0; } #else -- cgit v1.2.1 From 915c27520fb658ee0aa2e4d95487af700ceb5192 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 21 Dec 2014 01:16:38 +0100 Subject: Issue #22585: On OpenBSD 5.6 and newer, os.urandom() now calls getentropy(), instead of reading /dev/urandom, to get pseudo-random bytes. --- Python/random.c | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 654612ba29..93d300dae4 100644 --- a/Python/random.c +++ b/Python/random.c @@ -36,7 +36,7 @@ error: } /* Fill buffer with size pseudo-random bytes generated by the Windows CryptoGen - API. Return 0 on success, or -1 on error. */ + API. Return 0 on success, or raise an exception and return -1 on error. */ static int win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) { @@ -66,10 +66,35 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) } return 0; } -#endif /* MS_WINDOWS */ +#elif HAVE_GETENTROPY +/* Fill buffer with size pseudo-random bytes generated by getentropy(). + Return 0 on success, or raise an exception and return -1 on error. -#ifndef MS_WINDOWS + If fatal is nonzero, call Py_FatalError() instead of raising an exception + on error. */ +static int +py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal) +{ + while (size > 0) { + Py_ssize_t len = Py_MIN(size, 256); + int res = getentropy(buffer, len); + if (res < 0) { + if (fatal) { + Py_FatalError("getentropy() failed"); + } + else { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + } + buffer += len; + size -= len; + } + return 0; +} + +#else static struct { int fd; dev_t st_dev; @@ -201,7 +226,7 @@ dev_urandom_close(void) } } -#endif /* MS_WINDOWS */ +#endif /* HAVE_GETENTROPY */ /* Fill buffer with pseudo-random bytes generated by a linear congruent generator (LCG): @@ -242,6 +267,8 @@ _PyOS_URandom(void *buffer, Py_ssize_t size) #ifdef MS_WINDOWS return win32_urandom((unsigned char *)buffer, size, 1); +#elif HAVE_GETENTROPY + return py_getentropy(buffer, size, 0); #else return dev_urandom_python((char*)buffer, size); #endif @@ -287,6 +314,8 @@ _PyRandom_Init(void) else { #ifdef MS_WINDOWS (void)win32_urandom(secret, secret_size, 0); +#elif HAVE_GETENTROPY + (void)py_getentropy(secret, secret_size, 1); #else dev_urandom_noraise(secret, secret_size); #endif @@ -301,6 +330,8 @@ _PyRandom_Fini(void) CryptReleaseContext(hCryptProv, 0); hCryptProv = 0; } +#elif HAVE_GETENTROPY + /* nothing to clean */ #else dev_urandom_close(); #endif -- cgit v1.2.1 From 4b5c8edd294f02afef7407d9fd5236789bd4bc34 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Sat, 21 Feb 2015 08:44:05 -0800 Subject: Issue #23152: Implement _Py_fstat() to support files larger than 2 GB on Windows. fstat() may fail with EOVERFLOW on files larger than 2 GB because the file size type is an signed 32-bit integer. --- Python/random.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 93d300dae4..031d8875c9 100644 --- a/Python/random.c +++ b/Python/random.c @@ -139,14 +139,14 @@ dev_urandom_python(char *buffer, Py_ssize_t size) { int fd; Py_ssize_t n; - struct stat st; + struct _Py_stat_struct st; if (size <= 0) return 0; if (urandom_cache.fd >= 0) { /* Does the fd point to the same thing as before? (issue #21207) */ - if (fstat(urandom_cache.fd, &st) + if (_Py_fstat(urandom_cache.fd, &st) || st.st_dev != urandom_cache.st_dev || st.st_ino != urandom_cache.st_ino) { /* Something changed: forget the cached fd (but don't close it, @@ -178,7 +178,7 @@ dev_urandom_python(char *buffer, Py_ssize_t size) fd = urandom_cache.fd; } else { - if (fstat(fd, &st)) { + if (_Py_fstat(fd, &st)) { PyErr_SetFromErrno(PyExc_OSError); close(fd); return -1; -- cgit v1.2.1 From b8d0bcab60af86bb0d982ef0483eec7930a0a9d0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 18 Mar 2015 00:22:14 +0100 Subject: Issue #23694: Enhance _Py_open(), it now raises exceptions * _Py_open() now raises exceptions on error. If open() fails, it raises an OSError with the filename. * _Py_open() now releases the GIL while calling open() * Add _Py_open_noraise() when _Py_open() cannot be used because the GIL is not held --- Python/random.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 031d8875c9..39a389c79f 100644 --- a/Python/random.c +++ b/Python/random.c @@ -111,7 +111,7 @@ dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size) assert (0 < size); - fd = _Py_open("/dev/urandom", O_RDONLY); + fd = _Py_open_noraise("/dev/urandom", O_RDONLY); if (fd < 0) Py_FatalError("Failed to open /dev/urandom"); @@ -158,17 +158,13 @@ dev_urandom_python(char *buffer, Py_ssize_t size) if (urandom_cache.fd >= 0) fd = urandom_cache.fd; else { - Py_BEGIN_ALLOW_THREADS fd = _Py_open("/dev/urandom", O_RDONLY); - Py_END_ALLOW_THREADS - if (fd < 0) - { + if (fd < 0) { if (errno == ENOENT || errno == ENXIO || errno == ENODEV || errno == EACCES) PyErr_SetString(PyExc_NotImplementedError, "/dev/urandom (or equivalent) not found"); - else - PyErr_SetFromErrno(PyExc_OSError); + /* otherwise, keep the OSError exception raised by _Py_open() */ return -1; } if (urandom_cache.fd >= 0) { -- cgit v1.2.1 From cb2d306857151476ebbd659e3ef135d523455f23 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 18 Mar 2015 14:39:33 +0100 Subject: Issue #22181: On Linux, os.urandom() now uses the new getrandom() syscall if available, syscall introduced in the Linux kernel 3.17. It is more reliable and more secure, because it avoids the need of a file descriptor and waits until the kernel has enough entropy. --- Python/random.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 6 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 39a389c79f..dcc3aab79f 100644 --- a/Python/random.c +++ b/Python/random.c @@ -1,11 +1,17 @@ #include "Python.h" #ifdef MS_WINDOWS -#include +# include #else -#include -#ifdef HAVE_SYS_STAT_H -#include -#endif +# include +# ifdef HAVE_SYS_STAT_H +# include +# endif +# ifdef HAVE_SYS_SYSCALL_H +# include +# if defined(__linux__) && defined(SYS_getrandom) +# define HAVE_GETRANDOM +# endif +# endif #endif #ifdef Py_DEBUG @@ -94,13 +100,65 @@ py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal) return 0; } -#else +#else /* !HAVE_GETENTROPY */ + +#ifdef HAVE_GETRANDOM +static int +py_getrandom(void *buffer, Py_ssize_t size, int raise) +{ + /* is getrandom() supported by the running kernel? + * need Linux kernel 3.17 or later */ + static int getrandom_works = 1; + /* Use /dev/urandom, block if the kernel has no entropy */ + const int flags = 0; + int n; + + if (!getrandom_works) + return 0; + + while (0 < size) { + errno = 0; + /* the libc doesn't expose getrandom() yet, see: + * https://sourceware.org/bugzilla/show_bug.cgi?id=17252 */ + n = syscall(SYS_getrandom, buffer, size, flags); + if (n < 0) { + if (errno == ENOSYS) { + getrandom_works = 0; + return 0; + } + + if (errno == EINTR) { + /* Note: EINTR should not occur with flags=0 */ + if (PyErr_CheckSignals()) { + if (!raise) + Py_FatalError("getrandom() interrupted by a signal"); + return -1; + } + /* retry getrandom() */ + continue; + } + + if (raise) + PyErr_SetFromErrno(PyExc_OSError); + else + Py_FatalError("getrandom() failed"); + return -1; + } + + buffer += n; + size -= n; + } + return 1; +} +#endif + static struct { int fd; dev_t st_dev; ino_t st_ino; } urandom_cache = { -1 }; + /* Read size bytes from /dev/urandom into buffer. Call Py_FatalError() on error. */ static void @@ -115,6 +173,13 @@ dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size) if (fd < 0) Py_FatalError("Failed to open /dev/urandom"); +#ifdef HAVE_GETRANDOM + if (py_getrandom(buffer, size, 0) == 1) + return; + /* getrandom() is not supported by the running kernel, fall back + * on reading /dev/urandom */ +#endif + while (0 < size) { do { @@ -140,10 +205,23 @@ dev_urandom_python(char *buffer, Py_ssize_t size) int fd; Py_ssize_t n; struct _Py_stat_struct st; +#ifdef HAVE_GETRANDOM + int res; +#endif if (size <= 0) return 0; +#ifdef HAVE_GETRANDOM + res = py_getrandom(buffer, size, 1); + if (res < 0) + return -1; + if (res == 1) + return 0; + /* getrandom() is not supported by the running kernel, fall back + * on reading /dev/urandom */ +#endif + if (urandom_cache.fd >= 0) { /* Does the fd point to the same thing as before? (issue #21207) */ if (_Py_fstat(urandom_cache.fd, &st) -- cgit v1.2.1 From 82ab525ae487e6f735821276641415ab58213fd6 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Mar 2015 22:21:49 +0100 Subject: Issue #22181: The availability of the getrandom() is now checked in configure, and stored in pyconfig.h as the new HAVE_GETRANDOM_SYSCALL define. Fix os.urandom() tests using file descriptors if os.urandom() uses getrandom(). --- Python/random.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index dcc3aab79f..10fc505e0f 100644 --- a/Python/random.c +++ b/Python/random.c @@ -6,11 +6,8 @@ # ifdef HAVE_SYS_STAT_H # include # endif -# ifdef HAVE_SYS_SYSCALL_H +# ifdef HAVE_GETRANDOM_SYSCALL # include -# if defined(__linux__) && defined(SYS_getrandom) -# define HAVE_GETRANDOM -# endif # endif #endif @@ -102,7 +99,7 @@ py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal) #else /* !HAVE_GETENTROPY */ -#ifdef HAVE_GETRANDOM +#ifdef HAVE_GETRANDOM_SYSCALL static int py_getrandom(void *buffer, Py_ssize_t size, int raise) { @@ -173,7 +170,7 @@ dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size) if (fd < 0) Py_FatalError("Failed to open /dev/urandom"); -#ifdef HAVE_GETRANDOM +#ifdef HAVE_GETRANDOM_SYSCALL if (py_getrandom(buffer, size, 0) == 1) return; /* getrandom() is not supported by the running kernel, fall back @@ -205,14 +202,14 @@ dev_urandom_python(char *buffer, Py_ssize_t size) int fd; Py_ssize_t n; struct _Py_stat_struct st; -#ifdef HAVE_GETRANDOM +#ifdef HAVE_GETRANDOM_SYSCALL int res; #endif if (size <= 0) return 0; -#ifdef HAVE_GETRANDOM +#ifdef HAVE_GETRANDOM_SYSCALL res = py_getrandom(buffer, size, 1); if (res < 0) return -1; -- cgit v1.2.1 From 0f1b4c6827901688b24a202f9f93c25efd5d145f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Mar 2015 23:24:45 +0100 Subject: Issue #22181: Fix dev_urandom_noraise(), try calling py_getrandom() before opening /dev/urandom. --- Python/random.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 10fc505e0f..c924323fda 100644 --- a/Python/random.c +++ b/Python/random.c @@ -166,10 +166,6 @@ dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size) assert (0 < size); - fd = _Py_open_noraise("/dev/urandom", O_RDONLY); - if (fd < 0) - Py_FatalError("Failed to open /dev/urandom"); - #ifdef HAVE_GETRANDOM_SYSCALL if (py_getrandom(buffer, size, 0) == 1) return; @@ -177,6 +173,10 @@ dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size) * on reading /dev/urandom */ #endif + fd = _Py_open_noraise("/dev/urandom", O_RDONLY); + if (fd < 0) + Py_FatalError("Failed to open /dev/urandom"); + while (0 < size) { do { -- cgit v1.2.1 From b63df00ad9222e7a31bf2fc6fba43183a41dc841 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 19 Mar 2015 23:36:33 +0100 Subject: Issue #23707: On UNIX, os.urandom() now calls the Python signal handler when read() is interrupted by a signal. dev_urandom_python() now calls _Py_read() helper instead of calling directly read(). --- Python/random.c | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index c924323fda..a281829f97 100644 --- a/Python/random.c +++ b/Python/random.c @@ -262,29 +262,21 @@ dev_urandom_python(char *buffer, Py_ssize_t size) } } - Py_BEGIN_ALLOW_THREADS do { - do { - n = read(fd, buffer, (size_t)size); - } while (n < 0 && errno == EINTR); - if (n <= 0) - break; + n = _Py_read(fd, buffer, (size_t)size); + if (n == -1) + return -1; + if (n == 0) { + PyErr_Format(PyExc_RuntimeError, + "Failed to read %zi bytes from /dev/urandom", + size); + return -1; + } + buffer += n; - size -= (Py_ssize_t)n; + size -= n; } while (0 < size); - Py_END_ALLOW_THREADS - if (n <= 0) - { - /* stop on error or if read(size) returned 0 */ - if (n < 0) - PyErr_SetFromErrno(PyExc_OSError); - else - PyErr_Format(PyExc_RuntimeError, - "Failed to read %zi bytes from /dev/urandom", - size); - return -1; - } return 0; } -- cgit v1.2.1 From 0600ebc4d9a0fbb0f16689fb2c1d6a09482594cc Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 30 Mar 2015 10:09:31 +0200 Subject: Issue #23752: _Py_fstat() is now responsible to raise the Python exception Add _Py_fstat_noraise() function when a Python exception is not welcome. --- Python/random.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index a281829f97..a4eba3ccd9 100644 --- a/Python/random.c +++ b/Python/random.c @@ -221,7 +221,7 @@ dev_urandom_python(char *buffer, Py_ssize_t size) if (urandom_cache.fd >= 0) { /* Does the fd point to the same thing as before? (issue #21207) */ - if (_Py_fstat(urandom_cache.fd, &st) + if (_Py_fstat_noraise(urandom_cache.fd, &st) || st.st_dev != urandom_cache.st_dev || st.st_ino != urandom_cache.st_ino) { /* Something changed: forget the cached fd (but don't close it, @@ -250,7 +250,6 @@ dev_urandom_python(char *buffer, Py_ssize_t size) } else { if (_Py_fstat(fd, &st)) { - PyErr_SetFromErrno(PyExc_OSError); close(fd); return -1; } -- cgit v1.2.1 From 5f1c711bdaea4c383659129dd1563eb8c2de306f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 30 Mar 2015 11:16:40 +0200 Subject: Issue #22181: os.urandom() now releases the GIL when the getrandom() implementation is used. --- Python/random.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index a4eba3ccd9..f3a8ae552e 100644 --- a/Python/random.c +++ b/Python/random.c @@ -115,9 +115,18 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) while (0 < size) { errno = 0; - /* the libc doesn't expose getrandom() yet, see: + + /* Use syscall() because the libc doesn't expose getrandom() yet, see: * https://sourceware.org/bugzilla/show_bug.cgi?id=17252 */ - n = syscall(SYS_getrandom, buffer, size, flags); + if (raise) { + Py_BEGIN_ALLOW_THREADS + n = syscall(SYS_getrandom, buffer, size, flags); + Py_END_ALLOW_THREADS + } + else { + n = syscall(SYS_getrandom, buffer, size, flags); + } + if (n < 0) { if (errno == ENOSYS) { getrandom_works = 0; -- cgit v1.2.1 From 2ea2e83efdeebbd1d8280f36547068bb452c5a2d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 30 Jul 2015 10:13:52 +0200 Subject: py_getrandom(): getrandom() *can* return EINTR See the latest version of getrandom() manual page: http://man7.org/linux/man-pages/man2/getrandom.2.html#NOTES The behavior when a call to getrandom() that is blocked while reading from /dev/urandom is interrupted by a signal handler depends on the initialization state of the entropy buffer and on the request size, buflen. If the entropy is not yet initialized, then the call will fail with the EINTR error. If the entropy pool has been initialized and the request size is large (buflen > 256), the call either succeeds, returning a partially filled buffer, or fails with the error EINTR. If the entropy pool has been initialized and the request size is small (buflen <= 256), then getrandom() will not fail with EINTR. Instead, it will return all of the bytes that have been requested. Note: py_getrandom() calls getrandom() with flags=0. --- Python/random.c | 1 - 1 file changed, 1 deletion(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 9c9d5057c9..ea09e84a7b 100644 --- a/Python/random.c +++ b/Python/random.c @@ -142,7 +142,6 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) } if (errno == EINTR) { - /* Note: EINTR should not occur with flags=0 */ if (PyErr_CheckSignals()) { if (!raise) Py_FatalError("getrandom() interrupted by a signal"); -- cgit v1.2.1 From f4e099852a666678e2d66fc0b446805da3107d65 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 1 Oct 2015 09:47:30 +0200 Subject: Issue #25003: On Solaris 11.3 or newer, os.urandom() now uses the getrandom() function instead of the getentropy() function. The getentropy() function is blocking to generate very good quality entropy, os.urandom() doesn't need such high-quality entropy. --- Python/random.c | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index ea09e84a7b..1d57b1b662 100644 --- a/Python/random.c +++ b/Python/random.c @@ -6,7 +6,9 @@ # ifdef HAVE_SYS_STAT_H # include # endif -# ifdef HAVE_GETRANDOM_SYSCALL +# ifdef HAVE_GETRANDOM +# include +# elif defined(HAVE_GETRANDOM_SYSCALL) # include # endif #endif @@ -70,7 +72,9 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) return 0; } -#elif HAVE_GETENTROPY +#elif defined(HAVE_GETENTROPY) && !defined(sun) +#define PY_GETENTROPY 1 + /* Fill buffer with size pseudo-random bytes generated by getentropy(). Return 0 on success, or raise an exception and return -1 on error. @@ -105,16 +109,19 @@ py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal) return 0; } -#else /* !HAVE_GETENTROPY */ +#else + +#if defined(HAVE_GETRANDOM) || defined(HAVE_GETRANDOM_SYSCALL) +#define PY_GETRANDOM 1 -#ifdef HAVE_GETRANDOM_SYSCALL static int py_getrandom(void *buffer, Py_ssize_t size, int raise) { - /* is getrandom() supported by the running kernel? - * need Linux kernel 3.17 or later */ + /* Is getrandom() supported by the running kernel? + * Need Linux kernel 3.17 or newer, or Solaris 11.3 or newer */ static int getrandom_works = 1; - /* Use /dev/urandom, block if the kernel has no entropy */ + /* Use non-blocking /dev/urandom device. On Linux at boot, the getrandom() + * syscall blocks until /dev/urandom is initialized with enough entropy. */ const int flags = 0; int n; @@ -124,7 +131,18 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) while (0 < size) { errno = 0; - /* Use syscall() because the libc doesn't expose getrandom() yet, see: +#ifdef HAVE_GETRANDOM + if (raise) { + Py_BEGIN_ALLOW_THREADS + n = getrandom(buffer, size, flags); + Py_END_ALLOW_THREADS + } + else { + n = getrandom(buffer, size, flags); + } +#else + /* On Linux, use the syscall() function because the GNU libc doesn't + * expose the Linux getrandom() syscall yet. See: * https://sourceware.org/bugzilla/show_bug.cgi?id=17252 */ if (raise) { Py_BEGIN_ALLOW_THREADS @@ -134,6 +152,7 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) else { n = syscall(SYS_getrandom, buffer, size, flags); } +#endif if (n < 0) { if (errno == ENOSYS) { @@ -182,7 +201,7 @@ dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size) assert (0 < size); -#ifdef HAVE_GETRANDOM_SYSCALL +#ifdef PY_GETRANDOM if (py_getrandom(buffer, size, 0) == 1) return; /* getrandom() is not supported by the running kernel, fall back @@ -218,14 +237,14 @@ dev_urandom_python(char *buffer, Py_ssize_t size) int fd; Py_ssize_t n; struct _Py_stat_struct st; -#ifdef HAVE_GETRANDOM_SYSCALL +#ifdef PY_GETRANDOM int res; #endif if (size <= 0) return 0; -#ifdef HAVE_GETRANDOM_SYSCALL +#ifdef PY_GETRANDOM res = py_getrandom(buffer, size, 1); if (res < 0) return -1; @@ -304,7 +323,7 @@ dev_urandom_close(void) } } -#endif /* HAVE_GETENTROPY */ +#endif /* Fill buffer with pseudo-random bytes generated by a linear congruent generator (LCG): @@ -345,7 +364,7 @@ _PyOS_URandom(void *buffer, Py_ssize_t size) #ifdef MS_WINDOWS return win32_urandom((unsigned char *)buffer, size, 1); -#elif HAVE_GETENTROPY +#elif defined(PY_GETENTROPY) return py_getentropy(buffer, size, 0); #else return dev_urandom_python((char*)buffer, size); @@ -392,7 +411,7 @@ _PyRandom_Init(void) else { #ifdef MS_WINDOWS (void)win32_urandom(secret, secret_size, 0); -#elif HAVE_GETENTROPY +#elif defined(PY_GETENTROPY) (void)py_getentropy(secret, secret_size, 1); #else dev_urandom_noraise(secret, secret_size); @@ -408,7 +427,7 @@ _PyRandom_Fini(void) CryptReleaseContext(hCryptProv, 0); hCryptProv = 0; } -#elif HAVE_GETENTROPY +#elif defined(PY_GETENTROPY) /* nothing to clean */ #else dev_urandom_close(); -- cgit v1.2.1 From ab9342460bf6d26c19d73c506195737a35529433 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 7 Nov 2015 15:42:38 +0200 Subject: Issue #25558: Use compile-time asserts. --- Python/random.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 772bfef0e8..9b42d5498c 100644 --- a/Python/random.c +++ b/Python/random.c @@ -379,7 +379,7 @@ _PyRandom_Init(void) char *env; unsigned char *secret = (unsigned char *)&_Py_HashSecret.uc; Py_ssize_t secret_size = sizeof(_Py_HashSecret_t); - assert(secret_size == sizeof(_Py_HashSecret.uc)); + Py_BUILD_ASSERT(sizeof(_Py_HashSecret_t) == sizeof(_Py_HashSecret.uc)); if (_Py_HashSecret_Initialized) return; -- cgit v1.2.1 From 0e722e7d02cbeb9fe273459eb5655cea3451e73c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 8 Jun 2016 10:16:50 +0200 Subject: py_getrandom(): use char* instead of void* for the destination Fix a "gcc -pedantic" warning on "buffer += n" because buffer type is void*. --- Python/random.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index ef3a4cd9cf..cd8d945f8f 100644 --- a/Python/random.c +++ b/Python/random.c @@ -132,11 +132,14 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) * see https://bugs.python.org/issue26839. To avoid this, use the * GRND_NONBLOCK flag. */ const int flags = GRND_NONBLOCK; + + char *dest; int n; if (!getrandom_works) return 0; + dest = buffer; while (0 < size) { #ifdef sun /* Issue #26735: On Solaris, getrandom() is limited to returning up @@ -150,11 +153,11 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) #ifdef HAVE_GETRANDOM if (raise) { Py_BEGIN_ALLOW_THREADS - n = getrandom(buffer, n, flags); + n = getrandom(dest, n, flags); Py_END_ALLOW_THREADS } else { - n = getrandom(buffer, n, flags); + n = getrandom(dest, n, flags); } #else /* On Linux, use the syscall() function because the GNU libc doesn't @@ -162,11 +165,11 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) * https://sourceware.org/bugzilla/show_bug.cgi?id=17252 */ if (raise) { Py_BEGIN_ALLOW_THREADS - n = syscall(SYS_getrandom, buffer, n, flags); + n = syscall(SYS_getrandom, dest, n, flags); Py_END_ALLOW_THREADS } else { - n = syscall(SYS_getrandom, buffer, n, flags); + n = syscall(SYS_getrandom, dest, n, flags); } #endif @@ -204,7 +207,7 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) return -1; } - buffer += n; + dest += n; size -= n; } return 1; -- cgit v1.2.1 From 93bb5ddfb5524c68a3ab6f66644a976071f6a3f2 Mon Sep 17 00:00:00 2001 From: Martin Panter Date: Fri, 29 Jul 2016 04:00:44 +0000 Subject: Issue #17596: MINGW: add wincrypt.h in Python/random.c Based on patch by Roumen Petrov. --- Python/random.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index c8e844ee67..945269a2f6 100644 --- a/Python/random.c +++ b/Python/random.c @@ -1,6 +1,9 @@ #include "Python.h" #ifdef MS_WINDOWS # include +/* All sample MSDN wincrypt programs include the header below. It is at least + * required with Min GW. */ +# include #else # include # ifdef HAVE_SYS_STAT_H -- cgit v1.2.1 From 28538338b3b48b47b46658d5339c668a58b5b868 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 16 Aug 2016 15:23:58 +0200 Subject: Issue #27776: Cleanup random.c * Add pyurandom() helper function to factorize the code * don't call Py_FatalError() in helper functions, but only in _PyRandom_Init() if pyurandom() failed, to uniformize the code --- Python/random.c | 129 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 74 insertions(+), 55 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 945269a2f6..7d37fe9575 100644 --- a/Python/random.c +++ b/Python/random.c @@ -39,10 +39,9 @@ win32_urandom_init(int raise) return 0; error: - if (raise) + if (raise) { PyErr_SetFromWindowsErr(0); - else - Py_FatalError("Failed to initialize Windows random API (CryptoGen)"); + } return -1; } @@ -55,8 +54,9 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) if (hCryptProv == 0) { - if (win32_urandom_init(raise) == -1) + if (win32_urandom_init(raise) == -1) { return -1; + } } while (size > 0) @@ -65,11 +65,9 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) if (!CryptGenRandom(hCryptProv, (DWORD)chunk, buffer)) { /* CryptGenRandom() failed */ - if (raise) + if (raise) { PyErr_SetFromWindowsErr(0); - else - Py_FatalError("Failed to initialized the randomized hash " - "secret using CryptoGen)"); + } return -1; } buffer += chunk; @@ -86,29 +84,28 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) /* Fill buffer with size pseudo-random bytes generated by getentropy(). Return 0 on success, or raise an exception and return -1 on error. - If fatal is nonzero, call Py_FatalError() instead of raising an exception - on error. */ + If raise is zero, don't raise an exception on error. */ static int -py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal) +py_getentropy(char *buffer, Py_ssize_t size, int raise) { while (size > 0) { Py_ssize_t len = Py_MIN(size, 256); int res; - if (!fatal) { + if (raise) { Py_BEGIN_ALLOW_THREADS res = getentropy(buffer, len); Py_END_ALLOW_THREADS - - if (res < 0) { - PyErr_SetFromErrno(PyExc_OSError); - return -1; - } } else { res = getentropy(buffer, len); - if (res < 0) - Py_FatalError("getentropy() failed"); + } + + if (res < 0) { + if (raise) { + PyErr_SetFromErrno(PyExc_OSError); + } + return -1; } buffer += len; @@ -195,18 +192,15 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) if (errno == EINTR) { if (PyErr_CheckSignals()) { - if (!raise) - Py_FatalError("getrandom() interrupted by a signal"); return -1; } /* retry getrandom() */ continue; } - if (raise) + if (raise) { PyErr_SetFromErrno(PyExc_OSError); - else - Py_FatalError("getrandom() failed"); + } return -1; } @@ -225,9 +219,9 @@ static struct { /* Read size bytes from /dev/urandom into buffer. - Call Py_FatalError() on error. */ -static void -dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size) + Return 0 success, or return -1 on error. */ +static int +dev_urandom_noraise(char *buffer, Py_ssize_t size) { int fd; Py_ssize_t n; @@ -235,31 +229,35 @@ dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size) assert (0 < size); #ifdef PY_GETRANDOM - if (py_getrandom(buffer, size, 0) == 1) - return; + if (py_getrandom(buffer, size, 0) == 1) { + return 0; + } /* getrandom() is not supported by the running kernel, fall back * on reading /dev/urandom */ #endif fd = _Py_open_noraise("/dev/urandom", O_RDONLY); - if (fd < 0) - Py_FatalError("Failed to open /dev/urandom"); + if (fd < 0) { + return -1; + } while (0 < size) { do { n = read(fd, buffer, (size_t)size); } while (n < 0 && errno == EINTR); - if (n <= 0) - { + + if (n <= 0) { /* stop on error or if read(size) returned 0 */ - Py_FatalError("Failed to read bytes from /dev/urandom"); - break; + return -1; } + buffer += n; size -= n; } close(fd); + + return 0; } /* Read size bytes from /dev/urandom into buffer. @@ -379,31 +377,51 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size) } } -/* Fill buffer with size pseudo-random bytes from the operating system random - number generator (RNG). It is suitable for most cryptographic purposes - except long living private keys for asymmetric encryption. - - Return 0 on success, raise an exception and return -1 on error. */ -int -_PyOS_URandom(void *buffer, Py_ssize_t size) +/* If raise is zero: + * - Don't raise exceptions on error + * - Don't call PyErr_CheckSignals() on EINTR (retry directly the interrupted + * syscall) + * - Don't release the GIL to call syscalls. */ +static int +pyurandom(void *buffer, Py_ssize_t size, int raise) { if (size < 0) { - PyErr_Format(PyExc_ValueError, - "negative argument not allowed"); + if (raise) { + PyErr_Format(PyExc_ValueError, + "negative argument not allowed"); + } return -1; } - if (size == 0) + + if (size == 0) { return 0; + } #ifdef MS_WINDOWS - return win32_urandom((unsigned char *)buffer, size, 1); + return win32_urandom((unsigned char *)buffer, size, raise); #elif defined(PY_GETENTROPY) - return py_getentropy(buffer, size, 0); + return py_getentropy(buffer, size, raise); #else - return dev_urandom_python((char*)buffer, size); + if (raise) { + return dev_urandom_python(buffer, size); + } + else { + return dev_urandom_noraise(buffer, size); + } #endif } +/* Fill buffer with size pseudo-random bytes from the operating system random + number generator (RNG). It is suitable for most cryptographic purposes + except long living private keys for asymmetric encryption. + + Return 0 on success, raise an exception and return -1 on error. */ +int +_PyOS_URandom(void *buffer, Py_ssize_t size) +{ + return pyurandom(buffer, size, 1); +} + void _PyRandom_Init(void) { @@ -442,13 +460,14 @@ _PyRandom_Init(void) } } else { -#ifdef MS_WINDOWS - (void)win32_urandom(secret, secret_size, 0); -#elif defined(PY_GETENTROPY) - (void)py_getentropy(secret, secret_size, 1); -#else - dev_urandom_noraise(secret, secret_size); -#endif + int res; + + /* _PyRandom_Init() is called very early in the Python initialization + * and so exceptions cannot be used. */ + res = pyurandom(secret, secret_size, 0); + if (res < 0) { + Py_FatalError("failed to get random numbers to initialize Python"); + } } } -- cgit v1.2.1 From 5db5c4f457888511ef68c26ece1b4f987c8b2403 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 16 Aug 2016 15:19:09 +0200 Subject: Issue #27776: _PyRandom_Init() doesn't call PyErr_CheckSignals() anymore Modify py_getrandom() to not call PyErr_CheckSignals() if raise is zero. _PyRandom_Init() is called very early in the Python initialization, so it's safer to not call PyErr_CheckSignals(). --- Python/random.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 7d37fe9575..4d0eabc001 100644 --- a/Python/random.c +++ b/Python/random.c @@ -191,10 +191,13 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) } if (errno == EINTR) { - if (PyErr_CheckSignals()) { - return -1; + if (raise) { + if (PyErr_CheckSignals()) { + return -1; + } } - /* retry getrandom() */ + + /* retry getrandom() if it was interrupted by a signal */ continue; } -- cgit v1.2.1 From ddceb622b9c351bd2ac681a46f628e4d93f5e6c3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 16 Aug 2016 18:46:38 +0200 Subject: Issue #27776: Cleanup random.c Merge dev_urandom_python() and dev_urandom_noraise() functions to reduce code duplication. --- Python/random.c | 231 +++++++++++++++++++++++++++----------------------------- 1 file changed, 110 insertions(+), 121 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 4d0eabc001..511070add0 100644 --- a/Python/random.c +++ b/Python/random.c @@ -77,7 +77,7 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) } /* Issue #25003: Don't use getentropy() on Solaris (available since - * Solaris 11.3), it is blocking whereas os.urandom() should not block. */ + Solaris 11.3), it is blocking whereas os.urandom() should not block. */ #elif defined(HAVE_GETENTROPY) && !defined(sun) #define PY_GETENTROPY 1 @@ -119,25 +119,32 @@ py_getentropy(char *buffer, Py_ssize_t size, int raise) #if defined(HAVE_GETRANDOM) || defined(HAVE_GETRANDOM_SYSCALL) #define PY_GETRANDOM 1 +/* Call getrandom() + - Return 1 on success + - Return 0 if getrandom() syscall is not available (fails with ENOSYS). + - Raise an exception (if raise is non-zero) and return -1 on error: + getrandom() failed with EINTR and the Python signal handler raised an + exception, or getrandom() failed with a different error. */ static int py_getrandom(void *buffer, Py_ssize_t size, int raise) { /* Is getrandom() supported by the running kernel? - * Need Linux kernel 3.17 or newer, or Solaris 11.3 or newer */ + Need Linux kernel 3.17 or newer, or Solaris 11.3 or newer */ static int getrandom_works = 1; /* getrandom() on Linux will block if called before the kernel has - * initialized the urandom entropy pool. This will cause Python - * to hang on startup if called very early in the boot process - - * see https://bugs.python.org/issue26839. To avoid this, use the - * GRND_NONBLOCK flag. */ + initialized the urandom entropy pool. This will cause Python + to hang on startup if called very early in the boot process - + see https://bugs.python.org/issue26839. To avoid this, use the + GRND_NONBLOCK flag. */ const int flags = GRND_NONBLOCK; char *dest; long n; - if (!getrandom_works) + if (!getrandom_works) { return 0; + } dest = buffer; while (0 < size) { @@ -161,8 +168,8 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) } #else /* On Linux, use the syscall() function because the GNU libc doesn't - * expose the Linux getrandom() syscall yet. See: - * https://sourceware.org/bugzilla/show_bug.cgi?id=17252 */ + expose the Linux getrandom() syscall yet. See: + https://sourceware.org/bugzilla/show_bug.cgi?id=17252 */ if (raise) { Py_BEGIN_ALLOW_THREADS n = syscall(SYS_getrandom, dest, n, flags); @@ -180,12 +187,12 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) } if (errno == EAGAIN) { /* If we failed with EAGAIN, the entropy pool was - * uninitialized. In this case, we return failure to fall - * back to reading from /dev/urandom. - * - * Note: In this case the data read will not be random so - * should not be used for cryptographic purposes. Retaining - * the existing semantics for practical purposes. */ + uninitialized. In this case, we return failure to fall + back to reading from /dev/urandom. + + Note: In this case the data read will not be random so + should not be used for cryptographic purposes. Retaining + the existing semantics for practical purposes. */ getrandom_works = 0; return 0; } @@ -221,130 +228,117 @@ static struct { } urandom_cache = { -1 }; -/* Read size bytes from /dev/urandom into buffer. - Return 0 success, or return -1 on error. */ -static int -dev_urandom_noraise(char *buffer, Py_ssize_t size) -{ - int fd; - Py_ssize_t n; - - assert (0 < size); - -#ifdef PY_GETRANDOM - if (py_getrandom(buffer, size, 0) == 1) { - return 0; - } - /* getrandom() is not supported by the running kernel, fall back - * on reading /dev/urandom */ -#endif - - fd = _Py_open_noraise("/dev/urandom", O_RDONLY); - if (fd < 0) { - return -1; - } - - while (0 < size) - { - do { - n = read(fd, buffer, (size_t)size); - } while (n < 0 && errno == EINTR); - - if (n <= 0) { - /* stop on error or if read(size) returned 0 */ - return -1; - } - - buffer += n; - size -= n; - } - close(fd); - - return 0; -} +/* Read 'size' random bytes from getrandom(). Fall back on reading from + /dev/urandom if getrandom() is not available. -/* Read size bytes from /dev/urandom into buffer. - Return 0 on success, raise an exception and return -1 on error. */ + Return 0 on success. Raise an exception (if raise is non-zero) and return -1 + on error. */ static int -dev_urandom_python(char *buffer, Py_ssize_t size) +dev_urandom(char *buffer, Py_ssize_t size, int raise) { int fd; Py_ssize_t n; - struct _Py_stat_struct st; #ifdef PY_GETRANDOM int res; #endif - if (size <= 0) - return 0; + assert(size > 0); #ifdef PY_GETRANDOM - res = py_getrandom(buffer, size, 1); - if (res < 0) + res = py_getrandom(buffer, size, raise); + if (res < 0) { return -1; - if (res == 1) + } + if (res == 1) { return 0; + } /* getrandom() is not supported by the running kernel, fall back - * on reading /dev/urandom */ + on reading /dev/urandom */ #endif - if (urandom_cache.fd >= 0) { - /* Does the fd point to the same thing as before? (issue #21207) */ - if (_Py_fstat_noraise(urandom_cache.fd, &st) - || st.st_dev != urandom_cache.st_dev - || st.st_ino != urandom_cache.st_ino) { - /* Something changed: forget the cached fd (but don't close it, - since it probably points to something important for some - third-party code). */ - urandom_cache.fd = -1; - } - } - if (urandom_cache.fd >= 0) - fd = urandom_cache.fd; - else { - fd = _Py_open("/dev/urandom", O_RDONLY); - if (fd < 0) { - if (errno == ENOENT || errno == ENXIO || - errno == ENODEV || errno == EACCES) - PyErr_SetString(PyExc_NotImplementedError, - "/dev/urandom (or equivalent) not found"); - /* otherwise, keep the OSError exception raised by _Py_open() */ - return -1; - } + + if (raise) { + struct _Py_stat_struct st; + if (urandom_cache.fd >= 0) { - /* urandom_fd was initialized by another thread while we were - not holding the GIL, keep it. */ - close(fd); - fd = urandom_cache.fd; + /* Does the fd point to the same thing as before? (issue #21207) */ + if (_Py_fstat_noraise(urandom_cache.fd, &st) + || st.st_dev != urandom_cache.st_dev + || st.st_ino != urandom_cache.st_ino) { + /* Something changed: forget the cached fd (but don't close it, + since it probably points to something important for some + third-party code). */ + urandom_cache.fd = -1; + } } + if (urandom_cache.fd >= 0) + fd = urandom_cache.fd; else { - if (_Py_fstat(fd, &st)) { - close(fd); + fd = _Py_open("/dev/urandom", O_RDONLY); + if (fd < 0) { + if (errno == ENOENT || errno == ENXIO || + errno == ENODEV || errno == EACCES) + PyErr_SetString(PyExc_NotImplementedError, + "/dev/urandom (or equivalent) not found"); + /* otherwise, keep the OSError exception raised by _Py_open() */ return -1; } + if (urandom_cache.fd >= 0) { + /* urandom_fd was initialized by another thread while we were + not holding the GIL, keep it. */ + close(fd); + fd = urandom_cache.fd; + } else { - urandom_cache.fd = fd; - urandom_cache.st_dev = st.st_dev; - urandom_cache.st_ino = st.st_ino; + if (_Py_fstat(fd, &st)) { + close(fd); + return -1; + } + else { + urandom_cache.fd = fd; + urandom_cache.st_dev = st.st_dev; + urandom_cache.st_ino = st.st_ino; + } } } - } - do { - n = _Py_read(fd, buffer, (size_t)size); - if (n == -1) - return -1; - if (n == 0) { - PyErr_Format(PyExc_RuntimeError, - "Failed to read %zi bytes from /dev/urandom", - size); + do { + n = _Py_read(fd, buffer, (size_t)size); + if (n == -1) + return -1; + if (n == 0) { + PyErr_Format(PyExc_RuntimeError, + "Failed to read %zi bytes from /dev/urandom", + size); + return -1; + } + + buffer += n; + size -= n; + } while (0 < size); + } + else { + fd = _Py_open_noraise("/dev/urandom", O_RDONLY); + if (fd < 0) { return -1; } - buffer += n; - size -= n; - } while (0 < size); + while (0 < size) + { + do { + n = read(fd, buffer, (size_t)size); + } while (n < 0 && errno == EINTR); + if (n <= 0) { + /* stop on error or if read(size) returned 0 */ + return -1; + } + + buffer += n; + size -= n; + } + close(fd); + } return 0; } @@ -381,10 +375,10 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size) } /* If raise is zero: - * - Don't raise exceptions on error - * - Don't call PyErr_CheckSignals() on EINTR (retry directly the interrupted - * syscall) - * - Don't release the GIL to call syscalls. */ + - Don't raise exceptions on error + - Don't call PyErr_CheckSignals() on EINTR (retry directly the interrupted + syscall) + - Don't release the GIL to call syscalls. */ static int pyurandom(void *buffer, Py_ssize_t size, int raise) { @@ -405,12 +399,7 @@ pyurandom(void *buffer, Py_ssize_t size, int raise) #elif defined(PY_GETENTROPY) return py_getentropy(buffer, size, raise); #else - if (raise) { - return dev_urandom_python(buffer, size); - } - else { - return dev_urandom_noraise(buffer, size); - } + return dev_urandom(buffer, size, raise); #endif } @@ -466,7 +455,7 @@ _PyRandom_Init(void) int res; /* _PyRandom_Init() is called very early in the Python initialization - * and so exceptions cannot be used. */ + and so exceptions cannot be used (use raise=0). */ res = pyurandom(secret, secret_size, 0); if (res < 0) { Py_FatalError("failed to get random numbers to initialize Python"); -- cgit v1.2.1 From c0eea49e2df42814569b3e7fdc089021dfed668a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 16 Aug 2016 18:27:44 +0200 Subject: Issue #27776: dev_urandom(raise=0) now closes the file descriptor on error --- Python/random.c | 1 + 1 file changed, 1 insertion(+) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 511070add0..6fdce64bca 100644 --- a/Python/random.c +++ b/Python/random.c @@ -331,6 +331,7 @@ dev_urandom(char *buffer, Py_ssize_t size, int raise) if (n <= 0) { /* stop on error or if read(size) returned 0 */ + close(fd); return -1; } -- cgit v1.2.1 From 87da9f01c3bc74aebedc52a91aa908ddae83a579 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 6 Sep 2016 16:33:52 -0700 Subject: os.urandom() now blocks on Linux Issue #27776: The os.urandom() function does now block on Linux 3.17 and newer until the system urandom entropy pool is initialized to increase the security. This change is part of the PEP 524. --- Python/random.c | 77 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 31 deletions(-) (limited to 'Python/random.c') diff --git a/Python/random.c b/Python/random.c index 6fdce64bca..ea506eeeb2 100644 --- a/Python/random.c +++ b/Python/random.c @@ -77,7 +77,7 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) } /* Issue #25003: Don't use getentropy() on Solaris (available since - Solaris 11.3), it is blocking whereas os.urandom() should not block. */ + * Solaris 11.3), it is blocking whereas os.urandom() should not block. */ #elif defined(HAVE_GETENTROPY) && !defined(sun) #define PY_GETENTROPY 1 @@ -121,24 +121,20 @@ py_getentropy(char *buffer, Py_ssize_t size, int raise) /* Call getrandom() - Return 1 on success - - Return 0 if getrandom() syscall is not available (fails with ENOSYS). + - Return 0 if getrandom() syscall is not available (fails with ENOSYS) + or if getrandom(GRND_NONBLOCK) fails with EAGAIN (blocking=0 and system + urandom not initialized yet) and raise=0. - Raise an exception (if raise is non-zero) and return -1 on error: getrandom() failed with EINTR and the Python signal handler raised an exception, or getrandom() failed with a different error. */ static int -py_getrandom(void *buffer, Py_ssize_t size, int raise) +py_getrandom(void *buffer, Py_ssize_t size, int blocking, int raise) { - /* Is getrandom() supported by the running kernel? - Need Linux kernel 3.17 or newer, or Solaris 11.3 or newer */ + /* Is getrandom() supported by the running kernel? Set to 0 if getrandom() + fails with ENOSYS. Need Linux kernel 3.17 or newer, or Solaris 11.3 + or newer */ static int getrandom_works = 1; - - /* getrandom() on Linux will block if called before the kernel has - initialized the urandom entropy pool. This will cause Python - to hang on startup if called very early in the boot process - - see https://bugs.python.org/issue26839. To avoid this, use the - GRND_NONBLOCK flag. */ - const int flags = GRND_NONBLOCK; - + int flags; char *dest; long n; @@ -146,6 +142,7 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) return 0; } + flags = blocking ? 0 : GRND_NONBLOCK; dest = buffer; while (0 < size) { #ifdef sun @@ -185,15 +182,12 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise) getrandom_works = 0; return 0; } - if (errno == EAGAIN) { - /* If we failed with EAGAIN, the entropy pool was - uninitialized. In this case, we return failure to fall - back to reading from /dev/urandom. - - Note: In this case the data read will not be random so - should not be used for cryptographic purposes. Retaining - the existing semantics for practical purposes. */ - getrandom_works = 0; + + /* getrandom(GRND_NONBLOCK) fails with EAGAIN if the system urandom + is not initialiazed yet. For _PyRandom_Init(), we ignore their + error and fall back on reading /dev/urandom which never blocks, + even if the system urandom is not initialized yet. */ + if (errno == EAGAIN && !raise && !blocking) { return 0; } @@ -228,13 +222,13 @@ static struct { } urandom_cache = { -1 }; -/* Read 'size' random bytes from getrandom(). Fall back on reading from +/* Read 'size' random bytes from py_getrandom(). Fall back on reading from /dev/urandom if getrandom() is not available. Return 0 on success. Raise an exception (if raise is non-zero) and return -1 on error. */ static int -dev_urandom(char *buffer, Py_ssize_t size, int raise) +dev_urandom(char *buffer, Py_ssize_t size, int blocking, int raise) { int fd; Py_ssize_t n; @@ -245,7 +239,7 @@ dev_urandom(char *buffer, Py_ssize_t size, int raise) assert(size > 0); #ifdef PY_GETRANDOM - res = py_getrandom(buffer, size, raise); + res = py_getrandom(buffer, size, blocking, raise); if (res < 0) { return -1; } @@ -381,7 +375,7 @@ lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size) syscall) - Don't release the GIL to call syscalls. */ static int -pyurandom(void *buffer, Py_ssize_t size, int raise) +pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise) { if (size < 0) { if (raise) { @@ -400,7 +394,7 @@ pyurandom(void *buffer, Py_ssize_t size, int raise) #elif defined(PY_GETENTROPY) return py_getentropy(buffer, size, raise); #else - return dev_urandom(buffer, size, raise); + return dev_urandom(buffer, size, blocking, raise); #endif } @@ -408,11 +402,29 @@ pyurandom(void *buffer, Py_ssize_t size, int raise) number generator (RNG). It is suitable for most cryptographic purposes except long living private keys for asymmetric encryption. - Return 0 on success, raise an exception and return -1 on error. */ + On Linux 3.17 and newer, the getrandom() syscall is used in blocking mode: + block until the system urandom entropy pool is initialized (128 bits are + collected by the kernel). + + Return 0 on success. Raise an exception and return -1 on error. */ int _PyOS_URandom(void *buffer, Py_ssize_t size) { - return pyurandom(buffer, size, 1); + return pyurandom(buffer, size, 1, 1); +} + +/* Fill buffer with size pseudo-random bytes from the operating system random + number generator (RNG). It is not suitable for cryptographic purpose. + + On Linux 3.17 and newer (when getrandom() syscall is used), if the system + urandom is not initialized yet, the function returns "weak" entropy read + from /dev/urandom. + + Return 0 on success. Raise an exception and return -1 on error. */ +int +_PyOS_URandomNonblock(void *buffer, Py_ssize_t size) +{ + return pyurandom(buffer, size, 0, 1); } void @@ -456,8 +468,11 @@ _PyRandom_Init(void) int res; /* _PyRandom_Init() is called very early in the Python initialization - and so exceptions cannot be used (use raise=0). */ - res = pyurandom(secret, secret_size, 0); + and so exceptions cannot be used (use raise=0). + + _PyRandom_Init() must not block Python initialization: call + pyurandom() is non-blocking mode (blocking=0): see the PEP 524. */ + res = pyurandom(secret, secret_size, 0, 0); if (res < 0) { Py_FatalError("failed to get random numbers to initialize Python"); } -- cgit v1.2.1