Mercurial > dropbear
changeset 1760:2406a9987810
Add first try at fuzzing custom mutator
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Sun, 25 Oct 2020 22:52:36 +0800 |
parents | 4c5599435084 |
children | a339b1c4b9f2 |
files | Makefile.in fuzz/fuzz-common.c fuzz/fuzz-harness.c fuzz/fuzz-sshpacketmutator.c fuzz/fuzzer-client_mutator.c |
diffstat | 5 files changed, 195 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile.in Sun Oct 25 21:47:42 2020 +0800 +++ b/Makefile.in Sun Oct 25 22:52:36 2020 +0800 @@ -269,7 +269,8 @@ # list of fuzz targets FUZZ_TARGETS=fuzzer-preauth fuzzer-pubkey fuzzer-verify fuzzer-preauth_nomaths \ - fuzzer-kexdh fuzzer-kexecdh fuzzer-kexcurve25519 fuzzer-client fuzzer-client_nomaths + fuzzer-kexdh fuzzer-kexecdh fuzzer-kexcurve25519 fuzzer-client fuzzer-client_nomaths \ + fuzzer-client_mutator FUZZER_OPTIONS = $(addsuffix .options, $(FUZZ_TARGETS)) FUZZ_OBJS = $(addprefix fuzz/,$(addsuffix .o,$(FUZZ_TARGETS)))
--- a/fuzz/fuzz-common.c Sun Oct 25 21:47:42 2020 +0800 +++ b/fuzz/fuzz-common.c Sun Oct 25 22:52:36 2020 +0800 @@ -256,10 +256,12 @@ int fakesock = wrapfd_new(); m_malloc_set_epoch(1); + fuzz.do_jmp = 1; if (setjmp(fuzz.jmp) == 0) { svr_session(fakesock, fakesock); m_malloc_free_epoch(1, 0); } else { + fuzz.do_jmp = 0; m_malloc_free_epoch(1, 1); TRACE(("dropbear_exit longjmped")) /* dropbear_exit jumped here */ @@ -302,10 +304,12 @@ int fakesock = wrapfd_new(); m_malloc_set_epoch(1); + fuzz.do_jmp = 1; if (setjmp(fuzz.jmp) == 0) { cli_session(fakesock, fakesock, NULL, 0); m_malloc_free_epoch(1, 0); } else { + fuzz.do_jmp = 0; m_malloc_free_epoch(1, 1); TRACE(("dropbear_exit longjmped")) /* dropbear_exit jumped here */
--- a/fuzz/fuzz-harness.c Sun Oct 25 21:47:42 2020 +0800 +++ b/fuzz/fuzz-harness.c Sun Oct 25 22:52:36 2020 +0800 @@ -46,3 +46,9 @@ return 0; } + +size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) { + printf("standalone fuzzer harness shouldn't call LLVMFuzzerMutate"); + abort(); + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fuzz/fuzz-sshpacketmutator.c Sun Oct 25 22:52:36 2020 +0800 @@ -0,0 +1,175 @@ +#include "fuzz.h" + +size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); + +static void fuzz_get_packets(buffer *inp, buffer **out_packets, unsigned int *num_out_packets) { + /* Skip any existing banner. Format is + SSH-protoversion-softwareversion SP comments CR LF + so we look for SSH-2. then a subsequent LF */ + unsigned char* version = memmem(inp->data, inp->len, "SSH-2.", strlen("SSH-2.")); + if (version) { + buf_incrpos(inp, version - inp->data); + unsigned char* newline = memchr(&inp->data[inp->pos], '\n', inp->len - inp->pos); + if (newline) { + buf_incrpos(inp, newline - &inp->data[inp->pos]); + } else { + /* Give up on any version string */ + buf_setpos(inp, 0); + } + } + + const unsigned int max_out_packets = *num_out_packets; + *num_out_packets = 0; + while (1) { + if (inp->pos + 4 > inp->len) { + /* End of input */ + break; + } + + if (*num_out_packets >= max_out_packets) { + /* End of output */ + break; + } + + /* Read packet */ + unsigned int packet_len = buf_getint(inp); + if (packet_len <= RECV_MAX_PACKET_LEN) { + /* Bad length, try skipping a single byte */ + buf_decrpos(inp, 3); + continue; + } + packet_len = MIN(packet_len, inp->len - inp->pos); + + /* Copy to output buffer */ + buffer* new_packet = buf_new(RECV_MAX_PACKET_LEN); + buf_putint(new_packet, packet_len); + buf_putbytes(new_packet, buf_getptr(inp, packet_len), packet_len); + buf_incrpos(inp, packet_len); + + out_packets[*num_out_packets] = new_packet; + (*num_out_packets)++; + } + +} + +/* Mutate in-place */ +void buf_llvm_mutate(buffer *buf) { + /* Position it after packet_length and padding_length */ + const unsigned int offset = 5; + if (buf->len < offset) { + return; + } + buf_setpos(buf, offset); + size_t max_size = buf->size - buf->pos; + size_t new_size = LLVMFuzzerMutate(buf_getwriteptr(buf, max_size), + buf->len - buf->pos, max_size); + buf_setpos(buf, 0); + buf_putint(buf, new_size); + buf_setlen(buf, offset + new_size); +} + + +static const char* FIXED_VERSION = "SSH-2.0-dbfuzz\r\n"; +static const size_t MAX_FUZZ_PACKETS = 500; +/* XXX This might need tuning */ +static const size_t MAX_OUT_SIZE = 50000; + +size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, + size_t MaxSize, unsigned int Seed) { + int i; + unsigned short randstate[3] = {0,0,0}; + memcpy(randstate, &Seed, sizeof(Seed)); + + /* 1% chance straight llvm mutate */ + if (nrand48(randstate) % 100 == 0) { + return LLVMFuzzerMutate(Data, Size, MaxSize); + } + + buffer inp_buf = {.data = Data, .size = Size, .len = Size, .pos = 0}; + buffer *inp = &inp_buf; + + /* Parse packets */ + buffer* packets[MAX_FUZZ_PACKETS] = {0}; + unsigned int num_packets = MAX_FUZZ_PACKETS; + fuzz_get_packets(inp, packets, &num_packets); + + if (num_packets == 0) { + // gotta do something + memcpy(Data, FIXED_VERSION, MIN(strlen(FIXED_VERSION), MaxSize)); + return LLVMFuzzerMutate(Data, Size, MaxSize); + } + + /* Start output */ + buffer *oup = buf_new(MAX_OUT_SIZE); + /* Put a new banner to output */ + buf_putbytes(oup, FIXED_VERSION, strlen(FIXED_VERSION)); + + /* Iterate output */ + for (i = 0; i < num_packets+1; i++) { + // These are pointers to output + buffer *out_packetA = NULL, *out_packetB = NULL; + // These need to be freed + buffer *alloc_packetA = NULL, *alloc_packetB = NULL; + + /* 5% chance each */ + const int optA = nrand48(randstate) % 20; + const int other = nrand48(randstate) % num_packets; + if (optA == 0) { + /* Copy another */ + out_packetA = packets[nrand48(randstate) % num_packets]; + } + if (optA == 1) { + /* Mutate another */ + alloc_packetA = buf_new(RECV_MAX_PACKET_LEN); + buffer *from = packets[nrand48(randstate) % num_packets]; + buf_putbytes(alloc_packetA, from->data, from->len); + out_packetA = alloc_packetA; + buf_llvm_mutate(out_packetA); + } + + /* 10% chance each of mutate or drop */ + if (i < num_packets) { + int optB = nrand48(randstate) % 10; + if (optB == 0) { + /* Copy as-is */ + out_packetB = packets[i]; + } + if (optB == 1) { + /* Drop it */ + } + if (optB == 2) { + /* Mutate it */ + alloc_packetB = buf_new(RECV_MAX_PACKET_LEN); + buffer *from = packets[nrand48(randstate) % num_packets]; + buf_putbytes(alloc_packetB, from->data, from->len); + out_packetB = alloc_packetB; + buf_llvm_mutate(out_packetB); + } + } + + if (out_packetA && oup->len + out_packetA->len <= oup->size) { + buf_putbytes(oup, out_packetA->data, out_packetA->len); + } + if (out_packetB && oup->len + out_packetB->len <= oup->size) { + buf_putbytes(oup, out_packetB->data, out_packetB->len); + } + if (alloc_packetA) { + buf_free(alloc_packetA); + alloc_packetA = NULL; + } + if (alloc_packetB) { + buf_free(alloc_packetB); + alloc_packetB = NULL; + } + } + + for (i = 0; i < num_packets; i++) { + buf_free(packets[i]); + } + + size_t ret_len = MIN(MaxSize, oup->len); + memcpy(Data, oup->data, ret_len); + buf_free(oup); + return ret_len; +} +