Linux getrandom(): Fill Cryptographic Buffers Without Pathnames
Use Linux getrandom() safely by handling initialization, short reads, EINTR, nonblocking startup, libc versions, and the limits of portability.
Linux getrandom(2) fills a caller-provided buffer from the kernel random source without opening /dev/urandom, retaining a file descriptor, or resolving a device pathname. That makes it useful in early service startup, restricted filesystem namespaces, and code that needs random bytes for a cryptographic protocol. The interface is small, but correct use still depends on checking the returned byte count, choosing flags deliberately, and failing closed if the requested buffer was not completely filled.
This is a Linux-specific system call, not a POSIX interface. Linux added it in kernel 3.17, and glibc added its wrapper in 2.25. A program that supports older kernels or C libraries needs an explicit compatibility plan; it should not silently fall back to rand(), a timestamp, a PID, or a hand-built generator.
Default source and startup readiness
With flags set to zero, getrandom() reads from the same kernel source used by /dev/urandom. If that source has not yet been initialized, the call blocks until it is ready. This behavior avoids returning pre-initialization bytes to a cryptographic caller. In a boot-sensitive daemon, that can delay startup if the machine has not yet initialized its random source. Decide whether that delay is acceptable as part of the service’s readiness contract; do not remove it by asking for explicitly insecure output.
Once the source is initialized, requests of at most 256 bytes are guaranteed by the Linux man-pages contract to return all requested bytes without interruption by a signal. Larger requests can return fewer bytes or fail with EINTR. A general buffer-filling helper should therefore loop until it has collected the full request, even if its intended use is a short token. A successful return is a byte count, not a Boolean success value.
A complete-buffer helper
This helper uses the default source, retries only interrupted calls, and reports any other failure to its caller. If a positive short result arrives, it advances over the bytes already written and asks only for the remaining portion. The caller must discard the buffer whenever the helper returns -1.
#define _GNU_SOURCE
#include <errno.h>
#include <stddef.h>
#include <sys/random.h>
#include <sys/types.h>
int linux_random_fill(void *buffer, size_t length)
{
unsigned char *cursor = buffer;
while (length != 0) {
ssize_t received = getrandom(cursor, length, 0);
if (received > 0) {
cursor += (size_t)received;
length -= (size_t)received;
continue;
}
if (received == -1 && errno == EINTR)
continue;
if (received == 0)
errno = EIO; /* Avoid an infinite loop on an unexpected zero. */
return -1;
}
return 0;
}
The loop handles positive partial results and does not expose a partly initialized output as complete. The kernel API returns an error for an invalid user buffer; applications may also validate buffer != NULL when length != 0 to reject programmer errors before the call. Keep retry behavior within the application’s cancellation policy: a caller that must respond promptly to shutdown signals may choose to propagate EINTR instead of retrying indefinitely.
Use the helper with a protocol-defined size and an explicit failure path. Do not print secret bytes to a terminal or diagnostic log:
unsigned char token[32];
if (linux_random_fill(token, sizeof token) == -1) {
/* Abort token creation; do not return token or substitute weak bytes. */
return -1;
}
/* Encode token using the format required by the consuming protocol. */
Thirty-two bytes is only an example buffer size, not a universal key-length recommendation. A cryptographic library should normally generate keys through its own high-level API, which can validate algorithm-specific size and encoding requirements. getrandom() supplies bytes; it does not choose an algorithm, create a key schedule, define token expiry, or make an application protocol secure by itself.
Nonblocking behavior is a readiness decision
GRND_NONBLOCK changes the behavior before the default source is initialized: instead of waiting, the call fails with EAGAIN. This can be appropriate when a service must report “not ready” and let a supervisor retry later. It is not a reason to switch to a weaker source. Make the failure observable, keep secret-dependent endpoints unavailable until initialization succeeds, and test the retry path during early boot or in a constrained VM.
GRND_RANDOM selects the random source used by /dev/random. If some bytes are available but fewer than requested, the call can return a short positive count; if none are available, it blocks unless GRND_NONBLOCK is set, in which case it returns EAGAIN. A blocking call can also be interrupted by a signal. This flag is not required merely because output is used for cryptography. For common security-sensitive buffers, the zero-flags default is the simpler contract; if an application has a specialized reason to request another source, document and test that requirement instead of setting flags by habit.
Errors, compatibility, and testing
The wrapper reports failures through -1 and errno. EINTR means the call was interrupted; EAGAIN is expected when nonblocking mode was requested and data is not yet available; ENOSYS can indicate a kernel without the system call. A compatibility layer should select a supported system cryptographic API before deployment and make failure explicit. It should never turn an availability error into predictable output. When portability beyond Linux matters, use a vetted cryptographic library abstraction or a platform API selected at build time rather than compiling Linux headers into a cross-platform module.
Test the contract that belongs to the application: the helper fills the exact requested count, short reads advance rather than overwrite, EINTR follows the chosen retry policy, nonblocking EAGAIN keeps the service unready, and all other errors prevent use of the buffer. Dependency injection around the syscall wrapper is often safer than trying to force a production kernel into each error state. Statistical tests on a small sample cannot prove that a random source is cryptographically unpredictable.
The API’s lack of a file descriptor is also an operational advantage. Unlike a pathname-based read, it still works when /dev is hidden by a chroot or when a library has closed a device descriptor during daemon startup. That does not bypass access controls or make process compromise harmless: a caller that can read process memory may also read generated secrets. Minimize secret lifetime, restrict logs and crash dumps, and use a key-management service when keys must persist or be shared.
getrandom() is best treated as a narrow kernel boundary with a clear contract: wait for initialization by default, check and complete the requested length, propagate errors, and keep the resulting bytes inside the protocol or cryptographic API that knows what they mean.
Related:
- Linux Capabilities: Fine-Grained Privileges Beyond root and setuid
- Linux memfd_secret: Isolating Sensitive Userspace Memory
Sources: