glibc 2.25 makes randomness and memory erasure explicit
getentropy and getrandom expose kernel randomness through libc, while explicit_bzero preserves a memory clear that an optimizer could otherwise remove.
Clearing a key after its last use can look unnecessary to a C optimizer. The program has finished reading it, and correct execution should never inspect the object after its lifetime ends. Security code has a different concern, because a later bug might expose the bytes that remain. glibc 2.25 adds explicit_bzero to express that concern in a library operation whose writes the compiler must retain. The February 5 release announcement also introduces getentropy and getrandom, with declarations in sys/random.h.
These additions address two parts of handling secrets. Randomness acquisition supplies unpredictable input, while erasure reduces the time that a particular memory region retains sensitive output. Neither operation implements a complete cryptographic system. Their value is a documented contract at points where application code otherwise has to make assumptions about the operating system or compiler.
I like C because its rules and interfaces can be examined at this level. That does not make the generated program a literal transcription of the source. An optimizer can remove a carefully written clear when the language gives it no reason to preserve the write. A function with an explicit erasure guarantee gives that discussion a precise subject.
A clear that survives dead-store elimination
The 2.25 manual on erasing sensitive data describes the problem with memset, bzero, and a hand-written clearing loop. When the compiler proves that no correct execution can observe the cleared storage, it may delete the stores. Erasure after the final use of a key is particularly exposed to this reasoning, since the absence of subsequent reads is usually intentional.
explicit_bzero takes an address and a byte count. It writes zeroes to that region even when the compiler considers the writes unnecessary. The guarantee concerns removal of those stores. The manual explicitly allows other optimizations, including replacing the call with inline writes, so retaining a function call instruction is not the criterion for correct implementation.
A small wrapper can make the intended lifetime visible. This example assumes that the declared operation consumes the key synchronously and retains no pointer or copy after returning.
#define _DEFAULT_SOURCE 1
#include <stddef.h>
#include <string.h>
extern int use_key(const unsigned char *, size_t);
int use_then_clear(unsigned char *key, size_t length)
{
int result = use_key(key, length);
explicit_bzero(key, length);
return result;
}
Both return values from use_key lead through the clear in this wrapper. That is an application control-flow property, separate from the library guarantee that the clear survives optimization. The function still requires a valid writable region, and erasing the caller-provided region says nothing about copies made inside use_key.
The manual makes this limitation concrete. Registers and temporary stack storage may contain additional copies invisible to the source-level interface. Taking the address of an object for erasure can even cause a previously register-resident value to be copied into memory before that copy is cleared. explicit_bzero cannot identify every representation of a secret throughout a computation.
That distinction is useful when deciding what a security claim means. The supported claim is that a specified memory region receives zeroes despite dead-store elimination. A claim that the secret disappears from the entire computer would require substantially more control over the computation. Making a variable volatile does not establish that stronger property either, and the manual explains that it can increase memory copies.
A bounded request for unpredictable bytes
The versioned randomness documentation recommends a deterministic random bit generator for application code. Such a generator can use getentropy to obtain its seed. This separates the relatively small request for unpredictable seed material from the generator that supplies an application with further random output.
getentropy accepts a buffer and a length of at most 256 bytes. Its success return is zero, with failure reported as minus one and an error number. This differs from an interface that returns the number of bytes transferred. The bounded request and success convention make it suitable for a small fixed-size seed buffer.
#define _DEFAULT_SOURCE 1
#include <sys/random.h>
int obtain_seed(unsigned char seed[32])
{
return getentropy(seed, 32);
}
The caller may use the seed only when this function returns zero. A failure must remain a failure, since substituting predictable bytes would defeat the reason for acquiring entropy. The example deliberately leaves construction of a random bit generator to a cryptographic implementation, since the library call only obtains its input.
Boot-time behavior belongs to the contract. getentropy can block until the kernel entropy pool has initialized, and the manual says it continues waiting even when a signal arrives. It is not a cancellation point. It can fail with ENOSYS when the kernel lacks the required system call, so adding the libc declaration does not remove dependence on kernel support.
Control over blocking changes the caller contract
getrandom exposes more policy to low-level callers. Its return value is the number of bytes written, which may be smaller than the requested length. Code that treats every nonnegative result as a complete buffer would therefore be incorrect. Unlike getentropy, this function is a cancellation point. Thread cancellation therefore needs to be considered by code that owns resources across the call.
The flags specify behavior that the simpler interface keeps fixed. GRND_NONBLOCK requests an immediate return when no data is available, with EAGAIN describing that condition. GRND_RANDOM selects the random pool that may block even after initialization. With neither flag, obtaining bytes can still block during early boot while the usual pool initializes.
Those details distinguish absence of available randomness from an invalid request or an interrupted call. The manual lists EINVAL for invalid flags and EINTR for interruption. A general wrapper around getrandom has to account for partial results as well as those errors. For the small seeding request above, getentropy avoids requiring the application to reconstruct the complete-buffer behavior.
I prefer that division of responsibility. Ordinary seeding code gets a constrained operation with a simple success condition, while a lower-level component can choose how it handles blocking. The same release gives erasure an equally specific scope, preserving the write to the named region without promising control over every compiler-generated copy. These contracts let a program state what it needs at each boundary and leave the cryptographic algorithm to code designed for that purpose.