[RFC] random, initialize pool at compile time

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

 



I will submit this as a patch as soon as I work out how to handle it
in the kernel makefiles. That is not likely to be very soon since
those files are a bit complex, I'm new to them, & I have other things
on my plate just now.

In the meanwhile, it seems worth asking for comments and/or for advice
on the makefiles.

/*
 * Program to select random numbers for initialising things
 * in the random(4) driver.
 *
 * Sandy Harris sandyinchina@xxxxxxxxx
 *
 * In general, this falls well short of the ideal solution,
 * which would give every installation (rather than every
 * compiled kernel) a different seed.
 * For that, see John Denker's suggestions at:
 * http://www.av8n.com/computer/htm/secure-random.htm#sec-boot-image
 *
 * However, if every installation compiles a new kernel
 * then this becomes equivalent to Denker's suggestion,
 * or perhaps even better because it uses more random
 * material.
 *
 * Inserting random data at compile time can do no harm and
 * will make some attacks considerably harder. It is not an
 * ideal solution, and not absolutetely necessary, but cheap
 * and probably the best we can do during the build (rather
 * than install) process.
 *
 * This is not strictly necessary if any of these apply:
 *
 *      you have a trustworthy hardware RNG
 *      your CPU has a trustworthy random number instruction
 *      you have secure stored random data
 *
 * In those cases, the device can easily be initialised well;
 * the only difficulty is to ensure that is done early enough.
 * On the other hand, this is easy to do, and even if one or
 * more other methods are used as well this adds a layer for
 * defense in depth.
 *
 * The program takes one optional single-digit command-line option,
 * how many 512-bit contexts to initialise in addition to the
 * input pool
 *      -0, just the pool
 *      -1, initialise a context for Chacha as well
 *          (useful with current driver)
 *      -2, Chacha context plus a 512-bit hash context
 *          (possibe in future driver)
 * If no option is given, it defaults to just the pool.
 *
 * The program:
 *
 *      gets its data from /dev/urandom
 *      produces different output every time it runs
 *      limits the range of Hamming weights
 *      each byte has at least one bit 1, at least one 0
 *
 *      output is formatted as an array declaration
 *      suitable for inclusion by random.c
 *      declared as 64-bit unsigned to align it
 *      writes to stdout, expecting makefile to redirect
 *
 * makefile should also delete the output file after it is
 * used in compilation of random.c, ideally using a secure
 * deletion tool such as shred(1) srm(1) or wipe(1).
 * This forces the file to be rebuilt and a new version
 * used in every compile. It also prevents an enemy just
 * reading an output file in the build directory and
 * getting the data that is used in the current kernel.
 *
 * This is not full protection since they might look in
 * the kernel image or even in the running kernel. But
 * if an enemy has enough privileges to look in those
 * places, then the random driver is nowhere near the
 * most serious security worry.
 *
 * This is certainly done early enough and the data is random
 * enough, but it is not necessarily secret enough.
 *
 * For any machine that compiles its own kernel, this alone might
 * be enough to ensure secure initialisation since only an enemy
 * who already has root could discover this data. Of course even
 * there it should not be used alone, only as one layer of a
 * defense in depth.
 *
 * If a kernel is compiled once then installed on many machines
 * (for example used for an embedded device or in a Linux distro)
 * this is likely of very little value. It complicates an attack
 * somewhat, and might even be too much for some attackers, but
 * it clearly will not stop a serious attacker who has access
 * to another copy of the distro or the device. It may not even
 * slow them down much.
 *
 * In many cases there is a choice. An administrator who
 * distributes software to every machine in an organisation
 * could either compile once and give them all the same kernel
 * or produce a different kernel for each machine; the latter
 * course is not remarkably difficult or expensive.
 *
 * A Linux distro might suggest that users compile a new
 * kernel, or even require it as part of the install
 * process. This might be useful quite apart from the
 * random device, building a kernel optimised for the
 * customer's hardware.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <ctype.h>
#include <string.h>
#include <linux/random.h>

typedef uint32_t u32 ;
typedef uint64_t u64 ;

/*
 * Configuration information
 */
#define INPUT_POOL_BITS     4096
#define INPUT_POOL_WORDS    (INPUT_POOL_BITS >> 5)
/*
    for Salsa, SHA3 or another hash
*/
#define CONTEXT_BITS        512
#define CONTEXT_WORDS       (CONTEXT_BITS >> 5)
/*
    large enough for any expected usage
    may not all be used
*/
#define MAX_WORDS           (INPUT_POOL_WORDS + (2*CONTEXT_WORDS))
#define MAX_64              (MAX_WORDS >> 1)

u64 data64[MAX_64] ;
u32 *data32 ;

int accept(u32) ;
int hamming(u32);
void usage(void) ;

int urandom ;

int main(int argc, char **argv)
{
        u32 i, x, *p, temp, last, n_contexts, total_words,
total_bytes, total_64 ;
        u64 *p64 ;
        char *ptr ;

    if ((urandom = open("/dev/urandom", O_RDONLY)) == -1)  {
        fprintf(stderr, "gen_random_init: no /dev/urandom, cannot continue\n") ;
        exit(1) ;
    }
    switch(argc)   {
        case 1:
                /* just input pool */
                n_contexts = 0 ;
                break ;
        case 2:
                /*
                 * also some 512-bit contexts
                 * one for chacha
                 * maybe another for 512-bit hash
                 */
                ptr = argv[1] ;
                if (*ptr != '-')
                        usage() ;
                else    {
                        ptr++ ;
                        n_contexts = atoi( ptr ) ;
                }
                break ;
        default:
                usage() ;
        break ;
        }
        if ((n_contexts > 2) || (n_contexts < 0))   {
                fprintf( stderr, "This version of gen_random_init
supports only 0, 1 or 2 contexts\n" ) ;
                usage() ;
        }
        total_words = INPUT_POOL_WORDS + (n_contexts*CONTEXT_WORDS) ;
        total_bytes = total_words << 2 ;
        total_64 = total_words >> 1 ;
        data32 = (u32 *) data64 ;
    /*
     * Initialise the pools with random data
     */
    x = read( urandom, (char *) data64, total_bytes ) ;
    if (x != total_bytes)    {
        fprintf(stderr,"gen_random_init: big read() failed, cannot
continue\n") ;
                exit(1) ;
        }
    /*
     * Replace any array entries that fail criteria
     *
     * In theory, the while loop here could run for some
     * ridiculously long time, or even go infinite
     * In practice, this is astronomically unlikely
     * given any sensible definition of accept() and
     * input that is anywhere near random
    */
        for (i = 0, p = data32 ; i < total_words ; i++, p++)    {
                while (!accept(*p))      {
            x = read( urandom, (char *) &temp, 4) ;
                        if (x != 4)    {
                                fprintf(stderr,"gen_random_init: small
read() failed, cannot continue\n") ;
                                exit(1) ;
                        }
                        *p ^= temp ;
                }
        }
        /*
         * write a file suitable for inclusion
         * declare array as u64 so it is 64-bit aligned
         */
        last = total_64 - 1 ;
        printf("// File generated by gen_random_init.c\n") ;
        printf("// 4096-bit input pool plus %d 512-bit contexts\n\n",
n_contexts ) ;
        printf("#define TOTAL_WORDS %d // size in 32-bit words\n\n",
total_words ) ;
        printf( "static u64 generated_pools[] = {\n" ) ;
        for (i = 0, p64 = data64 ; i < total_64 ; i++, p64++)    {
                printf("0x%016lx", *p64) ;
                if (i == last)
                        printf("\n} ;\n") ;
                else    {
                        putchar(',') ;
                        switch( i % 4 ) {
                        case 3:
                                putchar('\n') ;
                                break ;
                        default:
                                putchar(' ') ;
                        break ;
                        }
                }
        }
}

void usage()
{
    fprintf(stderr, "usage: gen_random_init [-0|-1|-2]\n") ;
    exit(1) ;
}

/*
 * These tests are not strictly necessary
 *
 * We could just use the /dev/urandom output & take what comes
 * Arguably, that would be the best course;
 * "If it ain't broke, don't fix it."
 *
 * Applying any bias here makes output somewhat less random.
 *
 * However, a Hamming weight near 16 gives a better chance
 * of changing the output significantly when the number is
 * used in an addition or xor operation. This is a highly
 * desirable effect.
 *
 * Compromise: apply some bias, but not a very strong one
 */
#define MIN  6
#define MAX (32-MIN)

int accept( u32 u )
{
        int h, i ;
        char *p ;

        /* reject low or high Hamming weights */
        h = hamming(u) ;
        if (( h < MIN ) || ( h > MAX ))
                return(0) ;

        /* at least one 1 and at least one 0 in each byte */
        for (i = 0, p = (char *) &u ; i < 4 ; i++, p++)        {
                switch (*p)      {
                        case '\0':
                        case '\255':
                                return(0) ;
                        default:
                                break ;
                }
        }
        return(1) ;
}

/*
 * Hamming weight is the number of bits that are 1
 * Kernighan's method
 * http://graphics.stanford.edu/~seander/bithacks.html
 */
int hamming( u32 x )
{
        int h ;
        for (h = 0 ; x ; h++)
                x &= (x-1) ; /* clear the least significant bit set */
        return(h) ;
}



[Index of Archives]     [Kernel]     [Gnu Classpath]     [Gnu Crypto]     [DM Crypt]     [Netfilter]     [Bugtraq]

  Powered by Linux