5.11 Erasing Sensitive Data

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.

Function: 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.