Coverage Report

Created: 2026-09-03 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/fuzz_http.c
Line
Count
Source
1
/*
2
 * Copyright 2026 Google LLC
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
/*
18
 * Fuzzer for HAProxy's HTTP utility functions in src/http.c.
19
 *
20
 * This targets the core HTTP semantic parsing functions that process
21
 * untrusted input from clients and backends:
22
 *
23
 *   - URI parsing: http_parse_scheme, http_parse_authority, http_parse_path
24
 *   - Content-Length: http_parse_cont_len_header (request smuggling surface)
25
 *   - Cookie parsing: http_extract_cookie_value, http_extract_next_cookie_name
26
 *   - Header utilities: http_header_match2, http_find_hdr_value_end
27
 *   - HTTP line parsing: http_parse_header, http_parse_stline,
28
 *                         http_parse_status_val
29
 *   - Quality values: http_parse_qvalue
30
 *   - ETag comparison: http_compare_etags
31
 *   - Host/port: http_get_host_port, http_is_default_port
32
 *   - Method lookup: find_http_meth
33
 *   - Scheme validation: http_validate_scheme
34
 *   - URL parameters: http_find_next_url_param
35
 *
36
 * The first byte of fuzz input selects which function group to exercise,
37
 * maximizing coverage across the many independent parsers.
38
 */
39
40
#include <haproxy/http.h>
41
#include <haproxy/http-hdr.h>
42
#include <haproxy/global.h>
43
44
#include <stdint.h>
45
#include <string.h>
46
#include <stdlib.h>
47
48
/* Consume one byte from the fuzz data to use as a selector. */
49
2.08k
static inline uint8_t fuzz_consume_byte(const uint8_t **data, size_t *size) {
50
2.08k
  if (*size == 0)
51
0
    return 0;
52
2.08k
  uint8_t b = **data;
53
2.08k
  (*data)++;
54
2.08k
  (*size)--;
55
2.08k
  return b;
56
2.08k
}
57
58
/* Exercise URI parsing: scheme, authority, path */
59
113
static void fuzz_uri_parsing(const uint8_t *data, size_t size) {
60
113
  char *buf = (char *)malloc(size + 1);
61
113
  if (!buf)
62
0
    return;
63
113
  memcpy(buf, data, size);
64
113
  buf[size] = '\0';
65
66
113
  struct ist uri = ist2(buf, size);
67
113
  struct http_uri_parser parser = http_uri_parser_init(uri);
68
69
113
  http_parse_scheme(&parser);
70
113
  http_parse_authority(&parser, 1);
71
113
  http_parse_path(&parser);
72
73
  /* Also test with no_userinfo=0 */
74
113
  parser = http_uri_parser_init(uri);
75
113
  http_parse_scheme(&parser);
76
113
  http_parse_authority(&parser, 0);
77
78
113
  free(buf);
79
113
}
80
81
/* Exercise Content-Length header parsing — critical for request smuggling */
82
493
static void fuzz_content_length(const uint8_t *data, size_t size) {
83
493
  char *buf = (char *)malloc(size + 1);
84
493
  if (!buf)
85
0
    return;
86
493
  memcpy(buf, data, size);
87
493
  buf[size] = '\0';
88
89
493
  struct ist value = ist2(buf, size);
90
493
  unsigned long long body_len = 0;
91
92
  /* First occurrence */
93
493
  int ret = http_parse_cont_len_header(&value, &body_len, 0);
94
95
  /* Simulate a second header with same data (duplicate CL detection) */
96
493
  if (ret > 0) {
97
264
    struct ist value2 = ist2(buf, size);
98
264
    http_parse_cont_len_header(&value2, &body_len, 1);
99
264
  }
100
101
493
  free(buf);
102
493
}
103
104
/* Exercise cookie extraction */
105
329
static void fuzz_cookie_parsing(const uint8_t *data, size_t size) {
106
329
  if (size < 2)
107
1
    return;
108
109
328
  char *buf = (char *)malloc(size + 1);
110
328
  if (!buf)
111
0
    return;
112
328
  memcpy(buf, data, size);
113
328
  buf[size] = '\0';
114
115
328
  char *hdr = buf;
116
328
  const char *hdr_end = buf + size;
117
328
  char *value = NULL;
118
328
  size_t value_l = 0;
119
120
  /* Extract cookie with empty name (match any) */
121
328
  http_extract_cookie_value(hdr, hdr_end, "", 0, 1, &value, &value_l);
122
123
  /* Extract cookie with a specific 4-byte name from start of input */
124
328
  if (size > 6) {
125
216
    char name[5];
126
216
    memcpy(name, data, 4);
127
216
    name[4] = '\0';
128
216
    char *search_start = buf + 4;
129
216
    http_extract_cookie_value(search_start, hdr_end, name, 4, 1,
130
216
                              &value, &value_l);
131
216
  }
132
133
  /* Test http_extract_next_cookie_name */
134
328
  char *ptr = NULL;
135
328
  size_t len = 0;
136
328
  http_extract_next_cookie_name(hdr, buf + size, 1, &ptr, &len);
137
328
  http_extract_next_cookie_name(hdr, buf + size, 0, &ptr, &len);
138
139
328
  free(buf);
140
328
}
141
142
/* Exercise header/line parsing utilities */
143
315
static void fuzz_header_parsing(const uint8_t *data, size_t size) {
144
315
  char *buf = (char *)malloc(size + 1);
145
315
  if (!buf)
146
0
    return;
147
315
  memcpy(buf, data, size);
148
315
  buf[size] = '\0';
149
150
315
  struct ist hdr = ist2(buf, size);
151
315
  struct ist name, value;
152
153
  /* Parse a header line */
154
315
  http_parse_header(hdr, &name, &value);
155
156
  /* Parse a start line */
157
315
  struct ist p1, p2, p3;
158
315
  http_parse_stline(hdr, &p1, &p2, &p3);
159
160
  /* Parse status value */
161
315
  struct ist status, reason;
162
315
  http_parse_status_val(hdr, &status, &reason);
163
164
  /* header_match2 with a known header name */
165
315
  http_header_match2(buf, buf + size, "content-type", 12);
166
315
  http_header_match2(buf, buf + size, "host", 4);
167
168
  /* find_hdr_value_end */
169
315
  http_find_hdr_value_end(buf, buf + size);
170
171
315
  free(buf);
172
315
}
173
174
/* Exercise host/port, scheme validation, method lookup, qvalue, etags */
175
575
static void fuzz_misc(const uint8_t *data, size_t size) {
176
575
  char *buf = (char *)malloc(size + 1);
177
575
  if (!buf)
178
0
    return;
179
575
  memcpy(buf, data, size);
180
575
  buf[size] = '\0';
181
182
575
  struct ist s = ist2(buf, size);
183
184
  /* Host port extraction */
185
575
  http_get_host_port(s);
186
187
  /* Default port check */
188
575
  http_is_default_port(IST_NULL, s);
189
575
  http_is_default_port(ist("http://"), s);
190
575
  http_is_default_port(ist("https://"), s);
191
192
  /* Scheme validation */
193
575
  http_validate_scheme(s);
194
195
  /* Method lookup */
196
575
  find_http_meth(buf, size);
197
198
  /* Status index */
199
575
  if (size >= 2) {
200
538
    unsigned int status = (data[0] << 8) | data[1];
201
538
    http_get_status_idx(status);
202
538
    http_get_reason(status);
203
538
  }
204
205
  /* qvalue parsing */
206
575
  const char *end = NULL;
207
575
  http_parse_qvalue(buf, &end);
208
209
  /* ETag comparison: split input in half */
210
575
  if (size >= 4) {
211
293
    size_t half = size / 2;
212
293
    struct ist etag1 = ist2(buf, half);
213
293
    struct ist etag2 = ist2(buf + half, size - half);
214
293
    http_compare_etags(etag1, etag2);
215
293
  }
216
217
  /* Trim leading spaces (safe with standalone buffers) */
218
575
  http_trim_leading_spht(s);
219
  /* NOTE: http_trim_trailing_spht is intentionally not fuzzed here because
220
   * it reads ret.ptr[-1] which assumes the ist points into the middle of
221
   * a larger buffer. Calling it with a standalone allocation would cause
222
   * a false-positive heap-buffer-overflow. */
223
224
575
  free(buf);
225
575
}
226
227
/* Exercise URL parameter finding */
228
172
static void fuzz_url_params(const uint8_t *data, size_t size) {
229
172
  if (size < 4)
230
2
    return;
231
232
170
  char *buf = (char *)malloc(size + 1);
233
170
  if (!buf)
234
0
    return;
235
170
  memcpy(buf, data, size);
236
170
  buf[size] = '\0';
237
238
  /* Simple single-chunk search */
239
170
  const char *chunks[4];
240
170
  chunks[0] = buf;
241
170
  chunks[1] = buf + size;
242
170
  chunks[2] = NULL;
243
170
  chunks[3] = NULL;
244
245
170
  const char *vstart = NULL, *vend = NULL;
246
247
  /* Search with empty param name (first param) */
248
170
  http_find_next_url_param(chunks, "", 0, &vstart, &vend, '&', 0);
249
250
  /* Search with a 2-byte param name from start */
251
170
  if (size > 4) {
252
151
    char pname[3];
253
151
    memcpy(pname, data, 2);
254
151
    pname[2] = '\0';
255
151
    const char *chunks2[4];
256
151
    chunks2[0] = buf + 2;
257
151
    chunks2[1] = buf + size;
258
151
    chunks2[2] = NULL;
259
151
    chunks2[3] = NULL;
260
151
    http_find_next_url_param(chunks2, pname, 2, &vstart, &vend, '&', 0);
261
262
    /* Also try case-insensitive and ';' delimiter */
263
151
    http_find_next_url_param(chunks2, pname, 2, &vstart, &vend, ';', 1);
264
151
  }
265
266
170
  free(buf);
267
170
}
268
269
/* Exercise cookie value end finding */
270
89
static void fuzz_cookie_value_end(const uint8_t *data, size_t size) {
271
89
  char *buf = (char *)malloc(size + 1);
272
89
  if (!buf)
273
0
    return;
274
89
  memcpy(buf, data, size);
275
89
  buf[size] = '\0';
276
277
89
  http_find_cookie_value_end(buf, buf + size);
278
279
89
  free(buf);
280
89
}
281
282
2.08k
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
283
2.08k
  if (size < 2)
284
1
    return 0;
285
286
  /* Use first byte to select which function group to exercise */
287
2.08k
  uint8_t selector = fuzz_consume_byte(&data, &size);
288
289
2.08k
  switch (selector % 7) {
290
113
  case 0:
291
113
    fuzz_uri_parsing(data, size);
292
113
    break;
293
493
  case 1:
294
493
    fuzz_content_length(data, size);
295
493
    break;
296
329
  case 2:
297
329
    fuzz_cookie_parsing(data, size);
298
329
    break;
299
315
  case 3:
300
315
    fuzz_header_parsing(data, size);
301
315
    break;
302
575
  case 4:
303
575
    fuzz_misc(data, size);
304
575
    break;
305
172
  case 5:
306
172
    fuzz_url_params(data, size);
307
172
    break;
308
89
  case 6:
309
89
    fuzz_cookie_value_end(data, size);
310
89
    break;
311
2.08k
  }
312
313
2.08k
  return 0;
314
2.08k
}