How to Read getrandom and dev urandom in C

Obtaining cryptographically secure random bytes in the C programming language requires querying operating system entropy sources rather than relying on standard pseudo-random functions like rand(). On modern Unix-like and Linux systems, developers typically achieve this either by invoking the dedicated getrandom() system call or by directly opening and reading the /dev/urandom special device file. This guide explains how to implement both approaches directly in C, detailing their syntax, error handling, and practical differences.

Method 1: Using the getrandom() System Call

The getrandom() system call (available on Linux kernel 3.17 and newer via glibc 2.25+) is the preferred method for obtaining entropy. Unlike opening device files, it does not consume a file descriptor, cannot be blocked by chroot environments, and naturally prevents reads if the kernel's entropy pool has not yet been initialized.

Implementation

To use getrandom(), include <sys/random.h>. The function returns the number of bytes read or -1 if an error occurs. Like standard I/O operations, it can be interrupted by signals (EINTR), so reads should be wrapped in a loop until the target buffer is filled.

#define _GNU_SOURCE
#include <sys/random.h>
#include <stddef.h>
#include <errno.h>

int secure_random_getrandom(void *buffer, size_t length) {
    size_t total_read = 0;
    char *buf = (char *)buffer;

    while (total_read < length) {
        ssize_t result = getrandom(buf + total_read, length - total_read, 0);

        if (result < 0) {
            if (errno == EINTR) {
                continue; /* Interrupted by system signal, retry */
            }
            return -1; /* Failed to read entropy */
        }

        total_read += result;
    }

    return 0; /* Success */
}

The third argument specifies flags. Passing 0 defaults to standard blocking behavior, which waits only if the CSPRNG has not yet initialized during early boot. Passing GRND_NONBLOCK will return an error (EAGAIN) instead of waiting.


Method 2: Reading Directly from /dev/urandom

Reading /dev/urandom is the traditional, widely portable POSIX approach. It treats the kernel's CSPRNG as a file stream.

Implementation

The process involves opening the device file with open(), reading the requested byte length with read(), and closing the descriptor with close(). Using low-level POSIX file descriptors is preferred over FILE* streams (fopen/fread) to avoid unnecessary userspace buffering.

#include <fcntl.h>
#include <unistd.h>
#include <stddef.h>
#include <errno.h>

int secure_random_urandom(void *buffer, size_t length) {
    int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC);
    if (fd < 0) {
        return -1;
    }

    size_t total_read = 0;
    char *buf = (char *)buffer;

    while (total_read < length) {
        ssize_t result = read(fd, buf + total_read, length - total_read);

        if (result < 0) {
            if (errno == EINTR) {
                continue;
            }
            close(fd);
            return -1;
        }

        total_read += result;
    }

    close(fd);
    return 0;
}

The O_CLOEXEC flag prevents the file descriptor from leaking into child processes created via exec().


Key Differences and Best Practices

  1. File Descriptors: getrandom() does not allocate a file descriptor, eliminating failures caused by process file descriptor limits (EMFILE or ENFILE).
  2. Security During Boot: /dev/urandom can potentially return bytes before the kernel pool is fully seeded on early system boot. getrandom() without flags automatically blocks until the kernel CSPRNG is safe.
  3. Sandboxing: In heavily sandboxed systems, containers, or restricted chroot environments, /dev/urandom might not exist inside the filesystem tree. getrandom() functions independently of the filesystem layout.
  4. Portability: For Linux-specific modern applications, use getrandom(). For systems like older BSDs, macOS, or legacy Linux targets, fallback logic utilizing /dev/urandom is standard practice.