Sensitive data, such as cryptographic keys, should be erased from memory after use, to reduce the risk that a bug will expose it to the outside world. However, compiler optimizations may determine that an erasure operation is “unnecessary,” and remove it from the generated code, because no correct program could access the variable or heap object containing the sensitive data after it’s deallocated. Since erasure is a precaution against bugs, this optimization is inappropriate.
The function explicit_bzero
erases a block of memory, and
guarantees that the compiler will not remove the erasure as
“unnecessary.”
#include <string.h> extern void encrypt (const char *key, const char *in, char *out, size_t n); extern void genkey (const char *phrase, char *key); void encrypt_with_phrase (const char *phrase, const char *in, char *out, size_t n) { char key[16]; genkey (phrase, key); encrypt (key, in, out, n); explicit_bzero (key, 16); }
In this example, if memset
, bzero
, or a hand-written
loop had been used, the compiler might remove them as “unnecessary.”
Warning: explicit_bzero
does not guarantee that
sensitive data is completely erased from the computer’s memory.
There may be copies in temporary storage areas, such as registers and
“scratch” stack space; since these are invisible to the source code,
a library function cannot erase them.
Also, explicit_bzero
only operates on RAM. If a sensitive data
object never needs to have its address taken other than to call
explicit_bzero
, it might be stored entirely in CPU registers
until the call to explicit_bzero
. Then it will be
copied into RAM, the copy will be erased, and the original will remain
intact. Data in RAM is more likely to be exposed by a bug than data
in registers, so this creates a brief window where the data is at
greater risk of exposure than it would have been if the program didn’t
try to erase it at all.
Declaring sensitive variables as volatile
will make both the
above problems worse; a volatile
variable will be stored
in memory for its entire lifetime, and the compiler will make
more copies of it than it would otherwise have. Attempting to
erase a normal variable “by hand” through a
volatile
-qualified pointer doesn’t work at all—because the
variable itself is not volatile
, some compilers will ignore the
qualification on the pointer and remove the erasure anyway.
Having said all that, in most situations, using explicit_bzero
is better than not using it. At present, the only way to do a more
thorough job is to write the entire sensitive operation in assembly
language. We anticipate that future compilers will recognize calls to
explicit_bzero
and take appropriate steps to erase all the
copies of the affected data, wherever they may be.
void
explicit_bzero (void *block, size_t len)
¶Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
explicit_bzero
writes zero into len bytes of memory
beginning at block, just as bzero
would. The zeroes are
always written, even if the compiler could determine that this is
“unnecessary” because no correct program could read them back.
Note: The only optimization that explicit_bzero
disables is removal of “unnecessary” writes to memory. The compiler
can perform all the other optimizations that it could for a call to
memset
. For instance, it may replace the function call with
inline memory writes, and it may assume that block cannot be a
null pointer.
Portability Note: This function first appeared in OpenBSD 5.5
and has not been standardized. Other systems may provide the same
functionality under a different name, such as explicit_memset
,
memset_s
, or SecureZeroMemory
.
The GNU C Library declares this function in string.h, but on other systems it may be in strings.h instead.