Code anatomy · Code anatomy
Heartbleed: the length that was never proved
Four real lines from OpenSSL 1.0.1f reveal why proving destination capacity cannot prove that the source bytes exist.
Kinwilder research desk · · AI-assisted analysis
Code specimen
d1_both.c
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
bp += payload;- Line 1: A response marker is not a bounds check — The assignment identifies a heartbeat response. It establishes neither the received input length nor permission to read beyond it.
- Line 3: The source-read bound is the obligation — memcpy reads payload bytes from pl. The missing proof concerns whether those bytes fit inside the received record, independently of destination capacity.
What is established
The famous Heartbleed failure was not a clever encryption break. OpenSSL's April 7, 2014 advisory identifies a missing heartbeat bounds check, lists affected historical releases, and describes disclosure of up to 64 KB of memory. Those were the advisory's historical findings and remediation instructions, not a recommendation to install an obsolete release today.
The fragment below is genuine source from OpenSSL 1.0.1f's DTLS heartbeat response handler, pinned to an immutable revision. It is historically vulnerable implementation code, not attacker malware or a working exploit. The library retains the complete source fingerprint, line numbers and original OpenSSL license notice. Inspect the exact source.
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
bp += payload;
Our analysis
Read the three values separately. The response marker identifies a heartbeat response. The length is encoded into the output. The copy then takes that many bytes from the pointer into the received data. Finally, the destination pointer advances. These operations describe what the program does; they do not establish that every byte being read belongs to the received message.
The basic principle is a claimed length is not an established extent. Allocating a sufficiently large destination proves something about where a write may go. It does not prove that the source contains that many readable bytes. Source validity and destination capacity are separate obligations, even when the same length appears in both calculations.
Imagine an envelope that claims to contain twelve pages. Preparing a twelve-page return folder does not establish that the envelope really contains twelve pages. You must count what was received before deciding what can be copied. The analogy omits the memory model, but preserves the missing relation: the requested count must fit inside an independently measured input extent.
An important precondition is the provenance of the length. A constant chosen entirely by trusted code is a different problem from an externally influenced count. Another is the location of the pointer after protocol headers have been consumed. The remaining length must be measured from that position, not from a convenient earlier start. The quoted fragment alone cannot prove the absence of every surrounding check; the primary advisory establishes the defect, while the pinned full function supplies its implementation context.
Arithmetic deserves its own review. A check that adds a header, a payload length and padding can fail if that addition overflows before comparison. A defensible check establishes both representability of the arithmetic and containment within the actual received record. Saying that a buffer is large is not a substitute for either proof.
AI on both sides
A model could help an attacker spot a length flowing toward a copy. A defender can use the same ability to ask where the corresponding input bound is established, then verify the answer in the actual implementation. This is a present-day analysis exercise, not a claim that AI caused or discovered the 2014 incident.
Ask a coding assistant to name the invariant and cite the relevant source locations. Then remove one claimed check from the supplied context and see whether its conclusion changes. A useful result includes uncertainty when evidence is missing. An answer that stays reassuring regardless of the context is not evidence that the code is safe.
Defensive actions
On paper, create records whose claimed lengths are below, equal to and above their available payload. Include a truncated header, a boundary value and an arithmetic value that cannot be represented safely. For each case, write the expected rejection or accepted extent before looking at implementation behavior. This is an original regression-test design, not an exploit to run against a service.
Inventory the libraries and appliances that actually handle protocol input in your environment. Follow their current supported advisories and lifecycle guidance. When investigating a historical incident, keep patching, containment and any response to potentially exposed secrets as separate decisions rather than treating a new binary as proof that earlier exposure never occurred.
Limits
We do not execute the historical source, send heartbeat probes or reproduce memory disclosure here. This four-line excerpt is not the complete handler, a proof of exploitability on a particular device or an independent memory-safety audit. Its value is the precise question it makes unavoidable: who proved that the bytes about to be read were actually received?