Mike Bland

Instigator

Heartbleed Proof-of-Concept Unit Test

I wrote a rough skeleton of a proof-of-concept unit test for the Heartbleed bug that I hope to polish and complete over the weekend

- Boston
Tags: Heartbleed, programming, technical
Discuss: Discuss "Heartbleed Proof-of-Concept Unit Test" on Google+

Just wanted to share my modest success so far in getting the Heartbleed bug under test. It’s still very rough, and only tickles one of the Heartbleed corners and that of its fix right now, but it’s a solid proof-of-concept that I’ll polish and complete over the weekend. Plus, it’s really tiny.

If you want to play with building and running it, As per CVE-2014-0160, grab the following OpenSSL sources:

Unpack and run ./config && make for each. Then, add these lines to the bottom of test/Makefile:

heartbleed_test.o: heartbleed_test.c
heartbleed_test: heartbleed_test.o $(DLIBCRYPTO)
  @target=heartbleed_test; $(BUILD_CMD)

Copy the test from the bottom of this post into test/heartbleed_test.c and run:

$ make TESTS=heartbleed_test test
$ test/heartbleed_test

In the “fixed” version, 1.0.1g, you’ll see this output:

before: 10 characters
HEARTBLEED
after: 0 characters

In the “bleeding” version, you see:

before: 10 characters
HEARTBLEED
after: 1024 characters

and a bunch of data that shouldn’t be there. I’m not gonna post mine because, well, it’s private! (In the version of OpenSSL containing the bug, the test also aborts on exit in the default optimized mode; I’ll try to figure that out, too.)

It took me a while to figure out which dead chickens to wave and in what way to get the objects I needed in place, but the test’s still pretty damned small. It’s easier for me to produce this test knowing where to look for the bug, but still, it’s clear that a small unit test conceivably could have caught this, or at least accompanied its fix as a regression test.

So here it is; enjoy for now, and I’ll post a polished version later this weekend:

/* test/heartbleed_test.c */
/*
 * Test for the Heartbleed bug.
 *
 * Author:  Mike Bland (mbland@acm.org, https://mike-bland.com/)
 * Date:    2014-04-11
 * License: Creative Commons Attribution 4.0 International (CC By 4.0)
 *          http://creativecommons.org/licenses/by/4.0/deed.en_US
 */

#include "../ssl/ssl_locl.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

static void printbuf(const unsigned char *buf, const int n) {
  printf("%d character%s\n", n, n == 1 ? "" : "s");

  for (int i = 0; i != n; ++i) {
    const unsigned char c = buf[i];
    if (isprint(c)) fputc(c, stdout);
    else printf("\\x%02x", c);
  }
}

static void printwbuf(const SSL *s) {
  /* As per dtls1_get_record(), skipping the following:
   * type - 1 byte
   * version - 2 bytes
   * sequence number - 8 bytes
   * length - 2 bytes
   *
   * And then skipping the 1-byte type encoded by dtls1_process_handshake for
   * a total of 14 bytes, at which point we can grab the length and the
   * payload we seek.
   */
  unsigned const char *p = &(s->s3->wbuf.buf[14]);
  int len = 0;
  n2s(p, len);
  printbuf(p, len);
}

int main(int argc, char *argv[]) {
  const int kPayloadLen = 1024;
  unsigned char buf[] = "\0\0\0HEARTBLEED";
  unsigned char *s3 = &buf[0];

  SSL_library_init();
  SSL_load_error_strings();
  SSL_CTX *ctx = SSL_CTX_new(DTLSv1_server_method());
  if (!ctx) goto fail;
  SSL *s = SSL_new(ctx);
  if (!s) goto fail;
  ssl_init_wbio_buffer(s, 1);
  ssl3_setup_buffers(s);

  s->s3->rrec.data = s3;
  *s3++ = TLS1_HB_REQUEST;
  s2n(kPayloadLen, s3);

  /* subtract 1 byte for type, 2 bytes for payload length, and 1 for NUL */
  printf("before: ");
  printbuf(&s->s3->rrec.data[3], sizeof(buf) - 4);
  printf("\n");

  int result = dtls1_process_heartbeat(s);

  printf("after: ");
  printwbuf(s);
  printf("\n");

fail:
  ERR_print_errors_fp(stderr);
  SSL_free(s);
  SSL_CTX_free(ctx);
  return EXIT_SUCCESS;
}