/src/libcups/ossfuzz/fuzzhttpconn.c
Line | Count | Source |
1 | | /* |
2 | | Copyright The libcups Developers. |
3 | | Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | you may not use this file except in compliance with the License. |
5 | | You may obtain a copy of the License at |
6 | | http://www.apache.org/licenses/LICENSE-2.0 |
7 | | Unless required by applicable law or agreed to in writing, software |
8 | | distributed under the License is distributed on an "AS IS" BASIS, |
9 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
10 | | See the License for the specific language governing permissions and |
11 | | limitations under the License. |
12 | | */ |
13 | | |
14 | | // Fuzzes the HTTP/1.x connection state machine in http.c over a local socket. |
15 | | // |
16 | | // Input layout: byte 0 selects the mode, the rest is the raw peer byte stream. |
17 | | // bit 0 clear: server mode - input is one or more client requests. |
18 | | // bit 0 set: client mode - input is the server response to a GET request |
19 | | // (POST with a small chunked body if bit 1 is also set). |
20 | | |
21 | | #include <errno.h> |
22 | | #include <signal.h> |
23 | | #include <stdint.h> |
24 | | #include <stdio.h> |
25 | | #include <stdlib.h> |
26 | | #include <string.h> |
27 | | #include <sys/socket.h> |
28 | | #include <sys/un.h> |
29 | | #include <unistd.h> |
30 | | #include <zlib.h> |
31 | | #include "cups.h" |
32 | | #include "http-private.h" |
33 | | |
34 | 5.21k | #define MAX_INPUT 65536 |
35 | 3.31k | #define MAX_REQUESTS 8 |
36 | 432 | #define MAX_UPDATES 64 |
37 | 13.0k | #define MAX_BODY (1024 * 1024) |
38 | | |
39 | | static int listen_fd = -1; |
40 | | static char sock_path[sizeof(((struct sockaddr_un *)0)->sun_path)]; |
41 | | static const char resource[] = "/ipp/print"; |
42 | | |
43 | | |
44 | | static const char * |
45 | | password_cb(const char *prompt, http_t *http, const char *method, const char *res, void *cb_data) |
46 | 5.98k | { |
47 | 5.98k | (void)prompt; (void)http; (void)method; (void)res; (void)cb_data; |
48 | | |
49 | 5.98k | return ("fuzz-password"); |
50 | 5.98k | } |
51 | | |
52 | | |
53 | | static const char * |
54 | | oauth_cb(http_t *http, const char *realm, const char *scope, const char *res, void *cb_data) |
55 | 1.90k | { |
56 | 1.90k | (void)http; (void)realm; (void)res; (void)cb_data; |
57 | | |
58 | | // Decline when no scope so Basic/Digest after a Bearer challenge stay reachable. |
59 | 1.90k | return (scope ? "fuzz-token" : NULL); |
60 | 1.90k | } |
61 | | |
62 | | |
63 | | static void |
64 | | remove_socket(void) |
65 | 1 | { |
66 | 1 | if (sock_path[0]) |
67 | 1 | unlink(sock_path); |
68 | 1 | } |
69 | | |
70 | | |
71 | | static void |
72 | | make_addr(struct sockaddr_un *addr) |
73 | 1.73k | { |
74 | 1.73k | memset(addr, 0, sizeof(*addr)); |
75 | 1.73k | addr->sun_family = AF_LOCAL; |
76 | 1.73k | memcpy(addr->sun_path, sock_path, sizeof(addr->sun_path)); |
77 | 1.73k | } |
78 | | |
79 | | |
80 | | static bool |
81 | | init_once(void) |
82 | 5.21k | { |
83 | 5.21k | struct sockaddr_un addr; |
84 | | |
85 | 5.21k | if (listen_fd >= 0) |
86 | 5.21k | return (true); |
87 | | |
88 | | // Library writes to a peer that may already be gone. |
89 | 1 | signal(SIGPIPE, SIG_IGN); |
90 | | |
91 | 1 | cupsSetPasswordCB(password_cb, NULL); |
92 | 1 | cupsSetOAuthCB(oauth_cb, NULL); |
93 | | |
94 | 1 | snprintf(sock_path, sizeof(sock_path), "/tmp/fuzzhttpconn-%d.sock", (int)getpid()); |
95 | 1 | unlink(sock_path); |
96 | | |
97 | 1 | if ((listen_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) |
98 | 0 | return (false); |
99 | | |
100 | 1 | make_addr(&addr); |
101 | | |
102 | 1 | if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) || listen(listen_fd, 16)) |
103 | 0 | { |
104 | 0 | close(listen_fd); |
105 | 0 | listen_fd = -1; |
106 | 0 | return (false); |
107 | 0 | } |
108 | | |
109 | 1 | atexit(remove_socket); |
110 | | |
111 | 1 | return (true); |
112 | 1 | } |
113 | | |
114 | | |
115 | | // Queue the whole input on the peer socket, then half-close so reads hit EOF instead of blocking. |
116 | | static bool |
117 | | feed(int fd, const uint8_t *data, size_t size) |
118 | 5.21k | { |
119 | 10.4k | while (size > 0) |
120 | 5.21k | { |
121 | 5.21k | ssize_t bytes = send(fd, data, size, MSG_NOSIGNAL); |
122 | | |
123 | 5.21k | if (bytes < 0) |
124 | 0 | { |
125 | 0 | if (errno == EINTR) |
126 | 0 | continue; |
127 | | |
128 | 0 | return (false); |
129 | 0 | } |
130 | | |
131 | 5.21k | data += bytes; |
132 | 5.21k | size -= (size_t)bytes; |
133 | 5.21k | } |
134 | | |
135 | 5.21k | return (shutdown(fd, SHUT_WR) == 0); |
136 | 5.21k | } |
137 | | |
138 | | |
139 | | static void |
140 | | check_fields(http_t *http) |
141 | 5.67k | { |
142 | 5.67k | char value[256]; |
143 | | |
144 | 5.67k | httpGetSubField(http, HTTP_FIELD_AUTHORIZATION, "username", value, sizeof(value)); |
145 | 5.67k | httpGetSubField(http, HTTP_FIELD_CONTENT_TYPE, "charset", value, sizeof(value)); |
146 | 5.67k | httpGetSubField(http, HTTP_FIELD_KEEP_ALIVE, "timeout", value, sizeof(value)); |
147 | 5.67k | httpGetSubField(http, HTTP_FIELD_WWW_AUTHENTICATE, "realm", value, sizeof(value)); |
148 | 5.67k | httpGetCookieValue(http, "_TOKEN", value, sizeof(value)); |
149 | 5.67k | httpGetCookieValue(http, "_DEVGRANT", value, sizeof(value)); |
150 | 5.67k | } |
151 | | |
152 | | |
153 | | static void |
154 | | drain_body(http_t *http) |
155 | 4.23k | { |
156 | 4.23k | char buffer[8192]; |
157 | 4.23k | ssize_t bytes; |
158 | 4.23k | size_t total = 0; |
159 | 4.23k | int i; |
160 | | |
161 | 4.23k | (void)httpPeek(http, buffer, sizeof(buffer)); |
162 | | |
163 | 13.0k | for (i = 0; i < 1024 && total < MAX_BODY; i ++) |
164 | 13.0k | { |
165 | 13.0k | if ((bytes = httpRead(http, buffer, sizeof(buffer))) <= 0) |
166 | 4.23k | break; |
167 | | |
168 | 8.83k | total += (size_t)bytes; |
169 | 8.83k | } |
170 | 4.23k | } |
171 | | |
172 | | |
173 | | // httpClose() does not free an unfinished (de)compression stream, so release it first. |
174 | | static void |
175 | | close_http(http_t *http) |
176 | 5.21k | { |
177 | 5.21k | if (http && http->stream) |
178 | 339 | { |
179 | 339 | if (http->coding == _HTTP_CODING_GZIP || http->coding == _HTTP_CODING_DEFLATE) |
180 | 0 | deflateEnd((z_stream *)http->stream); |
181 | 339 | else |
182 | 339 | inflateEnd((z_stream *)http->stream); |
183 | | |
184 | 339 | free(http->stream); |
185 | 339 | free(http->sbuffer); |
186 | | |
187 | 339 | http->stream = NULL; |
188 | 339 | http->sbuffer = NULL; |
189 | 339 | http->coding = _HTTP_CODING_IDENTITY; |
190 | 339 | } |
191 | | |
192 | 5.21k | httpClose(http); |
193 | 5.21k | } |
194 | | |
195 | | |
196 | | static bool |
197 | | is_recv_state(http_state_t state) |
198 | 2.18k | { |
199 | 2.18k | return (state == HTTP_STATE_POST_RECV || state == HTTP_STATE_PUT_RECV || state == HTTP_STATE_LOCK_RECV || state == HTTP_STATE_PROPFIND_RECV || state == HTTP_STATE_PROPPATCH_RECV); |
200 | 2.18k | } |
201 | | |
202 | | |
203 | | // Mirrors respond_http() in tools/ippeveprinter.c. |
204 | | static bool |
205 | | server_respond(http_t *http, http_status_t code, const char *coding) |
206 | 2.94k | { |
207 | 2.94k | static const char body[] = "fuzz response\n"; |
208 | | |
209 | 2.94k | httpClearFields(http); |
210 | 2.94k | httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "text/plain"); |
211 | 2.94k | if (coding) |
212 | 204 | httpSetField(http, HTTP_FIELD_CONTENT_ENCODING, coding); |
213 | 2.94k | httpSetLength(http, 0); |
214 | | |
215 | 2.94k | if (!httpWriteResponse(http, code)) |
216 | 0 | return (false); |
217 | | |
218 | 2.94k | if (httpGetState(http) == HTTP_STATE_WAITING) |
219 | 1.20k | return (true); |
220 | | |
221 | 1.73k | if (httpWrite(http, body, sizeof(body) - 1) < 0) |
222 | 0 | return (false); |
223 | | |
224 | 1.73k | return (httpWrite(http, "", 0) >= 0); |
225 | 1.73k | } |
226 | | |
227 | | |
228 | | static void |
229 | | fuzz_server(const uint8_t *data, size_t size) |
230 | 1.72k | { |
231 | 1.72k | struct sockaddr_un addr; |
232 | 1.72k | http_t *http = NULL; |
233 | 1.72k | int cfd, i; |
234 | | |
235 | 1.72k | if ((cfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) |
236 | 0 | return; |
237 | | |
238 | 1.72k | make_addr(&addr); |
239 | | |
240 | 1.72k | if (connect(cfd, (struct sockaddr *)&addr, sizeof(addr))) |
241 | 0 | goto done; |
242 | | |
243 | 1.72k | if ((http = httpAcceptConnection(listen_fd, true)) == NULL) |
244 | 0 | goto done; |
245 | | |
246 | 1.72k | if (!feed(cfd, data, size)) |
247 | 0 | goto done; |
248 | | |
249 | 3.31k | for (i = 0; i < MAX_REQUESTS; i ++) |
250 | 3.26k | { |
251 | 3.26k | char uri[1024]; |
252 | 3.26k | http_state_t state; |
253 | 3.26k | http_status_t status; |
254 | 3.26k | const char *coding; |
255 | 3.26k | int tries = 0; |
256 | | |
257 | 3.26k | httpClearFields(http); |
258 | 3.26k | httpClearCookie(http); |
259 | | |
260 | 3.63k | while ((state = httpReadRequest(http, uri, sizeof(uri))) == HTTP_STATE_WAITING && ++ tries < 16); |
261 | | |
262 | 3.26k | if (state == HTTP_STATE_WAITING || state == HTTP_STATE_ERROR) |
263 | 324 | break; |
264 | | |
265 | 2.94k | if (state == HTTP_STATE_UNKNOWN_METHOD || state == HTTP_STATE_UNKNOWN_VERSION) |
266 | 740 | { |
267 | 740 | server_respond(http, HTTP_STATUS_BAD_REQUEST, NULL); |
268 | 740 | break; |
269 | 740 | } |
270 | | |
271 | 2.20k | tries = 0; |
272 | 2.20k | do |
273 | 2.20k | { |
274 | 2.20k | status = httpUpdate(http); |
275 | 2.20k | } |
276 | 2.20k | while (status == HTTP_STATUS_CONTINUE && ++ tries < MAX_UPDATES); |
277 | | |
278 | 2.20k | if (status != HTTP_STATUS_OK) |
279 | 14 | { |
280 | 14 | server_respond(http, HTTP_STATUS_BAD_REQUEST, NULL); |
281 | 14 | break; |
282 | 14 | } |
283 | | |
284 | 2.18k | check_fields(http); |
285 | | |
286 | 2.18k | if (is_recv_state(httpGetState(http))) |
287 | 748 | { |
288 | 748 | if (httpGetExpect(http) == HTTP_STATUS_CONTINUE && !httpWriteResponse(http, HTTP_STATUS_CONTINUE)) |
289 | 0 | break; |
290 | | |
291 | 748 | drain_body(http); |
292 | 748 | } |
293 | | |
294 | 2.18k | coding = httpGetContentEncoding(http); |
295 | | |
296 | 2.18k | if (!server_respond(http, HTTP_STATUS_OK, coding) || httpGetState(http) != HTTP_STATE_WAITING) |
297 | 604 | break; |
298 | 2.18k | } |
299 | | |
300 | 1.72k | done: |
301 | | |
302 | 1.72k | close_http(http); |
303 | 1.72k | close(cfd); |
304 | 1.72k | } |
305 | | |
306 | | |
307 | | static void |
308 | | fuzz_client(const uint8_t *data, size_t size, bool post) |
309 | 3.48k | { |
310 | 3.48k | http_t *http; |
311 | 3.48k | http_status_t status; |
312 | 3.48k | const char *method = post ? "POST" : "GET"; |
313 | 3.48k | int sfd = -1, tries = 0; |
314 | | |
315 | 3.48k | if ((http = httpConnect(sock_path, 0, NULL, AF_LOCAL, HTTP_ENCRYPTION_IF_REQUESTED, true, 1000, NULL)) == NULL) |
316 | 0 | return; |
317 | | |
318 | 3.48k | if ((sfd = accept(listen_fd, NULL, NULL)) < 0) |
319 | 0 | goto done; |
320 | | |
321 | 3.48k | if (!feed(sfd, data, size)) |
322 | 0 | goto done; |
323 | | |
324 | 3.48k | if (post) |
325 | 628 | { |
326 | 628 | httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "application/ipp"); |
327 | 628 | httpSetLength(http, 0); |
328 | 628 | } |
329 | | |
330 | 3.48k | if (!httpWriteRequest(http, method, resource)) |
331 | 0 | goto done; |
332 | | |
333 | 3.48k | if (post && (httpWrite(http, "fuzz", 4) < 0 || httpWrite(http, "", 0) < 0)) |
334 | 0 | goto done; |
335 | | |
336 | 3.48k | do |
337 | 3.91k | { |
338 | 3.91k | status = httpUpdate(http); |
339 | 3.91k | } |
340 | 3.91k | while (status == HTTP_STATUS_CONTINUE && ++ tries < MAX_UPDATES); |
341 | | |
342 | 3.48k | check_fields(http); |
343 | | |
344 | | // Never resend: http_send() would reconnect to a listener nobody accepts on and block. |
345 | 3.48k | if (status == HTTP_STATUS_UNAUTHORIZED) |
346 | 921 | cupsDoAuthentication(http, method, resource); |
347 | | |
348 | 3.48k | drain_body(http); |
349 | | |
350 | 3.48k | done: |
351 | | |
352 | 3.48k | close_http(http); |
353 | 3.48k | if (sfd >= 0) |
354 | 3.48k | close(sfd); |
355 | | |
356 | | // WWW-Authenticate "username" parameters change the per-thread user name. |
357 | 3.48k | cupsSetUser(NULL); |
358 | 3.48k | } |
359 | | |
360 | | |
361 | | int |
362 | | LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
363 | 5.21k | { |
364 | 5.21k | if (size < 1 || size > MAX_INPUT + 1 || !init_once()) |
365 | 0 | return (0); |
366 | | |
367 | 5.21k | if (data[0] & 1) |
368 | 3.48k | fuzz_client(data + 1, size - 1, (data[0] & 2) != 0); |
369 | 1.72k | else |
370 | 1.72k | fuzz_server(data + 1, size - 1); |
371 | | |
372 | 5.21k | return (0); |
373 | 5.21k | } |