/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 | | |