/src/curl_fuzzer/curl_fuzzer_doh.cc
Line | Count | Source |
1 | | /*************************************************************************** |
2 | | * _ _ ____ _ |
3 | | * Project ___| | | | _ \| | | |
4 | | * / __| | | | |_) | | |
5 | | * | (__| |_| | _ <| |___ |
6 | | * \___|\___/|_| \_\_____| |
7 | | * |
8 | | * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al. |
9 | | * |
10 | | * This software is licensed as described in the file COPYING, which |
11 | | * you should have received as part of this distribution. The terms |
12 | | * are also available at https://curl.se/docs/copyright.html. |
13 | | * |
14 | | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | | * copies of the Software, and permit persons to whom the Software is |
16 | | * furnished to do so, under the terms of the COPYING file. |
17 | | * |
18 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | | * KIND, either express or implied. |
20 | | * |
21 | | * SPDX-License-Identifier: curl |
22 | | * |
23 | | ***************************************************************************/ |
24 | | |
25 | | /** |
26 | | * @file |
27 | | * @brief Loopback HTTP responder for the legacy DoH coverage lane. |
28 | | * |
29 | | * curl creates its internal DoH easy handle itself and does not propagate the |
30 | | * harness socketpair callbacks to it. A seed that sets TLV_TYPE_DOH_LOOPBACK |
31 | | * therefore gets a real plaintext HTTP listener on 127.0.0.1, and |
32 | | * CURLOPT_DOH_URL points at it. The listener synthesizes a DNS answer matching |
33 | | * the probe's QTYPE or serves a seed-supplied response body verbatim. |
34 | | */ |
35 | | |
36 | | #include <stdio.h> |
37 | | #include <stdlib.h> |
38 | | #include <string.h> |
39 | | #include <strings.h> |
40 | | #include <unistd.h> |
41 | | #include <errno.h> |
42 | | #include <fcntl.h> |
43 | | #include <sys/select.h> |
44 | | #include <sys/socket.h> |
45 | | #include <netinet/in.h> |
46 | | #include <arpa/inet.h> |
47 | | #include <curl/curl.h> |
48 | | #include "curl_fuzzer.h" |
49 | | |
50 | | /** |
51 | | * No-op debug callback used by the verbose DoH lane. Turning on verbose makes |
52 | | * curl evaluate the arguments to infof(), which is what reaches the DoH |
53 | | * status/error string helpers. The callback keeps that tracing off stderr. |
54 | | */ |
55 | | int fuzz_doh_debug_callback(CURL *handle, |
56 | | curl_infotype type, |
57 | | char *data, |
58 | | size_t size, |
59 | | void *userptr) |
60 | 228k | { |
61 | 228k | FUZZ_DATA *fuzz = (FUZZ_DATA *)userptr; |
62 | | |
63 | 228k | (void)handle; |
64 | 228k | if(fuzz && fuzz->verbose && type == CURLINFO_TEXT && size) |
65 | 0 | printf("FUZZ: DoH trace: %.*s", (int)size, data); |
66 | 228k | (void)type; |
67 | 228k | (void)data; |
68 | 228k | (void)size; |
69 | 228k | return 0; |
70 | 228k | } |
71 | | |
72 | | /** |
73 | | * Reset the loopback DoH responder. FUZZ_DATA is memset twice (once before a |
74 | | * possible early exit and once inside fuzz_initialize_fuzz_data), so the |
75 | | * sentinel descriptors must be installed after each memset. |
76 | | */ |
77 | | void fuzz_doh_server_init(FUZZ_DATA *fuzz) |
78 | 454k | { |
79 | 454k | int i; |
80 | | |
81 | 454k | fuzz->doh.listener = CURL_SOCKET_BAD; |
82 | 454k | fuzz->doh.enabled = 0; |
83 | 454k | fuzz->doh.http_status = 200; |
84 | 454k | fuzz->doh.response = NULL; |
85 | 454k | fuzz->doh.response_len = 0; |
86 | 454k | fuzz->doh.url[0] = '\0'; |
87 | 454k | fuzz->doh.saved_no_proxy_lower = NULL; |
88 | 454k | fuzz->doh.saved_no_proxy_upper = NULL; |
89 | 454k | fuzz->doh.lower_no_proxy_was_set = 0; |
90 | 454k | fuzz->doh.upper_no_proxy_was_set = 0; |
91 | 454k | fuzz->doh.proxy_bypass_active = 0; |
92 | | |
93 | 2.27M | for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++) { |
94 | 1.81M | fuzz->doh.conns[i].fd = CURL_SOCKET_BAD; |
95 | 1.81M | fuzz->doh.conns[i].state = FUZZ_DOH_CONN_FREE; |
96 | 1.81M | fuzz->doh.conns[i].request_len = 0; |
97 | 1.81M | fuzz->doh.conns[i].response_len = 0; |
98 | 1.81M | fuzz->doh.conns[i].response_off = 0; |
99 | 1.81M | } |
100 | 454k | } |
101 | | |
102 | | static void fuzz_doh_conn_close(FUZZ_DOH_CONN *conn) |
103 | 911k | { |
104 | 911k | if(conn->fd != CURL_SOCKET_BAD) { |
105 | 3.00k | close(conn->fd); |
106 | 3.00k | conn->fd = CURL_SOCKET_BAD; |
107 | 3.00k | } |
108 | 911k | conn->state = FUZZ_DOH_CONN_FREE; |
109 | 911k | conn->request_len = 0; |
110 | 911k | conn->response_len = 0; |
111 | 911k | conn->response_off = 0; |
112 | 911k | } |
113 | | |
114 | | /** |
115 | | * Bind a plaintext HTTP listener on 127.0.0.1 and build the CURLOPT_DOH_URL |
116 | | * value that points at it. The DoH sub-handle is created by curl and therefore |
117 | | * opens a real socket instead of the harness socketpair; a real loopback |
118 | | * endpoint is the only harness-side way to complete the probe. |
119 | | */ |
120 | | int fuzz_doh_server_start(FUZZ_DATA *fuzz) |
121 | 3.53k | { |
122 | 3.53k | int fd; |
123 | 3.53k | int flags; |
124 | 3.53k | struct sockaddr_in addr; |
125 | 3.53k | socklen_t addrlen; |
126 | | |
127 | 3.53k | memset(&addr, 0, sizeof(addr)); |
128 | 3.53k | addr.sin_family = AF_INET; |
129 | 3.53k | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
130 | 3.53k | addr.sin_port = 0; |
131 | | |
132 | 3.53k | fd = socket(AF_INET, SOCK_STREAM, 0); |
133 | 3.53k | if(fd < 0) |
134 | 0 | return -1; |
135 | 3.53k | if(fd >= FD_SETSIZE) { |
136 | 0 | close(fd); |
137 | 0 | return -1; |
138 | 0 | } |
139 | | |
140 | 3.53k | if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { |
141 | 0 | close(fd); |
142 | 0 | return -1; |
143 | 0 | } |
144 | 3.53k | if(listen(fd, 8) < 0) { |
145 | 0 | close(fd); |
146 | 0 | return -1; |
147 | 0 | } |
148 | | |
149 | 3.53k | addrlen = sizeof(addr); |
150 | 3.53k | if(getsockname(fd, (struct sockaddr *)&addr, &addrlen) < 0) { |
151 | 0 | close(fd); |
152 | 0 | return -1; |
153 | 0 | } |
154 | | |
155 | 3.53k | flags = fcntl(fd, F_GETFL, 0); |
156 | 3.53k | if(flags < 0) { |
157 | 0 | close(fd); |
158 | 0 | return -1; |
159 | 0 | } |
160 | 3.53k | if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { |
161 | 0 | close(fd); |
162 | 0 | return -1; |
163 | 0 | } |
164 | | |
165 | 3.53k | fuzz->doh.listener = fd; |
166 | 3.53k | snprintf(fuzz->doh.url, sizeof(fuzz->doh.url), |
167 | 3.53k | "http://127.0.0.1:%d/dns-query", (int)ntohs(addr.sin_port)); |
168 | 3.53k | return 0; |
169 | 3.53k | } |
170 | | |
171 | | /** |
172 | | * Copy one proxy environment variable so it can be restored after the input. |
173 | | * @return 0 on success, -1 when a set value could not be copied. |
174 | | */ |
175 | | static int fuzz_save_env(const char *name, char **copy, int *was_set) |
176 | 7.06k | { |
177 | 7.06k | const char *value = getenv(name); |
178 | 7.06k | size_t length; |
179 | | |
180 | 7.06k | *copy = NULL; |
181 | 7.06k | *was_set = 0; |
182 | 7.06k | if(!value) |
183 | 7.06k | return 0; |
184 | | |
185 | 0 | length = strlen(value) + 1; |
186 | 0 | *copy = (char *)malloc(length); |
187 | 0 | if(!*copy) |
188 | 0 | return -1; |
189 | 0 | memcpy(*copy, value, length); |
190 | 0 | *was_set = 1; |
191 | 0 | return 0; |
192 | 0 | } |
193 | | |
194 | | static void fuzz_free_saved_env(char **copy, int *was_set) |
195 | 7.06k | { |
196 | 7.06k | free(*copy); |
197 | 7.06k | *copy = NULL; |
198 | 7.06k | *was_set = 0; |
199 | 7.06k | } |
200 | | |
201 | | /** |
202 | | * Force NO_PROXY for the duration of a loopback input. The internal DoH easy |
203 | | * handle consults the process environment and does not inherit proxy options |
204 | | * from the parent transfer, so an ambient http_proxy/ALL_PROXY could divert |
205 | | * probes away from the listener and generate off-loopback traffic. |
206 | | */ |
207 | | void fuzz_doh_proxy_bypass_begin(FUZZ_DATA *fuzz) |
208 | 3.53k | { |
209 | 3.53k | if(!fuzz->doh.enabled || fuzz->doh.proxy_bypass_active) |
210 | 0 | return; |
211 | | |
212 | 3.53k | if(fuzz_save_env("no_proxy", &fuzz->doh.saved_no_proxy_lower, |
213 | 3.53k | &fuzz->doh.lower_no_proxy_was_set) != 0 || |
214 | 3.53k | fuzz_save_env("NO_PROXY", &fuzz->doh.saved_no_proxy_upper, |
215 | 3.53k | &fuzz->doh.upper_no_proxy_was_set) != 0) { |
216 | 0 | fuzz_free_saved_env(&fuzz->doh.saved_no_proxy_lower, |
217 | 0 | &fuzz->doh.lower_no_proxy_was_set); |
218 | 0 | fuzz_free_saved_env(&fuzz->doh.saved_no_proxy_upper, |
219 | 0 | &fuzz->doh.upper_no_proxy_was_set); |
220 | 0 | return; |
221 | 0 | } |
222 | | |
223 | | /* Mark active before setenv so a partial failure is still restored. */ |
224 | 3.53k | fuzz->doh.proxy_bypass_active = 1; |
225 | 3.53k | if(setenv("no_proxy", "*", 1) != 0 || setenv("NO_PROXY", "*", 1) != 0) |
226 | 0 | fuzz_doh_proxy_bypass_end(fuzz); |
227 | 3.53k | else |
228 | 3.53k | FV_PRINTF(fuzz, "FUZZ: DoH proxy bypass enabled\n"); |
229 | 3.53k | } |
230 | | |
231 | | void fuzz_doh_proxy_bypass_end(FUZZ_DATA *fuzz) |
232 | 227k | { |
233 | 227k | if(!fuzz->doh.proxy_bypass_active) |
234 | 223k | return; |
235 | | |
236 | 3.53k | if(fuzz->doh.lower_no_proxy_was_set) |
237 | 0 | (void)setenv("no_proxy", fuzz->doh.saved_no_proxy_lower, 1); |
238 | 3.53k | else |
239 | 3.53k | (void)unsetenv("no_proxy"); |
240 | 3.53k | if(fuzz->doh.upper_no_proxy_was_set) |
241 | 0 | (void)setenv("NO_PROXY", fuzz->doh.saved_no_proxy_upper, 1); |
242 | 3.53k | else |
243 | 3.53k | (void)unsetenv("NO_PROXY"); |
244 | | |
245 | 3.53k | fuzz_free_saved_env(&fuzz->doh.saved_no_proxy_lower, |
246 | 3.53k | &fuzz->doh.lower_no_proxy_was_set); |
247 | 3.53k | fuzz_free_saved_env(&fuzz->doh.saved_no_proxy_upper, |
248 | 3.53k | &fuzz->doh.upper_no_proxy_was_set); |
249 | 3.53k | fuzz->doh.proxy_bypass_active = 0; |
250 | 3.53k | FV_PRINTF(fuzz, "FUZZ: DoH proxy bypass restored\n"); |
251 | 3.53k | } |
252 | | |
253 | | void fuzz_doh_server_stop(FUZZ_DATA *fuzz) |
254 | 227k | { |
255 | 227k | int i; |
256 | | |
257 | 227k | fuzz_doh_proxy_bypass_end(fuzz); |
258 | | |
259 | 227k | if(fuzz->doh.listener != CURL_SOCKET_BAD) { |
260 | 3.53k | close(fuzz->doh.listener); |
261 | 3.53k | fuzz->doh.listener = CURL_SOCKET_BAD; |
262 | 3.53k | } |
263 | 1.13M | for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++) |
264 | 908k | fuzz_doh_conn_close(&fuzz->doh.conns[i]); |
265 | 227k | } |
266 | | |
267 | | void fuzz_doh_server_add_fds(FUZZ_DATA *fuzz, |
268 | | fd_set *readfds, |
269 | | fd_set *writefds, |
270 | | int *maxfd) |
271 | 86.8k | { |
272 | 86.8k | int i; |
273 | | |
274 | 86.8k | if(fuzz->doh.listener != CURL_SOCKET_BAD) { |
275 | 8.69k | FD_SET(fuzz->doh.listener, readfds); |
276 | 8.69k | *maxfd = FUZZ_MAX((int)fuzz->doh.listener, *maxfd); |
277 | 8.69k | } |
278 | | |
279 | 434k | for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++) { |
280 | 347k | FUZZ_DOH_CONN *conn = &fuzz->doh.conns[i]; |
281 | 347k | if(conn->fd == CURL_SOCKET_BAD) |
282 | 341k | continue; |
283 | 6.00k | if(conn->state == FUZZ_DOH_CONN_READING) |
284 | 6.00k | FD_SET(conn->fd, readfds); |
285 | 3.00k | else if(conn->state == FUZZ_DOH_CONN_WRITING) |
286 | 3.00k | FD_SET(conn->fd, writefds); |
287 | 6.00k | *maxfd = FUZZ_MAX((int)conn->fd, *maxfd); |
288 | 6.00k | } |
289 | 86.8k | } |
290 | | |
291 | | /** |
292 | | * Find the end of the HTTP headers and the Content-Length-delimited body. |
293 | | * @return 1 when a complete request is buffered, 0 when more bytes are needed, |
294 | | * -1 when the request is too large or otherwise unusable. |
295 | | */ |
296 | | static int fuzz_doh_request_complete(FUZZ_DOH_CONN *conn, |
297 | | size_t *body_off, |
298 | | size_t *body_len) |
299 | 3.00k | { |
300 | 3.00k | size_t i; |
301 | 3.00k | size_t header_end = 0; |
302 | 3.00k | size_t content_length = 0; |
303 | 3.00k | int have_length = 0; |
304 | | |
305 | 360k | for(i = 0; (i + 3) < conn->request_len; i++) { |
306 | 360k | if(conn->request[i] == '\r' && conn->request[i + 1] == '\n' && |
307 | 15.0k | conn->request[i + 2] == '\r' && conn->request[i + 3] == '\n') { |
308 | 3.00k | header_end = i + 4; |
309 | 3.00k | break; |
310 | 3.00k | } |
311 | 360k | } |
312 | 3.00k | if(!header_end) |
313 | 0 | return 0; |
314 | | |
315 | 3.00k | i = 0; |
316 | 21.0k | while((i + 1) < header_end) { |
317 | 18.0k | size_t line_end = i; |
318 | 351k | while((line_end + 1) < header_end && |
319 | 351k | !(conn->request[line_end] == '\r' && |
320 | 18.0k | conn->request[line_end + 1] == '\n')) |
321 | 333k | line_end++; |
322 | | |
323 | 18.0k | if((line_end - i) > 15 && |
324 | 12.0k | !strncasecmp((const char *)&conn->request[i], "Content-Length:", 15)) { |
325 | 3.00k | const char *value = (const char *)&conn->request[i + 15]; |
326 | 3.00k | char *end = NULL; |
327 | 3.00k | unsigned long parsed = strtoul(value, &end, 10); |
328 | 3.00k | if(end != value) { |
329 | 3.00k | content_length = (size_t)parsed; |
330 | 3.00k | have_length = 1; |
331 | 3.00k | } |
332 | 3.00k | } |
333 | 18.0k | i = line_end + 2; |
334 | 18.0k | } |
335 | | |
336 | 3.00k | if(!have_length) |
337 | 0 | content_length = conn->request_len - header_end; |
338 | | |
339 | 3.00k | if(content_length > (sizeof(conn->request) - header_end)) |
340 | 0 | return -1; |
341 | 3.00k | if(conn->request_len < (header_end + content_length)) |
342 | 0 | return 0; |
343 | | |
344 | 3.00k | *body_off = header_end; |
345 | 3.00k | *body_len = content_length; |
346 | 3.00k | return 1; |
347 | 3.00k | } |
348 | | |
349 | | /** |
350 | | * Synthesize a minimal DNS response matching the query's QTYPE. A and AAAA |
351 | | * answers point at loopback so the outer transfer can continue through the |
352 | | * existing socketpair mock. HTTPS queries get an HTTPS RR carrying an alpn |
353 | | * SvcParam so the HTTPS-connect filter can select its first ALPN from the |
354 | | * resolver record. Other QTYPEs get NOTIMP. |
355 | | */ |
356 | | static size_t fuzz_doh_build_dns_response(const unsigned char *query, |
357 | | size_t query_len, |
358 | | unsigned char *resp, |
359 | | size_t resp_cap) |
360 | 2.97k | { |
361 | 2.97k | size_t i = 12; |
362 | 2.97k | size_t qname_end; |
363 | 2.97k | unsigned short qtype; |
364 | 2.97k | size_t question_len; |
365 | 2.97k | size_t rdlength; |
366 | 2.97k | size_t total; |
367 | 2.97k | unsigned char *p; |
368 | 2.97k | int has_answer; |
369 | 2.97k | int answer_count; |
370 | 2.97k | int n; |
371 | | |
372 | 2.97k | if(query_len < 12) |
373 | 0 | return 0; |
374 | | |
375 | 7.53k | while(i < query_len) { |
376 | 7.53k | unsigned char label = query[i]; |
377 | 7.53k | if(label == 0) { |
378 | 2.97k | i++; |
379 | 2.97k | break; |
380 | 2.97k | } |
381 | 4.56k | if(label & 0xc0) |
382 | 0 | return 0; |
383 | 4.56k | if((i + 1 + label) > query_len) |
384 | 0 | return 0; |
385 | 4.56k | i += (size_t)1 + label; |
386 | 4.56k | } |
387 | 2.97k | if((i + 4) > query_len) |
388 | 0 | return 0; |
389 | | |
390 | 2.97k | qname_end = i; |
391 | 2.97k | qtype = (unsigned short)((query[qname_end] << 8) | |
392 | 2.97k | query[qname_end + 1]); |
393 | 2.97k | question_len = (qname_end + 4) - 12; |
394 | 2.97k | has_answer = (qtype == 1 || qtype == 28 || qtype == 65); |
395 | 2.97k | if(qtype == 1) |
396 | 1.41k | rdlength = 4; |
397 | 1.55k | else if(qtype == 28) |
398 | 1.31k | rdlength = 16; |
399 | 245 | else |
400 | 245 | rdlength = 13; /* priority + root target + alpn SvcParam */ |
401 | 2.97k | answer_count = (qtype == 65) ? 1 : (has_answer ? 2 : 0); |
402 | 2.97k | total = 12 + question_len + |
403 | 2.97k | ((size_t)answer_count * (12 + rdlength)); |
404 | | |
405 | 2.97k | if(total > resp_cap) |
406 | 0 | return 0; |
407 | | |
408 | 2.97k | memset(resp, 0, 12); |
409 | 2.97k | resp[2] = 0x81; /* QR=1, RD=1 */ |
410 | 2.97k | resp[3] = has_answer ? 0x80 : 0x84; /* RA=1; rcode 4 for unsupported */ |
411 | 2.97k | resp[5] = 1; /* QDCOUNT */ |
412 | 2.97k | resp[7] = (unsigned char)answer_count; /* ANCOUNT */ |
413 | 2.97k | memcpy(&resp[12], &query[12], question_len); |
414 | | |
415 | 2.97k | p = &resp[12 + question_len]; |
416 | 8.66k | for(n = 0; n < answer_count; n++) { |
417 | 5.69k | *p++ = 0xc0; /* compressed name pointer to the question */ |
418 | 5.69k | *p++ = 0x0c; |
419 | 5.69k | *p++ = (unsigned char)(qtype >> 8); |
420 | 5.69k | *p++ = (unsigned char)(qtype & 0xff); |
421 | 5.69k | *p++ = 0; |
422 | 5.69k | *p++ = 1; /* CLASS IN */ |
423 | 5.69k | *p++ = 0; |
424 | 5.69k | *p++ = 0; |
425 | 5.69k | *p++ = 0; |
426 | 5.69k | *p++ = 60; /* TTL */ |
427 | 5.69k | *p++ = 0; |
428 | 5.69k | *p++ = (unsigned char)rdlength; |
429 | 5.69k | if(qtype == 1) { |
430 | 2.83k | *p++ = 127; |
431 | 2.83k | *p++ = 0; |
432 | 2.83k | *p++ = 0; |
433 | 2.83k | *p++ = (unsigned char)(1 + n); |
434 | 2.83k | } |
435 | 2.86k | else if(qtype == 28) { |
436 | 2.62k | memset(p, 0, 16); |
437 | 2.62k | p[15] = (unsigned char)(1 + n); /* ::1, ::2 */ |
438 | 2.62k | p += 16; |
439 | 2.62k | } |
440 | 245 | else { |
441 | | /* HTTPS RR: priority 1, root target, alpn=h2,h3. |
442 | | SvcParam key 1 (alpn) holds length-prefixed protocol names, so the |
443 | | value is 02 'h' '2' 02 'h' '3'. This exercises the HTTPS-connect |
444 | | filter's HTTPS-RR ALPN selection instead of falling back to the |
445 | | configured HTTP-version preference. */ |
446 | 245 | static const unsigned char httpsrr_alpn[] = { |
447 | 245 | 0, 1, /* priority = 1 */ |
448 | 245 | 0, /* root target (".") */ |
449 | 245 | 0, 1, /* SvcParamKey = alpn */ |
450 | 245 | 0, 6, /* SvcParamValue length */ |
451 | 245 | 2, 'h', '2', /* h2 */ |
452 | 245 | 2, 'h', '3' /* h3 */ |
453 | 245 | }; |
454 | 245 | memcpy(p, httpsrr_alpn, sizeof(httpsrr_alpn)); |
455 | 245 | p += sizeof(httpsrr_alpn); |
456 | 245 | } |
457 | 5.69k | } |
458 | | |
459 | 2.97k | return (size_t)(p - resp); |
460 | 2.97k | } |
461 | | |
462 | | static int fuzz_doh_build_http_response(FUZZ_DATA *fuzz, |
463 | | FUZZ_DOH_CONN *conn, |
464 | | const unsigned char *body, |
465 | | size_t body_len) |
466 | 3.00k | { |
467 | 3.00k | int header_len; |
468 | | |
469 | 3.00k | header_len = snprintf((char *)conn->response, sizeof(conn->response), |
470 | 3.00k | "HTTP/1.1 %u DoH\r\n" |
471 | 3.00k | "Content-Type: application/dns-message\r\n" |
472 | 3.00k | "Content-Length: %zu\r\n" |
473 | 3.00k | "Connection: close\r\n" |
474 | 3.00k | "\r\n", |
475 | 3.00k | fuzz->doh.http_status, body_len); |
476 | 3.00k | if(header_len < 0) |
477 | 0 | return -1; |
478 | 3.00k | if((size_t)header_len + body_len > sizeof(conn->response)) |
479 | 0 | return -1; |
480 | | |
481 | 3.00k | if(body_len) |
482 | 3.00k | memcpy(&conn->response[header_len], body, body_len); |
483 | 3.00k | conn->response_len = (size_t)header_len + body_len; |
484 | 3.00k | conn->response_off = 0; |
485 | 3.00k | return 0; |
486 | 3.00k | } |
487 | | |
488 | | /** |
489 | | * Accept pending probe connections and advance the small request/response |
490 | | * state machine. Called with the fd sets returned by select(). |
491 | | */ |
492 | | int fuzz_doh_server_service(FUZZ_DATA *fuzz, |
493 | | fd_set *readfds, |
494 | | fd_set *writefds) |
495 | 78.0k | { |
496 | 78.0k | int i; |
497 | 78.0k | int progress = 0; |
498 | | |
499 | 78.0k | if(fuzz->doh.listener != CURL_SOCKET_BAD && |
500 | 78.0k | FD_ISSET(fuzz->doh.listener, readfds)) { |
501 | 4.66k | for(;;) { |
502 | 4.66k | curl_socket_t fd = accept(fuzz->doh.listener, NULL, NULL); |
503 | 4.66k | FUZZ_DOH_CONN *conn = NULL; |
504 | 4.66k | int flags; |
505 | | |
506 | 4.66k | if(fd < 0) |
507 | 1.66k | break; |
508 | 3.00k | if(fd >= FD_SETSIZE) { |
509 | 0 | close(fd); |
510 | 0 | continue; |
511 | 0 | } |
512 | 4.53k | for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++) { |
513 | 4.53k | if(fuzz->doh.conns[i].fd == CURL_SOCKET_BAD) { |
514 | 3.00k | conn = &fuzz->doh.conns[i]; |
515 | 3.00k | break; |
516 | 3.00k | } |
517 | 4.53k | } |
518 | 3.00k | if(!conn) { |
519 | 0 | close(fd); |
520 | 0 | continue; |
521 | 0 | } |
522 | | |
523 | 3.00k | flags = fcntl(fd, F_GETFL, 0); |
524 | 3.00k | if(flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { |
525 | 0 | close(fd); |
526 | 0 | continue; |
527 | 0 | } |
528 | | |
529 | 3.00k | conn->fd = fd; |
530 | 3.00k | conn->state = FUZZ_DOH_CONN_READING; |
531 | 3.00k | conn->request_len = 0; |
532 | 3.00k | conn->response_len = 0; |
533 | 3.00k | conn->response_off = 0; |
534 | 3.00k | progress = 1; |
535 | 3.00k | FV_PRINTF(fuzz, "FUZZ: DoH accepted fd %d\n", (int)fd); |
536 | 3.00k | } |
537 | 1.66k | } |
538 | | |
539 | 390k | for(i = 0; i < FUZZ_DOH_MAX_CONNECTIONS; i++) { |
540 | 312k | FUZZ_DOH_CONN *conn = &fuzz->doh.conns[i]; |
541 | 312k | if(conn->fd == CURL_SOCKET_BAD) |
542 | 303k | continue; |
543 | | |
544 | 9.01k | if(conn->state == FUZZ_DOH_CONN_READING && |
545 | 9.01k | FD_ISSET(conn->fd, readfds)) { |
546 | 6.01k | for(;;) { |
547 | 6.01k | ssize_t n; |
548 | 6.01k | if(conn->request_len >= sizeof(conn->request)) |
549 | 0 | break; |
550 | 6.01k | n = recv(conn->fd, &conn->request[conn->request_len], |
551 | 6.01k | sizeof(conn->request) - conn->request_len, 0); |
552 | 6.01k | if(n > 0) { |
553 | 3.00k | conn->request_len += (size_t)n; |
554 | 3.00k | progress = 1; |
555 | 3.00k | continue; |
556 | 3.00k | } |
557 | 3.00k | if(n == 0) { |
558 | 0 | fuzz_doh_conn_close(conn); |
559 | 0 | break; |
560 | 0 | } |
561 | 3.00k | if(errno == EINTR) |
562 | 0 | continue; |
563 | 3.00k | if(errno == EAGAIN || errno == EWOULDBLOCK) |
564 | 3.00k | break; |
565 | 0 | fuzz_doh_conn_close(conn); |
566 | 0 | break; |
567 | 3.00k | } |
568 | | |
569 | 3.00k | if(conn->fd != CURL_SOCKET_BAD) { |
570 | 3.00k | size_t body_off = 0; |
571 | 3.00k | size_t body_len = 0; |
572 | 3.00k | int complete = fuzz_doh_request_complete(conn, &body_off, &body_len); |
573 | | |
574 | 3.00k | if(complete < 0) { |
575 | 0 | fuzz_doh_conn_close(conn); |
576 | 0 | } |
577 | 3.00k | else if(complete > 0) { |
578 | 3.00k | const unsigned char *body = &conn->request[body_off]; |
579 | 3.00k | unsigned char dns[512]; |
580 | | |
581 | 3.00k | if(fuzz->doh.response) { |
582 | 34 | body = fuzz->doh.response; |
583 | 34 | body_len = fuzz->doh.response_len; |
584 | 34 | } |
585 | 2.97k | else { |
586 | 2.97k | size_t dns_len = fuzz_doh_build_dns_response(body, body_len, |
587 | 2.97k | dns, sizeof(dns)); |
588 | 2.97k | if(!dns_len) { |
589 | 0 | fuzz_doh_conn_close(conn); |
590 | 0 | continue; |
591 | 0 | } |
592 | 2.97k | body = dns; |
593 | 2.97k | body_len = dns_len; |
594 | 2.97k | } |
595 | | |
596 | 3.00k | if(fuzz_doh_build_http_response(fuzz, conn, body, body_len) != 0) |
597 | 0 | fuzz_doh_conn_close(conn); |
598 | 3.00k | else |
599 | 3.00k | conn->state = FUZZ_DOH_CONN_WRITING; |
600 | 3.00k | progress = 1; |
601 | 3.00k | FV_PRINTF(fuzz, "FUZZ: DoH request complete, %zu byte body\n", |
602 | 3.00k | body_len); |
603 | 3.00k | } |
604 | 3.00k | } |
605 | 3.00k | } |
606 | | |
607 | 9.01k | if(conn->fd != CURL_SOCKET_BAD && |
608 | 9.01k | conn->state == FUZZ_DOH_CONN_WRITING && |
609 | 9.01k | FD_ISSET(conn->fd, writefds)) { |
610 | 3.00k | ssize_t n = send(conn->fd, &conn->response[conn->response_off], |
611 | 3.00k | conn->response_len - conn->response_off, 0); |
612 | 3.00k | if(n > 0) { |
613 | 3.00k | conn->response_off += (size_t)n; |
614 | 3.00k | progress = 1; |
615 | 3.00k | if(conn->response_off >= conn->response_len) |
616 | 3.00k | fuzz_doh_conn_close(conn); |
617 | 3.00k | } |
618 | 0 | else if(n == 0) |
619 | 0 | fuzz_doh_conn_close(conn); |
620 | 0 | else if(errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) |
621 | 0 | fuzz_doh_conn_close(conn); |
622 | 3.00k | } |
623 | 9.01k | } |
624 | | |
625 | 78.0k | return progress; |
626 | 78.0k | } |
627 | | |