flawopen.com/Incidents/Heartbleed
You shout to a colleague across the office: "Say the word cat back to me — it's three letters long." They shout back "cat". Now you shout: "Say the word cat back to me — it's sixty thousand letters long." Instead of noticing that "cat" is only three letters, your colleague reads out "cat" followed by whatever else happens to be written on the desk around them — other people's passwords, private notes, everything.
In April 2014, researchers at Google and Codenomicon independently found a flaw in OpenSSL's implementation of the TLS heartbeat extension. CVE-2014-0160 allowed any client to read up to 64 kilobytes of the server process's memory per request, without authentication, repeatedly, and — critically — without leaving any trace in the server's logs.
OpenSSL underpinned a very large share of HTTPS on the public internet at the time, so the exposure was close to universal. What leaked was whatever happened to be in the process heap: session cookies, usernames and passwords in flight, decrypted request bodies, and in some cases the server's TLS private key itself.
The flaw had been present in OpenSSL since version 1.0.1 (March 2012) — roughly two years of exposure before anyone noticed.
A heartbeat request carries a payload and a field stating that payload's length. The server was required to echo the payload back. OpenSSL allocated a response buffer using the attacker-supplied length and copied that many bytes out of the request — without ever checking that the request actually contained that much data.
The length field and the data it describes both came from the attacker, but only the length was believed. When a request claimed 65535 bytes while carrying 3, the copy read 3 bytes of payload and then 65532 bytes of whatever sat next to it in the heap — and dutifully sent all of it back.
/* payload length comes from the attacker's request */ n2s(p, payload); pl = p; buffer = OPENSSL_malloc(1 + 2 + payload + padding); bp = buffer; /* copies `payload` bytes regardless of how much data actually arrived */ memcpy(bp, pl, payload);
/* reject heartbeats whose claimed length exceeds the record we actually received */ if (1 + 2 + 16 > s->s3->rrec.length) return 0; /* silently discard */ n2s(p, payload); if (1 + 2 + payload + 16 > s->s3->rrec.length) return 0; /* silently discard */ pl = p;
The fix is two comparisons. That ratio — a two-line check against a two-year, internet-wide exposure — is the reason Heartbleed became the standard illustration of how little code stands between working software and catastrophic failure.
Patching OpenSSL was the easy part. The hard part followed from a property of the bug: because it was silent and untraceable, no operator could prove they had not been exploited. With no logs to examine, the only defensible assumption was that every secret resident in the process memory during the exposure window had leaked.
That meant regenerating private keys, reissuing and revoking certificates at a scale the certificate authority ecosystem had never handled at once, invalidating every session, and prompting global password resets. Deployments without forward secrecy were worse off again: an attacker who had recorded encrypted traffic earlier could decrypt it retroactively once they held the key.
memcpy is a valid program. This is the argument for memory-safe languages in parsing code specifically, where untrusted input meets manual buffer handling.An overflow writes past a boundary, corrupting adjacent memory and often enabling code execution. An over-read only reads past it. Heartbleed never corrupted anything — it simply handed the attacker memory contents, which in this case was more than sufficient.
Yes. It was not guaranteed on any given request, but private key material can reside in the same heap. Repeated requests sampling different memory made recovery practical, and it was demonstrated publicly.