/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 | 7.87M | static unsigned char *compute_buffer(unsigned char next_byte, unsigned char *buf) { |
66 | 7.87M | return buf + next_byte; |
67 | 7.87M | } |
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 | 1.29M | { |
83 | 1.29M | struct writer_cb_ctx *ctx = (struct writer_cb_ctx *)writer_ctx; |
84 | | |
85 | 1.29M | if (ctx->read_len <= 0) { |
86 | 2.66k | *err = CURLE_AGAIN; |
87 | 2.66k | return -1; |
88 | 2.66k | } |
89 | | |
90 | 1.28M | FV_PRINTF(ctx->verbose, "Writer CB: %zu space available, %zu pending\n", len, ctx->read_len); |
91 | | |
92 | 1.28M | size_t sz = len > ctx->read_len ? ctx->read_len : len; |
93 | | |
94 | 1.28M | unsigned char *compare = compute_buffer(ctx->next_byte_read, ctx->template_buf); |
95 | 1.28M | assert(memcmp(buf, compare, sz) == 0); |
96 | 1.28M | ctx->next_byte_read += sz; |
97 | 1.28M | ctx->read_len -= sz; |
98 | | |
99 | 1.28M | return sz; |
100 | 1.28M | } |
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 | 2.91M | { |
116 | 2.91M | struct reader_cb_ctx *ctx = (struct reader_cb_ctx *)reader_ctx; |
117 | | |
118 | 2.91M | if (ctx->write_len <= 0) { |
119 | 5.22k | *err = CURLE_AGAIN; |
120 | 5.22k | return -1; |
121 | 5.22k | } |
122 | | |
123 | 2.90M | FV_PRINTF(ctx->verbose, "Reader CB: %zu space available, %zu pending\n", len, ctx->write_len); |
124 | | |
125 | 2.90M | size_t sz = len > ctx->write_len ? ctx->write_len : len; |
126 | | |
127 | 2.90M | unsigned char *compare = compute_buffer(ctx->next_byte_write, ctx->template_buf); |
128 | 2.90M | memcpy(buf, compare, sz); |
129 | 2.90M | ctx->next_byte_write += sz; |
130 | 2.90M | ctx->write_len -= sz; |
131 | | |
132 | 2.90M | return sz; |
133 | 2.91M | } |
134 | | |
135 | | /** |
136 | | * Function for handling the operations |
137 | | */ |
138 | | int fuzz_handle_bufq(FuzzedDataProvider *fuzz) |
139 | 1.17k | { |
140 | 1.17k | static bool verbose = (getenv("FUZZ_VERBOSE") != NULL); |
141 | 1.17k | static unsigned char *template_buf = allocate_template_buffer(); |
142 | | |
143 | 1.17k | struct bufq q; |
144 | 1.17k | struct bufc_pool pool; |
145 | | |
146 | | /* Prepare basic configuration values */ |
147 | 1.17k | int max_chunks = fuzz->ConsumeIntegralInRange(1, FUZZ_MAX_CHUNKS_QTY); |
148 | 1.17k | int chunk_size = fuzz->ConsumeIntegralInRange(1, FUZZ_MAX_CHUNK_SIZE); |
149 | 1.17k | bool use_pool = fuzz->ConsumeBool(); |
150 | 1.17k | bool no_spare = fuzz->ConsumeBool(); |
151 | 1.17k | int max_spare = fuzz->ConsumeIntegralInRange(1, FUZZ_MAX_MAX_SPARE); |
152 | | |
153 | 1.17k | FV_PRINTF(verbose, "Begin fuzzing!\n"); |
154 | | |
155 | 1.17k | if (use_pool) { |
156 | 520 | FV_PRINTF(verbose, "Using pool init\n"); |
157 | 520 | Curl_bufcp_init(&pool, chunk_size, max_spare); |
158 | 520 | Curl_bufq_initp(&q, &pool, max_chunks, no_spare ? BUFQ_OPT_NO_SPARES : BUFQ_OPT_NONE); |
159 | 654 | } else { |
160 | 654 | FV_PRINTF(verbose, "Using normal init\n"); |
161 | 654 | Curl_bufq_init(&q, chunk_size, max_chunks); |
162 | 654 | } |
163 | | |
164 | 1.17k | ssize_t buffer_bytes = 0; |
165 | 1.17k | unsigned char next_byte_read = 0; |
166 | 1.17k | unsigned char next_byte_write = 0; |
167 | 7.28M | while (fuzz->remaining_bytes() > 0) { |
168 | 7.28M | CURLcode err = CURLE_OK; |
169 | 7.28M | uint32_t op_type = fuzz->ConsumeIntegralInRange(0, OP_TYPE_MAX); |
170 | | |
171 | 7.28M | assert(Curl_bufq_is_empty(&q) == !buffer_bytes); |
172 | 7.28M | assert(Curl_bufq_len(&q) == buffer_bytes); |
173 | | |
174 | 7.28M | switch (op_type) { |
175 | 906k | case OP_TYPE_RESET: { |
176 | 906k | FV_PRINTF(verbose, "OP: reset\n"); |
177 | 906k | Curl_bufq_reset(&q); |
178 | 906k | buffer_bytes = 0; |
179 | 906k | next_byte_read = next_byte_write; |
180 | 906k | break; |
181 | 0 | } |
182 | | |
183 | 68.6k | case OP_TYPE_PEEK: { |
184 | 68.6k | FV_PRINTF(verbose, "OP: peek\n"); |
185 | 68.6k | const unsigned char *pbuf; |
186 | 68.6k | size_t plen; |
187 | 68.6k | bool avail = Curl_bufq_peek(&q, &pbuf, &plen); |
188 | 68.6k | if (avail) { |
189 | 29.0k | unsigned char *compare = compute_buffer(next_byte_read, template_buf); |
190 | 29.0k | assert(memcmp(pbuf, compare, plen) == 0); |
191 | 39.6k | } else { |
192 | 39.6k | FV_PRINTF(verbose, "OP: peek, error\n"); |
193 | 39.6k | } |
194 | 68.6k | break; |
195 | 68.6k | } |
196 | | |
197 | 355k | case OP_TYPE_PEEK_AT: { |
198 | 355k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
199 | 355k | FV_PRINTF(verbose, "OP: peek at %zu\n", op_size); |
200 | 355k | const unsigned char *pbuf; |
201 | 355k | size_t plen; |
202 | 355k | bool avail = Curl_bufq_peek_at(&q, op_size, &pbuf, &plen); |
203 | 355k | if (avail) { |
204 | 1.32k | unsigned char *compare = compute_buffer(next_byte_read + op_size, template_buf); |
205 | 1.32k | assert(memcmp(pbuf, compare, plen) == 0); |
206 | 353k | } else { |
207 | 353k | FV_PRINTF(verbose, "OP: peek at, error\n"); |
208 | 353k | } |
209 | 355k | break; |
210 | 355k | } |
211 | | |
212 | 355k | case OP_TYPE_READ: { |
213 | 245k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
214 | 245k | FV_PRINTF(verbose, "OP: read, size %zu\n", op_size); |
215 | 245k | unsigned char *buf = (unsigned char *)malloc(op_size * sizeof(*buf)); |
216 | 245k | ssize_t read = Curl_bufq_read(&q, buf, op_size, &err); |
217 | 245k | if (read != -1) { |
218 | 7.61k | FV_PRINTF(verbose, "OP: read, success, read %zd, expect begins with %x\n", read, next_byte_read); |
219 | 7.61k | buffer_bytes -= read; |
220 | 7.61k | assert(buffer_bytes >= 0); |
221 | 7.61k | unsigned char *compare = compute_buffer(next_byte_read, template_buf); |
222 | 7.61k | next_byte_read += read; |
223 | 7.61k | assert(memcmp(buf, compare, read) == 0); |
224 | 237k | } else { |
225 | 237k | FV_PRINTF(verbose, "OP: read, error\n"); |
226 | 237k | } |
227 | 245k | free(buf); |
228 | 245k | break; |
229 | 245k | } |
230 | | |
231 | 81.6k | case OP_TYPE_SLURP: { |
232 | 81.6k | ssize_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
233 | 81.6k | FV_PRINTF(verbose, "OP: slurp, size %zd\n", op_size); |
234 | 81.6k | struct reader_cb_ctx ctx = { .verbose = verbose, .template_buf = template_buf, .write_len = op_size, .next_byte_write = next_byte_write }; |
235 | 81.6k | ssize_t write = Curl_bufq_slurp(&q, bufq_reader_cb, &ctx, &err); |
236 | 81.6k | if (write != -1) { |
237 | 28.4k | FV_PRINTF(verbose, "OP: slurp, success, wrote %zd, expect begins with %x\n", write, ctx.next_byte_write); |
238 | 28.4k | buffer_bytes += write; |
239 | 53.1k | } else { |
240 | 53.1k | FV_PRINTF(verbose, "OP: slurp, error\n"); |
241 | | /* in case of -1, it may still have wrote something, adjust for that */ |
242 | 53.1k | buffer_bytes += (op_size - ctx.write_len); |
243 | 53.1k | } |
244 | 81.6k | assert(buffer_bytes <= chunk_size * max_chunks); |
245 | 81.6k | next_byte_write = ctx.next_byte_write; |
246 | 81.6k | break; |
247 | 81.6k | } |
248 | | |
249 | 890k | case OP_TYPE_SIPN: { |
250 | 890k | ssize_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
251 | 890k | FV_PRINTF(verbose, "OP: sipn, size %zd\n", op_size); |
252 | 890k | struct reader_cb_ctx ctx = { .verbose = verbose, .template_buf = template_buf, .write_len = op_size, .next_byte_write = next_byte_write }; |
253 | 890k | ssize_t write = Curl_bufq_sipn(&q, op_size, bufq_reader_cb, &ctx, &err); |
254 | 890k | if (write != -1) { |
255 | 400k | FV_PRINTF(verbose, "OP: sipn, success, wrote %zd, expect begins with %x\n", write, ctx.next_byte_write); |
256 | 400k | buffer_bytes += write; |
257 | 400k | assert(buffer_bytes <= chunk_size * max_chunks); |
258 | 400k | next_byte_write = ctx.next_byte_write; |
259 | 490k | } else { |
260 | 490k | FV_PRINTF(verbose, "OP: sipn, error\n"); |
261 | 490k | } |
262 | 890k | break; |
263 | 890k | } |
264 | | |
265 | 890k | case OP_TYPE_PASS: { |
266 | 502k | ssize_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
267 | 502k | FV_PRINTF(verbose, "OP: pass, size %zd\n", op_size); |
268 | 502k | struct writer_cb_ctx ctx = { .verbose = verbose, .template_buf = template_buf, .read_len = op_size, .next_byte_read = next_byte_read }; |
269 | 502k | ssize_t read = Curl_bufq_pass(&q, bufq_writer_cb, &ctx, &err); |
270 | 502k | if (read != -1) { |
271 | 502k | FV_PRINTF(verbose, "OP: pass, success, read %zd, expect begins with %x\n", read, ctx.next_byte_read); |
272 | 502k | buffer_bytes -= read; |
273 | 502k | } else { |
274 | 653 | FV_PRINTF(verbose, "OP: pass, error\n"); |
275 | | /* in case of -1, it may still have read something, adjust for that */ |
276 | 653 | buffer_bytes -= (op_size - ctx.read_len); |
277 | 653 | } |
278 | 502k | assert(buffer_bytes >= 0); |
279 | 502k | next_byte_read = ctx.next_byte_read; |
280 | 502k | break; |
281 | 502k | } |
282 | | |
283 | 590k | case OP_TYPE_SKIP: { |
284 | 590k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
285 | 590k | FV_PRINTF(verbose, "OP: skip, size %zu\n", op_size); |
286 | 590k | Curl_bufq_skip(&q, op_size); |
287 | 590k | ssize_t old_buffer_bytes = buffer_bytes; |
288 | 590k | buffer_bytes = old_buffer_bytes > op_size ? old_buffer_bytes - op_size : 0; |
289 | 590k | next_byte_read += old_buffer_bytes > op_size ? op_size : old_buffer_bytes; |
290 | 590k | break; |
291 | 502k | } |
292 | | |
293 | 3.64M | case OP_TYPE_WRITE: { |
294 | 3.64M | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
295 | 3.64M | FV_PRINTF(verbose, "OP: write, size %zu, begins with %x\n", op_size, next_byte_write); |
296 | 3.64M | unsigned char *buf = compute_buffer(next_byte_write, template_buf); |
297 | 3.64M | ssize_t written = Curl_bufq_write(&q, buf, op_size, &err); |
298 | 3.64M | if (written != -1) { |
299 | 3.60M | FV_PRINTF(verbose, "OP: write, success, written %zd\n", written); |
300 | 3.60M | next_byte_write += written; |
301 | 3.60M | buffer_bytes += written; |
302 | 3.60M | assert(buffer_bytes <= chunk_size * max_chunks); |
303 | 3.60M | } else { |
304 | 34.3k | FV_PRINTF(verbose, "OP: write, error\n"); |
305 | 34.3k | } |
306 | 3.64M | break; |
307 | 3.64M | } |
308 | | |
309 | 3.64M | default: { |
310 | | /* Should never happen */ |
311 | 0 | assert(false); |
312 | 0 | } |
313 | 7.28M | } |
314 | 7.28M | } |
315 | | |
316 | 1.17k | Curl_bufq_free(&q); |
317 | 1.17k | if (use_pool) |
318 | 520 | { |
319 | 520 | Curl_bufcp_free(&pool); |
320 | 520 | } |
321 | | |
322 | 1.17k | return 0; |
323 | 1.17k | } |
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.17k | { |
332 | 1.17k | FuzzedDataProvider fuzzed_data(data, size); |
333 | | |
334 | | /* Ignore SIGPIPE errors. We'll handle the errors ourselves. */ |
335 | 1.17k | signal(SIGPIPE, SIG_IGN); |
336 | | |
337 | | /* Run the operations */ |
338 | 1.17k | fuzz_handle_bufq(&fuzzed_data); |
339 | | |
340 | | /* This function must always return 0. Non-zero codes are reserved. */ |
341 | 1.17k | return 0; |
342 | 1.17k | } |