From dddf4849ec1750ca02d03b9772eff7141ba626f3 Mon Sep 17 00:00:00 2001
From: Victor Stinner <victor.stinner@gmail.com>
Date: Tue, 7 Jun 2016 11:21:42 +0200
Subject: os.urandom() doesn't block on Linux anymore

Issue #26839: On Linux, os.urandom() now calls getrandom() with GRND_NONBLOCK
to fall back on reading /dev/urandom if the urandom entropy pool is not
initialized yet. Patch written by Colm Buckley.
---
 Doc/library/os.rst | 13 ++++++++++---
 Misc/ACKS          |  1 +
 Misc/NEWS          |  4 ++++
 Python/random.c    | 24 +++++++++++++++++++++---
 configure          |  8 +++++---
 configure.ac       |  7 ++++---
 pyconfig.h.in      |  3 +++
 7 files changed, 48 insertions(+), 12 deletions(-)

diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index e6b6465..785b080 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -3733,14 +3733,21 @@ Miscellaneous Functions
 
    This function returns random bytes from an OS-specific randomness source.  The
    returned data should be unpredictable enough for cryptographic applications,
-   though its exact quality depends on the OS implementation.  On a Unix-like
-   system this will query ``/dev/urandom``, and on Windows it will use
-   ``CryptGenRandom()``.  If a randomness source is not found,
+   though its exact quality depends on the OS implementation.
+
+   On Linux, ``getrandom()`` syscall is used if available and the urandom
+   entropy pool is initialized (``getrandom()`` does not block).
+   On a Unix-like system this will query ``/dev/urandom``. On Windows, it
+   will use ``CryptGenRandom()``.  If a randomness source is not found,
    :exc:`NotImplementedError` will be raised.
 
    For an easy-to-use interface to the random number generator
    provided by your platform, please see :class:`random.SystemRandom`.
 
+   .. versionchanged:: 3.5.2
+      On Linux, if ``getrandom()`` blocks (the urandom entropy pool is not
+      initialized yet), fall back on reading ``/dev/urandom``.
+
    .. versionchanged:: 3.5
       On Linux 3.17 and newer, the ``getrandom()`` syscall is now used
       when available.  On OpenBSD 5.6 and newer, the C ``getentropy()``
diff --git a/Misc/ACKS b/Misc/ACKS
index 709ce94..02e4821 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -200,6 +200,7 @@ Ian Bruntlett
 Floris Bruynooghe
 Matt Bryant
 Stan Bubrouski
+Colm Buckley
 Erik de Bueger
 Jan-Hein Bührman
 Lars Buitinck
diff --git a/Misc/NEWS b/Misc/NEWS
index da2dc3e..918f8c8 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -131,6 +131,10 @@ Core and Builtins
 Library
 -------
 
+- Issue #26839: On Linux, :func:`os.urandom` now calls ``getrandom()`` with
+  ``GRND_NONBLOCK`` to fall back on reading ``/dev/urandom`` if the urandom
+  entropy pool is not initialized yet. Patch written by Colm Buckley.
+
 - Issue #27164: In the zlib module, allow decompressing raw Deflate streams
   with a predefined zdict.  Based on patch by Xiang Zhang.
 
diff --git a/Python/random.c b/Python/random.c
index 79157b8..ecfd44b 100644
--- a/Python/random.c
+++ b/Python/random.c
@@ -6,6 +6,9 @@
 #  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)
@@ -122,9 +125,13 @@ 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;
-    /* 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;
+
+    /* 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)
@@ -168,6 +175,17 @@ 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;
+                return 0;
+            }
 
             if (errno == EINTR) {
                 if (PyErr_CheckSignals()) {
diff --git a/configure b/configure
index 3216ad7..c892a99 100755
--- a/configure
+++ b/configure
@@ -2876,6 +2876,7 @@ fi
 ac_config_headers="$ac_config_headers pyconfig.h"
 
 
+
 ac_aux_dir=
 for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
   if test -f "$ac_dir/install-sh"; then
@@ -7568,7 +7569,7 @@ sys/param.h sys/select.h sys/sendfile.h sys/socket.h sys/statvfs.h \
 sys/stat.h sys/syscall.h sys/sys_domain.h sys/termio.h sys/time.h \
 sys/times.h sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h pty.h \
 libutil.h sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \
-bluetooth/bluetooth.h linux/tipc.h spawn.h util.h alloca.h endian.h \
+bluetooth/bluetooth.h linux/tipc.h linux/random.h spawn.h util.h alloca.h endian.h \
 sys/endian.h
 do :
   as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
@@ -16325,12 +16326,13 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 
     #include <unistd.h>
     #include <sys/syscall.h>
+    #include <linux/random.h>
 
     int main() {
         char buffer[1];
         const size_t buflen = sizeof(buffer);
-        const int flags = 0;
-        /* ignore the result, Python checks for ENOSYS at runtime */
+        const int flags = GRND_NONBLOCK;
+        /* ignore the result, Python checks for ENOSYS and EAGAIN at runtime */
         (void)syscall(SYS_getrandom, buffer, buflen, flags);
         return 0;
     }
diff --git a/configure.ac b/configure.ac
index 4dc20d6..1c07c05 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1881,7 +1881,7 @@ sys/param.h sys/select.h sys/sendfile.h sys/socket.h sys/statvfs.h \
 sys/stat.h sys/syscall.h sys/sys_domain.h sys/termio.h sys/time.h \
 sys/times.h sys/types.h sys/uio.h sys/un.h sys/utsname.h sys/wait.h pty.h \
 libutil.h sys/resource.h netpacket/packet.h sysexits.h bluetooth.h \
-bluetooth/bluetooth.h linux/tipc.h spawn.h util.h alloca.h endian.h \
+bluetooth/bluetooth.h linux/tipc.h linux/random.h spawn.h util.h alloca.h endian.h \
 sys/endian.h)
 AC_HEADER_DIRENT
 AC_HEADER_MAJOR
@@ -5240,12 +5240,13 @@ AC_LINK_IFELSE(
   AC_LANG_SOURCE([[
     #include <unistd.h>
     #include <sys/syscall.h>
+    #include <linux/random.h>
 
     int main() {
         char buffer[1];
         const size_t buflen = sizeof(buffer);
-        const int flags = 0;
-        /* ignore the result, Python checks for ENOSYS at runtime */
+        const int flags = GRND_NONBLOCK;
+        /* ignore the result, Python checks for ENOSYS and EAGAIN at runtime */
         (void)syscall(SYS_getrandom, buffer, buflen, flags);
         return 0;
     }
diff --git a/pyconfig.h.in b/pyconfig.h.in
index d432a82..7895535 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -546,6 +546,9 @@
 /* Define to 1 if you have the <linux/tipc.h> header file. */
 #undef HAVE_LINUX_TIPC_H
 
+/* Define to 1 if you have the <linux/random.h> header file. */
+#undef HAVE_LINUX_RANDOM_H
+
 /* Define to 1 if you have the `lockf' function. */
 #undef HAVE_LOCKF
 
-- 
cgit v0.12