random: Add a feature to close device file descriptors.
authorWerner Koch <wk@gnupg.org>
Wed, 11 Dec 2013 15:59:41 +0000 (16:59 +0100)
committerWerner Koch <wk@gnupg.org>
Wed, 11 Dec 2013 16:01:15 +0000 (17:01 +0100)
* src/gcrypt.h.in (GCRYCTL_CLOSE_RANDOM_DEVICE): New.
* src/global.c (_gcry_vcontrol): Call _gcry_random_close_fds.
* random/random.c (_gcry_random_close_fds): New.
* random/random-csprng.c (_gcry_rngcsprng_close_fds): New.
* random/random-fips.c (_gcry_rngfips_close_fds): New.
* random/random-system.c (_gcry_rngsystem_close_fds): New.
* random/rndlinux.c (open_device): Add arg retry.
(_gcry_rndlinux_gather_random): Add mode to close open fds.

* tests/random.c (check_close_random_device): New.
(main): Call new test.

Signed-off-by: Werner Koch <wk@gnupg.org>
12 files changed:
NEWS
doc/gcrypt.texi
random/rand-internal.h
random/random-csprng.c
random/random-fips.c
random/random-system.c
random/random.c
random/random.h
random/rndlinux.c
src/gcrypt.h.in
src/global.c
tests/random.c

diff --git a/NEWS b/NEWS
index ec853c9..4c95e8a 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -72,6 +72,7 @@ Noteworthy changes in version 1.6.0 (unreleased)
  GCRYCTL_SET_ENFORCED_FIPS_FLAG  NEW.
  GCRYCTL_SET_PREFERRED_RNG_TYPE  NEW.
  GCRYCTL_GET_CURRENT_RNG_TYPE    NEW.
+ GCRYCTL_CLOSE_RANDOM_DEVICE     NEW.
  GCRY_RNG_TYPE_STANDARD          NEW.
  GCRY_RNG_TYPE_FIPS              NEW.
  GCRY_RNG_TYPE_SYSTEM            NEW.
index 927634f..97dac1c 100644 (file)
@@ -766,6 +766,14 @@ not an issue when using Linux (rndlinux driver), because this one
 guarantees to read full 16 bytes from /dev/urandom and thus there is no
 way for an attacker without kernel access to control these 16 bytes.
 
+@item GCRYCTL_CLOSE_RANDOM_DEVICE; Arguments: none
+Try to close the random device.  If on Unix system you call fork(),
+the child process does no call exec(), and you do not intend to use
+Libgcrypt in the child, it might be useful to use this control code to
+close the inherited file descriptors of the random device.  If
+Libgcrypt is later used again by the child, the device will be
+re-opened.  On non-Unix systems this control code is ignored.
+
 @item GCRYCTL_SET_VERBOSITY; Arguments: int level
 This command sets the verbosity of the logging.  A level of 0 disables
 all extra logging whereas positive numbers enable more verbose logging.
@@ -1355,6 +1363,10 @@ values for @var{what} are defined:
 Not enough entropy is available.  @var{total} holds the number of
 required bytes.
 
+@item wait_dev_random
+Waiting to re-open a random device.  @var{total} gives the number of
+seconds until the next try.
+
 @item primegen
 Values for @var{printchar}:
 @table @code
index f59a102..79b23ac 100644 (file)
@@ -44,6 +44,7 @@ void _gcry_random_progress (const char *what, int printchar,
 
 /*-- random-csprng.c --*/
 void _gcry_rngcsprng_initialize (int full);
+void _gcry_rngcsprng_close_fds (void);
 void _gcry_rngcsprng_dump_stats (void);
 void _gcry_rngcsprng_secure_alloc (void);
 void _gcry_rngcsprng_enable_quick_gen (void);
@@ -64,6 +65,7 @@ void _gcry_rngcsprng_fast_poll (void);
 
 /*-- random-fips.c --*/
 void _gcry_rngfips_initialize (int full);
+void _gcry_rngfips_close_fds (void);
 void _gcry_rngfips_dump_stats (void);
 int  _gcry_rngfips_is_faked (void);
 gcry_error_t _gcry_rngfips_add_bytes (const void *buf, size_t buflen,
@@ -89,6 +91,7 @@ void _gcry_rngfips_deinit_external_test (void *context);
 
 /*-- random-system.c --*/
 void _gcry_rngsystem_initialize (int full);
+void _gcry_rngsystem_close_fds (void);
 void _gcry_rngsystem_dump_stats (void);
 int  _gcry_rngsystem_is_faked (void);
 gcry_error_t _gcry_rngsystem_add_bytes (const void *buf, size_t buflen,
index 9921c4f..b6d7f66 100644 (file)
@@ -154,7 +154,7 @@ static int allow_seed_file_update;
 static int secure_alloc;
 
 /* This function pointer is set to the actual entropy gathering
-   function during initailization.  After initialization it is
+   function during initialization.  After initialization it is
    guaranteed to point to function.  (On systems without a random
    gatherer module a dummy function is used).*/
 static int (*slow_gather_fnc)(void (*)(const void*, size_t,
@@ -361,6 +361,20 @@ _gcry_rngcsprng_initialize (int full)
 }
 
 
+/* Try to close the FDs of the random gather module.  This is
+   currently only implemented for rndlinux. */
+void
+_gcry_rngcsprng_close_fds (void)
+{
+  lock_pool ();
+#if USE_RNDLINUX
+  _gcry_rndlinux_gather_random (NULL, 0, 0, 0);
+  pool_filled = 0; /* Force re-open on next use.  */
+#endif
+  unlock_pool ();
+}
+
+
 void
 _gcry_rngcsprng_dump_stats (void)
 {
index c8100a2..6ee52f1 100644 (file)
@@ -780,6 +780,19 @@ _gcry_rngfips_initialize (int full)
 }
 
 
+/* Try to close the FDs of the random gather module.  This is
+   currently only implemented for rndlinux. */
+void
+_gcry_rngfips_close_fds (void)
+{
+  lock_rng ();
+#if USE_RNDLINUX
+  _gcry_rndlinux_gather_random (NULL, 0, 0, 0);
+#endif
+  unlock_rng ();
+}
+
+
 /* Print some statistics about the RNG.  */
 void
 _gcry_rngfips_dump_stats (void)
index 0ef9d24..3962ab8 100644 (file)
@@ -193,6 +193,19 @@ _gcry_rngsystem_initialize (int full)
 }
 
 
+/* Try to close the FDs of the random gather module.  This is
+   currently only implemented for rndlinux. */
+void
+_gcry_rngsystem_close_fds (void)
+{
+  lock_rng ();
+#if USE_RNDLINUX
+  _gcry_rndlinux_gather_random (NULL, 0, 0, 0);
+#endif
+  unlock_rng ();
+}
+
+
 /* Print some statistics about the RNG.  */
 void
 _gcry_rngsystem_dump_stats (void)
index 4679301..97018c4 100644 (file)
@@ -165,6 +165,27 @@ _gcry_random_initialize (int full)
 }
 
 
+/* If possible close file descriptors used by the RNG. */
+void
+_gcry_random_close_fds (void)
+{
+  /* Note that we can't do that directly because each random system
+     has its own lock functions which need to be used for accessing
+     the entropy gatherer.  */
+
+  if (fips_mode ())
+    _gcry_rngfips_close_fds ();
+  else if (rng_types.standard)
+    _gcry_rngcsprng_close_fds ();
+  else if (rng_types.fips)
+    _gcry_rngfips_close_fds ();
+  else if (rng_types.system)
+    _gcry_rngsystem_close_fds ();
+  else
+    _gcry_rngcsprng_close_fds ();
+}
+
+
 /* Return the current RNG type.  IGNORE_FIPS_MODE is a flag used to
    skip the test for FIPS.  This is useful, so that we are able to
    return the type of the RNG even before we have setup FIPS mode
index aae07ab..2bc8cab 100644 (file)
@@ -28,6 +28,7 @@ void _gcry_register_random_progress (void (*cb)(void *,const char*,int,int,int),
 
 void _gcry_set_preferred_rng_type (int type);
 void _gcry_random_initialize (int full);
+void _gcry_random_close_fds (void);
 int  _gcry_get_rng_type (int ignore_fips_mode);
 void _gcry_random_dump_stats(void);
 void _gcry_secure_random_alloc(void);
index b304cc9..21ea8c4 100644 (file)
@@ -36,7 +36,7 @@
 #include "g10lib.h"
 #include "rand-internal.h"
 
-static int open_device ( const char *name );
+static int open_device (const char *name, int retry);
 
 
 static int
@@ -54,15 +54,30 @@ set_cloexec_flag (int fd)
 
 
 /*
- * Used to open the /dev/random devices (Linux, xBSD, Solaris (if it exists)).
+ * Used to open the /dev/random devices (Linux, xBSD, Solaris (if it
+ * exists)).  If RETRY is true, the function does not terminate with
+ * a fatal error but retries until it is able to reopen the device.
  */
 static int
-open_device ( const char *name )
+open_device (const char *name, int retry)
 {
   int fd;
 
-  fd = open ( name, O_RDONLY );
-  if ( fd == -1 )
+  if (retry)
+    _gcry_random_progress ("open_dev_random", 'X', 1, 0);
+ again:
+  fd = open (name, O_RDONLY);
+  if (fd == -1 && retry)
+    {
+      struct timeval tv;
+
+      tv.tv_sec = 5;
+      tv.tv_usec = 0;
+      _gcry_random_progress ("wait_dev_random", 'X', 0, (int)tv.tv_sec);
+      select (0, NULL, NULL, NULL, &tv);
+      goto again;
+    }
+  if (fd == -1)
     log_fatal ("can't open %s: %s\n", name, strerror(errno) );
 
   if (set_cloexec_flag (fd))
@@ -84,6 +99,10 @@ open_device ( const char *name )
 }
 
 
+/* Note that the caller needs to make sure that this function is only
+   called by one thread at a time.  The function returns 0 on success
+   or true on failure (in which case the caller will signal a fatal
+   error).  */
 int
 _gcry_rndlinux_gather_random (void (*add)(const void*, size_t,
                                           enum random_origins),
@@ -92,6 +111,7 @@ _gcry_rndlinux_gather_random (void (*add)(const void*, size_t,
 {
   static int fd_urandom = -1;
   static int fd_random = -1;
+  static unsigned char ever_opened;
   int fd;
   int n;
   byte buffer[768];
@@ -101,6 +121,23 @@ _gcry_rndlinux_gather_random (void (*add)(const void*, size_t,
   int any_need_entropy = 0;
   int delay;
 
+  if (!add)
+    {
+      /* Special mode to close the descriptors.  */
+      if (fd_random != -1)
+        {
+          close (fd_random);
+          fd_random = -1;
+        }
+      if (fd_urandom != -1)
+        {
+          close (fd_urandom);
+          fd_urandom = -1;
+        }
+      return 0;
+    }
+
+
   /* First read from a hardware source.  However let it account only
      for up to 50% of the requested bytes.  */
   n_hw = _gcry_rndhw_poll_slow (add, origin);
@@ -109,17 +146,29 @@ _gcry_rndlinux_gather_random (void (*add)(const void*, size_t,
   if (length > 1)
     length -= n_hw;
 
-  /* Open the requested device.  */
+  /* Open the requested device.  The first time a device is to be
+     opened we fail with a fatal error if the device does not exists.
+     In case the device has ever been closed, further open requests
+     will however retry indefinitely.  The rationale for this behaviour is
+     that we always require the device to be existent but want a more
+     graceful behaviour if the rarely needed close operation has been
+     used and the device needs to be re-opened later. */
   if (level >= 2)
     {
-      if( fd_random == -1 )
-        fd_random = open_device ( NAME_OF_DEV_RANDOM );
+      if (fd_random == -1)
+        {
+          fd_random = open_device (NAME_OF_DEV_RANDOM, (ever_opened & 1));
+          ever_opened |= 1;
+        }
       fd = fd_random;
     }
   else
     {
-      if( fd_urandom == -1 )
-        fd_urandom = open_device ( NAME_OF_DEV_URANDOM );
+      if (fd_urandom == -1)
+        {
+          fd_urandom = open_device (NAME_OF_DEV_URANDOM, (ever_opened & 2));
+          ever_opened |= 2;
+        }
       fd = fd_urandom;
     }
 
@@ -164,7 +213,7 @@ _gcry_rndlinux_gather_random (void (*add)(const void*, size_t,
               log_error ("select() error: %s\n", strerror(errno));
               if (!delay)
                 delay = 1; /* Use 1 second if we encounter an error before
-                          we have ever blocked.  */
+                              we have ever blocked.  */
               continue;
             }
         }
index 53133bf..5c771e5 100644 (file)
@@ -326,7 +326,8 @@ enum gcry_ctl_cmds
     GCRYCTL_GET_CURRENT_RNG_TYPE = 66,
     GCRYCTL_DISABLE_LOCKED_SECMEM = 67,
     GCRYCTL_DISABLE_PRIV_DROP = 68,
-    GCRYCTL_SET_CCM_LENGTHS = 69
+    GCRYCTL_SET_CCM_LENGTHS = 69,
+    GCRYCTL_CLOSE_RANDOM_DEVICE = 70
   };
 
 /* Perform various operations defined by CMD. */
index 8521e58..8a5d310 100644 (file)
@@ -540,6 +540,10 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
       _gcry_use_random_daemon (!! va_arg (arg_ptr, int));
       break;
 
+    case GCRYCTL_CLOSE_RANDOM_DEVICE:
+      _gcry_random_close_fds ();
+      break;
+
       /* This command dumps information pertaining to the
          configuration of libgcrypt to the given stream.  It may be
          used before the initialization has been finished but not
index ccaa3f9..10bf646 100644 (file)
@@ -270,6 +270,54 @@ check_nonce_forking (void)
 }
 
 
+/* Check that a closed random device os re-opened if needed. */
+static void
+check_close_random_device (void)
+{
+#ifdef HAVE_W32_SYSTEM
+  if (verbose)
+    inf ("check_close_random_device skipped: not applicable on Windows\n");
+#else /*!HAVE_W32_SYSTEM*/
+  pid_t pid;
+  int i, status;
+  char buf[4];
+
+  if (verbose)
+    inf ("checking that close_random_device works\n");
+
+  gcry_randomize (buf, sizeof buf, GCRY_STRONG_RANDOM);
+  if (verbose)
+    print_hex ("parent random: ", buf, sizeof buf);
+
+  pid = fork ();
+  if (pid == (pid_t)(-1))
+    die ("fork failed: %s\n", strerror (errno));
+  if (!pid)
+    {
+      gcry_control (GCRYCTL_CLOSE_RANDOM_DEVICE, 0);
+
+      /* The next call will re-open the device.  */
+      gcry_randomize (buf, sizeof buf, GCRY_STRONG_RANDOM);
+      if (verbose)
+        {
+          print_hex ("child random : ", buf, sizeof buf);
+          fflush (stdout);
+        }
+      _exit (0);
+    }
+
+  while ( (i=waitpid (pid, &status, 0)) == -1 && errno == EINTR)
+    ;
+  if (i != (pid_t)(-1)
+      && WIFEXITED (status) && !WEXITSTATUS (status))
+    ;
+  else
+    die ("child failed\n");
+
+#endif  /*!HAVE_W32_SYSTEM*/
+}
+
+
 static int
 rng_type (void)
 {
@@ -529,6 +577,7 @@ main (int argc, char **argv)
     {
       check_forking ();
       check_nonce_forking ();
+      check_close_random_device ();
     }
   check_rng_type_switching ();