/src/curl_fuzzer/fuzz_bufq.cc
Line | Count | Source (jump to first uncovered line) |
1 | | /*************************************************************************** |
2 | | * _ _ ____ _ |
3 | | * Project ___| | | | _ \| | |
4 | | * / __| | | | |_) | | |
5 | | * | (__| |_| | _ <| |___ |
6 | | * \___|\___/|_| \_\_____| |
7 | | * |
8 | | * Copyright (C) 2017 - 2022, Max Dymond, <cmeister2@gmail.com>, et al. |
9 | | * |
10 | | * This software is licensed as described in the file COPYING, which |
11 | | * you should have received as part of this distribution. The terms |
12 | | * are also available at https://curl.se/docs/copyright.html. |
13 | | * |
14 | | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | | * copies of the Software, and permit persons to whom the Software is |
16 | | * furnished to do so, under the terms of the COPYING file. |
17 | | * |
18 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | | * KIND, either express or implied. |
20 | | * |
21 | | ***************************************************************************/ |
22 | | |
23 | | #include <assert.h> |
24 | | #include <stdlib.h> |
25 | | #include <signal.h> |
26 | | #include <string.h> |
27 | | #include <sys/mman.h> |
28 | | #include <unistd.h> |
29 | | #include <curl/curl.h> |
30 | | |
31 | | #include <fuzzer/FuzzedDataProvider.h> |
32 | | #include "fuzz_bufq.h" |
33 | | |
34 | | extern "C" { |
35 | | #include "bufq.h" |
36 | | } |
37 | | |
38 | | /** |
39 | | * Allocate template buffer. This buffer is precomputed for performance and |
40 | | * used as a cyclic pattern when reading and writing. It can be useful to |
41 | | * detect unexpected data shifting or corruption. The buffer is marked |
42 | | * read-only so it cannot be written by mistake. |
43 | | */ |
44 | | static unsigned char *allocate_template_buffer(void) |
45 | 1 | { |
46 | 1 | size_t sz = FUZZ_MAX_RW_SIZE + 256; |
47 | 1 | unsigned char *buf = (unsigned char *)mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); |
48 | 1 | assert(buf != (unsigned char *)-1); |
49 | | |
50 | | /* Fill in with a cyclic pattern of 0, 1, ..., 255, 0, ... */ |
51 | 1 | unsigned char next_byte = 0; |
52 | 16.7M | for (size_t i = 0; i < sz; i++) { |
53 | 16.7M | buf[i] = next_byte++; |
54 | 16.7M | } |
55 | | |
56 | 1 | int err = mprotect(buf, sz, PROT_READ); |
57 | 1 | assert(err == 0); |
58 | 1 | return buf; |
59 | 1 | } |
60 | | |
61 | | /* |
62 | | * Compute a pointer to a read-only buffer with our pattern, knowing that the |
63 | | * first byte to appear is next_byte. |
64 | | */ |
65 | 10.5M | static unsigned char *compute_buffer(unsigned char next_byte, unsigned char *buf) { |
66 | 10.5M | return buf + next_byte; |
67 | 10.5M | } |
68 | | |
69 | | struct writer_cb_ctx { |
70 | | bool verbose; |
71 | | unsigned char *template_buf; |
72 | | ssize_t read_len; |
73 | | unsigned char next_byte_read; |
74 | | }; |
75 | | |
76 | | /** |
77 | | * Consume and verify up to read_len from a BUFQ via callback for Curl_bufq_pass. |
78 | | */ |
79 | | ssize_t bufq_writer_cb(void *writer_ctx, |
80 | | const unsigned char *buf, size_t len, |
81 | | CURLcode *err) |
82 | 2.22M | { |
83 | 2.22M | struct writer_cb_ctx *ctx = (struct writer_cb_ctx *)writer_ctx; |
84 | | |
85 | 2.22M | if (ctx->read_len <= 0) { |
86 | 3.55k | *err = CURLE_AGAIN; |
87 | 3.55k | return -1; |
88 | 3.55k | } |
89 | | |
90 | 2.22M | FV_PRINTF(ctx->verbose, "Writer CB: %zu space available, %zu pending\n", len, ctx->read_len); |
91 | | |
92 | 2.22M | size_t sz = len > ctx->read_len ? ctx->read_len : len; |
93 | | |
94 | 2.22M | unsigned char *compare = compute_buffer(ctx->next_byte_read, ctx->template_buf); |
95 | 2.22M | assert(memcmp(buf, compare, sz) == 0); |
96 | 2.22M | ctx->next_byte_read += sz; |
97 | 2.22M | ctx->read_len -= sz; |
98 | | |
99 | 2.22M | return sz; |
100 | 2.22M | } |
101 | | |
102 | | struct reader_cb_ctx { |
103 | | bool verbose; |
104 | | unsigned char *template_buf; |
105 | | ssize_t write_len; |
106 | | unsigned char next_byte_write; |
107 | | }; |
108 | | |
109 | | /** |
110 | | * Write up to write_len to a BUFQ via callback for Curl_bufq_slurp/sipn. |
111 | | */ |
112 | | static ssize_t bufq_reader_cb(void *reader_ctx, |
113 | | unsigned char *buf, size_t len, |
114 | | CURLcode *err) |
115 | 3.97M | { |
116 | 3.97M | struct reader_cb_ctx *ctx = (struct reader_cb_ctx *)reader_ctx; |
117 | | |
118 | 3.97M | if (ctx->write_len <= 0) { |
119 | 8.23k | *err = CURLE_AGAIN; |
120 | 8.23k | return -1; |
121 | 8.23k | } |
122 | | |
123 | 3.97M | FV_PRINTF(ctx->verbose, "Reader CB: %zu space available, %zu pending\n", len, ctx->write_len); |
124 | | |
125 | 3.97M | size_t sz = len > ctx->write_len ? ctx->write_len : len; |
126 | | |
127 | 3.97M | unsigned char *compare = compute_buffer(ctx->next_byte_write, ctx->template_buf); |
128 | 3.97M | memcpy(buf, compare, sz); |
129 | 3.97M | ctx->next_byte_write += sz; |
130 | 3.97M | ctx->write_len -= sz; |
131 | | |
132 | 3.97M | return sz; |
133 | 3.97M | } |
134 | | |
135 | | /** |
136 | | * Function for handling the operations |
137 | | */ |
138 | | int fuzz_handle_bufq(FuzzedDataProvider *fuzz) |
139 | 1.20k | { |
140 | 1.20k | static bool verbose = (getenv("FUZZ_VERBOSE") != NULL); |
141 | 1.20k | static unsigned char *template_buf = allocate_template_buffer(); |
142 | | |
143 | 1.20k | struct bufq q; |
144 | 1.20k | struct bufc_pool pool; |
145 | | |
146 | | /* Prepare basic configuration values */ |
147 | 1.20k | int max_chunks = fuzz->ConsumeIntegralInRange(1, FUZZ_MAX_CHUNKS_QTY); |
148 | 1.20k | int chunk_size = fuzz->ConsumeIntegralInRange(1, FUZZ_MAX_CHUNK_SIZE); |
149 | 1.20k | bool use_pool = fuzz->ConsumeBool(); |
150 | 1.20k | bool no_spare = fuzz->ConsumeBool(); |
151 | 1.20k | int max_spare = fuzz->ConsumeIntegralInRange(1, FUZZ_MAX_MAX_SPARE); |
152 | | |
153 | 1.20k | FV_PRINTF(verbose, "Begin fuzzing!\n"); |
154 | | |
155 | 1.20k | if (use_pool) { |
156 | 525 | FV_PRINTF(verbose, "Using pool init\n"); |
157 | 525 | Curl_bufcp_init(&pool, chunk_size, max_spare); |
158 | 525 | Curl_bufq_initp(&q, &pool, max_chunks, no_spare ? BUFQ_OPT_NO_SPARES : BUFQ_OPT_NONE); |
159 | 675 | } else { |
160 | 675 | FV_PRINTF(verbose, "Using normal init\n"); |
161 | 675 | Curl_bufq_init(&q, chunk_size, max_chunks); |
162 | 675 | } |
163 | | |
164 | 1.20k | ssize_t buffer_bytes = 0; |
165 | 1.20k | unsigned char next_byte_read = 0; |
166 | 1.20k | unsigned char next_byte_write = 0; |
167 | 7.19M | while (fuzz->remaining_bytes() > 0) { |
168 | 7.19M | CURLcode err = CURLE_OK; |
169 | 7.19M | uint32_t op_type = fuzz->ConsumeIntegralInRange(0, OP_TYPE_MAX); |
170 | | |
171 | 7.19M | assert(Curl_bufq_is_empty(&q) == !buffer_bytes); |
172 | 7.19M | assert(Curl_bufq_len(&q) == buffer_bytes); |
173 | | |
174 | 7.19M | switch (op_type) { |
175 | 484k | case OP_TYPE_RESET: { |
176 | 484k | FV_PRINTF(verbose, "OP: reset\n"); |
177 | 484k | Curl_bufq_reset(&q); |
178 | 484k | buffer_bytes = 0; |
179 | 484k | next_byte_read = next_byte_write; |
180 | 484k | break; |
181 | 0 | } |
182 | | |
183 | 54.7k | case OP_TYPE_PEEK: { |
184 | 54.7k | FV_PRINTF(verbose, "OP: peek\n"); |
185 | 54.7k | const unsigned char *pbuf; |
186 | 54.7k | size_t plen; |
187 | 54.7k | bool avail = Curl_bufq_peek(&q, &pbuf, &plen); |
188 | 54.7k | if (avail) { |
189 | 20.7k | unsigned char *compare = compute_buffer(next_byte_read, template_buf); |
190 | 20.7k | assert(memcmp(pbuf, compare, plen) == 0); |
191 | 33.9k | } else { |
192 | 33.9k | FV_PRINTF(verbose, "OP: peek, error\n"); |
193 | 33.9k | } |
194 | 54.7k | break; |
195 | 54.7k | } |
196 | | |
197 | 227k | case OP_TYPE_PEEK_AT: { |
198 | 227k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
199 | 227k | FV_PRINTF(verbose, "OP: peek at %zu\n", op_size); |
200 | 227k | const unsigned char *pbuf; |
201 | 227k | size_t plen; |
202 | 227k | bool avail = Curl_bufq_peek_at(&q, op_size, &pbuf, &plen); |
203 | 227k | if (avail) { |
204 | 2.39k | unsigned char *compare = compute_buffer(next_byte_read + op_size, template_buf); |
205 | 2.39k | assert(memcmp(pbuf, compare, plen) == 0); |
206 | 225k | } else { |
207 | 225k | FV_PRINTF(verbose, "OP: peek at, error\n"); |
208 | 225k | } |
209 | 227k | break; |
210 | 227k | } |
211 | | |
212 | 239k | case OP_TYPE_READ: { |
213 | 239k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
214 | 239k | FV_PRINTF(verbose, "OP: read, size %zu\n", op_size); |
215 | 239k | unsigned char *buf = (unsigned char *)malloc(op_size * sizeof(*buf)); |
216 | 239k | ssize_t read = Curl_bufq_read(&q, buf, op_size, &err); |
217 | 239k | if (read != -1) { |
218 | 9.59k | FV_PRINTF(verbose, "OP: read, success, read %zd, expect begins with %x\n", read, next_byte_read); |
219 | 9.59k | buffer_bytes -= read; |
220 | 9.59k | assert(buffer_bytes >= 0); |
221 | 9.59k | unsigned char *compare = compute_buffer(next_byte_read, template_buf); |
222 | 9.59k | next_byte_read += read; |
223 | 9.59k | assert(memcmp(buf, compare, read) == 0); |
224 | 229k | } else { |
225 | 229k | FV_PRINTF(verbose, "OP: read, error\n"); |
226 | 229k | } |
227 | 239k | free(buf); |
228 | 239k | break; |
229 | 239k | } |
230 | | |
231 | 24.0k | case OP_TYPE_SLURP: { |
232 | 24.0k | ssize_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
233 | 24.0k | FV_PRINTF(verbose, "OP: slurp, size %zd\n", op_size); |
234 | 24.0k | struct reader_cb_ctx ctx = { .verbose = verbose, .template_buf = template_buf, .write_len = op_size, .next_byte_write = next_byte_write }; |
235 | 24.0k | ssize_t write = Curl_bufq_slurp(&q, bufq_reader_cb, &ctx, &err); |
236 | 24.0k | if (write != -1) { |
237 | 9.87k | FV_PRINTF(verbose, "OP: slurp, success, wrote %zd, expect begins with %x\n", write, ctx.next_byte_write); |
238 | 9.87k | buffer_bytes += write; |
239 | 14.2k | } else { |
240 | 14.2k | FV_PRINTF(verbose, "OP: slurp, error\n"); |
241 | | /* in case of -1, it may still have wrote something, adjust for that */ |
242 | 14.2k | buffer_bytes += (op_size - ctx.write_len); |
243 | 14.2k | } |
244 | 24.0k | assert(buffer_bytes <= chunk_size * max_chunks); |
245 | 24.0k | next_byte_write = ctx.next_byte_write; |
246 | 24.0k | break; |
247 | 24.0k | } |
248 | | |
249 | 720k | case OP_TYPE_SIPN: { |
250 | 720k | ssize_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
251 | 720k | FV_PRINTF(verbose, "OP: sipn, size %zd\n", op_size); |
252 | 720k | struct reader_cb_ctx ctx = { .verbose = verbose, .template_buf = template_buf, .write_len = op_size, .next_byte_write = next_byte_write }; |
253 | 720k | ssize_t write = Curl_bufq_sipn(&q, op_size, bufq_reader_cb, &ctx, &err); |
254 | 720k | if (write != -1) { |
255 | 364k | FV_PRINTF(verbose, "OP: sipn, success, wrote %zd, expect begins with %x\n", write, ctx.next_byte_write); |
256 | 364k | buffer_bytes += write; |
257 | 364k | assert(buffer_bytes <= chunk_size * max_chunks); |
258 | 364k | next_byte_write = ctx.next_byte_write; |
259 | 364k | } else { |
260 | 355k | FV_PRINTF(verbose, "OP: sipn, error\n"); |
261 | 355k | } |
262 | 720k | break; |
263 | 720k | } |
264 | | |
265 | 720k | case OP_TYPE_PASS: { |
266 | 700k | ssize_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
267 | 700k | FV_PRINTF(verbose, "OP: pass, size %zd\n", op_size); |
268 | 700k | struct writer_cb_ctx ctx = { .verbose = verbose, .template_buf = template_buf, .read_len = op_size, .next_byte_read = next_byte_read }; |
269 | 700k | ssize_t read = Curl_bufq_pass(&q, bufq_writer_cb, &ctx, &err); |
270 | 700k | if (read != -1) { |
271 | 699k | FV_PRINTF(verbose, "OP: pass, success, read %zd, expect begins with %x\n", read, ctx.next_byte_read); |
272 | 699k | buffer_bytes -= read; |
273 | 699k | } else { |
274 | 1.04k | FV_PRINTF(verbose, "OP: pass, error\n"); |
275 | | /* in case of -1, it may still have read something, adjust for that */ |
276 | 1.04k | buffer_bytes -= (op_size - ctx.read_len); |
277 | 1.04k | } |
278 | 700k | assert(buffer_bytes >= 0); |
279 | 700k | next_byte_read = ctx.next_byte_read; |
280 | 700k | break; |
281 | 700k | } |
282 | | |
283 | 459k | case OP_TYPE_SKIP: { |
284 | 459k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
285 | 459k | FV_PRINTF(verbose, "OP: skip, size %zu\n", op_size); |
286 | 459k | Curl_bufq_skip(&q, op_size); |
287 | 459k | ssize_t old_buffer_bytes = buffer_bytes; |
288 | 459k | buffer_bytes = old_buffer_bytes > op_size ? old_buffer_bytes - op_size : 0; |
289 | 459k | next_byte_read += old_buffer_bytes > op_size ? op_size : old_buffer_bytes; |
290 | 459k | break; |
291 | 700k | } |
292 | | |
293 | 4.28M | case OP_TYPE_WRITE: { |
294 | 4.28M | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
295 | 4.28M | FV_PRINTF(verbose, "OP: write, size %zu, begins with %x\n", op_size, next_byte_write); |
296 | 4.28M | unsigned char *buf = compute_buffer(next_byte_write, template_buf); |
297 | 4.28M | ssize_t written = Curl_bufq_write(&q, buf, op_size, &err); |
298 | 4.28M | if (written != -1) { |
299 | 4.25M | FV_PRINTF(verbose, "OP: write, success, written %zd\n", written); |
300 | 4.25M | next_byte_write += written; |
301 | 4.25M | buffer_bytes += written; |
302 | 4.25M | assert(buffer_bytes <= chunk_size * max_chunks); |
303 | 4.25M | } else { |
304 | 32.7k | FV_PRINTF(verbose, "OP: write, error\n"); |
305 | 32.7k | } |
306 | 4.28M | break; |
307 | 4.28M | } |
308 | | |
309 | 4.28M | default: { |
310 | | /* Should never happen */ |
311 | 0 | assert(false); |
312 | 0 | } |
313 | 7.19M | } |
314 | 7.19M | } |
315 | | |
316 | 1.20k | Curl_bufq_free(&q); |
317 | 1.20k | if (use_pool) |
318 | 525 | { |
319 | 525 | Curl_bufcp_free(&pool); |
320 | 525 | } |
321 | | |
322 | 1.20k | return 0; |
323 | 1.20k | } |
324 | | |
325 | | /** |
326 | | * Fuzzing entry point. This function is passed a buffer containing a test |
327 | | * case. This test case should drive the cURL API into making a series of |
328 | | * BUFQ operations. |
329 | | */ |
330 | | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
331 | 1.20k | { |
332 | 1.20k | FuzzedDataProvider fuzzed_data(data, size); |
333 | | |
334 | | /* Ignore SIGPIPE errors. We'll handle the errors ourselves. */ |
335 | 1.20k | signal(SIGPIPE, SIG_IGN); |
336 | | |
337 | | /* Run the operations */ |
338 | 1.20k | fuzz_handle_bufq(&fuzzed_data); |
339 | | |
340 | | /* This function must always return 0. Non-zero codes are reserved. */ |
341 | 1.20k | return 0; |
342 | 1.20k | } |