/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.24k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
37 | 3.24k | if (size < 6) |
38 | 4 | return 0; |
39 | | |
40 | | /* Use first 2 bytes as control */ |
41 | 3.23k | uint8_t mode = data[0]; |
42 | 3.23k | uint8_t extra = data[1]; |
43 | 3.23k | data += 2; |
44 | 3.23k | size -= 2; |
45 | | |
46 | 3.23k | struct evhttp *http_val = evhttp_new(NULL); |
47 | 3.23k | if (!http_val) |
48 | 0 | return 0; |
49 | | |
50 | | /* Set up a fake connection context for request parsing */ |
51 | 3.23k | struct evhttp_connection evcon; |
52 | 3.23k | memset(&evcon, 0, sizeof(evcon)); |
53 | 3.23k | evcon.ext_method_cmp = NULL; |
54 | 3.23k | evcon.max_headers_size = (extra % 4 == 0) ? 256 : 8192; |
55 | 3.23k | evcon.http_server = http_val; |
56 | | |
57 | | /* === Parse as HTTP request with proper firstline -> headers flow === */ |
58 | 3.23k | if (mode & 0x01) { |
59 | 2.81k | struct evhttp_request *req = evhttp_request_new(NULL, NULL); |
60 | 2.81k | if (req) { |
61 | 2.81k | req->kind = EVHTTP_REQUEST; |
62 | 2.81k | req->evcon = &evcon; |
63 | | |
64 | 2.81k | struct evbuffer *buf = evbuffer_new(); |
65 | 2.81k | if (buf) { |
66 | 2.81k | evbuffer_add(buf, data, size); |
67 | | |
68 | 2.81k | enum message_read_status s = evhttp_parse_firstline_(req, buf); |
69 | 2.81k | if (s == ALL_DATA_READ) { |
70 | | /* Successfully parsed request line, now parse headers */ |
71 | 127 | s = evhttp_parse_headers_(req, buf); |
72 | 127 | if (s == ALL_DATA_READ) { |
73 | | /* Successfully parsed headers, examine parsed data */ |
74 | 36 | evhttp_request_get_host(req); |
75 | 36 | evhttp_request_get_uri(req); |
76 | 36 | evhttp_request_get_command(req); |
77 | | |
78 | 36 | struct evkeyvalq *hdrs = evhttp_request_get_input_headers(req); |
79 | 36 | if (hdrs) { |
80 | 36 | evhttp_find_header(hdrs, "Content-Length"); |
81 | 36 | evhttp_find_header(hdrs, "Transfer-Encoding"); |
82 | 36 | evhttp_find_header(hdrs, "Connection"); |
83 | 36 | evhttp_find_header(hdrs, "Host"); |
84 | 36 | evhttp_find_header(hdrs, "Expect"); |
85 | 36 | evhttp_find_header(hdrs, "Content-Type"); |
86 | 36 | } |
87 | | |
88 | 36 | const struct evhttp_uri *uri = evhttp_request_get_evhttp_uri(req); |
89 | 36 | if (uri) { |
90 | 36 | evhttp_uri_get_scheme(uri); |
91 | 36 | evhttp_uri_get_host(uri); |
92 | 36 | evhttp_uri_get_port(uri); |
93 | 36 | evhttp_uri_get_path(uri); |
94 | 36 | evhttp_uri_get_query(uri); |
95 | 36 | evhttp_uri_get_fragment(uri); |
96 | 36 | evhttp_uri_get_userinfo(uri); |
97 | 36 | } |
98 | | |
99 | | /* Move remaining data as body */ |
100 | 36 | size_t rem = evbuffer_get_length(buf); |
101 | 36 | if (rem > 0) { |
102 | 20 | struct evbuffer *body = evhttp_request_get_input_buffer(req); |
103 | 20 | if (body) |
104 | 20 | evbuffer_add_buffer(body, buf); |
105 | 20 | } |
106 | 36 | } |
107 | 127 | } |
108 | 2.81k | evbuffer_free(buf); |
109 | 2.81k | } |
110 | 2.81k | evhttp_request_free(req); |
111 | 2.81k | } |
112 | 2.81k | } |
113 | | |
114 | | /* === Parse as HTTP response === */ |
115 | 3.23k | if (mode & 0x02) { |
116 | 1.96k | struct evhttp_request *req = evhttp_request_new(NULL, NULL); |
117 | 1.96k | if (req) { |
118 | 1.96k | req->kind = EVHTTP_RESPONSE; |
119 | 1.96k | req->evcon = &evcon; |
120 | | |
121 | 1.96k | struct evbuffer *buf = evbuffer_new(); |
122 | 1.96k | if (buf) { |
123 | 1.96k | evbuffer_add(buf, data, size); |
124 | | |
125 | 1.96k | enum message_read_status s = evhttp_parse_firstline_(req, buf); |
126 | 1.96k | if (s == ALL_DATA_READ) { |
127 | 93 | s = evhttp_parse_headers_(req, buf); |
128 | 93 | if (s == ALL_DATA_READ) { |
129 | 11 | int code = evhttp_request_get_response_code(req); |
130 | 11 | (void)code; |
131 | 11 | const char *reason = evhttp_request_get_response_code_line(req); |
132 | 11 | (void)reason; |
133 | | |
134 | 11 | struct evkeyvalq *hdrs = evhttp_request_get_input_headers(req); |
135 | 11 | if (hdrs) { |
136 | 11 | evhttp_find_header(hdrs, "Content-Length"); |
137 | 11 | evhttp_find_header(hdrs, "Transfer-Encoding"); |
138 | 11 | evhttp_find_header(hdrs, "Set-Cookie"); |
139 | 11 | evhttp_find_header(hdrs, "Location"); |
140 | 11 | } |
141 | 11 | } |
142 | 93 | } |
143 | 1.96k | evbuffer_free(buf); |
144 | 1.96k | } |
145 | 1.96k | evhttp_request_free(req); |
146 | 1.96k | } |
147 | 1.96k | } |
148 | | |
149 | | /* === Parse request with prepended valid method lines to stress header parsing === */ |
150 | 3.23k | if (mode & 0x04) { |
151 | 2.07k | static const char *methods[] = { |
152 | 2.07k | "POST / HTTP/1.1\r\n", |
153 | 2.07k | "PUT /resource HTTP/1.1\r\n", |
154 | 2.07k | "DELETE /item HTTP/1.1\r\n", |
155 | 2.07k | "PATCH /update HTTP/1.1\r\n", |
156 | 2.07k | "OPTIONS * HTTP/1.1\r\n", |
157 | 2.07k | "PROPFIND /dav HTTP/1.1\r\n", |
158 | 2.07k | "PROPPATCH /dav HTTP/1.1\r\n", |
159 | 2.07k | "MKCOL /dir HTTP/1.1\r\n", |
160 | 2.07k | "LOCK /file HTTP/1.1\r\n", |
161 | 2.07k | "UNLOCK /file HTTP/1.1\r\n", |
162 | 2.07k | "COPY /src HTTP/1.1\r\n", |
163 | 2.07k | "MOVE /src HTTP/1.1\r\n", |
164 | 2.07k | }; |
165 | 2.07k | const char *method = methods[extra % 12]; |
166 | | |
167 | 2.07k | struct evhttp_request *req = evhttp_request_new(NULL, NULL); |
168 | 2.07k | if (req) { |
169 | 2.07k | req->kind = EVHTTP_REQUEST; |
170 | 2.07k | req->evcon = &evcon; |
171 | | |
172 | 2.07k | struct evbuffer *buf = evbuffer_new(); |
173 | 2.07k | if (buf) { |
174 | 2.07k | evbuffer_add(buf, method, strlen(method)); |
175 | 2.07k | evbuffer_add(buf, data, size); |
176 | | |
177 | 2.07k | enum message_read_status s = evhttp_parse_firstline_(req, buf); |
178 | 2.07k | if (s == ALL_DATA_READ) { |
179 | 2.07k | s = evhttp_parse_headers_(req, buf); |
180 | 2.07k | if (s == ALL_DATA_READ) { |
181 | 97 | evhttp_request_get_host(req); |
182 | 97 | evhttp_request_get_command(req); |
183 | 97 | } |
184 | 2.07k | } |
185 | 2.07k | evbuffer_free(buf); |
186 | 2.07k | } |
187 | 2.07k | evhttp_request_free(req); |
188 | 2.07k | } |
189 | 2.07k | } |
190 | | |
191 | | /* === Parse response with prepended valid status line to stress header parsing === */ |
192 | 3.23k | if (mode & 0x08) { |
193 | 2.36k | static const char *status_lines[] = { |
194 | 2.36k | "HTTP/1.1 200 OK\r\n", |
195 | 2.36k | "HTTP/1.0 404 Not Found\r\n", |
196 | 2.36k | "HTTP/1.1 301 Moved Permanently\r\n", |
197 | 2.36k | "HTTP/1.1 500 Internal Server Error\r\n", |
198 | 2.36k | "HTTP/1.1 100 Continue\r\n", |
199 | 2.36k | "HTTP/1.1 204 No Content\r\n", |
200 | 2.36k | "HTTP/1.0 302 Found\r\n", |
201 | 2.36k | "HTTP/1.1 403 Forbidden\r\n", |
202 | 2.36k | }; |
203 | 2.36k | const char *status = status_lines[extra % 8]; |
204 | | |
205 | 2.36k | struct evhttp_request *req = evhttp_request_new(NULL, NULL); |
206 | 2.36k | if (req) { |
207 | 2.36k | req->kind = EVHTTP_RESPONSE; |
208 | 2.36k | req->evcon = &evcon; |
209 | | |
210 | 2.36k | struct evbuffer *buf = evbuffer_new(); |
211 | 2.36k | if (buf) { |
212 | 2.36k | evbuffer_add(buf, status, strlen(status)); |
213 | 2.36k | evbuffer_add(buf, data, size); |
214 | | |
215 | 2.36k | enum message_read_status s = evhttp_parse_firstline_(req, buf); |
216 | 2.36k | if (s == ALL_DATA_READ) { |
217 | 2.36k | s = evhttp_parse_headers_(req, buf); |
218 | 2.36k | if (s == ALL_DATA_READ) { |
219 | 82 | evhttp_request_get_response_code(req); |
220 | 82 | evhttp_request_get_response_code_line(req); |
221 | | |
222 | 82 | struct evkeyvalq *hdrs = evhttp_request_get_input_headers(req); |
223 | 82 | if (hdrs) { |
224 | 82 | evhttp_find_header(hdrs, "Content-Type"); |
225 | 82 | evhttp_find_header(hdrs, "Server"); |
226 | 82 | } |
227 | 82 | } |
228 | 2.36k | } |
229 | 2.36k | evbuffer_free(buf); |
230 | 2.36k | } |
231 | 2.36k | evhttp_request_free(req); |
232 | 2.36k | } |
233 | 2.36k | } |
234 | | |
235 | | /* === Exercise evhttp_decode_uri with percent-encoded data === */ |
236 | 3.23k | if (mode & 0x10) { |
237 | 2.58k | char *uri_str = (char *)malloc(size + 1); |
238 | 2.58k | if (uri_str) { |
239 | 2.58k | memcpy(uri_str, data, size); |
240 | 2.58k | uri_str[size] = '\0'; |
241 | | |
242 | 2.58k | char *decoded = evhttp_decode_uri(uri_str); |
243 | 2.58k | if (decoded) |
244 | 2.58k | free(decoded); |
245 | | |
246 | 2.58k | char *encoded = evhttp_encode_uri(uri_str); |
247 | 2.58k | if (encoded) |
248 | 2.58k | free(encoded); |
249 | | |
250 | 2.58k | char *escaped = evhttp_htmlescape(uri_str); |
251 | 2.58k | if (escaped) |
252 | 2.58k | free(escaped); |
253 | | |
254 | 2.58k | struct evhttp_uri *uri = evhttp_uri_parse(uri_str); |
255 | 2.58k | if (uri) { |
256 | 675 | char uri_buf[256]; |
257 | 675 | evhttp_uri_join(uri, uri_buf, sizeof(uri_buf)); |
258 | 675 | evhttp_uri_free(uri); |
259 | 675 | } |
260 | | |
261 | 2.58k | free(uri_str); |
262 | 2.58k | } |
263 | 2.58k | } |
264 | | |
265 | | /* === Exercise evhttp_parse_query and evhttp_parse_query_str_flags === */ |
266 | 3.23k | if (mode & 0x20) { |
267 | 2.50k | char *query = (char *)malloc(size + 1); |
268 | 2.50k | if (query) { |
269 | 2.50k | memcpy(query, data, size); |
270 | 2.50k | query[size] = '\0'; |
271 | | |
272 | 2.50k | struct evkeyvalq params; |
273 | 2.50k | TAILQ_INIT(¶ms); |
274 | | |
275 | | /* Exercise both the simple and flagged versions */ |
276 | 2.50k | if (evhttp_parse_query(query, ¶ms) == 0) { |
277 | 574 | evhttp_clear_headers(¶ms); |
278 | 574 | } |
279 | | |
280 | 2.50k | int flags = (extra & 0x0F); |
281 | 2.50k | if (evhttp_parse_query_str_flags(query, ¶ms, flags) == 0) { |
282 | 1.55k | evhttp_clear_headers(¶ms); |
283 | 1.55k | } |
284 | | |
285 | 2.50k | free(query); |
286 | 2.50k | } |
287 | 2.50k | } |
288 | | |
289 | | /* === Exercise header add/remove/clear operations === */ |
290 | 3.23k | if (mode & 0x40) { |
291 | 2.21k | struct evkeyvalq headers; |
292 | 2.21k | TAILQ_INIT(&headers); |
293 | | |
294 | | /* Split fuzz data into key/value pairs to add as headers */ |
295 | 2.21k | size_t pos = 0; |
296 | 2.21k | int count = 0; |
297 | 37.5k | while (pos < size && count < 32) { |
298 | | /* Find a separator for key */ |
299 | 36.8k | size_t key_end = pos; |
300 | 22.8M | while (key_end < size && data[key_end] != ':' && data[key_end] != '\0') |
301 | 22.7M | key_end++; |
302 | 36.8k | if (key_end >= size) break; |
303 | | |
304 | 35.3k | size_t val_start = key_end + 1; |
305 | 35.3k | size_t val_end = val_start; |
306 | 26.0M | while (val_end < size && data[val_end] != '\n' && data[val_end] != '\0') |
307 | 25.9M | val_end++; |
308 | | |
309 | 35.3k | if (key_end > pos) { |
310 | 3.96k | char *key = (char *)malloc(key_end - pos + 1); |
311 | 3.96k | char *val = (char *)malloc(val_end - val_start + 1); |
312 | 3.96k | if (key && val) { |
313 | 3.96k | memcpy(key, data + pos, key_end - pos); |
314 | 3.96k | key[key_end - pos] = '\0'; |
315 | 3.96k | memcpy(val, data + val_start, val_end - val_start); |
316 | 3.96k | val[val_end - val_start] = '\0'; |
317 | | |
318 | 3.96k | evhttp_add_header(&headers, key, val); |
319 | 3.96k | count++; |
320 | 3.96k | } |
321 | 3.96k | free(key); |
322 | 3.96k | free(val); |
323 | 3.96k | } |
324 | 35.3k | pos = val_end + 1; |
325 | 35.3k | } |
326 | | |
327 | | /* Try to find some headers */ |
328 | 2.21k | if (count > 0) { |
329 | 948 | evhttp_find_header(&headers, "Content-Type"); |
330 | 948 | evhttp_find_header(&headers, "Host"); |
331 | 948 | } |
332 | | |
333 | 2.21k | evhttp_clear_headers(&headers); |
334 | 2.21k | } |
335 | | |
336 | 3.23k | evhttp_free(http_val); |
337 | 3.23k | return 0; |
338 | 3.23k | } |