/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 | 11.1M | static unsigned char *compute_buffer(unsigned char next_byte, unsigned char *buf) { |
66 | 11.1M | return buf + next_byte; |
67 | 11.1M | } |
68 | | |
69 | | struct writer_cb_ctx { |
70 | | bool verbose; |
71 | | unsigned char *template_buf; |
72 | | size_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 | | CURLcode bufq_writer_cb(void *writer_ctx, |
80 | | const unsigned char *buf, size_t len, |
81 | | size_t *pnwritten) |
82 | 2.34M | { |
83 | 2.34M | struct writer_cb_ctx *ctx = (struct writer_cb_ctx *)writer_ctx; |
84 | | |
85 | 2.34M | *pnwritten = 0; |
86 | 2.34M | if (ctx->read_len <= 0) |
87 | 4.67k | return CURLE_AGAIN; |
88 | | |
89 | 2.34M | FV_PRINTF(ctx->verbose, "Writer CB: %zu space available, %zu pending\n", len, ctx->read_len); |
90 | | |
91 | 2.34M | *pnwritten = len > ctx->read_len ? ctx->read_len : len; |
92 | | |
93 | 2.34M | unsigned char *compare = compute_buffer(ctx->next_byte_read, ctx->template_buf); |
94 | 2.34M | assert(memcmp(buf, compare, *pnwritten) == 0); |
95 | 2.34M | ctx->next_byte_read += *pnwritten; |
96 | 2.34M | ctx->read_len -= *pnwritten; |
97 | | |
98 | 2.34M | return CURLE_OK; |
99 | 2.34M | } |
100 | | |
101 | | struct reader_cb_ctx { |
102 | | bool verbose; |
103 | | unsigned char *template_buf; |
104 | | size_t write_len; |
105 | | unsigned char next_byte_write; |
106 | | }; |
107 | | |
108 | | /** |
109 | | * Write up to write_len to a BUFQ via callback for Curl_bufq_slurp/sipn. |
110 | | */ |
111 | | static CURLcode bufq_reader_cb(void *reader_ctx, |
112 | | unsigned char *buf, size_t len, |
113 | | size_t *pnread) |
114 | 4.82M | { |
115 | 4.82M | struct reader_cb_ctx *ctx = (struct reader_cb_ctx *)reader_ctx; |
116 | | |
117 | 4.82M | *pnread = 0; |
118 | 4.82M | if (ctx->write_len <= 0) |
119 | 5.72k | return CURLE_AGAIN; |
120 | | |
121 | 4.81M | FV_PRINTF(ctx->verbose, "Reader CB: %zu space available, %zu pending\n", len, ctx->write_len); |
122 | | |
123 | 4.81M | *pnread = len > ctx->write_len ? ctx->write_len : len; |
124 | | |
125 | 4.81M | unsigned char *compare = compute_buffer(ctx->next_byte_write, ctx->template_buf); |
126 | 4.81M | memcpy(buf, compare, *pnread); |
127 | 4.81M | ctx->next_byte_write += *pnread; |
128 | 4.81M | ctx->write_len -= *pnread; |
129 | | |
130 | 4.81M | return CURLE_OK; |
131 | 4.82M | } |
132 | | |
133 | | /** |
134 | | * Function for handling the operations |
135 | | */ |
136 | | int fuzz_handle_bufq(FuzzedDataProvider *fuzz) |
137 | 1.00k | { |
138 | 1.00k | static bool verbose = (getenv("FUZZ_VERBOSE") != NULL); |
139 | 1.00k | static unsigned char *template_buf = allocate_template_buffer(); |
140 | | |
141 | 1.00k | struct bufq q; |
142 | 1.00k | struct bufc_pool pool; |
143 | | |
144 | | /* Prepare basic configuration values */ |
145 | 1.00k | int max_chunks = fuzz->ConsumeIntegralInRange(1, FUZZ_MAX_CHUNKS_QTY); |
146 | 1.00k | int chunk_size = fuzz->ConsumeIntegralInRange(1, FUZZ_MAX_CHUNK_SIZE); |
147 | 1.00k | bool use_pool = fuzz->ConsumeBool(); |
148 | 1.00k | bool no_spare = fuzz->ConsumeBool(); |
149 | 1.00k | int max_spare = fuzz->ConsumeIntegralInRange(1, FUZZ_MAX_MAX_SPARE); |
150 | | |
151 | 1.00k | FV_PRINTF(verbose, "Begin fuzzing!\n"); |
152 | | |
153 | 1.00k | if (use_pool) { |
154 | 442 | FV_PRINTF(verbose, "Using pool init\n"); |
155 | 442 | Curl_bufcp_init(&pool, chunk_size, max_spare); |
156 | 442 | Curl_bufq_initp(&q, &pool, max_chunks, no_spare ? BUFQ_OPT_NO_SPARES : BUFQ_OPT_NONE); |
157 | 563 | } else { |
158 | 563 | FV_PRINTF(verbose, "Using normal init\n"); |
159 | 563 | Curl_bufq_init(&q, chunk_size, max_chunks); |
160 | 563 | } |
161 | | |
162 | 1.00k | size_t buffer_bytes = 0; |
163 | 1.00k | unsigned char next_byte_read = 0; |
164 | 1.00k | unsigned char next_byte_write = 0; |
165 | 8.63M | while (fuzz->remaining_bytes() > 0) { |
166 | 8.63M | CURLcode err = CURLE_OK; |
167 | 8.63M | uint32_t op_type = fuzz->ConsumeIntegralInRange(0, OP_TYPE_MAX); |
168 | | |
169 | 8.63M | assert(Curl_bufq_is_empty(&q) == !buffer_bytes); |
170 | 8.63M | assert(Curl_bufq_len(&q) == buffer_bytes); |
171 | | |
172 | 8.63M | switch (op_type) { |
173 | 1.53M | case OP_TYPE_RESET: { |
174 | 1.53M | FV_PRINTF(verbose, "OP: reset\n"); |
175 | 1.53M | Curl_bufq_reset(&q); |
176 | 1.53M | buffer_bytes = 0; |
177 | 1.53M | next_byte_read = next_byte_write; |
178 | 1.53M | break; |
179 | 0 | } |
180 | | |
181 | 325k | case OP_TYPE_PEEK: { |
182 | 325k | FV_PRINTF(verbose, "OP: peek\n"); |
183 | 325k | const unsigned char *pbuf; |
184 | 325k | size_t plen; |
185 | 325k | bool avail = Curl_bufq_peek(&q, &pbuf, &plen); |
186 | 325k | if (avail) { |
187 | 66.2k | unsigned char *compare = compute_buffer(next_byte_read, template_buf); |
188 | 66.2k | assert(memcmp(pbuf, compare, plen) == 0); |
189 | 258k | } else { |
190 | 258k | FV_PRINTF(verbose, "OP: peek, error\n"); |
191 | 258k | } |
192 | 325k | break; |
193 | 325k | } |
194 | | |
195 | 325k | case OP_TYPE_PEEK_AT: { |
196 | 263k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
197 | 263k | FV_PRINTF(verbose, "OP: peek at %zu\n", op_size); |
198 | 263k | const unsigned char *pbuf; |
199 | 263k | size_t plen; |
200 | 263k | bool avail = Curl_bufq_peek_at(&q, op_size, &pbuf, &plen); |
201 | 263k | if (avail) { |
202 | 5.22k | unsigned char *compare = compute_buffer(next_byte_read + op_size, template_buf); |
203 | 5.22k | assert(memcmp(pbuf, compare, plen) == 0); |
204 | 258k | } else { |
205 | 258k | FV_PRINTF(verbose, "OP: peek at, error\n"); |
206 | 258k | } |
207 | 263k | break; |
208 | 263k | } |
209 | | |
210 | 263k | case OP_TYPE_READ: { |
211 | 247k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
212 | 247k | FV_PRINTF(verbose, "OP: read, size %zu\n", op_size); |
213 | 247k | unsigned char *buf = (unsigned char *)malloc(op_size * sizeof(*buf)); |
214 | 247k | size_t read; |
215 | 247k | CURLcode result = Curl_bufq_read(&q, buf, op_size, &read); |
216 | 247k | if (!result) { |
217 | 12.2k | FV_PRINTF(verbose, "OP: read, success, read %zu, expect begins with %x\n", read, next_byte_read); |
218 | 12.2k | buffer_bytes -= read; |
219 | 12.2k | assert(buffer_bytes >= 0); |
220 | 12.2k | unsigned char *compare = compute_buffer(next_byte_read, template_buf); |
221 | 12.2k | next_byte_read += read; |
222 | 12.2k | assert(memcmp(buf, compare, read) == 0); |
223 | 235k | } else { |
224 | 235k | FV_PRINTF(verbose, "OP: read, error\n"); |
225 | 235k | } |
226 | 247k | free(buf); |
227 | 247k | break; |
228 | 247k | } |
229 | | |
230 | 800k | case OP_TYPE_SLURP: { |
231 | 800k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
232 | 800k | FV_PRINTF(verbose, "OP: slurp, size %zd\n", op_size); |
233 | 800k | struct reader_cb_ctx ctx = { .verbose = verbose, .template_buf = template_buf, .write_len = op_size, .next_byte_write = next_byte_write }; |
234 | 800k | size_t write; |
235 | 800k | CURLcode result = Curl_bufq_slurp(&q, bufq_reader_cb, &ctx, &write); |
236 | 800k | if (!result) { |
237 | 20.2k | FV_PRINTF(verbose, "OP: slurp, success, wrote %zu, expect begins with %x\n", write, ctx.next_byte_write); |
238 | 20.2k | buffer_bytes += write; |
239 | 779k | } else { |
240 | 779k | FV_PRINTF(verbose, "OP: slurp, error\n"); |
241 | | /* in case of -1, it may still have wrote something, adjust for that */ |
242 | 779k | buffer_bytes += (op_size - ctx.write_len); |
243 | 779k | } |
244 | 800k | assert(buffer_bytes <= chunk_size * max_chunks); |
245 | 800k | next_byte_write = ctx.next_byte_write; |
246 | 800k | break; |
247 | 800k | } |
248 | | |
249 | 628k | case OP_TYPE_SIPN: { |
250 | 628k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
251 | 628k | FV_PRINTF(verbose, "OP: sipn, size %zd\n", op_size); |
252 | 628k | struct reader_cb_ctx ctx = { .verbose = verbose, .template_buf = template_buf, .write_len = op_size, .next_byte_write = next_byte_write }; |
253 | 628k | size_t write; |
254 | 628k | CURLcode result = Curl_bufq_sipn(&q, op_size, bufq_reader_cb, &ctx, &write); |
255 | 628k | if (!result) { |
256 | 377k | FV_PRINTF(verbose, "OP: sipn, success, wrote %zu, expect begins with %x\n", write, ctx.next_byte_write); |
257 | 377k | buffer_bytes += write; |
258 | 377k | assert(buffer_bytes <= chunk_size * max_chunks); |
259 | 377k | next_byte_write = ctx.next_byte_write; |
260 | 377k | } else { |
261 | 251k | FV_PRINTF(verbose, "OP: sipn, error\n"); |
262 | 251k | } |
263 | 628k | break; |
264 | 628k | } |
265 | | |
266 | 628k | case OP_TYPE_PASS: { |
267 | 599k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
268 | 599k | FV_PRINTF(verbose, "OP: pass, size %zd\n", op_size); |
269 | 599k | struct writer_cb_ctx ctx = { .verbose = verbose, .template_buf = template_buf, .read_len = op_size, .next_byte_read = next_byte_read }; |
270 | 599k | size_t nread; |
271 | 599k | CURLcode result = Curl_bufq_pass(&q, bufq_writer_cb, &ctx, &nread); |
272 | 599k | if (!result) { |
273 | 598k | FV_PRINTF(verbose, "OP: pass, success, read %zu, expect begins with %x\n", nread, ctx.next_byte_read); |
274 | 598k | buffer_bytes -= nread; |
275 | 598k | } else { |
276 | 1.12k | FV_PRINTF(verbose, "OP: pass, error\n"); |
277 | | /* in case of -1, it may still have read something, adjust for that */ |
278 | 1.12k | buffer_bytes -= (op_size - ctx.read_len); |
279 | 1.12k | } |
280 | 599k | assert(buffer_bytes >= 0); |
281 | 599k | next_byte_read = ctx.next_byte_read; |
282 | 599k | break; |
283 | 599k | } |
284 | | |
285 | 352k | case OP_TYPE_SKIP: { |
286 | 352k | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
287 | 352k | FV_PRINTF(verbose, "OP: skip, size %zu\n", op_size); |
288 | 352k | Curl_bufq_skip(&q, op_size); |
289 | 352k | size_t old_buffer_bytes = buffer_bytes; |
290 | 352k | buffer_bytes = old_buffer_bytes > op_size ? old_buffer_bytes - op_size : 0; |
291 | 352k | next_byte_read += old_buffer_bytes > op_size ? op_size : old_buffer_bytes; |
292 | 352k | break; |
293 | 599k | } |
294 | | |
295 | 3.87M | case OP_TYPE_WRITE: { |
296 | 3.87M | size_t op_size = fuzz->ConsumeIntegralInRange(0, FUZZ_MAX_RW_SIZE); |
297 | 3.87M | FV_PRINTF(verbose, "OP: write, size %zu, begins with %x\n", op_size, next_byte_write); |
298 | 3.87M | unsigned char *buf = compute_buffer(next_byte_write, template_buf); |
299 | 3.87M | size_t written; |
300 | 3.87M | CURLcode result = Curl_bufq_write(&q, buf, op_size, &written); |
301 | 3.87M | if (!result) { |
302 | 3.78M | FV_PRINTF(verbose, "OP: write, success, written %zu\n", written); |
303 | 3.78M | next_byte_write += written; |
304 | 3.78M | buffer_bytes += written; |
305 | 3.78M | assert(buffer_bytes <= chunk_size * max_chunks); |
306 | 3.78M | } else { |
307 | 90.6k | FV_PRINTF(verbose, "OP: write, error\n"); |
308 | 90.6k | } |
309 | 3.87M | break; |
310 | 3.87M | } |
311 | | |
312 | 3.87M | default: { |
313 | | /* Should never happen */ |
314 | 0 | assert(false); |
315 | 0 | } |
316 | 8.63M | } |
317 | 8.63M | } |
318 | | |
319 | 1.00k | Curl_bufq_free(&q); |
320 | 1.00k | if (use_pool) |
321 | 442 | { |
322 | 442 | Curl_bufcp_free(&pool); |
323 | 442 | } |
324 | | |
325 | 1.00k | return 0; |
326 | 1.00k | } |
327 | | |
328 | | /** |
329 | | * Fuzzing entry point. This function is passed a buffer containing a test |
330 | | * case. This test case should drive the cURL API into making a series of |
331 | | * BUFQ operations. |
332 | | */ |
333 | | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
334 | 1.00k | { |
335 | 1.00k | FuzzedDataProvider fuzzed_data(data, size); |
336 | | |
337 | | /* Ignore SIGPIPE errors. We'll handle the errors ourselves. */ |
338 | 1.00k | signal(SIGPIPE, SIG_IGN); |
339 | | |
340 | | /* Run the operations */ |
341 | 1.00k | fuzz_handle_bufq(&fuzzed_data); |
342 | | |
343 | | /* This function must always return 0. Non-zero codes are reserved. */ |
344 | 1.00k | return 0; |
345 | 1.00k | } |