Coverage Report

Created: 2026-10-13 06:32

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.11k
static inline uint8_t fuzz_consume_byte(const uint8_t **data, size_t *size) {
50
2.11k
  if (*size == 0)
51
0
    return 0;
52
2.11k
  uint8_t b = **data;
53
2.11k
  (*data)++;
54
2.11k
  (*size)--;
55
2.11k
  return b;
56
2.11k
}
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
498
static void fuzz_content_length(const uint8_t *data, size_t size) {
83
498
  char *buf = (char *)malloc(size + 1);
84
498
  if (!buf)
85
0
    return;
86
498
  memcpy(buf, data, size);
87
498
  buf[size] = '\0';
88
89
498
  struct ist value = ist2(buf, size);
90
498
  unsigned long long body_len = 0;
91
92
  /* First occurrence */
93
498
  int ret = http_parse_cont_len_header(&value, &body_len, 0);
94
95
  /* Simulate a second header with same data (duplicate CL detection) */
96
498
  if (ret > 0) {
97
263
    struct ist value2 = ist2(buf, size);
98
263
    http_parse_cont_len_header(&value2, &body_len, 1);
99
263
  }
100
101
498
  free(buf);
102
498
}
103
104
/* Exercise cookie extraction */
105
338
static void fuzz_cookie_parsing(const uint8_t *data, size_t size) {
106
338
  if (size < 2)
107
1
    return;
108
109
337
  char *buf = (char *)malloc(size + 1);
110
337
  if (!buf)
111
0
    return;
112
337
  memcpy(buf, data, size);
113
337
  buf[size] = '\0';
114
115
337
  char *hdr = buf;
116
337
  const char *hdr_end = buf + size;
117
337
  char *value = NULL;
118
337
  size_t value_l = 0;
119
120
  /* Extract cookie with empty name (match any) */
121
337
  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
337
  if (size > 6) {
125
230
    char name[5];
126
230
    memcpy(name, data, 4);
127
230
    name[4] = '\0';
128
230
    char *search_start = buf + 4;
129
230
    http_extract_cookie_value(search_start, hdr_end, name, 4, 1,
130
230
                              &value, &value_l);
131
230
  }
132
133
  /* Test http_extract_next_cookie_name */
134
337
  char *ptr = NULL;
135
337
  size_t len = 0;
136
337
  http_extract_next_cookie_name(hdr, buf + size, 1, &ptr, &len);
137
337
  http_extract_next_cookie_name(hdr, buf + size, 0, &ptr, &len);
138
139
337
  free(buf);
140
337
}
141
142
/* Exercise header/line parsing utilities */
143
319
static void fuzz_header_parsing(const uint8_t *data, size_t size) {
144
319
  char *buf = (char *)malloc(size + 1);
145
319
  if (!buf)
146
0
    return;
147
319
  memcpy(buf, data, size);
148
319
  buf[size] = '\0';
149
150
319
  struct ist hdr = ist2(buf, size);
151
319
  struct ist name, value;
152
153
  /* Parse a header line */
154
319
  http_parse_header(hdr, &name, &value);
155
156
  /* Parse a start line */
157
319
  struct ist p1, p2, p3;
158
319
  http_parse_stline(hdr, &p1, &p2, &p3);
159
160
  /* Parse status value */
161
319
  struct ist status, reason;
162
319
  http_parse_status_val(hdr, &status, &reason);
163
164
  /* header_match2 with a known header name */
165
319
  http_header_match2(buf, buf + size, "content-type", 12);
166
319
  http_header_match2(buf, buf + size, "host", 4);
167
168
  /* find_hdr_value_end */
169
319
  http_find_hdr_value_end(buf, buf + size);
170
171
319
  free(buf);
172
319
}
173
174
/* Exercise host/port, scheme validation, method lookup, qvalue, etags */
175
591
static void fuzz_misc(const uint8_t *data, size_t size) {
176
591
  char *buf = (char *)malloc(size + 1);
177
591
  if (!buf)
178
0
    return;
179
591
  memcpy(buf, data, size);
180
591
  buf[size] = '\0';
181
182
591
  struct ist s = ist2(buf, size);
183
184
  /* Host port extraction */
185
591
  http_get_host_port(s);
186
187
  /* Default port check */
188
591
  http_is_default_port(IST_NULL, s);
189
591
  http_is_default_port(ist("http://"), s);
190
591
  http_is_default_port(ist("https://"), s);
191
192
  /* Scheme validation */
193
591
  http_validate_scheme(s);
194
195
  /* Method lookup */
196
591
  find_http_meth(buf, size);
197
198
  /* Status index */
199
591
  if (size >= 2) {
200
552
    unsigned int status = (data[0] << 8) | data[1];
201
552
    http_get_status_idx(status);
202
552
    http_get_reason(status);
203
552
  }
204
205
  /* qvalue parsing */
206
591
  const char *end = NULL;
207
591
  http_parse_qvalue(buf, &end);
208
209
  /* ETag comparison: split input in half */
210
591
  if (size >= 4) {
211
311
    size_t half = size / 2;
212
311
    struct ist etag1 = ist2(buf, half);
213
311
    struct ist etag2 = ist2(buf + half, size - half);
214
311
    http_compare_etags(etag1, etag2);
215
311
  }
216
217
  /* Trim leading spaces (safe with standalone buffers) */
218
591
  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
591
  free(buf);
225
591
}
226
227
/* Exercise URL parameter finding */
228
167
static void fuzz_url_params(const uint8_t *data, size_t size) {
229
167
  if (size < 4)
230
2
    return;
231
232
165
  char *buf = (char *)malloc(size + 1);
233
165
  if (!buf)
234
0
    return;
235
165
  memcpy(buf, data, size);
236
165
  buf[size] = '\0';
237
238
  /* Simple single-chunk search */
239
165
  const char *chunks[4];
240
165
  chunks[0] = buf;
241
165
  chunks[1] = buf + size;
242
165
  chunks[2] = NULL;
243
165
  chunks[3] = NULL;
244
245
165
  const char *vstart = NULL, *vend = NULL;
246
247
  /* Search with empty param name (first param) */
248
165
  http_find_next_url_param(chunks, "", 0, &vstart, &vend, '&', 0);
249
250
  /* Search with a 2-byte param name from start */
251
165
  if (size > 4) {
252
148
    char pname[3];
253
148
    memcpy(pname, data, 2);
254
148
    pname[2] = '\0';
255
148
    const char *chunks2[4];
256
148
    chunks2[0] = buf + 2;
257
148
    chunks2[1] = buf + size;
258
148
    chunks2[2] = NULL;
259
148
    chunks2[3] = NULL;
260
148
    http_find_next_url_param(chunks2, pname, 2, &vstart, &vend, '&', 0);
261
262
    /* Also try case-insensitive and ';' delimiter */
263
148
    http_find_next_url_param(chunks2, pname, 2, &vstart, &vend, ';', 1);
264
148
  }
265
266
165
  free(buf);
267
165
}
268
269
/* Exercise cookie value end finding */
270
91
static void fuzz_cookie_value_end(const uint8_t *data, size_t size) {
271
91
  char *buf = (char *)malloc(size + 1);
272
91
  if (!buf)
273
0
    return;
274
91
  memcpy(buf, data, size);
275
91
  buf[size] = '\0';
276
277
91
  http_find_cookie_value_end(buf, buf + size);
278
279
91
  free(buf);
280
91
}
281
282
2.11k
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
283
2.11k
  if (size < 2)
284
1
    return 0;
285
286
  /* Use first byte to select which function group to exercise */
287
2.11k
  uint8_t selector = fuzz_consume_byte(&data, &size);
288
289
2.11k
  switch (selector % 7) {
290
113
  case 0:
291
113
    fuzz_uri_parsing(data, size);
292
113
    break;
293
498
  case 1:
294
498
    fuzz_content_length(data, size);
295
498
    break;
296
338
  case 2:
297
338
    fuzz_cookie_parsing(data, size);
298
338
    break;
299
319
  case 3:
300
319
    fuzz_header_parsing(data, size);
301
319
    break;
302
591
  case 4:
303
591
    fuzz_misc(data, size);
304
591
    break;
305
167
  case 5:
306
167
    fuzz_url_params(data, size);
307
167
    break;
308
91
  case 6:
309
91
    fuzz_cookie_value_end(data, size);
310
91
    break;
311
2.11k
  }
312
313
2.11k
  return 0;
314
2.11k
}