Branch data Line data Source code
1 : : /* test/heartbeat_test.c */
2 : : /*
3 : : * Unit test for TLS heartbeats.
4 : : *
5 : : * Acts as a regression test against the Heartbleed bug (CVE-2014-0160).
6 : : *
7 : : * Author: Mike Bland (mbland@acm.org, http://mike-bland.com/)
8 : : * Date: 2014-04-12
9 : : * License: Creative Commons Attribution 4.0 International (CC By 4.0)
10 : : * http://creativecommons.org/licenses/by/4.0/deed.en_US
11 : : *
12 : : * OUTPUT
13 : : * ------
14 : : * The program returns zero on success. It will print a message with a count
15 : : * of the number of failed tests and return nonzero if any tests fail.
16 : : *
17 : : * It will print the contents of the request and response buffers for each
18 : : * failing test. In a "fixed" version, all the tests should pass and there
19 : : * should be no output.
20 : : *
21 : : * In a "bleeding" version, you'll see:
22 : : *
23 : : * test_dtls1_heartbleed failed:
24 : : * expected payload len: 0
25 : : * received: 1024
26 : : * sent 26 characters
27 : : * "HEARTBLEED "
28 : : * received 1024 characters
29 : : * "HEARTBLEED \xde\xad\xbe\xef..."
30 : : * ** test_dtls1_heartbleed failed **
31 : : *
32 : : * The contents of the returned buffer in the failing test will depend on the
33 : : * contents of memory on your machine.
34 : : *
35 : : * MORE INFORMATION
36 : : * ----------------
37 : : * http://mike-bland.com/2014/04/12/heartbleed.html
38 : : * http://mike-bland.com/tags/heartbleed.html
39 : : */
40 : :
41 : : #define OPENSSL_UNIT_TEST
42 : :
43 : : #include "../ssl/ssl_locl.h"
44 : :
45 : : #include "testutil.h"
46 : : #include <ctype.h>
47 : : #include <stdio.h>
48 : : #include <stdlib.h>
49 : : #include <string.h>
50 : :
51 : : #if !defined(OPENSSL_NO_HEARTBEATS) && !defined(OPENSSL_NO_UNIT_TEST)
52 : :
53 : : /* As per https://tools.ietf.org/html/rfc6520#section-4 */
54 : : #define MIN_PADDING_SIZE 16
55 : :
56 : : /* Maximum number of payload characters to print as test output */
57 : : #define MAX_PRINTABLE_CHARACTERS 1024
58 : :
59 : : typedef struct heartbeat_test_fixture
60 : : {
61 : : SSL_CTX *ctx;
62 : : SSL *s;
63 : : const char* test_case_name;
64 : : int (*process_heartbeat)(SSL* s);
65 : : unsigned char* payload;
66 : : int sent_payload_len;
67 : : int expected_return_value;
68 : : int return_payload_offset;
69 : : int expected_payload_len;
70 : : const char* expected_return_payload;
71 : : } HEARTBEAT_TEST_FIXTURE;
72 : :
73 : : static HEARTBEAT_TEST_FIXTURE set_up(const char* const test_case_name,
74 : : const SSL_METHOD* meth)
75 : : {
76 : : HEARTBEAT_TEST_FIXTURE fixture;
77 : : int setup_ok = 1;
78 : : memset(&fixture, 0, sizeof(fixture));
79 : : fixture.test_case_name = test_case_name;
80 : :
81 : : fixture.ctx = SSL_CTX_new(meth);
82 : : if (!fixture.ctx)
83 : : {
84 : : fprintf(stderr, "Failed to allocate SSL_CTX for test: %s\n",
85 : : test_case_name);
86 : : setup_ok = 0;
87 : : goto fail;
88 : : }
89 : :
90 : : fixture.s = SSL_new(fixture.ctx);
91 : : if (!fixture.s)
92 : : {
93 : : fprintf(stderr, "Failed to allocate SSL for test: %s\n", test_case_name);
94 : : setup_ok = 0;
95 : : goto fail;
96 : : }
97 : :
98 : : if (!ssl_init_wbio_buffer(fixture.s, 1))
99 : : {
100 : : fprintf(stderr, "Failed to set up wbio buffer for test: %s\n",
101 : : test_case_name);
102 : : setup_ok = 0;
103 : : goto fail;
104 : : }
105 : :
106 : : if (!ssl3_setup_buffers(fixture.s))
107 : : {
108 : : fprintf(stderr, "Failed to setup buffers for test: %s\n",
109 : : test_case_name);
110 : : setup_ok = 0;
111 : : goto fail;
112 : : }
113 : :
114 : : /* Clear the memory for the return buffer, since this isn't automatically
115 : : * zeroed in opt mode and will cause spurious test failures that will change
116 : : * with each execution.
117 : : */
118 : : memset(fixture.s->s3->wbuf.buf, 0, fixture.s->s3->wbuf.len);
119 : :
120 : : fail:
121 : : if (!setup_ok)
122 : : {
123 : : ERR_print_errors_fp(stderr);
124 : : exit(EXIT_FAILURE);
125 : : }
126 : : return fixture;
127 : : }
128 : :
129 : : static HEARTBEAT_TEST_FIXTURE set_up_dtls(const char* const test_case_name)
130 : : {
131 : : HEARTBEAT_TEST_FIXTURE fixture = set_up(test_case_name,
132 : : DTLSv1_server_method());
133 : : fixture.process_heartbeat = dtls1_process_heartbeat;
134 : :
135 : : /* As per dtls1_get_record(), skipping the following from the beginning of
136 : : * the returned heartbeat message:
137 : : * type-1 byte; version-2 bytes; sequence number-8 bytes; length-2 bytes
138 : : *
139 : : * And then skipping the 1-byte type encoded by process_heartbeat for
140 : : * a total of 14 bytes, at which point we can grab the length and the
141 : : * payload we seek.
142 : : */
143 : : fixture.return_payload_offset = 14;
144 : : return fixture;
145 : : }
146 : :
147 : : /* Needed by ssl3_write_bytes() */
148 : : static int dummy_handshake(SSL* s)
149 : : {
150 : : return 1;
151 : : }
152 : :
153 : : static HEARTBEAT_TEST_FIXTURE set_up_tls(const char* const test_case_name)
154 : : {
155 : : HEARTBEAT_TEST_FIXTURE fixture = set_up(test_case_name,
156 : : TLSv1_server_method());
157 : : fixture.process_heartbeat = tls1_process_heartbeat;
158 : : fixture.s->handshake_func = dummy_handshake;
159 : :
160 : : /* As per do_ssl3_write(), skipping the following from the beginning of
161 : : * the returned heartbeat message:
162 : : * type-1 byte; version-2 bytes; length-2 bytes
163 : : *
164 : : * And then skipping the 1-byte type encoded by process_heartbeat for
165 : : * a total of 6 bytes, at which point we can grab the length and the payload
166 : : * we seek.
167 : : */
168 : : fixture.return_payload_offset = 6;
169 : : return fixture;
170 : : }
171 : :
172 : : static void tear_down(HEARTBEAT_TEST_FIXTURE fixture)
173 : : {
174 : : ERR_print_errors_fp(stderr);
175 : : SSL_free(fixture.s);
176 : : SSL_CTX_free(fixture.ctx);
177 : : }
178 : :
179 : : static void print_payload(const char* const prefix,
180 : : const unsigned char *payload, const int n)
181 : : {
182 : : const int end = n < MAX_PRINTABLE_CHARACTERS ? n
183 : : : MAX_PRINTABLE_CHARACTERS;
184 : : int i = 0;
185 : :
186 : : printf("%s %d character%s", prefix, n, n == 1 ? "" : "s");
187 : : if (end != n) printf(" (first %d shown)", end);
188 : : printf("\n \"");
189 : :
190 : : for (; i != end; ++i)
191 : : {
192 : : const unsigned char c = payload[i];
193 : : if (isprint(c)) fputc(c, stdout);
194 : : else printf("\\x%02x", c);
195 : : }
196 : : printf("\"\n");
197 : : }
198 : :
199 : : static int execute_heartbeat(HEARTBEAT_TEST_FIXTURE fixture)
200 : : {
201 : : int result = 0;
202 : : SSL* s = fixture.s;
203 : : unsigned char *payload = fixture.payload;
204 : : unsigned char sent_buf[MAX_PRINTABLE_CHARACTERS + 1];
205 : : int return_value;
206 : : unsigned const char *p;
207 : : int actual_payload_len;
208 : :
209 : : s->s3->rrec.data = payload;
210 : : s->s3->rrec.length = strlen((const char*)payload);
211 : : *payload++ = TLS1_HB_REQUEST;
212 : : s2n(fixture.sent_payload_len, payload);
213 : :
214 : : /* Make a local copy of the request, since it gets overwritten at some
215 : : * point */
216 : : memcpy((char *)sent_buf, (const char*)payload, sizeof(sent_buf));
217 : :
218 : : return_value = fixture.process_heartbeat(s);
219 : :
220 : : if (return_value != fixture.expected_return_value)
221 : : {
222 : : printf("%s failed: expected return value %d, received %d\n",
223 : : fixture.test_case_name, fixture.expected_return_value,
224 : : return_value);
225 : : result = 1;
226 : : }
227 : :
228 : : /* If there is any byte alignment, it will be stored in wbuf.offset. */
229 : : p = &(s->s3->wbuf.buf[
230 : : fixture.return_payload_offset + s->s3->wbuf.offset]);
231 : : actual_payload_len = 0;
232 : : n2s(p, actual_payload_len);
233 : :
234 : : if (actual_payload_len != fixture.expected_payload_len)
235 : : {
236 : : printf("%s failed:\n expected payload len: %d\n received: %d\n",
237 : : fixture.test_case_name, fixture.expected_payload_len,
238 : : actual_payload_len);
239 : : print_payload("sent", sent_buf, strlen((const char*)sent_buf));
240 : : print_payload("received", p, actual_payload_len);
241 : : result = 1;
242 : : }
243 : : else
244 : : {
245 : : char* actual_payload = BUF_strndup((const char*)p, actual_payload_len);
246 : : if (strcmp(actual_payload, fixture.expected_return_payload) != 0)
247 : : {
248 : : printf("%s failed:\n expected payload: \"%s\"\n received: \"%s\"\n",
249 : : fixture.test_case_name, fixture.expected_return_payload,
250 : : actual_payload);
251 : : result = 1;
252 : : }
253 : : OPENSSL_free(actual_payload);
254 : : }
255 : :
256 : : if (result != 0)
257 : : {
258 : : printf("** %s failed **\n--------\n", fixture.test_case_name);
259 : : }
260 : : return result;
261 : : }
262 : :
263 : : static int honest_payload_size(unsigned char payload_buf[])
264 : : {
265 : : /* Omit three-byte pad at the beginning for type and payload length */
266 : : return strlen((const char*)&payload_buf[3]) - MIN_PADDING_SIZE;
267 : : }
268 : :
269 : : #define SETUP_HEARTBEAT_TEST_FIXTURE(type)\
270 : : SETUP_TEST_FIXTURE(HEARTBEAT_TEST_FIXTURE, set_up_##type)
271 : :
272 : : #define EXECUTE_HEARTBEAT_TEST()\
273 : : EXECUTE_TEST(execute_heartbeat, tear_down)
274 : :
275 : : static int test_dtls1_not_bleeding()
276 : : {
277 : : SETUP_HEARTBEAT_TEST_FIXTURE(dtls);
278 : : /* Three-byte pad at the beginning for type and payload length */
279 : : unsigned char payload_buf[MAX_PRINTABLE_CHARACTERS+4] =
280 : : " Not bleeding, sixteen spaces of padding"
281 : : " ";
282 : : const int payload_buf_len = honest_payload_size(payload_buf);
283 : :
284 : : fixture.payload = &payload_buf[0];
285 : : fixture.sent_payload_len = payload_buf_len;
286 : : fixture.expected_return_value = 0;
287 : : fixture.expected_payload_len = payload_buf_len;
288 : : fixture.expected_return_payload = "Not bleeding, sixteen spaces of padding";
289 : : EXECUTE_HEARTBEAT_TEST();
290 : : }
291 : :
292 : : static int test_dtls1_not_bleeding_empty_payload()
293 : : {
294 : : int payload_buf_len;
295 : :
296 : : SETUP_HEARTBEAT_TEST_FIXTURE(dtls);
297 : : /* Three-byte pad at the beginning for type and payload length, plus a NUL
298 : : * at the end */
299 : : unsigned char payload_buf[4 + MAX_PRINTABLE_CHARACTERS];
300 : : memset(payload_buf, ' ', MIN_PADDING_SIZE+3);
301 : : payload_buf[MIN_PADDING_SIZE+3] = '\0';
302 : : payload_buf_len = honest_payload_size(payload_buf);
303 : :
304 : : fixture.payload = &payload_buf[0];
305 : : fixture.sent_payload_len = payload_buf_len;
306 : : fixture.expected_return_value = 0;
307 : : fixture.expected_payload_len = payload_buf_len;
308 : : fixture.expected_return_payload = "";
309 : : EXECUTE_HEARTBEAT_TEST();
310 : : }
311 : :
312 : : static int test_dtls1_heartbleed()
313 : : {
314 : : SETUP_HEARTBEAT_TEST_FIXTURE(dtls);
315 : : /* Three-byte pad at the beginning for type and payload length */
316 : : unsigned char payload_buf[4+MAX_PRINTABLE_CHARACTERS] =
317 : : " HEARTBLEED ";
318 : :
319 : : fixture.payload = &payload_buf[0];
320 : : fixture.sent_payload_len = MAX_PRINTABLE_CHARACTERS;
321 : : fixture.expected_return_value = 0;
322 : : fixture.expected_payload_len = 0;
323 : : fixture.expected_return_payload = "";
324 : : EXECUTE_HEARTBEAT_TEST();
325 : : }
326 : :
327 : : static int test_dtls1_heartbleed_empty_payload()
328 : : {
329 : : SETUP_HEARTBEAT_TEST_FIXTURE(dtls);
330 : : /* Excluding the NUL at the end, one byte short of type + payload length +
331 : : * minimum padding */
332 : : unsigned char payload_buf[MAX_PRINTABLE_CHARACTERS + 4];
333 : : memset(payload_buf, ' ', MIN_PADDING_SIZE+2);
334 : : payload_buf[MIN_PADDING_SIZE+2] = '\0';
335 : :
336 : : fixture.payload = &payload_buf[0];
337 : : fixture.sent_payload_len = MAX_PRINTABLE_CHARACTERS;
338 : : fixture.expected_return_value = 0;
339 : : fixture.expected_payload_len = 0;
340 : : fixture.expected_return_payload = "";
341 : : EXECUTE_HEARTBEAT_TEST();
342 : : }
343 : :
344 : : static int test_dtls1_heartbleed_excessive_plaintext_length()
345 : : {
346 : : SETUP_HEARTBEAT_TEST_FIXTURE(dtls);
347 : : /* Excluding the NUL at the end, one byte in excess of maximum allowed
348 : : * heartbeat message length */
349 : : unsigned char payload_buf[SSL3_RT_MAX_PLAIN_LENGTH + 2];
350 : : memset(payload_buf, ' ', sizeof(payload_buf));
351 : : payload_buf[sizeof(payload_buf) - 1] = '\0';
352 : :
353 : : fixture.payload = &payload_buf[0];
354 : : fixture.sent_payload_len = honest_payload_size(payload_buf);
355 : : fixture.expected_return_value = 0;
356 : : fixture.expected_payload_len = 0;
357 : : fixture.expected_return_payload = "";
358 : : EXECUTE_HEARTBEAT_TEST();
359 : : }
360 : :
361 : : static int test_tls1_not_bleeding()
362 : : {
363 : : SETUP_HEARTBEAT_TEST_FIXTURE(tls);
364 : : /* Three-byte pad at the beginning for type and payload length */
365 : : unsigned char payload_buf[MAX_PRINTABLE_CHARACTERS+4] =
366 : : " Not bleeding, sixteen spaces of padding"
367 : : " ";
368 : : const int payload_buf_len = honest_payload_size(payload_buf);
369 : :
370 : : fixture.payload = &payload_buf[0];
371 : : fixture.sent_payload_len = payload_buf_len;
372 : : fixture.expected_return_value = 0;
373 : : fixture.expected_payload_len = payload_buf_len;
374 : : fixture.expected_return_payload = "Not bleeding, sixteen spaces of padding";
375 : : EXECUTE_HEARTBEAT_TEST();
376 : : }
377 : :
378 : : static int test_tls1_not_bleeding_empty_payload()
379 : : {
380 : : int payload_buf_len;
381 : :
382 : : SETUP_HEARTBEAT_TEST_FIXTURE(tls);
383 : : /* Three-byte pad at the beginning for type and payload length, plus a NUL
384 : : * at the end */
385 : : unsigned char payload_buf[4 + MAX_PRINTABLE_CHARACTERS];
386 : : memset(payload_buf, ' ', MIN_PADDING_SIZE+3);
387 : : payload_buf[MIN_PADDING_SIZE+3] = '\0';
388 : : payload_buf_len = honest_payload_size(payload_buf);
389 : :
390 : : fixture.payload = &payload_buf[0];
391 : : fixture.sent_payload_len = payload_buf_len;
392 : : fixture.expected_return_value = 0;
393 : : fixture.expected_payload_len = payload_buf_len;
394 : : fixture.expected_return_payload = "";
395 : : EXECUTE_HEARTBEAT_TEST();
396 : : }
397 : :
398 : : static int test_tls1_heartbleed()
399 : : {
400 : : SETUP_HEARTBEAT_TEST_FIXTURE(tls);
401 : : /* Three-byte pad at the beginning for type and payload length */
402 : : unsigned char payload_buf[MAX_PRINTABLE_CHARACTERS+4] =
403 : : " HEARTBLEED ";
404 : :
405 : : fixture.payload = &payload_buf[0];
406 : : fixture.sent_payload_len = MAX_PRINTABLE_CHARACTERS;
407 : : fixture.expected_return_value = 0;
408 : : fixture.expected_payload_len = 0;
409 : : fixture.expected_return_payload = "";
410 : : EXECUTE_HEARTBEAT_TEST();
411 : : }
412 : :
413 : : static int test_tls1_heartbleed_empty_payload()
414 : : {
415 : : SETUP_HEARTBEAT_TEST_FIXTURE(tls);
416 : : /* Excluding the NUL at the end, one byte short of type + payload length +
417 : : * minimum padding */
418 : : unsigned char payload_buf[MAX_PRINTABLE_CHARACTERS + 4];
419 : : memset(payload_buf, ' ', MIN_PADDING_SIZE+2);
420 : : payload_buf[MIN_PADDING_SIZE+2] = '\0';
421 : :
422 : : fixture.payload = &payload_buf[0];
423 : : fixture.sent_payload_len = MAX_PRINTABLE_CHARACTERS;
424 : : fixture.expected_return_value = 0;
425 : : fixture.expected_payload_len = 0;
426 : : fixture.expected_return_payload = "";
427 : : EXECUTE_HEARTBEAT_TEST();
428 : : }
429 : :
430 : : #undef EXECUTE_HEARTBEAT_TEST
431 : : #undef SETUP_HEARTBEAT_TEST_FIXTURE
432 : :
433 : : int main(int argc, char *argv[])
434 : : {
435 : : int result = 0;
436 : :
437 : : SSL_library_init();
438 : : SSL_load_error_strings();
439 : :
440 : : ADD_TEST(test_dtls1_not_bleeding);
441 : : ADD_TEST(test_dtls1_not_bleeding_empty_payload);
442 : : ADD_TEST(test_dtls1_heartbleed);
443 : : ADD_TEST(test_dtls1_heartbleed_empty_payload);
444 : : ADD_TEST(test_dtls1_heartbleed_excessive_plaintext_length);
445 : : ADD_TEST(test_tls1_not_bleeding);
446 : : ADD_TEST(test_tls1_not_bleeding_empty_payload);
447 : : ADD_TEST(test_tls1_heartbleed);
448 : : ADD_TEST(test_tls1_heartbleed_empty_payload);
449 : :
450 : : result = run_tests(argv[0]);
451 : : ERR_print_errors_fp(stderr);
452 : : return result;
453 : : }
454 : :
455 : : #else /* OPENSSL_NO_HEARTBEATS*/
456 : :
457 : 1 : int main(int argc, char *argv[])
458 : : {
459 : 1 : return EXIT_SUCCESS;
460 : : }
461 : : #endif /* OPENSSL_NO_HEARTBEATS */
|