Coverage Report

Created: 2026-04-28 07:09

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