A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
free does not erase data. It marks the block as available in the allocator's metadata. The bytes remain in memory until something else overwrites them. If an attacker can read memory after free, they can recover passwords, encryption keys, session tokens, and database credentials that the program believed were destroyed. This is heap inspection. It is the reason secure software explicitly zeroes sensitive buffers before freeing them.
#include <stdio.h>#include <stdlib.h>#include <string.h>int main(void) {char *secret = malloc(32);strcpy(secret, "API_KEY=sk-12345-SECRET");printf("Before free: %s\n", secret);printf("Address: %p\n", (void *)secret);free(secret);char *probe = malloc(32);printf("\nNew allocation at: %p\n", (void *)probe);printf("Data at that address: %s\n", probe);free(probe);return 0;}
The allocator reused the same address. The old data was never overwritten. The new allocation sees the previous tenant's contents. In a real attack, the adversary does not need to be in the same process. Cold boot attacks read RAM after power-off. /proc/pid/mem reads another process's memory on misconfigured systems. Core dumps contain the entire heap. The data persists until explicitly destroyed.
#include <stdio.h>#include <stdlib.h>#include <string.h>void secure_free(void *ptr, size_t len) {if (ptr == NULL) return;volatile unsigned char *p = ptr;while (len--) {*p++ = 0;}free(ptr);}int main(void) {char *secret = malloc(32);strcpy(secret, "API_KEY=sk-12345-SECRET");printf("Before: %s\n", secret);secure_free(secret, 32);char *probe = malloc(32);printf("After secure_free, new alloc sees: ");int all_zero = 1;for (int i = 0; i < 32; i++) {if (probe[i] != 0) all_zero = 0;}printf("%s\n", all_zero ? "all zeros" : "residual data!");free(probe);return 0;}
The volatile keyword prevents the compiler from optimizing away the zeroing loop. Without volatile, an optimizing compiler sees that the memory is about to be freed and deletes the writes as dead code. This is a real problem. Compilers are allowed to do this. The volatile qualifier forces the writes to happen. On POSIX systems, explicit_bzero provides a guaranteed-non-optimized zeroing function.
Never use memset(ptr, 0, len); free(ptr); for sensitive data without volatile or explicit_bzero. The compiler may eliminate the memset as a dead store. The data survives in memory. This has been documented in OpenSSL, LibreSSL, and multiple kernel codebases.
Heap inspection is the reason cryptographic libraries have secure wipe functions. It is the reason you zero password buffers after authentication. It is the reason memory forensics can extract keys from a running process. In C, memory safety is your job. The language will not help you. Next: pointer notation, where array syntax and pointer syntax become interchangeable.
Finish the lesson once you have worked through the material. This awards ★ 40 XP.