Coverage Report

Created: 2023-03-20 06:28

/src/dropbear/fuzz/fuzz-sshpacketmutator.c
Line
Count
Source (jump to first uncovered line)
1
/* A mutator/crossover for SSH protocol streams.
2
   Attempts to mutate each SSH packet individually, keeping
3
   lengths intact.
4
   It will prepend a SSH-2.0-dbfuzz\r\n version string.
5
6
   Linking this file to a binary will make libfuzzer pick up the custom mutator.
7
8
   Care is taken to avoid memory allocation which would otherwise
9
   slow exec/s substantially */
10
11
#include "fuzz.h"
12
#include "dbutil.h"
13
14
size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
15
16
static const char* FIXED_VERSION = "SSH-2.0-dbfuzz\r\n";
17
static const char* FIXED_IGNORE_MSG = 
18
        "\x00\x00\x00\x10\x06\x02\x00\x00\x00\x00\x11\x22\x33\x44\x55\x66";
19
static const unsigned int FIXED_IGNORE_MSG_LEN = 16;
20
2.00k
#define MAX_FUZZ_PACKETS 500
21
/* XXX This might need tuning */
22
static const size_t MAX_OUT_SIZE = 50000;
23
24
/* Splits packets from an input stream buffer "inp".
25
The initial SSH version identifier is discarded.
26
If packets are not recognised it will increment until an uint32 of valid
27
packet length is found. */
28
29
/* out_packets an array of num_out_packets*buffer, each of size RECV_MAX_PACKET_LEN */
30
0
static void fuzz_get_packets(buffer *inp, buffer **out_packets, unsigned int *num_out_packets) {
31
    /* Skip any existing banner. Format is
32
          SSH-protoversion-softwareversion SP comments CR LF
33
    so we look for SSH-2. then a subsequent LF */
34
0
    unsigned char* version = memmem(inp->data, inp->len, "SSH-2.", strlen("SSH-2."));
35
0
    if (version) {
36
0
        buf_incrpos(inp, version - inp->data);
37
0
        unsigned char* newline = memchr(&inp->data[inp->pos], '\n', inp->len - inp->pos);
38
0
        if (newline) {
39
0
            buf_incrpos(inp, newline - &inp->data[inp->pos]+1);
40
0
        } else {
41
            /* Give up on any version string */
42
0
            buf_setpos(inp, 0);
43
0
        }
44
0
    }
45
46
0
    const unsigned int max_out_packets = *num_out_packets;
47
0
    *num_out_packets = 0;
48
0
    while (1) {
49
0
        if (inp->pos + 4 > inp->len) {
50
            /* End of input */
51
0
            break;
52
0
        }
53
54
0
        if (*num_out_packets >= max_out_packets) {
55
            /* End of output */
56
0
            break;
57
0
        }
58
59
        /* Read packet */
60
0
        unsigned int packet_len = buf_getint(inp);
61
0
        if (packet_len > RECV_MAX_PACKET_LEN-4) {
62
            /* Bad length, try skipping a single byte */
63
0
            buf_decrpos(inp, 3);
64
0
            continue;
65
0
        }
66
0
        packet_len = MIN(packet_len, inp->len - inp->pos);
67
68
        /* Check the packet length makes sense */
69
0
        if (packet_len >= MIN_PACKET_LEN-4) {
70
            /* Copy to output buffer. We're reusing buffers */
71
0
            buffer* new_packet = out_packets[*num_out_packets];
72
0
            (*num_out_packets)++;
73
0
            buf_setlen(new_packet, 0);
74
            // packet_len doesn't include itself
75
0
            buf_putint(new_packet, packet_len);
76
0
            buf_putbytes(new_packet, buf_getptr(inp, packet_len), packet_len);
77
0
        }
78
0
        buf_incrpos(inp, packet_len);
79
0
    }
80
0
}
81
82
/* Mutate a packet buffer in-place.
83
Returns DROPBEAR_FAILURE if it's too short */
84
0
static int buf_llvm_mutate(buffer *buf) {
85
0
    int ret;
86
    /* Position it after packet_length and padding_length */
87
0
    const unsigned int offset = 5;
88
0
    buf_setpos(buf, 0);
89
0
    buf_incrwritepos(buf, offset);
90
0
    size_t max_size = buf->size - buf->pos;
91
0
    size_t new_size = LLVMFuzzerMutate(buf_getwriteptr(buf, max_size),
92
0
        buf->len - buf->pos, max_size);
93
0
    size_t new_total = new_size + 1 + 4;
94
    // Round down to a block size
95
0
    new_total = new_total - (new_total % dropbear_nocipher.blocksize);
96
97
0
    if (new_total >= 16) {
98
0
        buf_setlen(buf, new_total);
99
        // Fix up the length fields
100
0
        buf_setpos(buf, 0);
101
        // packet_length doesn't include itself, does include padding_length byte
102
0
        buf_putint(buf, new_size+1);
103
        // always just put minimum padding length = 4
104
0
        buf_putbyte(buf, 4);
105
0
        ret = DROPBEAR_SUCCESS;
106
0
    } else {
107
        // instead put a fake packet
108
0
        buf_setlen(buf, 0);
109
0
        buf_putbytes(buf, FIXED_IGNORE_MSG, FIXED_IGNORE_MSG_LEN);
110
0
        ret = DROPBEAR_FAILURE;
111
0
    }
112
0
    return ret;
113
0
}
114
115
116
/* Persistent buffers to avoid constant allocations */
117
static buffer *oup;
118
static buffer *alloc_packetA;
119
static buffer *alloc_packetB;
120
static buffer* packets1[MAX_FUZZ_PACKETS];
121
static buffer* packets2[MAX_FUZZ_PACKETS];
122
123
/* Allocate buffers once at startup.
124
   'constructor' here so it runs before dbmalloc's interceptor */
125
static void alloc_static_buffers() __attribute__((constructor));
126
2
static void alloc_static_buffers() {
127
128
2
    int i;
129
2
    oup = buf_new(MAX_OUT_SIZE);
130
2
    alloc_packetA = buf_new(RECV_MAX_PACKET_LEN);
131
2
    alloc_packetB = buf_new(RECV_MAX_PACKET_LEN);
132
133
1.00k
    for (i = 0; i < MAX_FUZZ_PACKETS; i++) {
134
1.00k
        packets1[i] = buf_new(RECV_MAX_PACKET_LEN);
135
1.00k
    }
136
1.00k
    for (i = 0; i < MAX_FUZZ_PACKETS; i++) {
137
1.00k
        packets2[i] = buf_new(RECV_MAX_PACKET_LEN);
138
1.00k
    }
139
2
}
140
141
size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
142
0
              size_t MaxSize, unsigned int Seed) {
143
144
0
    buf_setlen(alloc_packetA, 0);
145
0
    buf_setlen(alloc_packetB, 0);
146
0
    buf_setlen(oup, 0);
147
148
0
    unsigned int i;
149
0
    size_t ret_len;
150
0
    unsigned short randstate[3] = {0,0,0};
151
0
    memcpy(randstate, &Seed, sizeof(Seed));
152
153
    // printhex("mutator input", Data, Size);
154
155
    /* 0.1% chance straight llvm mutate */
156
    // if (nrand48(randstate) % 1000 == 0) {
157
    //     ret_len = LLVMFuzzerMutate(Data, Size, MaxSize);
158
    //     // printhex("mutator straight llvm", Data, ret_len);
159
    //     return ret_len;
160
    // }
161
162
0
    buffer inp_buf = {.data = Data, .size = Size, .len = Size, .pos = 0};
163
0
    buffer *inp = &inp_buf;
164
165
    /* Parse packets */
166
0
    unsigned int num_packets = MAX_FUZZ_PACKETS;
167
0
    buffer **packets = packets1;
168
0
    fuzz_get_packets(inp, packets, &num_packets);
169
170
0
    if (num_packets == 0) {
171
        // Make up a packet, writing direct to the buffer
172
0
        inp->size = MaxSize;
173
0
        buf_setlen(inp, 0);
174
0
        buf_putbytes(inp, FIXED_VERSION, strlen(FIXED_VERSION));
175
0
        buf_putbytes(inp, FIXED_IGNORE_MSG, FIXED_IGNORE_MSG_LEN);
176
        // printhex("mutator no input", Data, inp->len);
177
0
        return inp->len;
178
0
    }
179
180
    /* Start output */
181
    /* Put a new banner to output */
182
0
    buf_putbytes(oup, FIXED_VERSION, strlen(FIXED_VERSION));
183
184
    /* Iterate output */
185
0
    for (i = 0; i < num_packets+1; i++) {
186
        // These are pointers to output
187
0
        buffer *out_packetA = NULL, *out_packetB = NULL;
188
0
        buf_setlen(alloc_packetA, 0);
189
0
        buf_setlen(alloc_packetB, 0);
190
191
        /* 2% chance each */
192
0
        const int optA = nrand48(randstate) % 50;
193
0
        if (optA == 0) {
194
            /* Copy another */
195
0
            unsigned int other = nrand48(randstate) % num_packets;
196
0
            out_packetA = packets[other];
197
            // printf("copy another %d / %d len %u\n", other, num_packets, out_packetA->len);
198
0
        }
199
0
        if (optA == 1) {
200
            /* Mutate another */
201
0
            unsigned int other = nrand48(randstate) % num_packets;
202
0
            out_packetA = alloc_packetA;
203
0
            buffer *from = packets[other];
204
0
            buf_putbytes(out_packetA, from->data, from->len);
205
0
            if (buf_llvm_mutate(out_packetA) == DROPBEAR_FAILURE) {
206
0
                out_packetA = NULL;
207
0
            }
208
            // printf("mutate another %d / %d len %u -> %u\n", other, num_packets, from->len, out_packetA->len);
209
0
        }
210
211
0
        if (i < num_packets) {
212
0
            int optB = nrand48(randstate) % 100;
213
0
            if (optB == 1) {
214
                /* small chance of drop */
215
                /* Drop it */
216
                //printf("%d drop\n", i);
217
0
            } else { 
218
                /* Odds of modification are proportional to packet position.
219
                First packet has 20% chance, last has 100% chance */
220
0
                int optC = nrand48(randstate) % 1000;
221
0
                int mutate_cutoff = MAX(200, (1000 * (i+1) / num_packets));
222
0
                if (optC < mutate_cutoff) {
223
                    // // printf("%d mutate\n", i);
224
0
                    out_packetB = alloc_packetB;
225
0
                    buffer *from = packets[i];
226
0
                    buf_putbytes(out_packetB, from->data, from->len);
227
0
                    if (buf_llvm_mutate(out_packetB) == DROPBEAR_FAILURE) {
228
0
                        out_packetB = from;
229
0
                    }
230
                    // printf("mutate self %d / %d len %u -> %u\n", i, num_packets, from->len, out_packetB->len);
231
0
                } else {
232
                    /* Copy as-is */
233
0
                    out_packetB = packets[i];
234
                    // printf("%d as-is len %u\n", i, out_packetB->len);
235
0
                } 
236
0
            }
237
0
        }
238
239
0
        if (out_packetA && oup->len + out_packetA->len <= oup->size) {
240
0
            buf_putbytes(oup, out_packetA->data, out_packetA->len);
241
0
        }
242
0
        if (out_packetB && oup->len + out_packetB->len <= oup->size) {
243
0
            buf_putbytes(oup, out_packetB->data, out_packetB->len);
244
0
        }
245
0
    }
246
247
0
    ret_len = MIN(MaxSize, oup->len);
248
0
    memcpy(Data, oup->data, ret_len);
249
    // printhex("mutator done", Data, ret_len);
250
0
    return ret_len;
251
0
}
252
253
size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
254
                                            const uint8_t *Data2, size_t Size2,
255
                                            uint8_t *Out, size_t MaxOutSize,
256
0
                                            unsigned int Seed) {
257
0
    unsigned short randstate[3] = {0,0,0};
258
0
    memcpy(randstate, &Seed, sizeof(Seed));
259
260
0
    unsigned int i;
261
0
    buffer inp_buf1 = {.data = (void*)Data1, .size = Size1, .len = Size1, .pos = 0};
262
0
    buffer *inp1 = &inp_buf1;
263
0
    buffer inp_buf2 = {.data = (void*)Data2, .size = Size2, .len = Size2, .pos = 0};
264
0
    buffer *inp2 = &inp_buf2;
265
266
0
    unsigned int num_packets1 = MAX_FUZZ_PACKETS;
267
0
    fuzz_get_packets(inp1, packets1, &num_packets1);
268
0
    unsigned int num_packets2 = MAX_FUZZ_PACKETS;
269
0
    fuzz_get_packets(inp2, packets2, &num_packets2);
270
271
    // fprintf(stderr, "input 1 %u packets\n", num_packets1);
272
    // printhex("crossover input1", Data1, Size1);
273
    // fprintf(stderr, "input 2 %u packets\n", num_packets2);
274
    // printhex("crossover input2", Data2, Size2);
275
276
0
    buf_setlen(oup, 0);
277
    /* Put a new banner to output */
278
0
    buf_putbytes(oup, FIXED_VERSION, strlen(FIXED_VERSION));
279
280
0
    if (num_packets1 == 0 && num_packets2 == 0) {
281
0
        buf_putbytes(oup, FIXED_IGNORE_MSG, FIXED_IGNORE_MSG_LEN);
282
0
    } else {
283
0
        unsigned int min_out = MIN(num_packets1, num_packets2);
284
0
        unsigned int max_out = num_packets1 + num_packets2;
285
0
        unsigned int num_out = min_out + nrand48(randstate) % (max_out-min_out+1);
286
287
0
        for (i = 0; i < num_out; i++) {
288
0
            unsigned int choose = nrand48(randstate) % (num_packets1 + num_packets2);
289
0
            buffer *p = NULL;
290
0
            if (choose < num_packets1) {
291
0
                p = packets1[choose];
292
0
            } else {
293
0
                p = packets2[choose-num_packets1];
294
0
            }
295
0
            if (oup->len + p->len <= oup->size) {
296
0
                buf_putbytes(oup, p->data, p->len);
297
0
            }
298
0
        }
299
0
    }
300
301
0
    size_t ret_len = MIN(MaxOutSize, oup->len);
302
0
    memcpy(Out, oup->data, ret_len);
303
    // printhex("crossover output", Out, ret_len);
304
0
    return ret_len;
305
0
}
306