diff options
Diffstat (limited to 'Python/random.c')
| -rw-r--r-- | Python/random.c | 340 | 
1 files changed, 175 insertions, 165 deletions
| diff --git a/Python/random.c b/Python/random.c index f2ada5f..6f9f711 100644 --- a/Python/random.c +++ b/Python/random.c @@ -1,6 +1,9 @@  #include "Python.h"  #ifdef MS_WINDOWS  #  include <windows.h> +/* All sample MSDN wincrypt programs include the header below. It is at least + * required with Min GW. */ +#  include <wincrypt.h>  #else  #  include <fcntl.h>  #  ifdef HAVE_SYS_STAT_H @@ -36,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;  } @@ -52,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) @@ -62,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; @@ -83,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; @@ -128,25 +128,22 @@ py_getentropy(unsigned char *buffer, Py_ssize_t size, int fatal)       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? Set to 0 if getrandom()         failed with ENOSYS or EPERM. 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;      if (!getrandom_works) {          return 0;      } +    flags = blocking ? 0 : GRND_NONBLOCK; +    dest = buffer;      while (0 < size) {  #ifdef sun          /* Issue #26735: On Solaris, getrandom() is limited to returning up @@ -160,23 +157,23 @@ 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 -         * 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, 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 @@ -188,40 +185,33 @@ py_getrandom(void *buffer, Py_ssize_t size, int raise)                  getrandom_works = 0;                  return 0;              } -            if (errno == EAGAIN) { -                /* getrandom(GRND_NONBLOCK) fails with EAGAIN if the system -                   urandom is not initialiazed yet. In this case, fall back on -                   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;              }              if (errno == EINTR) { -                if (PyErr_CheckSignals()) { -                    if (!raise) { -                        Py_FatalError("getrandom() interrupted by a signal"); +                if (raise) { +                    if (PyErr_CheckSignals()) { +                        return -1;                      } -                    return -1;                  } -                /* retry getrandom() */ +                /* retry getrandom() if it was interrupted by a signal */                  continue;              }              if (raise) {                  PyErr_SetFromErrno(PyExc_OSError);              } -            else { -                Py_FatalError("getrandom() failed"); -            }              return -1;          } -        buffer += n; +        dest += n;          size -= n;      }      return 1; @@ -238,64 +228,21 @@ static struct {  /* Read 'size' random bytes from py_getrandom(). Fall back on reading from     /dev/urandom if getrandom() is not available. -   Call Py_FatalError() on error. */ -static void -dev_urandom_noraise(unsigned 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; -    } -    /* getrandom() failed with ENOSYS or EPERM, -       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"); -    } - -    while (0 < size) -    { -        do { -            n = read(fd, buffer, (size_t)size); -        } while (n < 0 && errno == EINTR); - -        if (n <= 0) { -            /* read() failed or returned 0 bytes */ -            Py_FatalError("Failed to read bytes from /dev/urandom"); -            break; -        } -        buffer += n; -        size -= n; -    } -    close(fd); -} - -/* 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 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 blocking, 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); +    res = py_getrandom(buffer, size, blocking, raise);      if (res < 0) {          return -1;      } @@ -306,64 +253,90 @@ dev_urandom_python(char *buffer, Py_ssize_t size)         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_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 */ +                close(fd); +                return -1; +            } +            buffer += n; +            size -= n; +        } +        close(fd); +    }      return 0;  } @@ -399,38 +372,71 @@ 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 blocking, 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); +    return dev_urandom(buffer, size, blocking, raise);  #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. + +   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, 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  _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; @@ -462,13 +468,17 @@ _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 (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"); +        }      }  } | 
