In this article, I'll attempt to explain the logic behind the notorious OpenSSL bug named "Heartbleed" that took the computing world by storm a week or so back. Even though many programs and services affected by the bug have now patched their installations of OpenSSL, it's still interesting to look at the code to see how the bug actually occurred. However, I will not explain how to setup and execute an exploit that takes advantage of the vulnerability.
OpenSSL is an open source implementation of the Secure Sockets Layer (SSL) and Transport Layer Security (TLS) network protocols that are used to ensure secure communication over the internet. Many well known websites, services and software use it to encrypt traffic such as Yahoo, SoundCloud, Steam, etc.
A small coding error in OpenSSL's (versions 1.0.1 through 1.0.1f inclusive) implementation of TLS protocol's Heartbeat mechanism is what caused the bug, hence, the name Heartbleed. The Heartbeat mechanism is there to ensure that the connection between two end points is kept alive even when there is no data being exchanged. The mechanism dictates that when a client sends a Heartbeat message, the server is supposed to send the exact same message back to indicate that the link is still active. A Hearbeat message contains a data string and its length, up to a maximum value of 65536 or 64 kilobytes.
The bug occurs when a Heartbeat Request message advertises a length that is larger than the actual length of the data. Since the erroneous code trusts the advertised length as is without any checks, the server would send back not just the actual data but additional data as well from the server's memory that would fall within the limits of the length. The following image hosted on WikiMedia Commons and also used for the Wikipedia Heartbleed article illustrates this brilliantly:
OpenSSL is implemented in C programming language. The vulnerable code resides in the functions tls1_process_heartbeat() and dtls1_process_heartbeat() found in the files, t1_lib.c and d1_both.c respectively, both located in the ssl folder. We'll just examine one of them. Here's the C code:
unsigned char *p = &s->s3->rrec.data, *pl;
unsigned short hbtype;
unsigned int payload;
unsigned int padding = 16; /* Use minimum padding */
/* Read type and payload length first */
hbtype = *p++;
pl = p;
if (hbtype == TLS1_HB_REQUEST)
unsigned char *buffer, *bp;
/* Allocate memory for the response, size is 1 byte
* message type, plus 2 bytes payload length, plus
* payload, plus padding
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;
/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
memcpy(bp, pl, payload);
The data arrives in the form of an SSL structure which contains two major pieces of information. The first is the actual data which is assigned to a char pointer "p" on the first line. This pointer holds the location of memory that contains the actual Heartbeat message. The second is the "length" which specifies the number of bytes in the received data. This will be important later on when we discuss the fix.
After assigning the data to "p", the length in the Heartbeat Message is read into an integer named "payload", at the line "n2s(p,payload)". A little way down, we'll see a buffer being allocated which is then assigned to the variable "bp". It is this variable that holds the location of the data that'll be returned to the client. The final line "memcpy(bp,pl,payload)" is where the bug is actually triggered. This line basically says that "payload" number of bytes of data starting from the memory location in "pl" and onward will be copied into the memory location pointed to by "bp". The code here does not check to see whether or not "payload" is actually the length of the data that is to be returned but instead, blindly copies the data specified. This allows the attacker to read up to 65536 bytes of additional data.
The fix here is pretty simple. We just check the total length specified in the Heartbeat Message against the length contained in the SSL structure mentioned previously. If the length of the Heartbeat Message is greater, we silently discard the message. The fix in the code is as follows:
return 0; /* silently discard per RFC 6520 sec. 4 */