Coverage Report

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