/src/http_message_fuzzer.cc
Line | Count | Source |
1 | | /* Copyright 2026 Google LLC |
2 | | Licensed under the Apache License, Version 2.0 (the "License"); |
3 | | you may not use this file except in compliance with the License. |
4 | | You may obtain a copy of the License at |
5 | | http://www.apache.org/licenses/LICENSE-2.0 |
6 | | Unless required by applicable law or agreed to in writing, software |
7 | | distributed under the License is distributed on an "AS IS" BASIS, |
8 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
9 | | See the License for the specific language governing permissions and |
10 | | limitations under the License. |
11 | | */ |
12 | | |
13 | | /* Fuzzer for the full HTTP parsing state machine: |
14 | | * - Request line and response line parsing with all HTTP methods |
15 | | * - Header parsing including continuation lines |
16 | | * - Body reading with Content-Length |
17 | | * - Chunked transfer encoding |
18 | | * - Trailer headers after chunked body |
19 | | * - URI parsing with various flags |
20 | | */ |
21 | | |
22 | | #include <stddef.h> |
23 | | #include <stdint.h> |
24 | | #include <stdlib.h> |
25 | | #include <string.h> |
26 | | |
27 | | extern "C" { |
28 | | #include "libevent/include/event2/event.h" |
29 | | #include "libevent/include/event2/buffer.h" |
30 | | #include "libevent/include/event2/http.h" |
31 | | #include "libevent/include/event2/http_struct.h" |
32 | | #include "libevent/include/event2/keyvalq_struct.h" |
33 | | #include "libevent/http-internal.h" |
34 | | } |
35 | | |
36 | 3.25k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
37 | 3.25k | if (size < 6) |
38 | 4 | return 0; |
39 | | |
40 | | /* Use first 2 bytes as control */ |
41 | 3.24k | uint8_t mode = data[0]; |
42 | 3.24k | uint8_t extra = data[1]; |
43 | 3.24k | data += 2; |
44 | 3.24k | size -= 2; |
45 | | |
46 | 3.24k | struct evhttp *http_val = evhttp_new(NULL); |
47 | 3.24k | if (!http_val) |
48 | 0 | return 0; |
49 | | |
50 | | /* Set up a fake connection context for request parsing */ |
51 | 3.24k | struct evhttp_connection evcon; |
52 | 3.24k | memset(&evcon, 0, sizeof(evcon)); |
53 | 3.24k | evcon.ext_method_cmp = NULL; |
54 | 3.24k | evcon.max_headers_size = (extra % 4 == 0) ? 256 : 8192; |
55 | 3.24k | evcon.http_server = http_val; |
56 | | |
57 | | /* === Parse as HTTP request with proper firstline -> headers flow === */ |
58 | 3.24k | if (mode & 0x01) { |
59 | 2.72k | struct evhttp_request *req = evhttp_request_new(NULL, NULL); |
60 | 2.72k | if (req) { |
61 | 2.72k | req->kind = EVHTTP_REQUEST; |
62 | 2.72k | req->evcon = &evcon; |
63 | | |
64 | 2.72k | struct evbuffer *buf = evbuffer_new(); |
65 | 2.72k | if (buf) { |
66 | 2.72k | evbuffer_add(buf, data, size); |
67 | | |
68 | 2.72k | enum message_read_status s = evhttp_parse_firstline_(req, buf); |
69 | 2.72k | if (s == ALL_DATA_READ) { |
70 | | /* Successfully parsed request line, now parse headers */ |
71 | 130 | s = evhttp_parse_headers_(req, buf); |
72 | 130 | if (s == ALL_DATA_READ) { |
73 | | /* Successfully parsed headers, examine parsed data */ |
74 | 42 | evhttp_request_get_host(req); |
75 | 42 | evhttp_request_get_uri(req); |
76 | 42 | evhttp_request_get_command(req); |
77 | | |
78 | 42 | struct evkeyvalq *hdrs = evhttp_request_get_input_headers(req); |
79 | 42 | if (hdrs) { |
80 | 42 | evhttp_find_header(hdrs, "Content-Length"); |
81 | 42 | evhttp_find_header(hdrs, "Transfer-Encoding"); |
82 | 42 | evhttp_find_header(hdrs, "Connection"); |
83 | 42 | evhttp_find_header(hdrs, "Host"); |
84 | 42 | evhttp_find_header(hdrs, "Expect"); |
85 | 42 | evhttp_find_header(hdrs, "Content-Type"); |
86 | 42 | } |
87 | | |
88 | 42 | const struct evhttp_uri *uri = evhttp_request_get_evhttp_uri(req); |
89 | 42 | if (uri) { |
90 | 42 | evhttp_uri_get_scheme(uri); |
91 | 42 | evhttp_uri_get_host(uri); |
92 | 42 | evhttp_uri_get_port(uri); |
93 | 42 | evhttp_uri_get_path(uri); |
94 | 42 | evhttp_uri_get_query(uri); |
95 | 42 | evhttp_uri_get_fragment(uri); |
96 | 42 | evhttp_uri_get_userinfo(uri); |
97 | 42 | } |
98 | | |
99 | | /* Move remaining data as body */ |
100 | 42 | size_t rem = evbuffer_get_length(buf); |
101 | 42 | if (rem > 0) { |
102 | 25 | struct evbuffer *body = evhttp_request_get_input_buffer(req); |
103 | 25 | if (body) |
104 | 25 | evbuffer_add_buffer(body, buf); |
105 | 25 | } |
106 | 42 | } |
107 | 130 | } |
108 | 2.72k | evbuffer_free(buf); |
109 | 2.72k | } |
110 | 2.72k | evhttp_request_free(req); |
111 | 2.72k | } |
112 | 2.72k | } |
113 | | |
114 | | /* === Parse as HTTP response === */ |
115 | 3.24k | if (mode & 0x02) { |
116 | 1.88k | struct evhttp_request *req = evhttp_request_new(NULL, NULL); |
117 | 1.88k | if (req) { |
118 | 1.88k | req->kind = EVHTTP_RESPONSE; |
119 | 1.88k | req->evcon = &evcon; |
120 | | |
121 | 1.88k | struct evbuffer *buf = evbuffer_new(); |
122 | 1.88k | if (buf) { |
123 | 1.88k | evbuffer_add(buf, data, size); |
124 | | |
125 | 1.88k | enum message_read_status s = evhttp_parse_firstline_(req, buf); |
126 | 1.88k | if (s == ALL_DATA_READ) { |
127 | 93 | s = evhttp_parse_headers_(req, buf); |
128 | 93 | if (s == ALL_DATA_READ) { |
129 | 12 | int code = evhttp_request_get_response_code(req); |
130 | 12 | (void)code; |
131 | 12 | const char *reason = evhttp_request_get_response_code_line(req); |
132 | 12 | (void)reason; |
133 | | |
134 | 12 | struct evkeyvalq *hdrs = evhttp_request_get_input_headers(req); |
135 | 12 | if (hdrs) { |
136 | 12 | evhttp_find_header(hdrs, "Content-Length"); |
137 | 12 | evhttp_find_header(hdrs, "Transfer-Encoding"); |
138 | 12 | evhttp_find_header(hdrs, "Set-Cookie"); |
139 | 12 | evhttp_find_header(hdrs, "Location"); |
140 | 12 | } |
141 | 12 | } |
142 | 93 | } |
143 | 1.88k | evbuffer_free(buf); |
144 | 1.88k | } |
145 | 1.88k | evhttp_request_free(req); |
146 | 1.88k | } |
147 | 1.88k | } |
148 | | |
149 | | /* === Parse request with prepended valid method lines to stress header parsing === */ |
150 | 3.24k | if (mode & 0x04) { |
151 | 1.97k | static const char *methods[] = { |
152 | 1.97k | "POST / HTTP/1.1\r\n", |
153 | 1.97k | "PUT /resource HTTP/1.1\r\n", |
154 | 1.97k | "DELETE /item HTTP/1.1\r\n", |
155 | 1.97k | "PATCH /update HTTP/1.1\r\n", |
156 | 1.97k | "OPTIONS * HTTP/1.1\r\n", |
157 | 1.97k | "PROPFIND /dav HTTP/1.1\r\n", |
158 | 1.97k | "PROPPATCH /dav HTTP/1.1\r\n", |
159 | 1.97k | "MKCOL /dir HTTP/1.1\r\n", |
160 | 1.97k | "LOCK /file HTTP/1.1\r\n", |
161 | 1.97k | "UNLOCK /file HTTP/1.1\r\n", |
162 | 1.97k | "COPY /src HTTP/1.1\r\n", |
163 | 1.97k | "MOVE /src HTTP/1.1\r\n", |
164 | 1.97k | }; |
165 | 1.97k | const char *method = methods[extra % 12]; |
166 | | |
167 | 1.97k | struct evhttp_request *req = evhttp_request_new(NULL, NULL); |
168 | 1.97k | if (req) { |
169 | 1.97k | req->kind = EVHTTP_REQUEST; |
170 | 1.97k | req->evcon = &evcon; |
171 | | |
172 | 1.97k | struct evbuffer *buf = evbuffer_new(); |
173 | 1.97k | if (buf) { |
174 | 1.97k | evbuffer_add(buf, method, strlen(method)); |
175 | 1.97k | evbuffer_add(buf, data, size); |
176 | | |
177 | 1.97k | enum message_read_status s = evhttp_parse_firstline_(req, buf); |
178 | 1.97k | if (s == ALL_DATA_READ) { |
179 | 1.97k | s = evhttp_parse_headers_(req, buf); |
180 | 1.97k | if (s == ALL_DATA_READ) { |
181 | 88 | evhttp_request_get_host(req); |
182 | 88 | evhttp_request_get_command(req); |
183 | 88 | } |
184 | 1.97k | } |
185 | 1.97k | evbuffer_free(buf); |
186 | 1.97k | } |
187 | 1.97k | evhttp_request_free(req); |
188 | 1.97k | } |
189 | 1.97k | } |
190 | | |
191 | | /* === Parse response with prepended valid status line to stress header parsing === */ |
192 | 3.24k | if (mode & 0x08) { |
193 | 2.42k | static const char *status_lines[] = { |
194 | 2.42k | "HTTP/1.1 200 OK\r\n", |
195 | 2.42k | "HTTP/1.0 404 Not Found\r\n", |
196 | 2.42k | "HTTP/1.1 301 Moved Permanently\r\n", |
197 | 2.42k | "HTTP/1.1 500 Internal Server Error\r\n", |
198 | 2.42k | "HTTP/1.1 100 Continue\r\n", |
199 | 2.42k | "HTTP/1.1 204 No Content\r\n", |
200 | 2.42k | "HTTP/1.0 302 Found\r\n", |
201 | 2.42k | "HTTP/1.1 403 Forbidden\r\n", |
202 | 2.42k | }; |
203 | 2.42k | const char *status = status_lines[extra % 8]; |
204 | | |
205 | 2.42k | struct evhttp_request *req = evhttp_request_new(NULL, NULL); |
206 | 2.42k | if (req) { |
207 | 2.42k | req->kind = EVHTTP_RESPONSE; |
208 | 2.42k | req->evcon = &evcon; |
209 | | |
210 | 2.42k | struct evbuffer *buf = evbuffer_new(); |
211 | 2.42k | if (buf) { |
212 | 2.42k | evbuffer_add(buf, status, strlen(status)); |
213 | 2.42k | evbuffer_add(buf, data, size); |
214 | | |
215 | 2.42k | enum message_read_status s = evhttp_parse_firstline_(req, buf); |
216 | 2.42k | if (s == ALL_DATA_READ) { |
217 | 2.42k | s = evhttp_parse_headers_(req, buf); |
218 | 2.42k | if (s == ALL_DATA_READ) { |
219 | 87 | evhttp_request_get_response_code(req); |
220 | 87 | evhttp_request_get_response_code_line(req); |
221 | | |
222 | 87 | struct evkeyvalq *hdrs = evhttp_request_get_input_headers(req); |
223 | 87 | if (hdrs) { |
224 | 87 | evhttp_find_header(hdrs, "Content-Type"); |
225 | 87 | evhttp_find_header(hdrs, "Server"); |
226 | 87 | } |
227 | 87 | } |
228 | 2.42k | } |
229 | 2.42k | evbuffer_free(buf); |
230 | 2.42k | } |
231 | 2.42k | evhttp_request_free(req); |
232 | 2.42k | } |
233 | 2.42k | } |
234 | | |
235 | | /* === Exercise evhttp_decode_uri with percent-encoded data === */ |
236 | 3.24k | if (mode & 0x10) { |
237 | 2.53k | char *uri_str = (char *)malloc(size + 1); |
238 | 2.53k | if (uri_str) { |
239 | 2.53k | memcpy(uri_str, data, size); |
240 | 2.53k | uri_str[size] = '\0'; |
241 | | |
242 | 2.53k | char *decoded = evhttp_decode_uri(uri_str); |
243 | 2.53k | if (decoded) |
244 | 2.53k | free(decoded); |
245 | | |
246 | 2.53k | char *encoded = evhttp_encode_uri(uri_str); |
247 | 2.53k | if (encoded) |
248 | 2.53k | free(encoded); |
249 | | |
250 | 2.53k | char *escaped = evhttp_htmlescape(uri_str); |
251 | 2.53k | if (escaped) |
252 | 2.53k | free(escaped); |
253 | | |
254 | 2.53k | struct evhttp_uri *uri = evhttp_uri_parse(uri_str); |
255 | 2.53k | if (uri) { |
256 | 713 | char uri_buf[256]; |
257 | 713 | evhttp_uri_join(uri, uri_buf, sizeof(uri_buf)); |
258 | 713 | evhttp_uri_free(uri); |
259 | 713 | } |
260 | | |
261 | 2.53k | free(uri_str); |
262 | 2.53k | } |
263 | 2.53k | } |
264 | | |
265 | | /* === Exercise evhttp_parse_query and evhttp_parse_query_str_flags === */ |
266 | 3.24k | if (mode & 0x20) { |
267 | 2.60k | char *query = (char *)malloc(size + 1); |
268 | 2.60k | if (query) { |
269 | 2.60k | memcpy(query, data, size); |
270 | 2.60k | query[size] = '\0'; |
271 | | |
272 | 2.60k | struct evkeyvalq params; |
273 | 2.60k | TAILQ_INIT(¶ms); |
274 | | |
275 | | /* Exercise both the simple and flagged versions */ |
276 | 2.60k | if (evhttp_parse_query(query, ¶ms) == 0) { |
277 | 624 | evhttp_clear_headers(¶ms); |
278 | 624 | } |
279 | | |
280 | 2.60k | int flags = (extra & 0x0F); |
281 | 2.60k | if (evhttp_parse_query_str_flags(query, ¶ms, flags) == 0) { |
282 | 1.60k | evhttp_clear_headers(¶ms); |
283 | 1.60k | } |
284 | | |
285 | 2.60k | free(query); |
286 | 2.60k | } |
287 | 2.60k | } |
288 | | |
289 | | /* === Exercise header add/remove/clear operations === */ |
290 | 3.24k | if (mode & 0x40) { |
291 | 2.11k | struct evkeyvalq headers; |
292 | 2.11k | TAILQ_INIT(&headers); |
293 | | |
294 | | /* Split fuzz data into key/value pairs to add as headers */ |
295 | 2.11k | size_t pos = 0; |
296 | 2.11k | int count = 0; |
297 | 1.63M | while (pos < size && count < 32) { |
298 | | /* Find a separator for key */ |
299 | 1.62M | size_t key_end = pos; |
300 | 23.9M | while (key_end < size && data[key_end] != ':' && data[key_end] != '\0') |
301 | 22.3M | key_end++; |
302 | 1.62M | if (key_end >= size) break; |
303 | | |
304 | 1.62M | size_t val_start = key_end + 1; |
305 | 1.62M | size_t val_end = val_start; |
306 | 43.9M | while (val_end < size && data[val_end] != '\n' && data[val_end] != '\0') |
307 | 42.3M | val_end++; |
308 | | |
309 | 1.62M | if (key_end > pos) { |
310 | 3.32k | char *key = (char *)malloc(key_end - pos + 1); |
311 | 3.32k | char *val = (char *)malloc(val_end - val_start + 1); |
312 | 3.32k | if (key && val) { |
313 | 3.32k | memcpy(key, data + pos, key_end - pos); |
314 | 3.32k | key[key_end - pos] = '\0'; |
315 | 3.32k | memcpy(val, data + val_start, val_end - val_start); |
316 | 3.32k | val[val_end - val_start] = '\0'; |
317 | | |
318 | 3.32k | evhttp_add_header(&headers, key, val); |
319 | 3.32k | count++; |
320 | 3.32k | } |
321 | 3.32k | free(key); |
322 | 3.32k | free(val); |
323 | 3.32k | } |
324 | 1.62M | pos = val_end + 1; |
325 | 1.62M | } |
326 | | |
327 | | /* Try to find some headers */ |
328 | 2.11k | if (count > 0) { |
329 | 945 | evhttp_find_header(&headers, "Content-Type"); |
330 | 945 | evhttp_find_header(&headers, "Host"); |
331 | 945 | } |
332 | | |
333 | 2.11k | evhttp_clear_headers(&headers); |
334 | 2.11k | } |
335 | | |
336 | 3.24k | evhttp_free(http_val); |
337 | 3.24k | return 0; |
338 | 3.24k | } |