openlang

memset before free is a dead store

memset(key, 0, len) followed by free(key) is not a wipe. Nothing reads the buffer again, so the store is dead, and the standard lets the compiler drop it. 5.1.2.3 says so in one sentence. "An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced".

void drop_key(unsigned char *key, size_t len) {
    memset(key, 0, len);
    free(key);
}

clang -O2 for x86-64 Linux turns that into a lone jmp free@PLT. The memset is gone. Read the source and you see a wipe. The binary never had one.

C11 had the right words in Annex K. memset_s "shall be evaluated strictly according to the rules of the abstract machine". Annex K is optional and glibc never shipped it. C23 puts memset_explicit in <string.h>. Its normative text says the purpose is "to make sensitive information stored in the object inaccessible". The part about the store never being elided lives in a footnote that calls it "the intention", and footnotes aren't normative.

Until your libc has it, explicit_bzero on glibc and the BSDs, SecureZeroMemory on Windows. None of them reach copies the compiler left in registers or on the stack.

The conversation continues 1

Follow the reasoning
  1. Permalink

    Lio's list stops at the buffer being freed, and one more copy is easy to miss. If the key buffer was ever grown with realloc, the old object was deallocated with its contents as they were. 7.22.3.5 requires the new object to hold the same contents and says nothing about the old one beyond deallocating it. memset_explicit on the final buffer reaches the final buffer only.

    So growth needs its own rule for keys. Allocate key storage at its final size and never grow it. If it has to grow, do it by hand. Allocate the new block, copy, wipe the old block with memset_explicit, then free it.

Other sides of the same question

  1. A typed merge helper still walks into `__proto__`

    The settings import uses a small recursive merge. The parameter types look careful, but they only describe what the program e…

  2. Read the axioms of a dependency's theorem before you cite it

    A theorem that compiles is not yet a theorem with a proof. sorry closes any goal, and Lean accepts the file with a warning. I…

    Replies 1lean-4
  3. Testing a Go channel handoff without sleep

    Most handoff tests I run into end with a sleep and a hopeful assertion. This one can do without. A request carries an identif…

    Replies 23go