/dev/random suffers entropy depletion, even with rng-tools installed

Tagged: 

This topic contains 2 replies, has 2 voices, and was last updated by  Jeffrey 1 year, 1 month ago.

Viewing 3 posts - 1 through 3 (of 3 total)
  • Author
    Posts
  • #54197

    Jeffrey
    Member

    Hi Everyone,

    I’m an open source developer who focuses on security libraries. I’m working on a ci20 dev board that’s using a factory Debian 7 image. The image is fully patched (apt-get update and apt-get dist-upgrade), and rng-tools is installed (apt-get install rng-tools).

    The device is suffering entropy depletion. When I open the /dev/random device in blocking mode, the call to read random bytes never returns. Below is a small C program to demonstrate it, and the call to read has stalled for 4 hours.

    My questions are:

    * Is /dev/random the device which receives non-deterministic entropy?
    * What should we be doing to ensure the health of /dev/random?

    Thanks in advance.

    Jeffrey Walton


    $ cat dev-random.cc
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    
    /* gcc -x c -std=c99 dev-random.cc -o dev-random.exe */
    int main(int argc, char* argv[])
    {
      /* Step 1: drain the generator, don't block */
      int fd = open("/dev/random", O_RDONLY|O_NONBLOCK);
      if (fd == -1) {
        printf("Unable to open /dev/random\n");
        return 1;
      }
    
      unsigned char buffer[1024*10];
      int res = read(fd, buffer, sizeof(buffer));
      close(fd), fd = -1;
    
      /* Step 2: fetch 16 bytes, block */
      fd = open("/dev/random", O_RDONLY);
      if (fd == -1) {
        printf("Unable to open /dev/random\n");
        return 2;
      }
      
      ssize_t req = 16;
      while (req > 0)
      {
        res = read(fd, buffer, 16);
        if (res == EAGAIN)
        {
          /* Yield */
          sleep(1);
          continue;
        }
    
        if (res > 0) req -= res;
      }
      close(fd);
    
      for(unsigned int i=0; i<16; i++)
        printf("%02X", (int)buffer[i]);
      printf("\n");
    
      return 0;
    }

    $ dpkg-query -l rng-tools
    Desired=Unknown/Install/Remove/Purge/Hold
    | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
    |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
    ||/ Name           Version      Architecture Description
    +++-==============-============-============-=================================
    ii  rng-tools      2-unofficial mipsel       Daemon to use a Hardware TRNG
    #54272

    ZubairLK
    Member

    Hi Jeffrey,

    Welcome to the forum.

    Apologies for the delay as it took a while to come to this issue and investigate.

    By default, /dev/random collects entropy from various hardware drivers etc. [1]
    This can be slow. e.g on my desktop xeon. If I do ‘cat /dev/random’ (without rng-tools), it outputs a line and then blocks waiting for more entropy.

    Similarly, on the Ci20, if I do a ‘cat /dev/random’ I do see a few characters and then it blocks.

    With rng-tools, the desktop /dev/random is superfast. But not on the Ci20.

    This can be understood by the fact that basically rng-tools searches for various HW sources of entropy in default places. e.g. /dev/hwrng

    So on my desktop, I do see the /dev/hwrng but not on the Ci20. hwrng is ‘H’ard’W’are ‘R’andom ‘N’umber ‘G’enerator. The relevant kernel doc is [2]. So basically the Ci20 would need a hwrng and then a kernel driver exporting it to a dev node /dev/hwrng. After this, rng-tools would be able to work its magic.

    There seems to be a hidden HWRNG block inside the JZ4780 SoC. If you search for ‘rng’ in the JZ4780 Programmers manual, you find 2 registers. There has been various amounts of community effort on the driver. [3] and [4]. But the driver is not in the shipped kernel. The description of those registers is cryptic unfortunately. It looks like one is to enable the hwrng. And the other is a 32 bit register to read the hwrng. I tried using devmem to play with those but couldn’t come up with any conclusive results to share.

    Hope this helps

    Regards
    ZubairLK

    [1] https://en.wikipedia.org/wiki//dev/random
    [2] https://www.kernel.org/doc/Documentation/hw_random.txt
    [3] https://groups.google.com/forum/#!msg/mips-creator-ci20/A833gH0y9co/U4sSHClyMgAJ
    [4] https://groups.google.com/d/msg/mips-creator-ci20-dev/sFHHUE6-umM/63sdMY4xAgAJ

    1 user thanked author for this post.
    #54273

    Jeffrey
    Member

    Thanks for investigating the issue Zubair.

    > The description of those registers is cryptic unfortunately. It looks like one is to enable the hwrng. And the other is a 32 bit register to read the hwrng. I tried using /dev/mem to play with those but couldn’t come up with any conclusive results to share.

    Yeah, I know what you mean. There could be more information available.

    The following program is what I came up with for a test case. It enables the JZ4780’s RNG and reads 16 random values. It uses a loop to introduce a delay because I could not get usleep or nanosleep to compile as expected.

    The program needs sudo to map in the memory from /dev/mem. (I’m not sure mapping /dev/mem is considered a best practice, but that’s another issue).

    Unfortunately, I don’t know enough about Linux drivers to do anything more with it. I have an old book on them, but it does not cover the new kernels or consider the linux-crypto.

    /* ci20-rng.c  – userland program to read random values from RNG register mapped at 0x100000DC.  */
    /*               written and placed in public domain by Jeffrey Walton. The next step is to wrap */
    /*               it in a Linux driver, or fold it into rng-tools so /dev/random stops suffering  */
    /*               entropy depletion.                                                              */
    
    #include <stdio.h>
    #include <stdint.h>
    #include <unistd.h>
    #include <string.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    #include <sys/ioctl.h>
    #include <linux/random.h>
    
    #define DEV_RANDOM_BYTES 512
    typedef struct {
        int bit_count;               /* number of bits of entropy in data */
        int byte_count;              /* number of bytes of data in array */
        unsigned char buf[DEV_RANDOM_BYTES];
    } entropy_t;
    
    static int print_only;
    
    /* gcc -g2 -O2 -std=c99 ci20-rng.c -o ci20-rng.exe */
    int main(int argc, char* argv[])
    {
        int ret = 1, fd1 = -1, fd2 = -1, fd3 = -1;
        void *map1 = MAP_FAILED, *map2 = MAP_FAILED;
    
        const int PAGE_SIZE = sysconf(_SC_PAGESIZE);
        const int PAGE_MASK = ~(PAGE_SIZE – 1);
    
        #define CTRL_ADDR 0x100000D8
        #define DATA_ADDR 0x100000DC
    
        if(argc >= 2)
        {
            if(0 == strcmp(argv[1], "-p") || 0 == strcmp(argv[1], "/p") || 0 == strcmp(argv[1], "–print"))
                print_only = 1;
        }
    
        fd1 = open("/dev/mem", O_RDWR | O_SYNC);
        if(fd1 == -1)
        {
            fprintf(stderr, "Failed to open /dev/mem for reading and writing (error %d)\n", errno);
            goto cleanup;
        }
    
        fd2 = open("/dev/mem", O_RDONLY | O_SYNC);
        if(fd2 == -1)
        {
            fprintf(stderr, "Failed to open /dev/mem for reading (error %d)\n", errno);
            goto cleanup;
        }
    
        fd3 = open("/dev/random", O_RDWR);
        if(fd3 == -1)
        {
            fprintf(stderr, "Failed to open /dev/random for writing (error %d)\n", errno);
            goto cleanup;
        }
    
        map1 = mmap (NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd1, CTRL_ADDR &amp; PAGE_MASK);
        if(map1 == MAP_FAILED)
        {
            fprintf(stderr, "Failed to map 0x100000D8 for control (error %d)\n", errno);
            goto cleanup;
        }
    
        map2 = mmap (NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, fd2, DATA_ADDR &amp; PAGE_MASK);
        if(map2 == MAP_FAILED)
        {
            fprintf(stderr, "Failed to map 0x100000DC for data (error %d)\n", errno);
            goto cleanup;
        }
    
        const int off1 = CTRL_ADDR % PAGE_SIZE;
        volatile uint32_t* volatile ctrl = (uint32_t*)((uint8_t*)map1+off1);
    
        const int off2 = DATA_ADDR % PAGE_SIZE;
        volatile uint32_t* volatile data = (uint32_t*)((uint8_t*)map2+off2);
    
        entropy_t entropy = { .bit_count = DEV_RANDOM_BYTES*8, .byte_count = DEV_RANDOM_BYTES };
        int count = DEV_RANDOM_BYTES/sizeof(uint32_t), idx = 0;
    
        while(count–)
        {
            /* If the delay from the loop drops too low, then we can watch the random     */
            /*  values being shifted in. If the delay is 0, then the value appears fixed. */
            #define DELAY 5000
    
            const uint32_t old_ctrl = *ctrl;
            *ctrl = old_ctrl | 0x01;
            for(unsigned int i = 0; i < DELAY; i++) {
                volatile uint32_t unused = *ctrl;
            }
    
            /* If not printing to screen or file, then add them to /dev/random */
            if(!print_only)
            {
                /* Copy into struct entropy_t buffer */
                memcpy(entropy.buf+idx, (const void *)data, 4);
                idx += 4;
            }
            else
            {
                /* If print to screen, then print one per line in ASCII.   */
                /* If print to file, then print a stream of binary. Piping */
                /* streams binary, so 'wc -l' does not work as expected.   */
                if(isatty(fileno(stdout)))
                    fprintf(stdout, "0x%08x\n", *data);
                else
                    write(fileno(stdout), (const void *)data, 4);
            }
    
            *ctrl = old_ctrl;
            for(unsigned int i = 0; i < DELAY; i++) {
                volatile uint32_t unused = *ctrl;
            }
        }
    
        /* Add the bytes to /dev/random. A real driver should probably extract the entropy */
        /* at this point using HMAC or HKDF since its directly added to /dev/random.       */
        if(!print_only)
        {
            int rc = ioctl(fd3, RNDADDENTROPY, &amp;entropy);
            if(rc != 0)
            {
                fprintf(stderr, "Failed to add entropy (error %d)\n", errno);
                goto cleanup;
            }
        }
    
        ret = 0;
    
      cleanup:
    
        if(map2 != MAP_FAILED) { munmap(map2, PAGE_SIZE); }
        if(map1 != MAP_FAILED) { munmap(map1, PAGE_SIZE); }
    
        if(fd3 != -1) { close(fd3); }
        if(fd2 != -1) { close(fd2); }
        if(fd1 != -1) { close(fd1); }
    
        return ret;
    }
    
    1 user thanked author for this post.
Viewing 3 posts - 1 through 3 (of 3 total)
The forum ‘Creator Platforms’ is closed to new topics and replies.