random: Try to use getrandom() instead of /dev/urandom (Linux only).
authorWerner Koch <wk@gnupg.org>
Tue, 26 Apr 2016 13:46:30 +0000 (15:46 +0200)
committerWerner Koch <wk@gnupg.org>
Wed, 8 Jun 2016 16:17:56 +0000 (18:17 +0200)
* configure.ac: Check for syscall.
* random/rndlinux.c [HAVE_SYSCALL]: Include sys/syscall.h.
(_gcry_rndlinux_gather_random): Use getrandom is available.

Signed-off-by: Werner Koch <wk@gnupg.org>
configure.ac
random/rndlinux.c

index 5f9f711..ad06dfd 100644 (file)
@@ -1514,7 +1514,7 @@ AC_CHECK_FUNCS(strtoul memmove stricmp atexit raise)
 # Other checks
 AC_CHECK_FUNCS(strerror rand mmap getpagesize sysconf waitpid wait4)
 AC_CHECK_FUNCS(gettimeofday getrusage gethrtime clock_gettime syslog)
-AC_CHECK_FUNCS(fcntl ftruncate flockfile)
+AC_CHECK_FUNCS(syscall fcntl ftruncate flockfile)
 
 GNUPG_CHECK_MLOCK
 
index 0cb65df..592b9ac 100644 (file)
 #include <string.h>
 #include <unistd.h>
 #include <fcntl.h>
+#if defined(__linux__) && defined(HAVE_SYSCALL)
+# include <sys/syscall.h>
+#endif
+
 #include "types.h"
 #include "g10lib.h"
 #include "rand-internal.h"
@@ -232,6 +236,50 @@ _gcry_rndlinux_gather_random (void (*add)(const void*, size_t,
             }
         }
 
+      /* If we have a modern Linux kernel and we want to read from the
+       * the non-blocking /dev/urandom, we first try to use the new
+       * getrandom syscall.  That call guarantees that the kernel's
+       * RNG has been properly seeded before returning any data.  This
+       * is different from /dev/urandom which may, due to its
+       * non-blocking semantics, return data even if the kernel has
+       * not been properly seeded.  Unfortunately we need to use a
+       * syscall and not a new device and thus we are not able to use
+       * select(2) to have a timeout. */
+#if defined(__linux__) && defined(HAVE_SYSCALL) && defined(__NR_getrandom)
+      if (fd == fd_urandom)
+        {
+          long ret;
+          size_t nbytes;
+
+          do
+            {
+              nbytes = length < sizeof(buffer)? length : sizeof(buffer);
+              if (nbytes > 256)
+                nbytes = 256;
+              ret = syscall (__NR_getrandom,
+                             (void*)buffer, (size_t)nbytes, (unsigned int)0);
+            }
+          while (ret == -1 && errno == EINTR);
+          if (ret == -1 && errno == ENOSYS)
+            ; /* The syscall is not supported - fallback to /dev/urandom.  */
+          else
+            { /* The syscall is supported.  Some sanity checks.  */
+              if (ret == -1)
+                log_fatal ("unexpected error from getrandom: %s\n",
+                           strerror (errno));
+              else if (ret != nbytes)
+                log_fatal ("getrandom returned only"
+                           " %ld of %zu requested bytes\n", ret, nbytes);
+
+              log_debug ("getrandom returned %zu requested bytes\n", nbytes);
+              (*add)(buffer, nbytes, origin);
+              length -= nbytes;
+              continue; /* until LENGTH is zero.  */
+            }
+          log_debug ("syscall(getrandom) not supported; errno = %d\n", errno);
+        }
+#endif
+
       do
         {
           size_t nbytes;