tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: /dev/random is hot garbage



> Date: Sun, 21 Jul 2019 17:28:17 +0200
> From: Martin Husemann <martin%duskware.de@localhost>
> 
> Replacing the /dev/random device node by a symlink to /dev/urandom sounds
> fine. For binaries it is easy to just use the sysctl instead to get high
> quality randomness. Are there any shell script like applications that
> seriously would require something better than /dev/urandom?
> 
> The other issue is the urban rumour that you may want to pull a real random
> byte out of /dev/random before using /dev/urandom - maybe we should have
> a "aggregate" sysctl doing just that (so applications can get a single byte
> real entropy + as many /dev/urandom ones as they like in a single call)?

This is the correct way -- that works pretty reliably on almost any
platform that has /dev/u?random at all -- for a program to block until
the entropy pool has been seeded; there's essentially no other reason
ever to read from /dev/random.

What may not be clear is _which_ programs need to do this or when, and
the farther from the holistic view of system engineering you are, the
murkier it gets.

 * The system view.  Someone who is assembling a platform with
   pre-installed NetBSD to be shipped in a box and deployed needs to
   ensure, in the system they're shipping, that the entropy pool be
   seeded by an unpredictable secret _before_ you use any secrets
   derived from it, e.g. for cryptography.

   - A system engineer might choose hardware with a hardware RNG.

   - A system engineer might write an independent seed from their
     laptop to /var/db/entropy-file on each device (or cloud instance)
     after flashing it with the standard OS image.

   - A system engineer might
     (a) start a daemon in one rc script that reads a seed over a
         serial port to a Geiger counter with a radiation source,
         asynchronously; and
     (b) read a byte from /dev/random in another rc script that has to
         wait until the seeding daemon has done its job.

   - A system engineer might flip a coin 256 times, open a shell, and
     type `echo tthhhhhthhhththtt... > /dev/random', before starting
     any applications in a live system.

 * The application view.  Someone who writes an application, like a
   mail server, which might run in many different systems, needs a way
   to generate secrets that will be used for cryptography.  This is
   safe only after the entropy pool is seeded -- but the application
   engineer, who is just writing software, is not assembling the whole
   system and so can't arrange to set the application up next to a
   real radiation source and Geiger counter.

   - An application that has a definite startup phase, like the Postfix
     master daemon, might reasonably read a single byte from /dev/random
     at startup, and then use /dev/urandom in all its subprocesses.

   - An application might reasonably have a command-line argument for
     a seed file, which is also useful because it facilitates
     deterministic testing, like gcc's -frandom-seed.

   - An application might just quietly defer the decision to the
     system engineer, but the quieter this is, the greater the risk
     the application will be deployed with a fatal insecurity.

 * The library view.  Someone who writes a library used by many
   applications, like this Rust vendor/rand library, needs a way to
   get at secrets from the operating system that will be used to
   derive other secrets in the library.  This, again, is safe only
   after the entropy pool is seeded, but the library engineer has to
   make it usable in _many_ applications.

   A library might read from
   - /dev/random,
   - /dev/urandom,
   - getentropy(),
   - getrandom(),
   - sysctl kern.arandom,
   or something like that.  For example, our arc4random library reads
   from sysctl kern.arandom.  Alternatively, a library could accept a
   parameter -- to be passed by the application -- for a seed, and
   avoid talking to the operating system at all.

   Libraries may also be constrained by blocking: it is at least rude,
   and sometimes a fatal bug or a deadlock, for a library to block
   when it is expected not to block.  It may be especially bad if a
   library is expected not to block, but blocks _sometimes and only in
   extremely infrequent circumstances_, like how a POSIX clock skips a
   beat sometimes but only once every year or two (and simultaneously
   all over the world, when it does), making it unlikely that the code
   path will be exercised during tests.

   Some programs like gpg function more like libraries than like
   applications in that they are used as subroutines by other
   programs; the fact that gpg insists on reading every byte of every
   candidate RSA modulus from /dev/random has led to decades of
   justifiable frustration with using it as a subroutine.

 * The OS view.  Someone who writes an operating system used by many
   system engineers that run many applications, using many libraries,
   needs to provide interfaces for: a way to seed the entropy pool, a
   way to wait for the entropy pool to be seeded, a way to generate
   secrets.

   In NetBSD, we to seed the entropy pool, we have:
   - drivers for hardware RNG devices (and faux RNG devices like clocks),
   - boot loader support for loading a seed from disk,
   - /etc/rc.d support for loading a seed from disk, and
   - a writable /dev/random into which you can dump seed material.

   In NetBSD, to wait for the entropy pool to be seeded, we have:
   - a readable /dev/random which may block until something happens to
     bring the entropy pool over a threshold.

   In NetBSD, to generate secrets, we have:
   - a readable /dev/urandom,
   - sysctl kern.arandom / kern.urandom.

The Rust logic at issue in the tech-pkg@ thread is:

- inside a system, our bulk build process;
- inside an application, rustc and the Rust bootstrap build;
- inside a library, vendor/rand;
- running on NetBSD.

We could in principle resolve the problem any four of these levels --
system, application, library, and OS:

- Change the bulk build system.  We could replace /dev/random by a
  symlink to /dev/urandom, as Nia suggested on tech-pkg@, in the
  chroot or Xen guest where the builds happen.

  Provided that chroot or Xen guest is _not_ used for (e.g.) signing
  packages, key generation, sshd exposed to the internet, &c., and is
  limited only to building packages, this is safe.  Of course, it only
  addresses our bulk builders.  So, it's a _generally_ risky and
  limited change, but it would likely serve our needs here.

- Patch the Rust build.  Maybe we could patch rustc or the Rust
  bootstrap process to use a seed as a command-line argument, like
  gcc's -frandom-seed; as gdt mentioned on tech-pkg@, it is hard to
  imagine that it actually needs _secrets_.  Or maybe there's a way to
  do this already, e.g. for reproducible builds, and we could take
  advantage of that.

  (Conceivably it might use a hash table with a universal hash family,
  which does need a key that is unpredictable in advance, at least, to
  prevent a hash-flooding attack via Rust source code, but this is
  far-fetched.)

- Patch the Rust library.  We could -- and indeed, we apparently _do_
  -- patch the library vendor/rand so that it _does not_ read from
  /dev/random, and only uses /dev/urandom or (better) kern.arandom.

  But there's a risk here: applications may _rely_ on the library
  vendor/rand to block until the entropy pool is seeded.  So this
  change can introduce a vulnerability where there was none before.
  It's hard to imagine such a change could affect the build process,
  but it could certainly affect Rust applications, especially those
  deployed in appliances, without careful system engineering.

  I don't know what assumptions downstream consumers of this library
  make.  Maybe this is fine -- maybe blocking on /dev/random is
  actually a bug, because _every_ application using it actually
  ensures the entropy pool is seeded some other way, or doesn't care.
  But the fact that the logic was here to begin with makes me
  suspicious that it is actually important to block until seeded.

- Change NetBSD.  We could change NetBSD's interfaces: /dev/random,
  /dev/urandom, kern.arandom; maybe add Linux getrandom(2), OpenBSD
  getentropy(2).

  It has become popular to redefine the traditional semantics of
  /dev/random or /dev/urandom so that one or both will block once at
  boot until the OS thinks the entropy pool may have been seeded, and
  then never block again.

  I don't want to do this because code paths that may block but only
  in extreme circumstances, like early at boot on an embedded system,
  are likely never to be exercised even during what might otherwise be
  extensive testing, and as noted blocking when not expected can have
  severe consequences.

  But maybe it would not be so bad to do this in a new interface like
  getentropy(2), much as I think it is a bad idea to establish such
  extremely unlikely blocking behaviour in an API, since there is now
  lots of existing code that uses exactly that interface.

  Of course, that doesn't help with building Rust on netbsd-8.


Home | Main Index | Thread Index | Old Index