Coverage Report

Created: 2026-06-30 06:44

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/fuzz_h1_htx.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/1-to-HTX conversion pipeline.
19
 *
20
 * This targets h1_htx.c which converts raw HTTP/1 wire data into HAProxy's
21
 * internal HTX (HTTP Transaction) representation. Every HTTP/1 request from
22
 * an untrusted client passes through this code path:
23
 *
24
 *   h1_parse_msg_hdrs()  - parses request/response line + headers into HTX
25
 *   h1_parse_msg_data()  - parses body (content-length / chunked / EOF)
26
 *   h1_parse_msg_tlrs()  - parses chunked-encoding trailers
27
 *
28
 * This pipeline exercises:
29
 *   - h1_htx.c  (header post-processing, HTX block construction)
30
 *   - htx.c     (HTX buffer management, block allocation)
31
 *   - h1.c      (low-level H1 state machine via h1_headers_to_hdr_list)
32
 *
33
 * The first byte of fuzz input selects request vs response mode.
34
 * The remaining bytes are treated as a raw HTTP/1 message (headers + body).
35
 */
36
37
#include <haproxy/h1.h>
38
#include <haproxy/h1_htx.h>
39
#include <haproxy/htx.h>
40
#include <haproxy/http-hdr.h>
41
#include <haproxy/global.h>
42
#include <haproxy/buf.h>
43
#include <haproxy/chunk.h>
44
45
#include <stdint.h>
46
#include <string.h>
47
#include <stdlib.h>
48
49
1
#define MAX_HDR_NUM    101
50
17.8k
#define HTX_BUF_SIZE   65536
51
2
#define TRASH_BUF_SIZE 65536
52
53
static int fuzz_initialized = 0;
54
static char *htx_area = NULL;
55
56
5.42k
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
57
5.42k
  struct h1m h1m;
58
5.42k
  union h1_sl h1sl;
59
5.42k
  struct buffer srcbuf;
60
5.42k
  struct buffer htxbuf;
61
5.42k
  struct htx *htx;
62
5.42k
  char *input;
63
5.42k
  int ret;
64
5.42k
  size_t ofs;
65
66
5.42k
  if (size < 2)
67
1
    return 0;
68
69
  /* One-time init: replicate the trash buffer setup that haproxy normally
70
   * performs at startup (alloc_early_trash + alloc_trash_buffers_per_thread).
71
   * init_trash_buffers() is the public entry point that creates the pool and
72
   * allocates the two alternating trash buffers (trash_buf1/trash_buf2) plus
73
   * the scratch buffer (trash) used throughout haproxy.  The first call with
74
   * first=1 mirrors alloc_early_trash(); the second with first=0 also creates
75
   * the large and small trash pools and their per-thread buffers. */
76
5.42k
  if (!fuzz_initialized) {
77
1
    htx_area = malloc(HTX_BUF_SIZE);
78
1
    if (!htx_area)
79
0
      return 0;
80
1
    global.tune.bufsize = TRASH_BUF_SIZE;
81
1
    global.tune.bufsize_large = TRASH_BUF_SIZE * 2;
82
1
    global.tune.bufsize_small = 1024;
83
1
    global.tune.max_http_hdr = MAX_HDR_NUM;
84
1
    if (!init_trash_buffers(1))
85
0
      return 0;
86
1
    if (!init_trash_buffers(0))
87
0
      return 0;
88
1
    fuzz_initialized = 1;
89
1
  }
90
91
  /* First byte: bit 0 selects request vs response */
92
5.42k
  int parse_response = data[0] & 1;
93
5.42k
  data++;
94
5.42k
  size--;
95
96
  /* We need a mutable copy for the source buffer.
97
   * IMPORTANT: HAProxy uses ring buffers where b_tail wraps around when
98
   * data == capacity. We must allocate capacity > data to prevent
99
   * b_tail from wrapping to the start (which would give zero-length input
100
   * to the parser). */
101
5.42k
  input = (char *)malloc(size + 1);
102
5.42k
  if (!input)
103
0
    return 0;
104
5.42k
  memcpy(input, data, size);
105
106
  /* Set up source buffer: capacity = size+1 so b_tail doesn't wrap */
107
5.42k
  srcbuf = b_make(input, size + 1, 0, size);
108
109
  /* Set up destination HTX buffer */
110
5.42k
  memset(htx_area, 0, HTX_BUF_SIZE);
111
5.42k
  htxbuf = b_make(htx_area, HTX_BUF_SIZE, 0, 0);
112
5.42k
  htx = htx_from_buf(&htxbuf);
113
114
  /* Initialize the H1 message parser */
115
5.42k
  if (parse_response)
116
2.95k
    h1m_init_res(&h1m);
117
2.47k
  else
118
2.47k
    h1m_init_req(&h1m);
119
120
  /* Phase 1: Parse headers into HTX */
121
5.42k
  ret = h1_parse_msg_hdrs(&h1m, &h1sl, htx, &srcbuf, 0, HTX_BUF_SIZE);
122
123
5.42k
  if (ret > 0) {
124
2.55k
    ofs = (size_t)ret;
125
126
    /* Phase 2: Parse body data */
127
2.55k
    if (ofs < size && h1m.state < H1_MSG_DONE) {
128
1.43k
      h1_parse_msg_data(&h1m, &htx, &srcbuf, ofs, HTX_BUF_SIZE, &htxbuf);
129
1.43k
    }
130
131
    /* Phase 3: Parse trailers (for chunked encoding) */
132
2.55k
    if (h1m.state == H1_MSG_TRAILERS) {
133
95
      h1_parse_msg_tlrs(&h1m, htx, &srcbuf, ofs, HTX_BUF_SIZE);
134
95
    }
135
2.55k
  }
136
137
5.42k
  free(input);
138
5.42k
  return 0;
139
5.42k
}