diff options
Diffstat (limited to 'Python/random.c')
| -rw-r--r-- | Python/random.c | 186 | 
1 files changed, 149 insertions, 37 deletions
| diff --git a/Python/random.c b/Python/random.c index af3d0bd..07dacfe 100644 --- a/Python/random.c +++ b/Python/random.c @@ -1,11 +1,19 @@  #include "Python.h"  #ifdef MS_WINDOWS -#include <windows.h> +#  include <windows.h>  #else -#include <fcntl.h> -#ifdef HAVE_SYS_STAT_H -#include <sys/stat.h> -#endif +#  include <fcntl.h> +#  ifdef HAVE_SYS_STAT_H +#    include <sys/stat.h> +#  endif +#  ifdef HAVE_LINUX_RANDOM_H +#    include <linux/random.h> +#  endif +#  ifdef HAVE_GETRANDOM +#    include <sys/random.h> +#  elif defined(HAVE_GETRANDOM_SYSCALL) +#    include <sys/syscall.h> +#  endif  #endif  #ifdef Py_DEBUG @@ -67,7 +75,7 @@ win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)      return 0;  } -/* Issue #25003: Don' use getentropy() on Solaris (available since +/* Issue #25003: Don't use getentropy() on Solaris (available since   * Solaris 11.3), it is blocking whereas os.urandom() should not block. */  #elif defined(HAVE_GETENTROPY) && !defined(sun)  #define PY_GETENTROPY 1 @@ -107,12 +115,109 @@ py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal)  }  #else + +#if defined(HAVE_GETRANDOM) || defined(HAVE_GETRANDOM_SYSCALL) +#define PY_GETRANDOM 1 + +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 */ +    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 n; + +    if (!getrandom_works) +        return 0; + +    while (0 < size) { +#ifdef sun +        /* Issue #26735: On Solaris, getrandom() is limited to returning up +           to 1024 bytes */ +        n = Py_MIN(size, 1024); +#else +        n = size; +#endif + +        errno = 0; +#ifdef HAVE_GETRANDOM +        if (raise) { +            Py_BEGIN_ALLOW_THREADS +            n = getrandom(buffer, n, flags); +            Py_END_ALLOW_THREADS +        } +        else { +            n = getrandom(buffer, n, 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 +            n = syscall(SYS_getrandom, buffer, n, flags); +            Py_END_ALLOW_THREADS +        } +        else { +            n = syscall(SYS_getrandom, buffer, n, flags); +        } +#endif + +        if (n < 0) { +            if (errno == ENOSYS) { +                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; +                return 0; +            } + +            if (errno == EINTR) { +                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 @@ -123,7 +228,14 @@ dev_urandom_noraise(unsigned char *buffer, Py_ssize_t size)      assert (0 < size); -    fd = _Py_open("/dev/urandom", O_RDONLY); +#ifdef PY_GETRANDOM +    if (py_getrandom(buffer, size, 0) == 1) +        return; +    /* 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"); @@ -151,14 +263,27 @@ dev_urandom_python(char *buffer, Py_ssize_t size)  {      int fd;      Py_ssize_t n; -    struct stat st; +    struct _Py_stat_struct st; +#ifdef PY_GETRANDOM +    int res; +#endif      if (size <= 0)          return 0; +#ifdef PY_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 (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, @@ -170,17 +295,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) { @@ -190,8 +311,7 @@ dev_urandom_python(char *buffer, Py_ssize_t size)              fd = urandom_cache.fd;          }          else { -            if (fstat(fd, &st)) { -                PyErr_SetFromErrno(PyExc_OSError); +            if (_Py_fstat(fd, &st)) {                  close(fd);                  return -1;              } @@ -203,29 +323,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;  } @@ -238,7 +350,7 @@ dev_urandom_close(void)      }  } -#endif /* HAVE_GETENTROPY */ +#endif  /* Fill buffer with pseudo-random bytes generated by a linear congruent     generator (LCG): | 
