Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2025 Google LLC |
2 | | // |
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 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | // |
15 | | //////////////////////////////////////////////////////////////////////////////// |
16 | | #include "mhd_helper.h" |
17 | | |
18 | | #include <arpa/inet.h> |
19 | | #include <netinet/in.h> |
20 | | #include <sys/socket.h> |
21 | | #include <unistd.h> |
22 | | |
23 | | #include <algorithm> |
24 | | #include <cstring> |
25 | | #include <fcntl.h> |
26 | | #include <errno.h> |
27 | | #include <poll.h> |
28 | | |
29 | | std::unique_ptr<FuzzedDataProvider> g_fdp; |
30 | | std::mutex g_fdp_mu; |
31 | | |
32 | 579 | std::string b64encode(const std::string &in) { |
33 | 579 | static const char* tbl = |
34 | 579 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
35 | 579 | std::string out; |
36 | 579 | size_t i = 0; |
37 | 2.24k | while (i + 2 < in.size()) { |
38 | 1.66k | unsigned v = (unsigned((unsigned char)in[i]) << 16) | |
39 | 1.66k | (unsigned((unsigned char)in[i+1]) << 8) | |
40 | 1.66k | (unsigned((unsigned char)in[i+2])); |
41 | 1.66k | out.push_back(tbl[(v >> 18) & 63]); |
42 | 1.66k | out.push_back(tbl[(v >> 12) & 63]); |
43 | 1.66k | out.push_back(tbl[(v >> 6) & 63]); |
44 | 1.66k | out.push_back(tbl[(v) & 63]); |
45 | 1.66k | i += 3; |
46 | 1.66k | } |
47 | 579 | if (i + 1 == in.size()) { |
48 | 353 | unsigned v = (unsigned((unsigned char)in[i]) << 16); |
49 | 353 | out.push_back(tbl[(v >> 18) & 63]); |
50 | 353 | out.push_back(tbl[(v >> 12) & 63]); |
51 | 353 | out.push_back('='); |
52 | 353 | out.push_back('='); |
53 | 353 | } else if (i + 2 == in.size()) { |
54 | 87 | unsigned v = (unsigned((unsigned char)in[i]) << 16) | |
55 | 87 | (unsigned((unsigned char)in[i+1]) << 8); |
56 | 87 | out.push_back(tbl[(v >> 18) & 63]); |
57 | 87 | out.push_back(tbl[(v >> 12) & 63]); |
58 | 87 | out.push_back(tbl[(v >> 6) & 63]); |
59 | 87 | out.push_back('='); |
60 | 87 | } |
61 | 579 | return out; |
62 | 579 | } |
63 | | |
64 | 0 | enum MHD_Bool ToMhdBool(bool b) { |
65 | 0 | return b ? MHD_YES : MHD_NO; |
66 | 0 | } |
67 | | |
68 | 349 | std::string safe_ascii(const std::string& in, bool allow_space) { |
69 | 349 | std::string out; out.reserve(in.size()); |
70 | 3.72k | for (unsigned char c : in) { |
71 | 3.72k | if (!c || c=='\r' || c=='\n' || c<32 || c>=127 || (!allow_space && c==' ')) { |
72 | 2.31k | continue; |
73 | 2.31k | } |
74 | 1.40k | out.push_back((char)c); |
75 | 1.40k | } |
76 | 349 | if (out.empty()) { |
77 | 195 | out = "x"; |
78 | 195 | } |
79 | | |
80 | 349 | return out; |
81 | 349 | } |
82 | | |
83 | | // Dummy functions |
84 | 0 | static enum MHD_Bool kv_cb(void*, enum MHD_ValueKind, const struct MHD_NameAndValue*) { |
85 | 0 | return MHD_YES; |
86 | 0 | } |
87 | 0 | static enum MHD_Bool post_cb(void*, const struct MHD_PostField* pf) { |
88 | 0 | return MHD_YES; |
89 | 0 | } |
90 | | |
91 | | /* Start of internal helpers for sending http message to daemon through localhost socket */ |
92 | 1.19k | static int create_socket(uint16_t port) { |
93 | 1.19k | int fd = socket(AF_INET, SOCK_STREAM, 0); |
94 | 1.19k | if (fd < 0) { |
95 | 0 | return -1; |
96 | 0 | } |
97 | | |
98 | | // Use flag to avoid blocking on socket |
99 | 1.19k | int flags = fcntl(fd, F_GETFL, 0); |
100 | 1.19k | if (flags >= 0) { |
101 | 1.19k | fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
102 | 1.19k | } |
103 | 1.19k | struct linger lg{1, 0}; |
104 | 1.19k | setsockopt(fd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg)); |
105 | | |
106 | | // configure the socket to target the daemon |
107 | 1.19k | sockaddr_in addr{}; |
108 | 1.19k | addr.sin_family = AF_INET; |
109 | 1.19k | addr.sin_port = htons(port); |
110 | 1.19k | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
111 | | |
112 | | // Try connect to the daemon on the binded port in localhost |
113 | 1.19k | int rc = connect(fd, (sockaddr*)&addr, sizeof(addr)); |
114 | 1.19k | if (rc == 0) { |
115 | 0 | return fd; |
116 | 0 | } |
117 | | |
118 | | // Early exit for invalid connection |
119 | 1.19k | if (errno != EINPROGRESS) { |
120 | 0 | close(fd); |
121 | 0 | return -1; |
122 | 0 | } |
123 | 1.19k | pollfd p{fd, POLLOUT, 0}; |
124 | 1.19k | if (poll(&p, 1, 5) <= 0) { |
125 | 0 | close(fd); |
126 | 0 | return -1; |
127 | 0 | } |
128 | 1.19k | int err = 0; |
129 | 1.19k | socklen_t elen = sizeof(err); |
130 | 1.19k | if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &elen) != 0 || err != 0) { |
131 | 0 | close(fd); |
132 | 0 | return -1; |
133 | 0 | } |
134 | | |
135 | | // Return the created socket |
136 | 1.19k | return fd; |
137 | 1.19k | } |
138 | | |
139 | 1.19k | static void generate_daemon_options(const std::string& method, DaemonOpts& opts) { |
140 | 1.19k | std::lock_guard<std::mutex> lk(g_fdp_mu); |
141 | 1.19k | if (!g_fdp) { |
142 | 0 | return; |
143 | 0 | } |
144 | | |
145 | | // Generate general daemon options |
146 | 1.19k | opts.omit_host = g_fdp->ConsumeBool(); |
147 | 1.19k | opts.bad_cl = g_fdp->ConsumeBool() && g_fdp->ConsumeBool(); |
148 | 1.19k | opts.keep_alive = g_fdp->ConsumeBool(); |
149 | 1.19k | opts.extra_headers = g_fdp->ConsumeBool(); |
150 | 1.19k | opts.use_digest = g_fdp->ConsumeBool(); |
151 | 1.19k | opts.send_malformed_digest = g_fdp->ConsumeBool(); |
152 | 1.19k | if (g_fdp->ConsumeBool()) { |
153 | 456 | opts.realm_hint = g_fdp->ConsumeRandomLengthString(16); |
154 | 456 | if (opts.realm_hint.empty()) opts.realm_hint = "hint"; |
155 | 456 | } |
156 | | |
157 | | // Generate specific daemon options with specific method |
158 | 1.19k | if (!method.empty() && (method == "POST" || g_fdp->ConsumeBool())) { |
159 | 604 | opts.te_chunked = g_fdp->ConsumeBool(); |
160 | 604 | opts.as_multipart = g_fdp->ConsumeBool(); |
161 | 604 | if (g_fdp->ConsumeBool()) { |
162 | 349 | opts.boundary = safe_ascii(g_fdp->ConsumeRandomLengthString(24), false); |
163 | 349 | if (opts.boundary.empty()) opts.boundary = "b"; |
164 | 349 | } |
165 | 604 | } |
166 | 1.19k | } |
167 | | |
168 | | static std::string generate_auth_header(const DaemonOpts& opts, |
169 | | const std::string& method, |
170 | | const std::string& path, |
171 | | const std::string& auth_user, |
172 | | const std::string& auth_pass, |
173 | 1.19k | bool garble_auth) { |
174 | | // For basic auth only request |
175 | 1.19k | if (!opts.use_digest) { |
176 | 754 | if (!garble_auth) { |
177 | 579 | std::string up = auth_user + ":" + auth_pass; |
178 | 579 | return "Authorization: Basic " + b64encode(up) + "\r\n"; |
179 | 579 | } |
180 | 175 | static const char* kBad[] = { |
181 | 175 | "Authorization: Basic\r\n", |
182 | 175 | "Authorization: Basic =\r\n", |
183 | 175 | "Authorization: Bearer ???\r\n", |
184 | 175 | "Authorization:\r\n" |
185 | 175 | }; |
186 | 175 | unsigned idx = (auth_user.empty() ? 0u : (unsigned char)auth_user[0]) % |
187 | 175 | (unsigned)(sizeof(kBad)/sizeof(kBad[0])); |
188 | 175 | return std::string(kBad[idx]); |
189 | 754 | } |
190 | | |
191 | | // For digest auth with malformed headers |
192 | 445 | if (!opts.send_malformed_digest) { |
193 | 134 | std::string u = auth_user.empty() ? "user" : auth_user; |
194 | 134 | std::string r = opts.realm_hint; |
195 | 134 | std::string uri = (path.empty() || path[0] != '/') ? ("/" + path) : path; |
196 | 134 | if (uri.empty()) uri = "/"; |
197 | 134 | std::string h = "Authorization: Digest "; |
198 | 134 | h += "username=\"" + u + "\", "; |
199 | 134 | h += "realm=\"" + r + "\", "; |
200 | 134 | h += "nonce=\"deadbeef\", "; |
201 | 134 | h += "uri=\"" + uri + "\", "; |
202 | 134 | h += "response=\"00000000000000000000000000000000\", "; |
203 | 134 | h += "opaque=\"cafebabe\", "; |
204 | 134 | h += "qop=auth, "; |
205 | 134 | h += "nc=00000001, cnonce=\"0123456789abcdef\"\r\n"; |
206 | 134 | return h; |
207 | 134 | } |
208 | | |
209 | | // For digest auth with correctly formatted headers with random data |
210 | 311 | static const char* kBadDigest[] = { |
211 | 311 | "Authorization: Digest\r\n", |
212 | 311 | "Authorization: Digest username=\r\n", |
213 | 311 | "Authorization: Digest realm=\"\", uri=/, response=\r\n", |
214 | 311 | "Authorization: Digest nonce=,opaque=\r\n" |
215 | 311 | }; |
216 | 311 | unsigned idx = (unsigned char)(auth_user.empty()?0:auth_user[0]) % |
217 | 311 | (unsigned)(sizeof(kBadDigest)/sizeof(kBadDigest[0])); |
218 | | |
219 | 311 | return std::string(kBadDigest[idx]); |
220 | 445 | } |
221 | | |
222 | | static void append_headers(std::string& req, |
223 | | const DaemonOpts& opts, |
224 | 1.19k | const std::string& auth_header) { |
225 | | // Set host |
226 | 1.19k | if (!opts.omit_host) { |
227 | 725 | req += "Host: 127.0.0.1\r\n"; |
228 | 725 | } |
229 | | |
230 | | // Append auth headers |
231 | 1.19k | req += auth_header; |
232 | | |
233 | | // Append general headers |
234 | 1.19k | if (opts.extra_headers) { |
235 | 428 | req += "User-Agent: fuzz\r\n"; |
236 | 428 | req += "Accept: */*\r\n"; |
237 | 428 | req += "X-Fuzz: 1\r\n"; |
238 | 428 | req += "X-Dup: a\r\nX-Dup: b\r\n"; |
239 | 428 | } |
240 | 1.19k | } |
241 | | |
242 | | static std::string make_multipart(const DaemonOpts& opts, |
243 | | const std::string& body, |
244 | 1.19k | std::string& content_type_line_out) { |
245 | | // Do nothing for non-multipart iteration |
246 | 1.19k | if (!opts.as_multipart) { |
247 | 840 | content_type_line_out.clear(); |
248 | 840 | return body; |
249 | 840 | } |
250 | | |
251 | | // Configure the request body to be multipart format |
252 | 359 | std::string b = opts.boundary.empty() ? "b" : opts.boundary; |
253 | 359 | std::string mp; |
254 | 359 | mp += "--" + b + "\r\n"; |
255 | 359 | mp += "Content-Disposition: form-data; name=\"f\"; filename=\"x\"\r\n"; |
256 | 359 | if (!body.empty()) mp += "Content-Type: application/octet-stream\r\n"; |
257 | 359 | mp += "\r\n"; |
258 | 359 | mp += body; |
259 | 359 | mp += "\r\n--" + b + "--\r\n"; |
260 | 359 | content_type_line_out = "Content-Type: multipart/form-data; boundary=" + b + "\r\n"; |
261 | | |
262 | 359 | return mp; |
263 | 1.19k | } |
264 | | |
265 | | |
266 | | static void append_request_headers(std::string& req, |
267 | | const DaemonOpts& opts, |
268 | | size_t payload_size, |
269 | 1.19k | const std::string& content_type_line) { |
270 | | // Append content type header |
271 | 1.19k | if (!content_type_line.empty()) { |
272 | 359 | req += content_type_line; |
273 | 359 | } |
274 | 1.19k | if (payload_size == 0) { |
275 | 655 | return; |
276 | 655 | } |
277 | | |
278 | | // Append encoding and payload size headers |
279 | 544 | if (opts.te_chunked) { |
280 | 335 | req += "Transfer-Encoding: chunked\r\n"; |
281 | 335 | } else { |
282 | 209 | if (!opts.bad_cl) { |
283 | 154 | req += "Content-Length: " + std::to_string(payload_size) + "\r\n"; |
284 | 154 | } else { |
285 | 55 | req += "Content-Length: " + std::to_string(payload_size + 5) + "\r\n"; |
286 | 55 | } |
287 | 209 | } |
288 | | |
289 | | // Append connection lifeline header |
290 | 544 | req += opts.keep_alive ? "Connection: keep-alive\r\n" : "Connection: close\r\n"; |
291 | 544 | } |
292 | | |
293 | 376 | static void append_chunked_payload(std::string& req, const std::string& payload) { |
294 | | // End the request body gracefully for empty payload |
295 | 376 | if (payload.empty()) { |
296 | 41 | req += "0\r\n\r\n"; |
297 | 41 | return; |
298 | 41 | } |
299 | | |
300 | | // Continue to write all the body data chunk by chunk to the request |
301 | 335 | size_t off = 0; |
302 | 3.32k | while (off < payload.size()) { |
303 | 2.99k | char szbuf[32]; |
304 | | |
305 | 2.99k | size_t remain = payload.size() - off; |
306 | 2.99k | size_t chunk = std::min(remain > 1 ? remain / 2 : 1, size_t(4096)); |
307 | | |
308 | 2.99k | int n = snprintf(szbuf, sizeof(szbuf), "%zx\r\n", chunk); |
309 | 2.99k | if (n > 0) { |
310 | 2.99k | req.append(szbuf, (size_t)n); |
311 | 2.99k | } |
312 | 2.99k | req.append(payload.data() + off, chunk); |
313 | 2.99k | req += "\r\n"; |
314 | 2.99k | off += chunk; |
315 | 2.99k | } |
316 | 335 | req += "0\r\n\r\n"; |
317 | 335 | } |
318 | | |
319 | | |
320 | 1.19k | static void send_request(int fd, const std::string& req) { |
321 | | // Configure sent parameters and max timeout |
322 | 1.19k | size_t off = 0; |
323 | 1.19k | const int kSendStepMs = 2; |
324 | 1.19k | const int kSendMaxMs = 8; |
325 | 1.19k | int waited = 0; |
326 | | |
327 | | // Continue to send data though the socket until all data is sent or timeout is reached |
328 | 2.39k | while (off < req.size()) { |
329 | 1.19k | ssize_t s = send(fd, req.data() + off, req.size() - off, 0); |
330 | 1.19k | if (s > 0) { |
331 | 1.19k | off += (size_t)s; |
332 | 1.19k | continue; |
333 | 1.19k | } |
334 | | |
335 | 0 | if (s < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) { |
336 | 0 | pollfd p{fd, POLLOUT, 0}; |
337 | 0 | if (poll(&p, 1, kSendStepMs) <= 0) { |
338 | 0 | waited += kSendStepMs; |
339 | 0 | if (waited >= kSendMaxMs) { |
340 | 0 | break; |
341 | 0 | } |
342 | 0 | } |
343 | 0 | continue; |
344 | 0 | } |
345 | 0 | break; |
346 | 0 | } |
347 | | |
348 | 1.19k | shutdown(fd, SHUT_WR); |
349 | 1.19k | } |
350 | | |
351 | 1.19k | static void wait_for_response(int fd) { |
352 | | // Configure response waiting parameters and timeout |
353 | 1.19k | static const int kStepMs = 2; |
354 | 1.19k | static const int kMaxMs = 8; |
355 | 1.19k | static const size_t kMaxRead = 64 * 1024; |
356 | 1.19k | int waited = 0; |
357 | 1.19k | size_t total = 0; |
358 | | |
359 | | // Continue to receive data from daemon throug the socket until max data threshold or timeout is reached |
360 | 4.79k | while (true) { |
361 | 4.79k | pollfd p{fd, POLLIN, 0}; |
362 | 4.79k | int pr = poll(&p, 1, kStepMs); |
363 | 4.79k | if (pr <= 0) { |
364 | 4.79k | waited += kStepMs; |
365 | 4.79k | if (waited >= kMaxMs) { |
366 | 1.19k | break; |
367 | 1.19k | } |
368 | | |
369 | 3.59k | continue; |
370 | 4.79k | } |
371 | | |
372 | 0 | char buf[1024]; |
373 | 0 | ssize_t r = recv(fd, buf, sizeof(buf), 0); |
374 | 0 | if (r > 0) { |
375 | 0 | total += (size_t)r; |
376 | 0 | if (total >= kMaxRead) break; |
377 | 0 | continue; |
378 | 0 | } |
379 | 0 | if (r == 0) { |
380 | 0 | break; |
381 | 0 | } |
382 | 0 | if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { |
383 | 0 | continue; |
384 | 0 | } |
385 | | |
386 | 0 | break; |
387 | 0 | } |
388 | 1.19k | } |
389 | | /* End of internal helpers for sending http message to daemon through localhost socket */ |
390 | | |
391 | | void send_http_request_blocking(uint16_t port, |
392 | | const std::string& method, |
393 | | const std::string& path, |
394 | | const std::string& auth_user, |
395 | | const std::string& auth_pass, |
396 | | const std::string& body, |
397 | 1.19k | bool garble_auth) { |
398 | | // Create and connect to the daemon with HTTP localhost socket |
399 | 1.19k | int fd = create_socket(port); |
400 | 1.19k | if (fd < 0) { |
401 | 0 | return; |
402 | 0 | } |
403 | | |
404 | | // Generate options for the daemon connection and request |
405 | 1.19k | DaemonOpts opts; |
406 | 1.19k | generate_daemon_options(method, opts); |
407 | | |
408 | | // Build the request body for the random request |
409 | 1.19k | std::string req; |
410 | 1.19k | req.reserve(512 + body.size()); |
411 | 1.19k | const std::string& m = method.empty() ? std::string("GET") : method; |
412 | 1.19k | req += m; |
413 | 1.19k | req += " "; |
414 | 1.19k | std::string full_path = (path.empty() || path[0] != '/') ? ("/" + path) : path; |
415 | 1.19k | if (full_path.empty()) full_path = "/"; |
416 | 1.19k | req += full_path; |
417 | 1.19k | req += " HTTP/1.1\r\n"; |
418 | | |
419 | | // Generate auth headers |
420 | 1.19k | std::string auth_header = generate_auth_header(opts, m, path, auth_user, auth_pass, garble_auth); |
421 | | |
422 | | // Append headers into the daemon request |
423 | 1.19k | append_headers(req, opts, auth_header); |
424 | | |
425 | | // Randomly make the request with multipart data |
426 | 1.19k | std::string content_type_line; |
427 | 1.19k | std::string payload = make_multipart(opts, body, content_type_line); |
428 | | |
429 | | // Append additional request specific headers |
430 | 1.19k | append_request_headers(req, opts, payload.size(), content_type_line); |
431 | 1.19k | req += "\r\n"; |
432 | | |
433 | | // Add random body content to the request object depends on the content type chosen |
434 | 1.19k | if (opts.te_chunked) { |
435 | 376 | append_chunked_payload(req, payload); |
436 | 823 | } else { |
437 | 823 | req += payload; |
438 | 823 | } |
439 | | |
440 | | // Sending the request to the daemon through the localhost socket |
441 | 1.19k | send_request(fd, req); |
442 | | |
443 | | // Wait for a while for daemon response |
444 | 1.19k | wait_for_response(fd); |
445 | | |
446 | | // Close the socket |
447 | 1.19k | close(fd); |
448 | 1.19k | } |
449 | | |
450 | | /* Start of internal helpers for daemon request handling */ |
451 | | static void generate_daemon_req_opts(DaemonReqOpts& o, |
452 | 0 | enum MHD_HTTP_Method method) { |
453 | 0 | std::lock_guard<std::mutex> lk(g_fdp_mu); |
454 | 0 | if (!g_fdp) { |
455 | 0 | o.realm = "realm"; |
456 | 0 | return; |
457 | 0 | } |
458 | | |
459 | | // Generate basic daemon request options |
460 | 0 | o.realm = g_fdp->ConsumeRandomLengthString(16); |
461 | 0 | if (o.realm.empty()) { |
462 | 0 | o.realm = "realm"; |
463 | 0 | } |
464 | 0 | o.allowed_user = g_fdp->ConsumeRandomLengthString(12); |
465 | 0 | o.allowed_pass = g_fdp->ConsumeRandomLengthString(12); |
466 | 0 | o.force_challenge = g_fdp->ConsumeBool(); |
467 | 0 | o.force_bad = g_fdp->ConsumeBool(); |
468 | 0 | o.flip_ok_to_forbidden = g_fdp->ConsumeBool(); |
469 | 0 | o.prefer_utf8 = ToMhdBool(g_fdp->ConsumeBool()); |
470 | 0 | o.use_digest = g_fdp->ConsumeBool(); |
471 | |
|
472 | 0 | o.qop = g_fdp->ConsumeBool() ? MHD_DIGEST_AUTH_MULT_QOP_AUTH |
473 | 0 | : MHD_DIGEST_AUTH_MULT_QOP_AUTH_INT; |
474 | 0 | o.algo = g_fdp->ConsumeBool() ? MHD_DIGEST_AUTH_MULT_ALGO_ANY |
475 | 0 | : MHD_DIGEST_AUTH_MULT_ALGO_MD5; |
476 | |
|
477 | 0 | o.send_stale = g_fdp->ConsumeBool() ? MHD_YES : MHD_NO; |
478 | 0 | o.allow_userhash = g_fdp->ConsumeBool() ? MHD_YES : MHD_NO; |
479 | 0 | o.use_opaque = g_fdp->ConsumeBool(); |
480 | | |
481 | | // Generate daemon request options for form encoded request |
482 | 0 | o.buf_sz = 64 + g_fdp->ConsumeIntegralInRange<size_t>(0, 8192); |
483 | 0 | o.max_nonstream = g_fdp->ConsumeIntegralInRange<size_t>(0, 64 * 1024); |
484 | 0 | o.enc = g_fdp->PickValueInArray<enum MHD_HTTP_PostEncoding>({ |
485 | 0 | MHD_HTTP_POST_ENCODING_FORM_URLENCODED, |
486 | 0 | MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, |
487 | 0 | MHD_HTTP_POST_ENCODING_TEXT_PLAIN, |
488 | 0 | MHD_HTTP_POST_ENCODING_OTHER |
489 | 0 | }); |
490 | 0 | o.do_parse_post = g_fdp->ConsumeBool() || (method == MHD_HTTP_METHOD_POST); |
491 | 0 | o.do_process_upload = g_fdp->ConsumeBool(); |
492 | 0 | } |
493 | | |
494 | | static const struct MHD_UploadAction* |
495 | 0 | post_done_cb(struct MHD_Request *req, void *cls, enum MHD_PostParseResult pr) { |
496 | 0 | return MHD_upload_action_continue(req); |
497 | 0 | } |
498 | | |
499 | | static const struct MHD_UploadAction* |
500 | | post_reader(struct MHD_Request *req, |
501 | | void *cls, |
502 | | const struct MHD_String *name, |
503 | | const struct MHD_StringNullable *filename, |
504 | | const struct MHD_StringNullable *content_type, |
505 | | const struct MHD_StringNullable *encoding, |
506 | | size_t size, |
507 | | const void *data, |
508 | | uint_fast64_t off, |
509 | 0 | enum MHD_Bool final_data) { |
510 | 0 | return MHD_upload_action_continue(req); |
511 | 0 | } |
512 | | |
513 | | static const struct MHD_UploadAction* |
514 | | upload_cb(void *upload_cls, |
515 | | struct MHD_Request *request, |
516 | | size_t content_data_size, |
517 | 0 | void *content_data) { |
518 | 0 | enum MHD_HTTP_StatusCode sc = content_data_size ? MHD_HTTP_STATUS_OK : MHD_HTTP_STATUS_NO_CONTENT; |
519 | 0 | struct MHD_Response *r = MHD_response_from_empty(sc); |
520 | 0 | if (!r) { |
521 | 0 | return NULL; |
522 | 0 | } |
523 | | |
524 | 0 | return MHD_upload_action_from_response(request, r); |
525 | 0 | } |
526 | | |
527 | 0 | static const struct MHD_Action* request_parsing(struct MHD_Request* request, const DaemonReqOpts& o) { |
528 | | // Handle parsing of post request with form data or general parameters |
529 | 0 | if (o.do_parse_post) { |
530 | 0 | const struct MHD_Action *a = |
531 | 0 | MHD_action_parse_post(request, |
532 | 0 | o.buf_sz, |
533 | 0 | o.max_nonstream, |
534 | 0 | o.enc, |
535 | 0 | &post_reader, nullptr, |
536 | 0 | &post_done_cb, nullptr); |
537 | 0 | if (a) { |
538 | 0 | MHD_request_get_post_data_cb( |
539 | 0 | request, |
540 | 0 | [](void *cls, const struct MHD_PostField *pf)->enum MHD_Bool { |
541 | 0 | std::lock_guard<std::mutex> lk(g_fdp_mu); |
542 | 0 | if (!g_fdp) { |
543 | 0 | return MHD_YES; |
544 | 0 | } |
545 | 0 | return ToMhdBool(g_fdp->ConsumeBool()); |
546 | 0 | }, |
547 | 0 | nullptr); |
548 | 0 | return a; |
549 | 0 | } |
550 | 0 | } |
551 | | |
552 | | // Handle parsing of upload request |
553 | 0 | if (o.do_process_upload) { |
554 | 0 | const struct MHD_Action *a = |
555 | 0 | MHD_action_process_upload(request, |
556 | 0 | o.buf_sz, |
557 | 0 | &upload_cb, nullptr, |
558 | 0 | &upload_cb, nullptr); |
559 | 0 | if (a) { |
560 | 0 | return a; |
561 | 0 | } |
562 | 0 | } |
563 | | |
564 | 0 | return nullptr; |
565 | 0 | } |
566 | | |
567 | | static const struct MHD_Action* |
568 | | handle_basic_auth(struct MHD_Request* request, |
569 | 0 | const DaemonReqOpts& o) { |
570 | 0 | union MHD_RequestInfoDynamicData req_data; |
571 | 0 | enum MHD_StatusCode res = MHD_request_get_info_dynamic( |
572 | 0 | request, MHD_REQUEST_INFO_DYNAMIC_AUTH_BASIC_CREDS, &req_data); |
573 | | |
574 | | // Send bad header response |
575 | 0 | if (o.force_bad || (MHD_SC_REQ_AUTH_DATA_BROKEN == res)) { |
576 | 0 | return MHD_action_from_response( |
577 | 0 | request, |
578 | 0 | MHD_response_from_buffer_static( |
579 | 0 | MHD_HTTP_STATUS_BAD_REQUEST, 10, "bad_header")); |
580 | 0 | } |
581 | | |
582 | | // Send unauthorized response |
583 | 0 | if (o.force_challenge || (MHD_SC_AUTH_ABSENT == res)) { |
584 | 0 | const char* realm_cstr = o.realm.c_str(); |
585 | 0 | return MHD_action_basic_auth_challenge_a( |
586 | 0 | request, |
587 | 0 | realm_cstr, |
588 | 0 | o.prefer_utf8, |
589 | 0 | MHD_response_from_buffer_static( |
590 | 0 | MHD_HTTP_STATUS_UNAUTHORIZED, 4, "auth")); |
591 | 0 | } |
592 | | |
593 | | // Fail safe to abort request if unknown request status is provided |
594 | 0 | if (MHD_SC_OK != res) { |
595 | 0 | return MHD_action_abort_request(request); |
596 | 0 | } |
597 | | |
598 | | // Prepeare and perform basic auth check |
599 | 0 | const struct MHD_AuthBasicCreds *creds = req_data.v_auth_basic_creds; |
600 | 0 | bool user_ok = (creds->username.len == o.allowed_user.size()) && |
601 | 0 | (0 == memcmp(o.allowed_user.data(), |
602 | 0 | creds->username.cstr, |
603 | 0 | creds->username.len)); |
604 | 0 | bool pass_ok = (creds->password.len == o.allowed_pass.size()) && |
605 | 0 | (0 == memcmp(o.allowed_pass.data(), |
606 | 0 | creds->password.cstr, |
607 | 0 | creds->password.len)); |
608 | 0 | bool ok = user_ok && pass_ok; |
609 | | |
610 | | // Try randomly flip the result |
611 | 0 | if (o.flip_ok_to_forbidden) { |
612 | 0 | ok = false; |
613 | 0 | } |
614 | | |
615 | | // Return result with status in response |
616 | 0 | if (ok) { |
617 | 0 | return MHD_action_from_response( |
618 | 0 | request, |
619 | 0 | MHD_response_from_buffer_static( |
620 | 0 | MHD_HTTP_STATUS_OK, 2, "OK")); |
621 | 0 | } |
622 | | |
623 | 0 | return MHD_action_from_response( |
624 | 0 | request, |
625 | 0 | MHD_response_from_buffer_static( |
626 | 0 | MHD_HTTP_STATUS_FORBIDDEN, 9, "FORBIDDEN")); |
627 | 0 | } |
628 | | |
629 | | static const struct MHD_Action* |
630 | | handle_digest_auth(struct MHD_Request* request, |
631 | 0 | const DaemonReqOpts& o) { |
632 | 0 | union MHD_RequestInfoDynamicData req_data; |
633 | 0 | enum MHD_StatusCode res = MHD_request_get_info_dynamic( |
634 | 0 | request, MHD_REQUEST_INFO_DYNAMIC_AUTH_DIGEST_INFO, &req_data); |
635 | |
|
636 | 0 | const char* opaque_opt = o.use_opaque ? "opaque-token" : nullptr; |
637 | | |
638 | | // Early exit for missing auth in request |
639 | 0 | if (MHD_SC_AUTH_ABSENT == res || o.force_challenge) { |
640 | 0 | return MHD_action_digest_auth_challenge_a( |
641 | 0 | request, |
642 | 0 | o.realm.c_str(), |
643 | 0 | "0", |
644 | 0 | opaque_opt, |
645 | 0 | o.send_stale, |
646 | 0 | o.qop, |
647 | 0 | o.algo, |
648 | 0 | o.allow_userhash, |
649 | 0 | MHD_YES, |
650 | 0 | MHD_response_from_buffer_static( |
651 | 0 | MHD_HTTP_STATUS_UNAUTHORIZED, 4, "auth")); |
652 | 0 | } |
653 | | |
654 | | // Early exit for invalid or malformed request headers |
655 | 0 | if (MHD_SC_REQ_AUTH_DATA_BROKEN == res || o.force_bad) { |
656 | 0 | return MHD_action_from_response( |
657 | 0 | request, |
658 | 0 | MHD_response_from_buffer_static( |
659 | 0 | MHD_HTTP_STATUS_BAD_REQUEST, 15, "Header Invalid.")); |
660 | 0 | } |
661 | | |
662 | | // Fail safe to abort request if unknown response status is provided |
663 | 0 | if (MHD_SC_OK != res) { |
664 | 0 | return MHD_action_abort_request(request); |
665 | 0 | } |
666 | | |
667 | | // Prepeare and perform digest auth check |
668 | 0 | const struct MHD_AuthDigestInfo *di = req_data.v_auth_digest_info; |
669 | 0 | bool user_ok = (di->username.len == o.allowed_user.size()) && |
670 | 0 | (0 == memcmp(o.allowed_user.data(), |
671 | 0 | di->username.cstr, |
672 | 0 | di->username.len)); |
673 | |
|
674 | 0 | if (user_ok) { |
675 | 0 | enum MHD_DigestAuthResult auth_res = |
676 | 0 | MHD_digest_auth_check(request, |
677 | 0 | o.realm.c_str(), |
678 | 0 | o.allowed_user.c_str(), |
679 | 0 | o.allowed_pass.c_str(), |
680 | 0 | 0, |
681 | 0 | o.qop, |
682 | 0 | o.algo); |
683 | | |
684 | | // Return auth result or randomly flip the result |
685 | 0 | if (MHD_DAUTH_OK == auth_res) { |
686 | 0 | if (!o.flip_ok_to_forbidden) { |
687 | 0 | return MHD_action_from_response( |
688 | 0 | request, |
689 | 0 | MHD_response_from_buffer_static( |
690 | 0 | MHD_HTTP_STATUS_OK, 2, "OK")); |
691 | 0 | } |
692 | 0 | return MHD_action_from_response( |
693 | 0 | request, |
694 | 0 | MHD_response_from_buffer_static( |
695 | 0 | MHD_HTTP_STATUS_FORBIDDEN, 9, "FORBIDDEN")); |
696 | 0 | } |
697 | | |
698 | 0 | if (MHD_DAUTH_NONCE_STALE == auth_res) { |
699 | 0 | return MHD_action_digest_auth_challenge_a( |
700 | 0 | request, |
701 | 0 | o.realm.c_str(), |
702 | 0 | "0", |
703 | 0 | opaque_opt, |
704 | 0 | MHD_YES, |
705 | 0 | o.qop, |
706 | 0 | o.algo, |
707 | 0 | o.allow_userhash, |
708 | 0 | MHD_YES, |
709 | 0 | MHD_response_from_buffer_static( |
710 | 0 | MHD_HTTP_STATUS_UNAUTHORIZED, 4, "auth")); |
711 | 0 | } |
712 | | |
713 | 0 | return MHD_action_from_response( |
714 | 0 | request, |
715 | 0 | MHD_response_from_buffer_static( |
716 | 0 | MHD_HTTP_STATUS_FORBIDDEN, 9, "FORBIDDEN")); |
717 | 0 | } |
718 | | |
719 | 0 | return MHD_action_from_response( |
720 | 0 | request, |
721 | 0 | MHD_response_from_buffer_static( |
722 | 0 | MHD_HTTP_STATUS_FORBIDDEN, 9, "FORBIDDEN")); |
723 | 0 | } |
724 | | /* End of internal helpers for daemon request handling */ |
725 | | |
726 | | MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_NONNULL_(3) |
727 | | const struct MHD_Action* |
728 | | req_cb(void* cls, |
729 | | struct MHD_Request* MHD_RESTRICT request, |
730 | | const struct MHD_String* MHD_RESTRICT path, |
731 | | enum MHD_HTTP_Method method, |
732 | 0 | uint_fast64_t upload_size) { |
733 | 0 | DaemonReqOpts opts; |
734 | 0 | generate_daemon_req_opts(opts, method); |
735 | | |
736 | | // Try parinsg or streaming request |
737 | 0 | if (const struct MHD_Action* a = request_parsing(request, opts)) { |
738 | 0 | return a; |
739 | 0 | } |
740 | | |
741 | | // Perform basic or digest auth on the request |
742 | 0 | if (!opts.use_digest) { |
743 | 0 | return handle_basic_auth(request, opts); |
744 | 0 | } |
745 | 0 | return handle_digest_auth(request, opts); |
746 | 0 | } |
747 | | |
748 | | MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_NONNULL_(3) |
749 | | const struct MHD_Action* |
750 | | req_cb_stream(void*, |
751 | | struct MHD_Request* MHD_RESTRICT request, |
752 | | const struct MHD_String* MHD_RESTRICT path, |
753 | | enum MHD_HTTP_Method method, |
754 | 0 | uint_fast64_t upload_size) { |
755 | | // Fuzz MHD_request_get_value for different parameters on random request |
756 | 0 | MHD_request_get_value(request, MHD_VK_HEADER, "host"); |
757 | 0 | MHD_request_get_value(request, MHD_VK_HEADER, "content-type"); |
758 | 0 | MHD_request_get_value(request, MHD_VK_COOKIE, "cookie"); |
759 | 0 | MHD_request_get_value(request, MHD_VK_GET_ARGUMENT, "q"); |
760 | 0 | MHD_request_get_values_cb(request, MHD_VK_HEADER, kv_cb, nullptr); |
761 | 0 | MHD_request_get_values_cb(request, MHD_VK_COOKIE, kv_cb, nullptr); |
762 | 0 | MHD_request_get_values_cb(request, MHD_VK_GET_ARGUMENT, kv_cb, nullptr); |
763 | | |
764 | | // Fuzz MHD_request_get_post_data_cb on random request |
765 | 0 | MHD_request_get_post_data_cb(request, post_cb, nullptr); |
766 | | |
767 | | |
768 | | // Fuzz MHD_request_get_info_fixed for different parameters on random request |
769 | 0 | union MHD_RequestInfoFixedData fix; |
770 | 0 | MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_HTTP_VER, &fix); |
771 | 0 | MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_HTTP_METHOD, &fix); |
772 | 0 | MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_DAEMON, &fix); |
773 | 0 | MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_CONNECTION, &fix); |
774 | 0 | MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_STREAM, &fix); |
775 | 0 | MHD_request_get_info_fixed(request, MHD_REQUEST_INFO_FIXED_APP_CONTEXT, &fix); |
776 | | |
777 | | // Fuzz MHD_request_get_info_dynamic for different parameters on random request |
778 | 0 | union MHD_RequestInfoDynamicData dyn; |
779 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_HTTP_METHOD_STRING, &dyn); |
780 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_URI, &dyn); |
781 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_URI_PARAMS, &dyn); |
782 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_COOKIES, &dyn); |
783 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_HEADER_SIZE, &dyn); |
784 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_POST_PARAMS, &dyn); |
785 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_UPLOAD_PRESENT, &dyn); |
786 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_UPLOAD_CHUNKED, &dyn); |
787 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_UPLOAD_SIZE_TOTAL, &dyn); |
788 | 0 | MHD_request_get_info_dynamic(request, MHD_REQUEST_INFO_DYNAMIC_UPLOAD_SIZE_RECIEVED, &dyn); |
789 | | |
790 | | // Fuzz response creation from random request processing |
791 | 0 | struct MHD_Response* resp = MHD_response_from_empty(MHD_HTTP_STATUS_NO_CONTENT); |
792 | 0 | if (!resp) { |
793 | 0 | return MHD_action_abort_request(request); |
794 | 0 | } |
795 | | |
796 | | // Fuzz response and request abortion |
797 | 0 | MHD_response_add_header(resp, "x-fuzz", "values"); |
798 | 0 | const struct MHD_Action* act = MHD_action_from_response(request, resp); |
799 | 0 | MHD_response_destroy(resp); |
800 | 0 | return act ? act : MHD_action_abort_request(request); |
801 | 0 | } |
802 | | |
803 | | MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_NONNULL_(3) |
804 | | const struct MHD_Action* |
805 | | req_cb_process(void*, |
806 | | struct MHD_Request* MHD_RESTRICT request, |
807 | | const struct MHD_String* MHD_RESTRICT path, |
808 | | enum MHD_HTTP_Method method, |
809 | 0 | uint_fast64_t upload_size) { |
810 | | // Create info unions |
811 | 0 | union MHD_RequestInfoFixedData f; |
812 | 0 | union MHD_RequestInfoDynamicData d; |
813 | | |
814 | | // Fuzz MHD_request_get_info_fixed_sz for different parameters on random request |
815 | 0 | MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_HTTP_VER, &f, sizeof(f)); |
816 | 0 | MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_HTTP_METHOD, &f, sizeof(f)); |
817 | 0 | MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_DAEMON, &f, sizeof(f)); |
818 | 0 | MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_CONNECTION, &f, sizeof(f)); |
819 | 0 | MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_STREAM, &f, sizeof(f)); |
820 | 0 | MHD_request_get_info_fixed_sz(request, MHD_REQUEST_INFO_FIXED_APP_CONTEXT, &f, sizeof(f)); |
821 | | |
822 | | // Fuzz MHD_request_get_info_dynamic_sz for different parameters on random request |
823 | 0 | MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_HTTP_METHOD_STRING, &d, sizeof(d)); |
824 | 0 | MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_URI, &d, sizeof(d)); |
825 | 0 | MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_URI_PARAMS, &d, sizeof(d)); |
826 | 0 | MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_NUMBER_COOKIES, &d, sizeof(d)); |
827 | 0 | MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_HEADER_SIZE, &d, sizeof(d)); |
828 | 0 | MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_AUTH_DIGEST_INFO, &d, sizeof(d)); |
829 | 0 | MHD_request_get_info_dynamic_sz(request, MHD_REQUEST_INFO_DYNAMIC_AUTH_BASIC_CREDS, &d, sizeof(d)); |
830 | |
|
831 | 0 | { |
832 | 0 | static const char realm[] = "fuzz-realm"; |
833 | 0 | static const char user[] = "u"; |
834 | 0 | static const char pass[] = "p"; |
835 | |
|
836 | 0 | enum MHD_DigestAuthAlgo algos[] = { |
837 | 0 | MHD_DIGEST_AUTH_ALGO_MD5, |
838 | 0 | MHD_DIGEST_AUTH_ALGO_SHA256, |
839 | 0 | MHD_DIGEST_AUTH_ALGO_SHA512_256 |
840 | 0 | }; |
841 | |
|
842 | 0 | for (unsigned i = 0; i < (unsigned)(sizeof(algos)/sizeof(algos[0])); ++i) { |
843 | 0 | size_t sz = MHD_digest_get_hash_size(algos[i]); |
844 | 0 | if (sz == 0 || sz > 64) { |
845 | 0 | continue; |
846 | 0 | } |
847 | 0 | unsigned char ha1[64]; |
848 | 0 | if (MHD_SC_OK == MHD_digest_auth_calc_userdigest(algos[i], user, realm, pass, sz, ha1)) { |
849 | 0 | MHD_digest_auth_check_digest( |
850 | 0 | request, realm, user, sz, ha1, |
851 | 0 | 0, MHD_DIGEST_AUTH_MULT_QOP_AUTH_ANY, |
852 | 0 | MHD_DIGEST_AUTH_MULT_ALGO_ANY_NON_SESSION); |
853 | 0 | } |
854 | 0 | } |
855 | 0 | } |
856 | | |
857 | | // Force OK response |
858 | 0 | struct MHD_Response* r = MHD_response_from_empty(MHD_HTTP_STATUS_OK); |
859 | 0 | return MHD_action_from_response(request, r); |
860 | 0 | } |