FlawOpen

flawopen.com/Teardowns/cve-2014-0160-heartbleed

● CVE-2014-0160 · CVSS 7.5 · High
FlawOpen Security Research

CVE-2014-0160: OpenSSL Heartbleed memory leak

CVE-2014-0160 let a TLS or DTLS peer read up to 64 KiB beyond a Heartbeat record when OpenSSL trusted a declared payload length that the record did not contain. The upstream fix validates the record before parsing and echoing the payload.[1], [2]

💡 Plain English Explainer (ELI5)

Imagine a clerk receives a card with a few words but trusts a label asking for a much longer copy. Instead of stopping at the card, the clerk keeps copying whatever text follows in the shared tray. Heartbleed made OpenSSL copy past the received record; the bytes exposed depended on what was present in process memory.[1], [3]

Core Concepts & Subsystem Terms

TLS/DTLS Heartbeat (RFC 6520)
A keep-alive message in TLS or DTLS. A request carries a payload that the peer echoes, with padding required by the protocol.[3]
payload_length
The 16-bit field that states the Heartbeat payload size. It must fit inside the received record after the message type, length field, and minimum padding.[1], [3]
memcpy()
Copies a requested byte count from a source pointer. It cannot determine the source buffer’s valid length; the caller must validate that length first.[1], [2]
Buffer over-read (CWE-126)
Reading past the end of the intended source buffer. In Heartbleed, the echoed response could disclose up to 64 KiB of process memory, depending on memory contents.[5]

Framework mappings

Root Cause Analysis

tls1_process_heartbeat() and dtls1_process_heartbeat() read a Heartbeat message’s 16-bit payload length and, in the request path, passed it to memcpy() without proving that many payload bytes existed in s->s3->rrec.length. A peer could declare more data than the TLS or DTLS record carried. OpenSSL then echoed bytes past the received record, potentially disclosing up to 64 KiB of process memory; the contents were memory-dependent, so a particular key, password, or cookie was not guaranteed to appear. Upstream commit 96db902 first rejects records too short for the type, length, and minimum padding, then checks 1 + 2 + payload + 16 against the received record length. It also bounds the reply with SSL3_RT_MAX_PLAIN_LENGTH and uses the computed write_length. The same guard pattern was applied to both TLS and DTLS handlers.[1], [2], [3], [4]

Step-by-Step Attack Flow

Step 1

A peer sends a short Heartbeat record

A TLS or DTLS peer sends a Heartbeat message whose declared payload length is larger than the payload bytes present in the record.[1], [3]

Step 2

OpenSSL trusts the declared length

Before the fix, the handler reads the type and length without first validating the minimum record size or reconciling the payload length with the received record.[1], [2]

Step 3

The reply copies beyond the record

memcpy() copies the declared number of bytes from the record buffer. The resulting reply can include bytes beyond the received Heartbeat message, up to the protocol’s 64 KiB limit.[1], [2]

Step 4

Memory exposure depends on process contents

A peer may receive bytes from process memory. A response does not guarantee any particular secret; the result depends on what memory is present and the endpoint’s state.[1], [4]

Step 5

The patched handler rejects inconsistency

The fix checks the header and padding before parsing, verifies the payload fits the received record, and bounds the response before copying.[2], [1]

Source Code: Flaw vs. Secure Implementation

These are excerpts from OpenSSL’s upstream change, not a standalone program. The vulnerable code parses fields before checking that even the fixed header and minimum padding are present, then copies the peer-declared payload length. The patch checks the minimum record size before parsing, validates the declared payload against the received record, and bounds the encoded reply. OpenSSL made corresponding changes in tls1_process_heartbeat() and dtls1_process_heartbeat().[2], [1]

Source excerpt: ssl/t1_lib.c

UNPATCHED FLAW
/* Excerpt from tls1_process_heartbeat() before the fix. */
hbtype = *p++;
n2s(p, payload);
pl = p;

/* In the TLS1_HB_REQUEST response path: */
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT,
                     buffer, 3 + payload + padding);
HARDENED SECURE PATCH
/* Reject a record too short for type, length, and minimum padding. */
if (1 + 2 + 16 > s->s3->rrec.length)
    return 0;

hbtype = *p++;
n2s(p, payload);
if (1 + 2 + payload + 16 > s->s3->rrec.length)
    return 0;
pl = p;

/* In the TLS1_HB_REQUEST response path: */
unsigned int write_length = 1 + 2 + payload + padding;
if (write_length > SSL3_RT_MAX_PLAIN_LENGTH)
    return 0;
buffer = OPENSSL_malloc(write_length);
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, write_length);

Incident Timeline

2012-03-14

OpenSSL 1.0.1 is released with TLS/DTLS Heartbeat handling that lacks a record-length check.[3], [1]

2014-04-07

OpenSSL publishes the CVE-2014-0160 advisory and releases 1.0.1g; it credits Neel Mehta of Google Security. The 1.0.2 beta fix follows in beta2.[1], [2]

2014-04

Operators patch affected endpoints and assess possible exposure. Heartbleed.com notes that ordinary logs do not record which memory bytes a response returned; key, certificate, session, and credential replacement is considered after patching.[6], [1]

Engineering & System Hardening Checklist

Sources