[PATCH] Kernel entropy rework

Taylor R Campbell <[email protected]> Sat, 21 Dec 2019 22:08:20 +0000
Newsgroups gmane.os.netbsd.devel.security
Message-ID <20191221220822.1253760B4D__13286.2629297719$1576967700$gmane$org@jupiter.mumble.net>
The attached patch set reworks the kernel entropy subsystem.

Why?  Goals:

1. Use cryptography primitives designed and vetted by cryptographers.
2. Be honest about entropy estimation.
3. Propagate full entropy as soon as possible.
4. Simplify the APIs.
5. Reduce overhead of rnd_add_data and cprng_strong.
6. Reduce side channels of HWRNG data and human input sources.
7. Improve visibility of operation with sysctl and event counters.

How?  General overview:

- Replace SHA-1/LFSR entropy pool by Keccak sponge.
- Replace global sample queue -- and global locks on cprng_strong --
  by per-CPU state.
- Eliminate counterproductive entropy `depletion' (though it is still
  available for testing, in very small code paths).

It's mostly ready but has a couple loose ends I'd like to tie up
before commit (marked with XXX below).  I tried a few times to split
it up into smaller incremental changes -- perhaps could have done the
core entropy subsystem first, and /dev/random and cprng_strong next
separately -- but after spending a while on this, I'm not sure it's
worthwhile to split up much more.

Comments and testing welcome!


What?  Details:

1. Use cryptography primitives designed and vetted by cryptographers.

   => WHAT WE DO NOW: A kind of wacky composition of LFSR with SHA-1
      feedback that was cooked up in the '90s, without clear analysis
      by cryptographers.

   => WHAT THE PATCH DOES: Keccak sponge in duplex mode.  The Keccak
      sponge is the basis for the SHA-3 standard; the duplex mode is a
      simple and well-understood way to feed input in and extract
      output that is also used for authenticated encryption and other
      purposes.  See, e.g.,

         Guido Bertoni, Joan Daemen, Michael Peeters, and Gilles van
         Assche, `Duplexing the Sponge: Single-Pass Authenticated
         Encryption and Other Applications', in Ali Miri and Serge
         Vaudenay, eds., Selected Areas in Cryptography---SAC 2011,
         Springer LNCS 7118, pp. 320--337.
         https://link.springer.com/chapter/10.1007/978-3-642-28496-0_19
         https://keccak.team/files/SpongeDuplex.pdf

      Everything works with 256-bit intermediate quantities in order
      to guarantee a standard 128-bit security level even against
      multi-target quantum adversaries and modest cryptanalytic
      advances.

2. Be honest about entropy estimation.

   => WHAT WE DO NOW: Feed samples from entropy sources into an
      extremely simple-minded algorithm to fit parameters for an
      extremely simple-minded model of the sampling process in the
      underlying device in order to derive a heuristic estimate of the
      sampling process's entropy as we feed samples into the pool.

      This model bears no resemblance to the sampling process of the
      actual device, and so is essentially designed to guarantee
      overestimating the entropy of the system.  And by feeding the
      same samples into the entropy estimation algorithm and into the
      entropy pool, we are almost certainly leaking information
      through timing side channels too.

   => WHAT THE PATCH DOES: Leaves it entirely to the driver to specify
      the entropy of the sampling process based on knowledge of how
      the device works.

      - For (e.g.) keyboard interrupt and network packet timings, this
        is zero, because an adversary can cause events to happen with
        timing that leads to predictable samples entering the pool.

      - For alleged hardware RNGs, it's up to the author of the driver
        to do science on the device (and identify reasonable health
        checks for the device based on its physics and engineering).

      Of course, this means that the kernel will now believe it has
      much lower entropy than it did before.  This is more honest, but
      may lead to usability issues particularly on platforms that do
      not have hardware RNGs.  To mitigate this:

      - I recently fixed two long-standing bugs in rndctl(8) whereby

        (a) it would erase, rather than update, the seed file at boot;
        and
        (b) it would always store an entropy estimate of zero in the
            seed file.

        Now it updates the file at boot (I also taught /etc/security
        to update the seed), and records the system's entropy in the
        file, so you don't lose all entropy if the system crashes
        after boot.

        This means it should be enough to, e.g., save a seed file with
        rndctl -S on your laptop and copy it onto an appliance that
        has an otherwise pristine OS image flashed onto it -- even if
        the appliance has no hardware RNG of its own.

      - I implemented a driver for the alleged TRNG in the Allwinner
        Crypto Engine found on sun8i platforms like the Pinebook, and
        would be happy to help anyone else do similarly for other
        hardware RNGs.

      - Entropy `depletion' -- which is widely understood to make no
        sense if you're using /dev/random output for keys in modern
        cryptography -- no longer happens by default, so you only need
        to wait early at boot.  (But `depletion' is supported by a
        handful of very small code paths which can be re-enabled by
        setting sysctl kern.entropy.depletion=1 in order to facilitate
        testing.)

3. Propagate full entropy as soon as possible.

   => WHAT WE DO NOW: Stash samples into buffers that can sit in a
      queue for unbounded durations even if the samples happen early
      at boot when we need entropy ASAP.  (To compensate for this,
      systems like rump pile on as much data as they can to make sure
      it gets through the buffering, when 256 bits should always be
      enough.)

   => WHAT THE PATCH DOES: Notifies all waiters the moment full
      entropy is attained early at boot, while avoiding the overhead
      afterward and imposing rate limits when we've already attained
      full entropy afterward so that interrupt storms don't eat all
      the CPU entering data.

      The `overhead' here arises because samples are buffered only on
      the local CPU, not in a global queue; this requires a cross-call
      to consolidate the per-CPU entropy.  The patch also adds a
      sysctl node kern.entropy.consolidate that you can write to in
      order to trigger consolidation of entropy from all CPUs (at most
      once per second).

4. Simplify the APIs.

   => WHAT WE DO NOW:
      - Various bookkeeping, on which notification decisions are
        based, is mixed up with the cryptography in rndpool(9).
      - Bookkeeping and wakeup notification is split across
        kern_rndq.c and kern_rndpool.c, and hasn't always been
        consistent.
      - A system of threaded callbacks, rndsink(9), serves to notify
        other subsystems when reseeding is appropriate, except it's
        only used in subr_cprng.c.
      - An MI cpu_rng API that is and has only ever been implemented
        on x86, and is used internally for an x86-only cpu_earlyrng
        which works only on x86 machines with RDRAND/RDSEED or VIA C3
        RNG.

   => WHAT THE PATCH DOES:
      - Puts all the cryptography (along with deterministic
        known-answer test vectors) into libkern/entpool.c.  (This could
        also be used for other purposes as a simple reseedable PRNG,
        if we didn't already use NIST Hash_DRBG.)
      - Puts all the bookkeeping and wakeup notifications together
        into kern/kern_entropy.c.
      - Replaces the rndsink(9) API by a single entropy_epoch()
        function to return a generation number that changes whenever
        the system warrants reseeding, and is never 0 so you can
        safely use that to mean unseeded.
      - . Keeps the RDRAND/RDSEED and VIA C3 logic under x86.
          -> If we find more CPUs have anything that works similarly,
             and the logic factors out meaningfully, then we can make
             an MI API for it, but at the moment an MI API isn't
             really helpful.
        . Makes the entropy pool available sooner.
        . Uses the entropy pool instead of cpu_earlyrng.
          -> In principle, this could use the on-disk seed, although
             at the moment that is loaded too late.

5. Reduce overhead of rnd_add_data and cprng_strong.

   => WHAT WE DO NOW:

      - Every rnd_add_data takes a global lock to serialize access to
        the sample buffers.
        => If I recall correctly, this has been observed to be a
           bottleneck in high-volume network interfaces.

      - Every call to cprng_strong takes a global lock to draw from a
        global NIST Hash_DRBG.

   => WHAT THE PATCH DOES:

      - Samples are always entered into a per-CPU pool normally, and
        only affect the global pool immediately early at boot.
        Samples can be entered in one of two ways:

        (a) xor sample data into sponge and truncate if it's too long
        (b) xor sample data into sponge and permute sponge as needed

        Option (a) is done for samples entered from interrupt context,
        to avoid doing any cryptography in interrupt context; then a
        softint is scheduled to permute the sponge so there's room for
        more data.

        Option (b) is done for samples entered from thread context,
        and during option (b), new samples from interrupt context are
        simply ignored -- without blocking interrupts.

        I haven't measured under high network load, but I expect this
        should significantly reduce the cost of rnd_add_data so that
        it should no longer be a bottleneck for just about anything
        doing interrupts.

      - cprng_strong is a per-CPU NIST Hash_DRBG, using a simpler
        version of the logic we already had in dev/rndpseudo.c for a
        per-CPU /dev/urandom cprng_strong.

6. Reduce side channels of HWRNG data and human input sources.

   => WHAT WE DO NOW:

      - Run all RND_TYPE_RNG data through software implementing the
        FIPS 140-1 statistical tests -- monobit, poker, runs.  (These
        tests were all rescinded with strikethrough in FIPS 140-2 in
        2001 and completely eliminated in FIPS 140-3 in 2019.)  The
        software is full of secret-dependent branches and array
        indices and therefore likely has side channels.

      - Wake when any sample changes the entropy estimate.  This can
        happen immediately after a keystroke, for example; it is
        limited only by the sample buffering.

   => WHAT THE PATCH DOES:

      - Provide an API for a constant-time repetition count test, from
        NIST SP800-90B, to be used explicitly in a device driver if it
        is appropriate.  This has high statistical power to detect
        known failure modes of widespread hardware RNGs, such as the
        recent AMD bug which makes RDSEED return all bits 1, and much
        simpler to implement in constant time.

        (XXX I'm not set on the rngreptest API here.  It is plausible
        that there are hardware RNGs with failure modes that are
        detected with high statistical power by the FIPS 140-1
        monobit/poker/runs tests but not by the the NIST SP800-90B
        repetition count test.  It is sort of nice that in the status
        quo, the existing FIPS 140-1 tests report, e.g., that the
        sun8icrypto TRNG is not very uniform when you rnd_add_data --
        but to be meaningful, RNG health tests really have to depend
        on the physics and engineering of the underlying device and
        its realistic failure modes; sun8icrypto's TRNG can have
        entropy even if the distribution is not uniform.)

      - Rate-limits wakeups to happen at most once per second if
        explicitly requested by the operator, and once per minute
        otherwise, after the system has attained full entropy for the
        first time.

7. Improve visibility of operation with sysctl and event counters.

   => WHAT WE DO NOW: Print some warnings if statistical tests fail,
      and sometimes block on reads from /dev/random, unless you enable
      the RND_VERBOSE kernel option in which case the kernel output is
      much more verbose (but it can't be done dynamically).

   => WHAT THE PATCH DOES: Exposes several sysctl nodes and event
      counters:

         kern.entropy.needed - how much entropy is needed, in bits;
           256 at boot, 0 when we have reached full entropy.

         kern.entropy.pending - how much entropy is pending in CPUs,
           if the system is not at full entropy; updated at most once
           per minute when the system has full entropy, so that it
           doesn't serve as a side channel for events

         kern.entropy.epoch - generation number that increments
           whenever the system warrants reseeding, never set to 0
           (userland programs could consult this too to decide when to
           reseed from /dev/urandom)

      The sysctls -- and event counters -- that are affected by timing
      of samples are rate-limited to change once per minute, so they
      are not useful, e.g., to find typing rate.

      This should make it much easier for operators to diagnose why
      their system is not reaching full entropy if it seems to get
      stuck at boot in a predictable state.

      (XXX Exception: The softint counter is not rate-limited, so it
      could serve as a reasonably high-precision side channel for some
      types of keyboard event timings.  This warrants reconsideration,
      and the event counters should be more generally reviewed again
      for their role as potential side channels.)
entropy.patch (text/plain, 267.9 KB) - not displayed