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 <stdint.h> |
17 | | #include <stddef.h> |
18 | | #include <string> |
19 | | #include <vector> |
20 | | #include <algorithm> |
21 | | #include <cstdarg> |
22 | | #include <unistd.h> |
23 | | #include <fcntl.h> |
24 | | #include <sys/stat.h> |
25 | | #include <sys/types.h> |
26 | | |
27 | | #include "microhttpd2.h" |
28 | | #include "fuzzer/FuzzedDataProvider.h" |
29 | | |
30 | 7.12k | static inline enum MHD_Bool ToMhdBool(bool b) { |
31 | 7.12k | return b ? MHD_YES : MHD_NO; |
32 | 7.12k | } |
33 | | |
34 | 0 | static void dummy_log(void*, enum MHD_StatusCode, const char*, va_list) { |
35 | | // Do nothing |
36 | 0 | } |
37 | | |
38 | | static MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_NONNULL_(3) |
39 | | const struct MHD_Action* req_cb(void* cls, |
40 | | struct MHD_Request* request, |
41 | | const struct MHD_String* path, |
42 | | enum MHD_HTTP_Method method, |
43 | 0 | uint_fast64_t upload_size) { |
44 | |
|
45 | 0 | const std::string* body = static_cast<const std::string*>(cls); |
46 | 0 | struct MHD_Response* r = MHD_response_from_buffer( |
47 | 0 | MHD_HTTP_STATUS_OK, body->size(), body->c_str(), |
48 | 0 | nullptr, nullptr); |
49 | 0 | if (!r) { |
50 | 0 | return nullptr; |
51 | 0 | } |
52 | 0 | return MHD_action_from_response(request, r); |
53 | 0 | } |
54 | | |
55 | 903 | void fuzz_digest_auth_calc(FuzzedDataProvider& fdp) { |
56 | 903 | std::string realm = fdp.ConsumeRandomLengthString(40); |
57 | 903 | std::string user = fdp.ConsumeRandomLengthString(24); |
58 | 903 | std::string pass = fdp.ConsumeRandomLengthString(24); |
59 | | |
60 | 903 | enum MHD_DigestAuthAlgo alg = |
61 | 903 | fdp.PickValueInArray<enum MHD_DigestAuthAlgo>({ |
62 | 903 | MHD_DIGEST_AUTH_ALGO_MD5, |
63 | 903 | MHD_DIGEST_AUTH_ALGO_SHA256, |
64 | 903 | MHD_DIGEST_AUTH_ALGO_SHA512_256 |
65 | 903 | }); |
66 | 903 | size_t hsz = MHD_digest_get_hash_size(alg); |
67 | 903 | if (hsz > 0 && hsz <= 128) { |
68 | 903 | std::vector<uint8_t> bin(hsz); |
69 | 903 | std::vector<uint8_t> ud(hsz); |
70 | 903 | std::vector<char> hex(hsz * 2 + 1); |
71 | 903 | (void) MHD_digest_auth_calc_userhash(alg, user.c_str(), realm.c_str(), bin.size(), bin.data()); |
72 | 903 | (void) MHD_digest_auth_calc_userhash_hex(alg, user.c_str(), realm.c_str(), hex.size(), hex.data()); |
73 | 903 | (void) MHD_digest_auth_calc_userdigest(alg, user.c_str(), realm.c_str(), pass.c_str(), ud.size(), ud.data()); |
74 | 903 | } |
75 | 903 | } |
76 | | |
77 | 903 | struct MHD_Response* fuzz_response_creation(FuzzedDataProvider& fdp) { |
78 | | // Create random body string for response |
79 | 903 | const size_t body_len = fdp.ConsumeIntegralInRange<size_t>(0, std::min<size_t>(fdp.remaining_bytes(), 2048)); |
80 | 903 | std::string body = fdp.ConsumeBytesAsString(body_len); |
81 | | |
82 | 903 | struct MHD_Response* r = nullptr; |
83 | 903 | enum MHD_HTTP_StatusCode sc = |
84 | 903 | fdp.PickValueInArray<enum MHD_HTTP_StatusCode>({ |
85 | 903 | MHD_HTTP_STATUS_OK, MHD_HTTP_STATUS_CREATED, MHD_HTTP_STATUS_NO_CONTENT, |
86 | 903 | MHD_HTTP_STATUS_PARTIAL_CONTENT, MHD_HTTP_STATUS_BAD_REQUEST, |
87 | 903 | MHD_HTTP_STATUS_UNAUTHORIZED, MHD_HTTP_STATUS_FORBIDDEN, |
88 | 903 | MHD_HTTP_STATUS_NOT_FOUND, MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR |
89 | 903 | }); |
90 | | |
91 | 903 | if (fdp.ConsumeBool()) { |
92 | 659 | r = MHD_response_from_buffer(sc, body.size(), body.data(), nullptr, nullptr); |
93 | 659 | } else if (fdp.ConsumeBool()) { |
94 | 24 | r = MHD_response_from_buffer_static(sc, body.size(), body.c_str()); |
95 | 220 | } else { |
96 | 220 | r = MHD_response_from_empty(sc); |
97 | 220 | } |
98 | | |
99 | 903 | return r; |
100 | 903 | } |
101 | | |
102 | 903 | void fuzz_response_config(FuzzedDataProvider& fdp, struct MHD_Response* r) { |
103 | 903 | std::string header1 = fdp.ConsumeRandomLengthString(24); if (header1.empty()) header1 = "H1"; |
104 | 903 | std::string header2 = fdp.ConsumeRandomLengthString(24); if (header2.empty()) header2 = "H2"; |
105 | 903 | std::string header3 = fdp.ConsumeRandomLengthString(24); if (header3.empty()) header3 = "H3"; |
106 | 903 | std::string val1 = fdp.ConsumeRandomLengthString(64); if (val1.empty()) val1 = "V1"; |
107 | 903 | std::string val2 = fdp.ConsumeRandomLengthString(64); if (val2.empty()) val2 = "V2"; |
108 | 903 | std::string val3 = fdp.ConsumeRandomLengthString(64); if (val3.empty()) val3 = "V3"; |
109 | | |
110 | | // Set random headers |
111 | 903 | MHD_response_add_header(r, "Content-Type", fdp.ConsumeBool() ? "text/plain" : "application/octet-stream"); |
112 | 903 | MHD_response_add_header(r, header1.c_str(), val1.c_str()); |
113 | 903 | MHD_response_add_header(r, header2.c_str(), val2.c_str()); |
114 | 903 | MHD_response_add_header(r, header3.c_str(), val3.c_str()); |
115 | | |
116 | | // Set predefined headers |
117 | 903 | MHD_response_add_predef_header( |
118 | 903 | r, fdp.PickValueInArray<enum MHD_PredefinedHeader>({MHD_PREDEF_ACCEPT_CHARSET, MHD_PREDEF_ACCEPT_LANGUAGE}), |
119 | 903 | fdp.ConsumeRandomLengthString(32).c_str() |
120 | 903 | ); |
121 | | |
122 | | // Set boolean configurations |
123 | 903 | { auto opt = MHD_R_OPTION_REUSABLE( ToMhdBool(fdp.ConsumeBool())); (void) MHD_response_set_option(r, &opt); } |
124 | 903 | { auto opt = MHD_R_OPTION_HEAD_ONLY_RESPONSE( ToMhdBool(fdp.ConsumeBool())); (void) MHD_response_set_option(r, &opt); } |
125 | 903 | { auto opt = MHD_R_OPTION_CHUNKED_ENC( ToMhdBool(fdp.ConsumeBool())); (void) MHD_response_set_option(r, &opt); } |
126 | 903 | { auto opt = MHD_R_OPTION_CONN_CLOSE( ToMhdBool(fdp.ConsumeBool())); (void) MHD_response_set_option(r, &opt); } |
127 | | |
128 | | // Create random data for response generation |
129 | 903 | char tmpl[] = "/tmp/mhd2_fuzz_XXXXXX"; |
130 | 903 | int fd = mkstemp(tmpl); |
131 | 903 | if (fd >= 0) { |
132 | 903 | std::string bytes = fdp.ConsumeRandomLengthString(512); |
133 | 903 | if (!bytes.empty()) { |
134 | 216 | write(fd, bytes.data(), bytes.size()); |
135 | 216 | } |
136 | | |
137 | 903 | off_t sz = lseek(fd, 0, SEEK_END); |
138 | 903 | if (sz > 0) { |
139 | 216 | uint_fast64_t off = fdp.ConsumeIntegralInRange<uint_fast64_t>(0, (uint_fast64_t)sz); |
140 | 216 | uint_fast64_t len = fdp.ConsumeIntegralInRange<uint_fast64_t>(0, (uint_fast64_t)(sz - off)); |
141 | 216 | struct MHD_Response* rf = MHD_response_from_fd( |
142 | 216 | fdp.PickValueInArray<enum MHD_HTTP_StatusCode>({ |
143 | 216 | MHD_HTTP_STATUS_OK, MHD_HTTP_STATUS_PARTIAL_CONTENT, MHD_HTTP_STATUS_NO_CONTENT |
144 | 216 | }), |
145 | 216 | fd, off, len); |
146 | 216 | if (rf) MHD_response_destroy(rf); else close(fd); |
147 | 687 | } else { |
148 | 687 | close(fd); |
149 | 687 | } |
150 | 903 | unlink(tmpl); |
151 | 903 | } |
152 | | |
153 | | // Pipe random response |
154 | 903 | int pfd[2]; |
155 | 903 | if (0 == pipe(pfd)) { |
156 | 903 | std::string pbytes = fdp.ConsumeRandomLengthString(256); |
157 | 903 | if (!pbytes.empty()) (void)!write(pfd[1], pbytes.data(), pbytes.size()); |
158 | 903 | close(pfd[1]); |
159 | 903 | struct MHD_Response* rp = MHD_response_from_pipe( |
160 | 903 | fdp.PickValueInArray<enum MHD_HTTP_StatusCode>({ |
161 | 903 | MHD_HTTP_STATUS_OK, MHD_HTTP_STATUS_NO_CONTENT |
162 | 903 | }), |
163 | 903 | pfd[0]); |
164 | 903 | if (rp) { |
165 | 903 | MHD_response_destroy(rp); |
166 | 903 | } else { |
167 | 0 | close(pfd[0]); |
168 | 0 | } |
169 | 903 | } |
170 | 903 | } |
171 | | |
172 | 3.51k | void daemon_configuration(FuzzedDataProvider& fdp, MHD_Daemon* d) { |
173 | 3.51k | using PollEnum = decltype(MHD_SPS_AUTO); |
174 | 3.51k | static constexpr PollEnum kPollChoices[] = { |
175 | 3.51k | MHD_SPS_AUTO, MHD_SPS_SELECT, MHD_SPS_POLL, MHD_SPS_EPOLL, |
176 | 3.51k | }; |
177 | 3.51k | PollEnum ps = fdp.PickValueInArray(kPollChoices); |
178 | 3.51k | auto opt1 = MHD_D_OPTION_POLL_SYSCALL(ps); |
179 | 3.51k | MHD_daemon_set_option(d, &opt1); |
180 | | |
181 | 3.51k | using AddrEnum = decltype(MHD_AF_NONE); |
182 | 3.51k | static constexpr AddrEnum kAddrChoices[] = { |
183 | 3.51k | MHD_AF_NONE, MHD_AF_AUTO, MHD_AF_INET4, MHD_AF_INET6, |
184 | 3.51k | }; |
185 | 3.51k | uint_least16_t port = fdp.ConsumeIntegralInRange<uint_least16_t>(0, 65535); |
186 | 3.51k | AddrEnum af = fdp.PickValueInArray(kAddrChoices); |
187 | 3.51k | auto opt2 = MHD_D_OPTION_BIND_PORT(af, port); |
188 | 3.51k | (void) MHD_daemon_set_option(d, &opt2); |
189 | | |
190 | 3.51k | auto opt3 = MHD_D_OPTION_DEFAULT_TIMEOUT(fdp.ConsumeIntegralInRange<unsigned>(0, 10)); |
191 | 3.51k | MHD_daemon_set_option(d, &opt3); |
192 | | |
193 | 3.51k | auto opt4 = MHD_D_OPTION_CONN_MEMORY_LIMIT(fdp.ConsumeIntegralInRange<size_t>(0, 1<<16)); |
194 | 3.51k | MHD_daemon_set_option(d, &opt4); |
195 | | |
196 | 3.51k | auto opt5 = MHD_D_OPTION_LOG_CALLBACK(&dummy_log, nullptr); |
197 | 3.51k | MHD_daemon_set_option(d, &opt5); |
198 | | |
199 | 3.51k | std::vector<uint8_t> ent = fdp.ConsumeBytes<uint8_t>(fdp.ConsumeIntegralInRange<size_t>(0, 32)); |
200 | 3.51k | auto opt6 = MHD_D_OPTION_RANDOM_ENTROPY(ent.size(), |
201 | 3.51k | const_cast<void*>(static_cast<const void*>(ent.data())) |
202 | 3.51k | ); |
203 | 3.51k | MHD_daemon_set_option(d, &opt6); |
204 | | |
205 | 3.51k | auto opt7 = MHD_D_OPTION_REREGISTER_ALL(ToMhdBool(fdp.ConsumeBool())); |
206 | 3.51k | MHD_daemon_set_option(d, &opt7); |
207 | 3.51k | } |
208 | | |
209 | 903 | void fuzz_daemon_lifecycle(FuzzedDataProvider& fdp) { |
210 | | // Create random body string for response |
211 | 903 | const size_t body_len = fdp.ConsumeIntegralInRange<size_t>(0, std::min<size_t>(fdp.remaining_bytes(), 2048)); |
212 | 903 | std::string body = fdp.ConsumeBytesAsString(body_len); |
213 | | |
214 | 903 | struct MHD_Daemon* d = MHD_daemon_create(&req_cb, &body); |
215 | 903 | if (!d) { |
216 | 0 | return; |
217 | 0 | } |
218 | | |
219 | | // Fuzz with random fixed queries |
220 | 903 | union MHD_DaemonInfoFixedData dfix{}; |
221 | 903 | const int n = fdp.ConsumeIntegralInRange<int>(1, 6); |
222 | 903 | using FixedEnum = decltype(MHD_DAEMON_INFO_FIXED_POLL_SYSCALL); |
223 | 903 | static constexpr FixedEnum kFixedChoices[] = { |
224 | 903 | MHD_DAEMON_INFO_FIXED_POLL_SYSCALL, |
225 | 903 | MHD_DAEMON_INFO_FIXED_AGGREAGATE_FD, |
226 | 903 | MHD_DAEMON_INFO_FIXED_NUM_WORK_THREADS, |
227 | 903 | MHD_DAEMON_INFO_FIXED_BIND_PORT, |
228 | 903 | MHD_DAEMON_INFO_FIXED_LISTEN_SOCKET, |
229 | 903 | MHD_DAEMON_INFO_FIXED_TLS_BACKEND, |
230 | 903 | MHD_DAEMON_INFO_FIXED_DEFAULT_TIMEOUT, |
231 | 903 | MHD_DAEMON_INFO_FIXED_GLOBAL_CONNECTION_LIMIT, |
232 | 903 | MHD_DAEMON_INFO_FIXED_PER_IP_LIMIT, |
233 | 903 | MHD_DAEMON_INFO_FIXED_SUPPRESS_DATE_HEADER, |
234 | 903 | MHD_DAEMON_INFO_FIXED_CONN_MEMORY_LIMIT, |
235 | 903 | MHD_DAEMON_INFO_FIXED_FD_NUMBER_LIMIT, |
236 | 903 | }; |
237 | 2.67k | for (int i = 0; i < n; ++i) { |
238 | 1.77k | daemon_configuration(fdp, d); |
239 | 1.77k | FixedEnum which = fdp.PickValueInArray(kFixedChoices); |
240 | 1.77k | MHD_daemon_get_info_fixed(d, which, &dfix); |
241 | 1.77k | } |
242 | | |
243 | | // Fuzz with random dynamic queries |
244 | 903 | union MHD_DaemonInfoDynamicData ddyn{}; |
245 | 903 | const int m = fdp.ConsumeIntegralInRange<int>(1, 6); |
246 | 903 | using DynEnum = decltype(MHD_DAEMON_INFO_DYNAMIC_MAX_TIME_TO_WAIT); |
247 | 903 | static constexpr DynEnum kDynChoices[] = { |
248 | 903 | MHD_DAEMON_INFO_DYNAMIC_MAX_TIME_TO_WAIT, |
249 | 903 | MHD_DAEMON_INFO_DYNAMIC_HAS_CONNECTIONS, |
250 | 903 | }; |
251 | 2.64k | for (int i = 0; i < m; ++i) { |
252 | 1.73k | daemon_configuration(fdp, d); |
253 | 1.73k | DynEnum which = fdp.PickValueInArray(kDynChoices); |
254 | 1.73k | MHD_daemon_get_info_dynamic(d, which, &ddyn); |
255 | 1.73k | } |
256 | | |
257 | 903 | MHD_daemon_destroy(d); |
258 | 903 | } |
259 | | |
260 | 903 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
261 | 903 | if (size <= 0) { |
262 | 0 | return 0; |
263 | 0 | } |
264 | 903 | FuzzedDataProvider fdp(data, size); |
265 | | |
266 | | // Fuzz digest_auth_calc targets |
267 | 903 | fuzz_digest_auth_calc(fdp); |
268 | | |
269 | | // Create responses with random choices |
270 | 903 | struct MHD_Response* r = fuzz_response_creation(fdp); |
271 | | |
272 | | // Fuzz response configurations |
273 | 903 | if (r) { |
274 | 903 | fuzz_response_config(fdp, r); |
275 | 903 | MHD_response_destroy(r); |
276 | 903 | } |
277 | | |
278 | | // Fuzz daemon lifecycle |
279 | 903 | fuzz_daemon_lifecycle(fdp); |
280 | | |
281 | 903 | return 0; |
282 | 903 | } |