changeset 1774:833bf9947603

Fuzzing - get rid of "prefix" for streams Improved packet generation with sshpacketmutator
author Matt Johnston <matt@ucc.asn.au>
date Sun, 01 Nov 2020 23:44:58 +0800
parents c3ca130d193a
children 8179eabe16c9
files dbrandom.c fuzz/fuzz-common.c fuzz/fuzz-sshpacketmutator.c
diffstat 3 files changed, 123 insertions(+), 102 deletions(-) [+]
line wrap: on
line diff
--- a/dbrandom.c	Sun Nov 01 14:01:37 2020 +0800
+++ b/dbrandom.c	Sun Nov 01 23:44:58 2020 +0800
@@ -151,17 +151,11 @@
 
 #if DROPBEAR_FUZZ
 void fuzz_seed(const unsigned char* dat, unsigned int len) {
-	static unsigned char keep_pool[SHA1_HASH_SIZE];
-	static int once = 0;
-	if (!once) {
-		once = 1;
-		hash_state hs;
-		sha1_init(&hs);
-		sha1_process(&hs, "fuzzfuzzfuzz", strlen("fuzzfuzzfuzz"));
-		sha1_process(&hs, dat, len);
-		sha1_done(&hs, keep_pool);
-	}
-	memcpy(hashpool, keep_pool, sizeof(keep_pool));
+	hash_state hs;
+	sha1_init(&hs);
+	sha1_process(&hs, "fuzzfuzzfuzz", strlen("fuzzfuzzfuzz"));
+	sha1_process(&hs, dat, len);
+	sha1_done(&hs, hashpool);
 	counter = 0;
 	donerandinit = 1;
 }
--- a/fuzz/fuzz-common.c	Sun Nov 01 14:01:37 2020 +0800
+++ b/fuzz/fuzz-common.c	Sun Nov 01 23:44:58 2020 +0800
@@ -64,6 +64,7 @@
     memset(&svr_ses, 0x0, sizeof(svr_ses));
     memset(&cli_ses, 0x0, sizeof(cli_ses));
     wrapfd_setup(fuzz.input);
+    // printhex("input", fuzz.input->data, fuzz.input->len);
 
     fuzz_seed(fuzz.input->data, MIN(fuzz.input->len, 16));
 
@@ -187,6 +188,7 @@
 
 void fuzz_kex_fakealgos(void) {
     ses.newkeys->recv.crypt_mode = &dropbear_mode_none;
+    ses.newkeys->recv.algo_mac = &dropbear_nohash;
 }
 
 void fuzz_get_socket_address(int UNUSED(fd), char **local_host, char **local_port,
@@ -236,23 +238,8 @@
         return 0;
     }
 
-    /*
-      get prefix, allowing for future extensibility. input format is
-      string prefix
-          uint32 wrapfd seed
-          ... to be extended later
-      [bytes] ssh input stream
-    */
-
-    /* be careful to avoid triggering buffer.c assertions */
-    if (fuzz.input->len < 8) {
-        return 0;
-    }
-    size_t prefix_size = buf_getint(fuzz.input);
-    if (prefix_size != 4) {
-        return 0;
-    }
-    uint32_t wrapseed = buf_getint(fuzz.input);
+    uint32_t wrapseed;
+    genrandom(&wrapseed, sizeof(wrapseed));
     wrapfd_setseed(wrapseed);
 
     int fakesock = wrapfd_new();
@@ -284,23 +271,11 @@
         return 0;
     }
 
-    /*
-      get prefix, allowing for future extensibility. input format is
-      string prefix
-          uint32 wrapfd seed
-          ... to be extended later
-      [bytes] ssh input stream
-    */
+    // Allow to proceed sooner
+    ses.kexstate.donefirstkex = 1;
 
-    /* be careful to avoid triggering buffer.c assertions */
-    if (fuzz.input->len < 8) {
-        return 0;
-    }
-    size_t prefix_size = buf_getint(fuzz.input);
-    if (prefix_size != 4) {
-        return 0;
-    }
-    uint32_t wrapseed = buf_getint(fuzz.input);
+    uint32_t wrapseed;
+    genrandom(&wrapseed, sizeof(wrapseed));
     wrapfd_setseed(wrapseed);
 
     int fakesock = wrapfd_new();
--- a/fuzz/fuzz-sshpacketmutator.c	Sun Nov 01 14:01:37 2020 +0800
+++ b/fuzz/fuzz-sshpacketmutator.c	Sun Nov 01 23:44:58 2020 +0800
@@ -14,7 +14,10 @@
 size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
 
 static const char* FIXED_VERSION = "SSH-2.0-dbfuzz\r\n";
-static const size_t MAX_FUZZ_PACKETS = 500;
+static const char* FIXED_IGNORE_MSG = 
+        "\x00\x00\x00\x10\x06\x02\x00\x00\x00\x00\x11\x22\x33\x44\x55\x66";
+static const unsigned int FIXED_IGNORE_MSG_LEN = 16;
+#define MAX_FUZZ_PACKETS 500
 /* XXX This might need tuning */
 static const size_t MAX_OUT_SIZE = 50000;
 
@@ -62,30 +65,51 @@
         }
         packet_len = MIN(packet_len, inp->len - inp->pos);
 
-        /* Copy to output buffer. We're reusing buffers */
-        buffer* new_packet = out_packets[*num_out_packets];
-        (*num_out_packets)++;
-        buf_setlen(new_packet, 0);
-        buf_putint(new_packet, packet_len);
-        buf_putbytes(new_packet, buf_getptr(inp, packet_len), packet_len);
+        /* Check the packet length makes sense */
+        if (packet_len >= MIN_PACKET_LEN-4) {
+            /* Copy to output buffer. We're reusing buffers */
+            buffer* new_packet = out_packets[*num_out_packets];
+            (*num_out_packets)++;
+            buf_setlen(new_packet, 0);
+            // packet_len doesn't include itself
+            buf_putint(new_packet, packet_len);
+            buf_putbytes(new_packet, buf_getptr(inp, packet_len), packet_len);
+        }
         buf_incrpos(inp, packet_len);
     }
 }
 
-/* Mutate a packet buffer in-place */
-static void buf_llvm_mutate(buffer *buf) {
+/* Mutate a packet buffer in-place.
+Returns DROPBEAR_FAILURE if it's too short */
+static int buf_llvm_mutate(buffer *buf) {
+    int ret;
     /* Position it after packet_length and padding_length */
     const unsigned int offset = 5;
-    if (buf->len < offset) {
-        return;
-    }
-    buf_setpos(buf, offset);
+    buf_setpos(buf, 0);
+    buf_incrwritepos(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);
+    size_t new_total = new_size + 1 + 4;
+    // Round down to a block size
+    new_total = new_total - (new_total % dropbear_nocipher.blocksize);
+
+    if (new_total >= 16) {
+        buf_setlen(buf, new_total);
+        // Fix up the length fields
+        buf_setpos(buf, 0);
+        // packet_length doesn't include itself, does include padding_length byte
+        buf_putint(buf, new_size+1);
+        // always just put minimum padding length = 4
+        buf_putbyte(buf, 4);
+        ret = DROPBEAR_SUCCESS;
+    } else {
+        // instead put a fake packet
+        buf_setlen(buf, 0);
+        buf_putbytes(buf, FIXED_IGNORE_MSG, FIXED_IGNORE_MSG_LEN);
+        ret = DROPBEAR_FAILURE;
+    }
+    return ret;
 }
 
 
@@ -93,8 +117,8 @@
 static buffer *oup;
 static buffer *alloc_packetA;
 static buffer *alloc_packetB;
-buffer* packets1[MAX_FUZZ_PACKETS];
-buffer* packets2[MAX_FUZZ_PACKETS];
+static buffer* packets1[MAX_FUZZ_PACKETS];
+static buffer* packets2[MAX_FUZZ_PACKETS];
 
 /* Allocate buffers once at startup.
    'constructor' here so it runs before dbmalloc's interceptor */
@@ -122,15 +146,18 @@
     buf_setlen(oup, 0);
 
     unsigned int i;
+    size_t ret_len;
     unsigned short randstate[3] = {0,0,0};
     memcpy(randstate, &Seed, sizeof(Seed));
 
     // printhex("mutator input", Data, Size);
 
     /* 0.1% chance straight llvm mutate */
-    if (nrand48(randstate) % 1000 == 0) {
-        return LLVMFuzzerMutate(Data, Size, MaxSize);
-    }
+    // if (nrand48(randstate) % 1000 == 0) {
+    //     ret_len = LLVMFuzzerMutate(Data, Size, MaxSize);
+    //     // printhex("mutator straight llvm", Data, ret_len);
+    //     return ret_len;
+    // }
 
     buffer inp_buf = {.data = Data, .size = Size, .len = Size, .pos = 0};
     buffer *inp = &inp_buf;
@@ -141,9 +168,13 @@
     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);
+        // Make up a packet, writing direct to the buffer
+        inp->size = MaxSize;
+        buf_setlen(inp, 0);
+        buf_putbytes(inp, FIXED_VERSION, strlen(FIXED_VERSION));
+        buf_putbytes(inp, FIXED_IGNORE_MSG, FIXED_IGNORE_MSG_LEN);
+        // printhex("mutator no input", Data, inp->len);
+        return inp->len;
     }
 
     /* Start output */
@@ -157,40 +188,52 @@
         buf_setlen(alloc_packetA, 0);
         buf_setlen(alloc_packetB, 0);
 
-        /* 5% chance each */
-        const int optA = nrand48(randstate) % 20;
+        /* 2% chance each */
+        const int optA = nrand48(randstate) % 50;
         if (optA == 0) {
             /* Copy another */
             unsigned int other = nrand48(randstate) % num_packets;
             out_packetA = packets[other];
+            // printf("copy another %d / %d len %u\n", other, num_packets, out_packetA->len);
         }
         if (optA == 1) {
             /* Mutate another */
             unsigned int other = nrand48(randstate) % num_packets;
+            out_packetA = alloc_packetA;
             buffer *from = packets[other];
-            buf_putbytes(alloc_packetA, from->data, from->len);
-            out_packetA = alloc_packetA;
-            buf_llvm_mutate(out_packetA);
+            buf_putbytes(out_packetA, from->data, from->len);
+            if (buf_llvm_mutate(out_packetA) == DROPBEAR_FAILURE) {
+                out_packetA = NULL;
+            }
+            // printf("mutate another %d / %d len %u -> %u\n", other, num_packets, from->len, out_packetA->len);
         }
 
         if (i < num_packets) {
-            int optB = nrand48(randstate) % 10;
+            int optB = nrand48(randstate) % 100;
             if (optB == 1) {
-                /* 10% chance of drop */
+                /* small chance of drop */
                 /* Drop it */
-                // printf("%d drop\n", i);
-            } else if (optB <= 6) {
-                /* Mutate it, 50% chance */
-                // printf("%d mutate\n", i);
-                buffer *from = packets[nrand48(randstate) % num_packets];
-                buf_putbytes(alloc_packetB, from->data, from->len);
-                out_packetB = alloc_packetB;
-                buf_llvm_mutate(out_packetB);
-            } else {
-                /* Copy as-is */
-                out_packetB = packets[i];
-                // printf("%d as-is\n", i);
-            } 
+                //printf("%d drop\n", i);
+            } else { 
+                /* Odds of modification are proportional to packet position.
+                First packet has 20% chance, last has 100% chance */
+                int optC = nrand48(randstate) % 1000;
+                int mutate_cutoff = MAX(200, (1000 * (i+1) / num_packets));
+                if (optC < mutate_cutoff) {
+                    // // printf("%d mutate\n", i);
+                    out_packetB = alloc_packetB;
+                    buffer *from = packets[i];
+                    buf_putbytes(out_packetB, from->data, from->len);
+                    if (buf_llvm_mutate(out_packetB) == DROPBEAR_FAILURE) {
+                        out_packetB = from;
+                    }
+                    // printf("mutate self %d / %d len %u -> %u\n", i, num_packets, from->len, out_packetB->len);
+                } else {
+                    /* Copy as-is */
+                    out_packetB = packets[i];
+                    // printf("%d as-is len %u\n", i, out_packetB->len);
+                } 
+            }
         }
 
         if (out_packetA && oup->len + out_packetA->len <= oup->size) {
@@ -201,7 +244,7 @@
         }
     }
 
-    size_t ret_len = MIN(MaxSize, oup->len);
+    ret_len = MIN(MaxSize, oup->len);
     memcpy(Data, oup->data, ret_len);
     // printhex("mutator done", Data, ret_len);
     return ret_len;
@@ -225,30 +268,39 @@
     unsigned int num_packets2 = MAX_FUZZ_PACKETS;
     fuzz_get_packets(inp2, packets2, &num_packets2);
 
+    // fprintf(stderr, "input 1 %u packets\n", num_packets1);
+    // printhex("crossover input1", Data1, Size1);
+    // fprintf(stderr, "input 2 %u packets\n", num_packets2);
+    // printhex("crossover input2", Data2, Size2);
+
     buf_setlen(oup, 0);
     /* Put a new banner to output */
     buf_putbytes(oup, FIXED_VERSION, strlen(FIXED_VERSION));
 
-    for (i = 0; i < num_packets1+1; i++) {
-        if (num_packets2 > 0 && nrand48(randstate) % 10 == 0) {
-            /* 10% chance of taking another packet at each position */
-            int other = nrand48(randstate) % num_packets2;
-            // printf("inserted other packet %d at %d\n", other, i);
-            buffer *otherp = packets2[other];
-            if (oup->len + otherp->len <= oup->size) {
-                buf_putbytes(oup, otherp->data, otherp->len);
+    if (num_packets1 == 0 && num_packets2 == 0) {
+        buf_putbytes(oup, FIXED_IGNORE_MSG, FIXED_IGNORE_MSG_LEN);
+    } else {
+        unsigned int min_out = MIN(num_packets1, num_packets2);
+        unsigned int max_out = num_packets1 + num_packets2;
+        unsigned int num_out = min_out + nrand48(randstate) % (max_out-min_out+1);
+
+        for (i = 0; i < num_out; i++) {
+            int choose = nrand48(randstate) % (num_packets1 + num_packets2);
+            buffer *p = NULL;
+            if (choose < num_packets1) {
+                p = packets1[choose];
+            } else {
+                p = packets2[choose-num_packets1];
             }
-        }
-        if (i < num_packets1) {
-            buffer *thisp = packets1[i];
-            if (oup->len + thisp->len <= oup->size) {
-                buf_putbytes(oup, thisp->data, thisp->len);
+            if (oup->len + p->len <= oup->size) {
+                buf_putbytes(oup, p->data, p->len);
             }
         }
     }
 
     size_t ret_len = MIN(MaxOutSize, oup->len);
     memcpy(Out, oup->data, ret_len);
+    // printhex("crossover output", Out, ret_len);
     return ret_len;
 }