/src/coturn/fuzzing/FuzzStun.c
Line | Count | Source |
1 | | /* |
2 | | * SPDX-License-Identifier: BSD-3-Clause |
3 | | * |
4 | | * https://opensource.org/license/bsd-3-clause |
5 | | * |
6 | | * Multi-harness libFuzzer entry point for server-side STUN parsing. |
7 | | * |
8 | | * Every iteration runs all sub-harnesses in sequence on the same input: |
9 | | * RFC 5769 integrity checks, multi-SHA/credential integrity, attribute |
10 | | * iteration, attribute serialization, and legacy (pre-RFC 5389) STUN |
11 | | * detection. Keeping everything behind a single binary allows the |
12 | | * upstream OSS-Fuzz build recipe to stay unchanged. |
13 | | */ |
14 | | |
15 | | #include <stdbool.h> |
16 | | #include <stdint.h> |
17 | | #include <stdio.h> |
18 | | #include <string.h> |
19 | | |
20 | | #include "apputils.h" |
21 | | #include "ns_turn_ioaddr.h" |
22 | | #include "ns_turn_msg.h" |
23 | | #include "ns_turn_msg_defs.h" |
24 | | #include "ns_turn_utils.h" |
25 | | #include "stun_buffer.h" |
26 | | |
27 | 0 | static uint8_t fuzz_byte(const uint8_t *Data, size_t Size, size_t idx) { return Size ? Data[idx % Size] : 0; } |
28 | | |
29 | 0 | static uint16_t fuzz_u16(const uint8_t *Data, size_t Size, size_t idx) { |
30 | 0 | return (uint16_t)(((uint16_t)fuzz_byte(Data, Size, idx) << 8) | (uint16_t)fuzz_byte(Data, Size, idx + 1)); |
31 | 0 | } |
32 | | |
33 | 0 | static void fuzz_printable_string(const uint8_t *Data, size_t Size, size_t idx, uint8_t *out, size_t out_size) { |
34 | 0 | if (!out_size) { |
35 | 0 | return; |
36 | 0 | } |
37 | | |
38 | 0 | const size_t max_len = out_size - 1; |
39 | 0 | const size_t len = max_len ? (size_t)(fuzz_byte(Data, Size, idx) % (max_len + 1)) : 0; |
40 | 0 | for (size_t i = 0; i < len; ++i) { |
41 | 0 | out[i] = (uint8_t)(33 + (fuzz_byte(Data, Size, idx + 1 + i) % 94)); |
42 | 0 | } |
43 | 0 | out[len] = 0; |
44 | 0 | } |
45 | | |
46 | | /* ------------------------------------------------------------------ */ |
47 | | /* Integrity: SHA1 short-term + SHA256 long-term (original FuzzStun). */ |
48 | | /* ------------------------------------------------------------------ */ |
49 | 0 | static void harness_integrity_sha1(const uint8_t *Data, size_t Size) { |
50 | 0 | if (Size < 10 || Size > 5120) { |
51 | 0 | return; |
52 | 0 | } |
53 | | |
54 | 0 | stun_is_command_message_full_check_str((uint8_t *)Data, Size, 1, NULL); |
55 | |
|
56 | 0 | uint8_t uname[STUN_MAX_USERNAME_SIZE + 1] = "fuzzuser"; |
57 | 0 | uint8_t realm[STUN_MAX_REALM_SIZE + 1] = "fuzz.realm"; |
58 | 0 | uint8_t upwd[STUN_MAX_PWD_SIZE + 1] = "VOkJxbRl1RmTxUk/WvJxBt"; |
59 | |
|
60 | 0 | stun_check_message_integrity_str(TURN_CREDENTIALS_SHORT_TERM, (uint8_t *)Data, Size, uname, realm, upwd, |
61 | 0 | SHATYPE_SHA1); |
62 | 0 | stun_check_message_integrity_str(TURN_CREDENTIALS_LONG_TERM, (uint8_t *)Data, Size, uname, realm, upwd, |
63 | 0 | SHATYPE_SHA256); |
64 | 0 | } |
65 | | |
66 | | /* ------------------------------------------------------------------ */ |
67 | | /* Integrity across all SHA types + credential modes (FuzzStunIntegrity). */ |
68 | | /* ------------------------------------------------------------------ */ |
69 | 0 | static void harness_integrity_multi(const uint8_t *Data, size_t Size) { |
70 | 0 | if (Size < STUN_HEADER_LENGTH || Size > 5120) { |
71 | 0 | return; |
72 | 0 | } |
73 | | |
74 | 0 | uint8_t buf[5120]; |
75 | 0 | uint8_t uname[STUN_MAX_USERNAME_SIZE + 1] = "fuzzuser"; |
76 | 0 | uint8_t realm[STUN_MAX_REALM_SIZE + 1] = "fuzz.realm"; |
77 | 0 | uint8_t upwd[STUN_MAX_PWD_SIZE + 1] = "VOkJxbRl1RmTxUk/WvJxBt"; |
78 | |
|
79 | 0 | static const SHATYPE sha_types[] = {SHATYPE_SHA1, SHATYPE_SHA256, SHATYPE_SHA384, SHATYPE_SHA512}; |
80 | 0 | const size_t num_sha = sizeof(sha_types) / sizeof(sha_types[0]); |
81 | |
|
82 | 0 | for (size_t s = 0; s < num_sha; s++) { |
83 | 0 | memcpy(buf, Data, Size); |
84 | 0 | stun_is_command_message_full_check_str(buf, Size, 1, NULL); |
85 | 0 | stun_check_message_integrity_str(TURN_CREDENTIALS_SHORT_TERM, buf, Size, uname, realm, upwd, sha_types[s]); |
86 | |
|
87 | 0 | memcpy(buf, Data, Size); |
88 | 0 | stun_check_message_integrity_str(TURN_CREDENTIALS_LONG_TERM, buf, Size, uname, realm, upwd, sha_types[s]); |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | | /* ------------------------------------------------------------------ */ |
93 | | /* Attribute TLV iteration + typed extraction (FuzzStunAttrIter). */ |
94 | | /* ------------------------------------------------------------------ */ |
95 | | static const uint16_t kAllAttrTypes[] = { |
96 | | STUN_ATTRIBUTE_MAPPED_ADDRESS, |
97 | | OLD_STUN_ATTRIBUTE_RESPONSE_ADDRESS, |
98 | | STUN_ATTRIBUTE_CHANGE_REQUEST, |
99 | | OLD_STUN_ATTRIBUTE_SOURCE_ADDRESS, |
100 | | OLD_STUN_ATTRIBUTE_CHANGED_ADDRESS, |
101 | | STUN_ATTRIBUTE_USERNAME, |
102 | | OLD_STUN_ATTRIBUTE_PASSWORD, |
103 | | STUN_ATTRIBUTE_MESSAGE_INTEGRITY, |
104 | | STUN_ATTRIBUTE_ERROR_CODE, |
105 | | STUN_ATTRIBUTE_UNKNOWN_ATTRIBUTES, |
106 | | OLD_STUN_ATTRIBUTE_REFLECTED_FROM, |
107 | | STUN_ATTRIBUTE_CHANNEL_NUMBER, |
108 | | STUN_ATTRIBUTE_LIFETIME, |
109 | | STUN_ATTRIBUTE_BANDWIDTH, |
110 | | STUN_ATTRIBUTE_XOR_PEER_ADDRESS, |
111 | | STUN_ATTRIBUTE_DATA, |
112 | | STUN_ATTRIBUTE_REALM, |
113 | | STUN_ATTRIBUTE_NONCE, |
114 | | STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS, |
115 | | STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY, |
116 | | STUN_ATTRIBUTE_EVEN_PORT, |
117 | | STUN_ATTRIBUTE_REQUESTED_TRANSPORT, |
118 | | STUN_ATTRIBUTE_DONT_FRAGMENT, |
119 | | STUN_ATTRIBUTE_OAUTH_ACCESS_TOKEN, |
120 | | STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, |
121 | | OLD_STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, |
122 | | STUN_ATTRIBUTE_TIMER_VAL, |
123 | | STUN_ATTRIBUTE_RESERVATION_TOKEN, |
124 | | STUN_ATTRIBUTE_PRIORITY, |
125 | | STUN_ATTRIBUTE_PADDING, |
126 | | STUN_ATTRIBUTE_RESPONSE_PORT, |
127 | | STUN_ATTRIBUTE_CONNECTION_ID, |
128 | | STUN_ATTRIBUTE_ADDITIONAL_ADDRESS_FAMILY, |
129 | | STUN_ATTRIBUTE_ADDRESS_ERROR_CODE, |
130 | | STUN_ATTRIBUTE_SOFTWARE, |
131 | | STUN_ATTRIBUTE_ALTERNATE_SERVER, |
132 | | STUN_ATTRIBUTE_FINGERPRINT, |
133 | | STUN_ATTRIBUTE_ICE_CONTROLLED, |
134 | | STUN_ATTRIBUTE_RESPONSE_ORIGIN, |
135 | | STUN_ATTRIBUTE_OTHER_ADDRESS, |
136 | | STUN_ATTRIBUTE_THIRD_PARTY_AUTHORIZATION, |
137 | | STUN_ATTRIBUTE_ORIGIN, |
138 | | STUN_ATTRIBUTE_MOBILITY_TICKET, |
139 | | STUN_ATTRIBUTE_NEW_BANDWIDTH, |
140 | | }; |
141 | | |
142 | | static const uint16_t kAddrAttrs[] = { |
143 | | STUN_ATTRIBUTE_MAPPED_ADDRESS, STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, OLD_STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, |
144 | | STUN_ATTRIBUTE_XOR_PEER_ADDRESS, STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS, STUN_ATTRIBUTE_ALTERNATE_SERVER, |
145 | | STUN_ATTRIBUTE_RESPONSE_ORIGIN, STUN_ATTRIBUTE_OTHER_ADDRESS, |
146 | | }; |
147 | | |
148 | 0 | static void harness_attr_iter(const uint8_t *Data, size_t Size) { |
149 | 0 | if (Size < STUN_HEADER_LENGTH || Size > 8192) { |
150 | 0 | return; |
151 | 0 | } |
152 | | |
153 | 0 | uint8_t buf[8192]; |
154 | 0 | memcpy(buf, Data, Size); |
155 | |
|
156 | 0 | if (!stun_is_command_message_str(buf, Size)) { |
157 | 0 | return; |
158 | 0 | } |
159 | | |
160 | 0 | stun_attr_ref sar = stun_attr_get_first_str(buf, Size); |
161 | 0 | while (sar) { |
162 | 0 | (void)stun_attr_get_type(sar); |
163 | 0 | (void)stun_attr_get_len(sar); |
164 | 0 | (void)stun_attr_get_value(sar); |
165 | 0 | (void)stun_attr_is_addr(sar); |
166 | 0 | sar = stun_attr_get_next_str(buf, Size, sar); |
167 | 0 | } |
168 | |
|
169 | 0 | ioa_addr addr; |
170 | 0 | const size_t num_addr_attrs = sizeof(kAddrAttrs) / sizeof(kAddrAttrs[0]); |
171 | 0 | for (size_t i = 0; i < num_addr_attrs; i++) { |
172 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, kAddrAttrs[i]); |
173 | 0 | if (sar) { |
174 | 0 | memset(&addr, 0, sizeof(addr)); |
175 | 0 | stun_attr_get_addr_str(buf, Size, sar, &addr, NULL); |
176 | 0 | } |
177 | 0 | memset(&addr, 0, sizeof(addr)); |
178 | 0 | stun_attr_get_first_addr_str(buf, Size, kAddrAttrs[i], &addr, NULL); |
179 | 0 | } |
180 | |
|
181 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_CHANNEL_NUMBER); |
182 | 0 | if (sar) { |
183 | 0 | (void)stun_attr_get_channel_number(sar); |
184 | 0 | } |
185 | 0 | (void)stun_attr_get_first_channel_number_str(buf, Size); |
186 | |
|
187 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY); |
188 | 0 | if (sar) { |
189 | 0 | (void)stun_get_requested_address_family(sar); |
190 | 0 | } |
191 | |
|
192 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_ADDITIONAL_ADDRESS_FAMILY); |
193 | 0 | if (sar) { |
194 | 0 | (void)stun_get_requested_address_family(sar); |
195 | 0 | } |
196 | |
|
197 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_EVEN_PORT); |
198 | 0 | if (sar) { |
199 | 0 | (void)stun_attr_get_even_port(sar); |
200 | 0 | } |
201 | |
|
202 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_BANDWIDTH); |
203 | 0 | if (sar) { |
204 | 0 | (void)stun_attr_get_bandwidth(sar); |
205 | 0 | } |
206 | |
|
207 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_NEW_BANDWIDTH); |
208 | 0 | if (sar) { |
209 | 0 | (void)stun_attr_get_bandwidth(sar); |
210 | 0 | } |
211 | |
|
212 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_RESERVATION_TOKEN); |
213 | 0 | if (sar) { |
214 | 0 | (void)stun_attr_get_reservation_token_value(sar); |
215 | 0 | } |
216 | |
|
217 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_CHANGE_REQUEST); |
218 | 0 | if (sar) { |
219 | 0 | bool change_ip = false, change_port = false; |
220 | 0 | stun_attr_get_change_request_str(sar, &change_ip, &change_port); |
221 | 0 | } |
222 | |
|
223 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_RESPONSE_PORT); |
224 | 0 | if (sar) { |
225 | 0 | (void)stun_attr_get_response_port_str(sar); |
226 | 0 | } |
227 | |
|
228 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, STUN_ATTRIBUTE_PADDING); |
229 | 0 | if (sar) { |
230 | 0 | (void)stun_attr_get_padding_len_str(sar); |
231 | 0 | } |
232 | |
|
233 | 0 | { |
234 | 0 | int err_code = 0; |
235 | 0 | uint8_t err_msg[1024] = {0}; |
236 | 0 | stun_is_error_response_str(buf, Size, &err_code, err_msg, sizeof(err_msg)); |
237 | 0 | } |
238 | 0 | { |
239 | 0 | int err_code = 0; |
240 | 0 | uint8_t err_msg[1024] = {0}; |
241 | 0 | uint8_t chal_realm[STUN_MAX_REALM_SIZE + 1] = {0}; |
242 | 0 | uint8_t chal_nonce[STUN_MAX_NONCE_SIZE + 1] = {0}; |
243 | 0 | uint8_t server_name[STUN_MAX_SERVER_NAME_SIZE + 1] = {0}; |
244 | 0 | bool oauth = false; |
245 | 0 | stun_is_challenge_response_str(buf, Size, &err_code, err_msg, sizeof(err_msg), chal_realm, chal_nonce, server_name, |
246 | 0 | &oauth); |
247 | 0 | } |
248 | |
|
249 | 0 | const size_t num_all_attrs = sizeof(kAllAttrTypes) / sizeof(kAllAttrTypes[0]); |
250 | 0 | for (size_t i = 0; i < num_all_attrs; i++) { |
251 | 0 | sar = stun_attr_get_first_by_type_str(buf, Size, kAllAttrTypes[i]); |
252 | 0 | if (sar) { |
253 | 0 | (void)stun_attr_get_type(sar); |
254 | 0 | (void)stun_attr_get_len(sar); |
255 | 0 | (void)stun_attr_get_value(sar); |
256 | 0 | } |
257 | 0 | } |
258 | 0 | } |
259 | | |
260 | | /* ------------------------------------------------------------------ */ |
261 | | /* Attribute serialization / append paths (FuzzStunAttrAdd). */ |
262 | | /* ------------------------------------------------------------------ */ |
263 | 0 | static void harness_attr_add(const uint8_t *Data, size_t Size) { |
264 | 0 | if (Size < STUN_HEADER_LENGTH || Size > 4096) { |
265 | 0 | return; |
266 | 0 | } |
267 | | |
268 | 0 | uint8_t buf[MAX_STUN_MESSAGE_SIZE] = {0}; |
269 | 0 | memcpy(buf, Data, Size); |
270 | 0 | size_t len = Size; |
271 | |
|
272 | 0 | if (!stun_is_command_message_str(buf, len)) { |
273 | 0 | return; |
274 | 0 | } |
275 | | |
276 | 0 | uint8_t test_uname[] = "fuzzuser@fuzz.realm"; |
277 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_USERNAME, test_uname, (int)(sizeof(test_uname) - 1)); |
278 | |
|
279 | 0 | uint8_t test_realm[] = "fuzz.realm"; |
280 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_REALM, test_realm, (int)(sizeof(test_realm) - 1)); |
281 | |
|
282 | 0 | uint8_t test_nonce[] = "fuzznonce0123456789abcdef"; |
283 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_NONCE, test_nonce, (int)(sizeof(test_nonce) - 1)); |
284 | |
|
285 | 0 | uint8_t test_sw[] = "coturn-fuzz/1.0"; |
286 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_SOFTWARE, test_sw, (int)(sizeof(test_sw) - 1)); |
287 | |
|
288 | 0 | uint8_t lifetime_val[4] = {0x00, 0x00, 0x02, 0x58}; |
289 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_LIFETIME, lifetime_val, 4); |
290 | |
|
291 | 0 | uint8_t transport_val[4] = {STUN_ATTRIBUTE_TRANSPORT_UDP_VALUE, 0x00, 0x00, 0x00}; |
292 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_REQUESTED_TRANSPORT, transport_val, 4); |
293 | |
|
294 | 0 | uint8_t af_val[4] = {STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV4, 0x00, 0x00, 0x00}; |
295 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY, af_val, 4); |
296 | |
|
297 | 0 | uint8_t even_port_val[1] = {0x80}; |
298 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_EVEN_PORT, even_port_val, 1); |
299 | |
|
300 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_DONT_FRAGMENT, NULL, 0); |
301 | |
|
302 | 0 | stun_attr_add_channel_number_str(buf, &len, 0x4001); |
303 | |
|
304 | 0 | stun_attr_add_bandwidth_str(buf, &len, 1000000); |
305 | |
|
306 | 0 | ioa_addr addr4 = {0}; |
307 | 0 | addr4.s4.sin_family = AF_INET; |
308 | 0 | addr4.s4.sin_port = htons(12345); |
309 | 0 | addr4.s4.sin_addr.s_addr = htonl(0xC0A80001); |
310 | 0 | stun_attr_add_addr_str(buf, &len, STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, &addr4); |
311 | 0 | stun_attr_add_addr_str(buf, &len, STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &addr4); |
312 | 0 | stun_attr_add_addr_str(buf, &len, STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS, &addr4); |
313 | 0 | stun_attr_add_addr_str(buf, &len, STUN_ATTRIBUTE_MAPPED_ADDRESS, &addr4); |
314 | |
|
315 | 0 | ioa_addr addr6 = {0}; |
316 | 0 | addr6.s6.sin6_family = AF_INET6; |
317 | 0 | addr6.s6.sin6_port = htons(54321); |
318 | 0 | addr6.s6.sin6_addr.s6_addr[15] = 1; |
319 | 0 | stun_attr_add_addr_str(buf, &len, STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, &addr6); |
320 | 0 | stun_attr_add_addr_str(buf, &len, STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS, &addr6); |
321 | |
|
322 | 0 | stun_attr_add_address_error_code(buf, &len, STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6, 440); |
323 | |
|
324 | 0 | stun_attr_add_change_request_str(buf, &len, true, true); |
325 | 0 | stun_attr_add_response_port_str(buf, &len, 3479); |
326 | 0 | stun_attr_add_padding_str(buf, &len, 64); |
327 | |
|
328 | 0 | if (Size > STUN_HEADER_LENGTH + 4) { |
329 | 0 | int data_len = (int)(Size - STUN_HEADER_LENGTH); |
330 | 0 | if (data_len > 1024) { |
331 | 0 | data_len = 1024; |
332 | 0 | } |
333 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_DATA, Data + STUN_HEADER_LENGTH, data_len); |
334 | 0 | } |
335 | |
|
336 | 0 | stun_attr_add_fingerprint_str(buf, &len); |
337 | 0 | } |
338 | | |
339 | | /* ------------------------------------------------------------------ */ |
340 | | /* Legacy (pre-RFC 5389) STUN detection (FuzzOldStun). */ |
341 | | /* ------------------------------------------------------------------ */ |
342 | 0 | static void harness_old_stun(const uint8_t *Data, size_t Size) { |
343 | 0 | if (Size < STUN_HEADER_LENGTH || Size > 5120) { |
344 | 0 | return; |
345 | 0 | } |
346 | | |
347 | 0 | uint8_t buf[5120]; |
348 | 0 | memcpy(buf, Data, Size); |
349 | |
|
350 | 0 | uint32_t cookie = 0; |
351 | 0 | bool is_old = old_stun_is_command_message_str(buf, Size, &cookie); |
352 | 0 | (void)stun_is_command_message_str(buf, Size); |
353 | |
|
354 | 0 | if (is_old) { |
355 | 0 | (void)stun_get_msg_type_str(buf, Size); |
356 | 0 | (void)stun_get_method_str(buf, Size); |
357 | |
|
358 | 0 | stun_is_request_str(buf, Size); |
359 | 0 | stun_is_indication_str(buf, Size); |
360 | 0 | stun_is_success_response_str(buf, Size); |
361 | |
|
362 | 0 | int err_code = 0; |
363 | 0 | uint8_t err_msg[256] = {0}; |
364 | 0 | stun_is_error_response_str(buf, Size, &err_code, err_msg, sizeof(err_msg)); |
365 | |
|
366 | 0 | int fp_present = 0; |
367 | 0 | stun_is_command_message_full_check_str(buf, Size, 1, &fp_present); |
368 | 0 | stun_is_command_message_full_check_str(buf, Size, 0, &fp_present); |
369 | |
|
370 | 0 | stun_is_binding_request_str(buf, Size, 0); |
371 | 0 | stun_is_binding_response_str(buf, Size); |
372 | |
|
373 | 0 | stun_attr_ref sar = stun_attr_get_first_str(buf, Size); |
374 | 0 | while (sar) { |
375 | 0 | (void)stun_attr_get_type(sar); |
376 | 0 | (void)stun_attr_get_len(sar); |
377 | 0 | sar = stun_attr_get_next_str(buf, Size, sar); |
378 | 0 | } |
379 | 0 | } |
380 | 0 | } |
381 | | |
382 | | /* ------------------------------------------------------------------ */ |
383 | | /* Challenge-response builder (FuzzStunChallengeResponse). */ |
384 | | /* */ |
385 | | /* stun_is_challenge_response_str only descends into its three inner */ |
386 | | /* stun_attr_get_first_by_type_str calls when: */ |
387 | | /* 1) the message is an error response, */ |
388 | | /* 2) err_code is 401 OR 438, */ |
389 | | /* 3) a REALM attribute is present, */ |
390 | | /* 4) a NONCE attribute is present. */ |
391 | | /* The OAuth branch additionally requires THIRD-PARTY-AUTHORIZATION. */ |
392 | | /* */ |
393 | | /* The fuzzer-driven harness in harness_attr_iter calls the function */ |
394 | | /* every iteration but the conjunction of conditions is too specific */ |
395 | | /* for libFuzzer to discover from binary mutation alone — OSS-Fuzz */ |
396 | | /* introspector flags 9 unreached callsites under */ |
397 | | /* stun_attr_get_first_by_type_str gated on this function. */ |
398 | | /* */ |
399 | | /* Build six deterministic message variants on every iteration so */ |
400 | | /* every internal branch is exercised regardless of the input bytes. */ |
401 | | /* Realm / nonce / server-name lengths are derived from fuzz bytes so */ |
402 | | /* each iteration is still meaningfully distinct. */ |
403 | | /* ------------------------------------------------------------------ */ |
404 | 0 | static void harness_challenge_response_builder(const uint8_t *Data, size_t Size) { |
405 | 0 | if (!Size) { |
406 | 0 | return; |
407 | 0 | } |
408 | | |
409 | 0 | static const uint16_t kMethods[] = { |
410 | 0 | STUN_METHOD_ALLOCATE, |
411 | 0 | STUN_METHOD_BINDING, |
412 | 0 | STUN_METHOD_REFRESH, |
413 | 0 | STUN_METHOD_CHANNEL_BIND, |
414 | 0 | }; |
415 | 0 | const uint16_t method = kMethods[Data[0] % (sizeof(kMethods) / sizeof(kMethods[0]))]; |
416 | | |
417 | | /* Length of each variable-length string is taken from a single fuzz byte |
418 | | * so libFuzzer still varies the inputs across iterations. Floors keep the |
419 | | * attribute non-empty so the inner copies in stun_is_challenge_response_str |
420 | | * actually populate the output buffers. */ |
421 | 0 | const size_t realm_len = 1 + (Size > 1 ? (Data[1] % 16) : 0); |
422 | 0 | const size_t nonce_len = 1 + (Size > 2 ? (Data[2] % 16) : 0); |
423 | 0 | const size_t server_len = 1 + (Size > 3 ? (Data[3] % 16) : 0); |
424 | |
|
425 | 0 | uint8_t realm_buf[STUN_MAX_REALM_SIZE + 1] = {0}; |
426 | 0 | uint8_t nonce_buf[STUN_MAX_NONCE_SIZE + 1] = {0}; |
427 | 0 | uint8_t server_buf[STUN_MAX_SERVER_NAME_SIZE + 1] = {0}; |
428 | 0 | for (size_t i = 0; i < realm_len; ++i) { |
429 | 0 | realm_buf[i] = (uint8_t)('a' + (i % 26)); |
430 | 0 | } |
431 | 0 | for (size_t i = 0; i < nonce_len; ++i) { |
432 | 0 | nonce_buf[i] = (uint8_t)('0' + (i % 10)); |
433 | 0 | } |
434 | 0 | for (size_t i = 0; i < server_len; ++i) { |
435 | 0 | server_buf[i] = (uint8_t)('A' + (i % 26)); |
436 | 0 | } |
437 | |
|
438 | 0 | stun_tid tid = {0}; |
439 | 0 | for (size_t i = 0; i < STUN_TID_SIZE; ++i) { |
440 | 0 | tid.tsx_id[i] = (uint8_t)(Size > i + 4 ? Data[i + 4] : (uint8_t)i); |
441 | 0 | } |
442 | | |
443 | | /* Each variant builds a fresh error response and runs it through the |
444 | | * predicate so the inner attribute lookups fire. */ |
445 | 0 | static const struct { |
446 | 0 | uint16_t err_code; |
447 | 0 | bool include_realm; |
448 | 0 | bool include_nonce; |
449 | 0 | bool include_third_party_auth; |
450 | 0 | } kVariants[] = { |
451 | 0 | {401, true, true, false}, /* canonical 401 challenge: REALM + NONCE */ |
452 | 0 | {401, true, true, true}, /* same + THIRD-PARTY-AUTHORIZATION (OAuth branch) */ |
453 | 0 | {438, true, true, false}, /* covers the (*err_code) == 438 disjunct */ |
454 | 0 | {401, true, false, false}, /* REALM present, NONCE missing — negative inner path */ |
455 | 0 | {401, false, false, false}, /* err_code matches but no REALM — outer negative path */ |
456 | 0 | {400, true, true, false}, /* err_code does not match 401/438 — early-out path */ |
457 | 0 | }; |
458 | |
|
459 | 0 | for (size_t v = 0; v < sizeof(kVariants) / sizeof(kVariants[0]); ++v) { |
460 | 0 | uint8_t buf[MAX_STUN_MESSAGE_SIZE] = {0}; |
461 | 0 | size_t len = 0; |
462 | 0 | stun_init_error_response_str(method, buf, &len, kVariants[v].err_code, (const uint8_t *)"unauthorized", &tid, true); |
463 | |
|
464 | 0 | if (kVariants[v].include_realm) { |
465 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_REALM, realm_buf, (int)realm_len); |
466 | 0 | } |
467 | 0 | if (kVariants[v].include_third_party_auth) { |
468 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_THIRD_PARTY_AUTHORIZATION, server_buf, (int)server_len); |
469 | 0 | } |
470 | 0 | if (kVariants[v].include_nonce) { |
471 | 0 | stun_attr_add_str(buf, &len, STUN_ATTRIBUTE_NONCE, nonce_buf, (int)nonce_len); |
472 | 0 | } |
473 | |
|
474 | 0 | int err_code = 0; |
475 | 0 | uint8_t err_msg[1024] = {0}; |
476 | 0 | uint8_t out_realm[STUN_MAX_REALM_SIZE + 1] = {0}; |
477 | 0 | uint8_t out_nonce[STUN_MAX_NONCE_SIZE + 1] = {0}; |
478 | 0 | uint8_t out_server[STUN_MAX_SERVER_NAME_SIZE + 1] = {0}; |
479 | 0 | bool oauth = false; |
480 | 0 | (void)stun_is_challenge_response_str(buf, len, &err_code, err_msg, sizeof(err_msg), out_realm, out_nonce, |
481 | 0 | out_server, &oauth); |
482 | | |
483 | | /* Also exercise the NULL-oauth-pointer branch, which is reachable from |
484 | | * other call sites in the codebase. */ |
485 | 0 | (void)stun_is_challenge_response_str(buf, len, &err_code, err_msg, sizeof(err_msg), out_realm, out_nonce, |
486 | 0 | out_server, NULL); |
487 | 0 | } |
488 | 0 | } |
489 | | |
490 | | /* ------------------------------------------------------------------ */ |
491 | | /* Address parser coverage for make_ioa_addr_from_full_string. */ |
492 | | /* ------------------------------------------------------------------ */ |
493 | 0 | static void harness_full_addr_parser(const uint8_t *Data, size_t Size) { |
494 | 0 | ioa_addr addr; |
495 | 0 | const uint16_t default_port = fuzz_u16(Data, Size, 0); |
496 | 0 | const uint16_t explicit_port = fuzz_u16(Data, Size, 2); |
497 | |
|
498 | 0 | char ipv4_plain[MAX_IOA_ADDR_STRING]; |
499 | 0 | char ipv4_with_port[MAX_IOA_ADDR_STRING]; |
500 | 0 | char ipv6_bracketed[MAX_IOA_ADDR_STRING]; |
501 | 0 | char ipv6_with_port[MAX_IOA_ADDR_STRING]; |
502 | 0 | char ipv4_spaced[MAX_IOA_ADDR_STRING]; |
503 | |
|
504 | 0 | snprintf(ipv4_plain, sizeof(ipv4_plain), "%u.%u.%u.%u", fuzz_byte(Data, Size, 4), fuzz_byte(Data, Size, 5), |
505 | 0 | fuzz_byte(Data, Size, 6), fuzz_byte(Data, Size, 7)); |
506 | 0 | snprintf(ipv4_with_port, sizeof(ipv4_with_port), "%u.%u.%u.%u:%u", fuzz_byte(Data, Size, 8), fuzz_byte(Data, Size, 9), |
507 | 0 | fuzz_byte(Data, Size, 10), fuzz_byte(Data, Size, 11), explicit_port); |
508 | 0 | snprintf(ipv6_bracketed, sizeof(ipv6_bracketed), "[2001:db8:%x:%x::%x]", fuzz_u16(Data, Size, 12), |
509 | 0 | fuzz_u16(Data, Size, 14), fuzz_u16(Data, Size, 16)); |
510 | 0 | snprintf(ipv6_with_port, sizeof(ipv6_with_port), "[2001:db8:%x:%x::%x]:%u", fuzz_u16(Data, Size, 18), |
511 | 0 | fuzz_u16(Data, Size, 20), fuzz_u16(Data, Size, 22), explicit_port); |
512 | 0 | snprintf(ipv4_spaced, sizeof(ipv4_spaced), " %u.%u.%u.%u:%u", fuzz_byte(Data, Size, 24), fuzz_byte(Data, Size, 25), |
513 | 0 | fuzz_byte(Data, Size, 26), fuzz_byte(Data, Size, 27), explicit_port); |
514 | |
|
515 | 0 | static const char *kFixedInputs[] = { |
516 | 0 | "", "0.0.0.0", "127.0.0.1:3478", "192.0.2.1:", "[::1]", "[::1]:5349", "[2001:db8::1] ", "::1", |
517 | 0 | }; |
518 | |
|
519 | 0 | for (size_t i = 0; i < sizeof(kFixedInputs) / sizeof(kFixedInputs[0]); ++i) { |
520 | 0 | memset(&addr, 0, sizeof(addr)); |
521 | 0 | (void)make_ioa_addr_from_full_string((const uint8_t *)kFixedInputs[i], default_port, &addr); |
522 | 0 | } |
523 | |
|
524 | 0 | const char *generated[] = {ipv4_plain, ipv4_with_port, ipv6_bracketed, ipv6_with_port, ipv4_spaced}; |
525 | 0 | for (size_t i = 0; i < sizeof(generated) / sizeof(generated[0]); ++i) { |
526 | 0 | memset(&addr, 0, sizeof(addr)); |
527 | 0 | (void)make_ioa_addr_from_full_string((const uint8_t *)generated[i], default_port, &addr); |
528 | 0 | } |
529 | |
|
530 | 0 | (void)make_ioa_addr_from_full_string((const uint8_t *)ipv4_plain, default_port, NULL); |
531 | 0 | } |
532 | | |
533 | | /* ------------------------------------------------------------------ */ |
534 | | /* Direct MESSAGE-INTEGRITY append helpers. */ |
535 | | /* ------------------------------------------------------------------ */ |
536 | 0 | static void harness_integrity_attr_add(const uint8_t *Data, size_t Size) { |
537 | 0 | static const SHATYPE kShaTypes[] = {SHATYPE_SHA1, SHATYPE_SHA256, SHATYPE_SHA384, SHATYPE_SHA512}; |
538 | 0 | const SHATYPE shatype = kShaTypes[fuzz_byte(Data, Size, 0) % (sizeof(kShaTypes) / sizeof(kShaTypes[0]))]; |
539 | |
|
540 | 0 | uint8_t uname[STUN_MAX_USERNAME_SIZE + 1] = {0}; |
541 | 0 | uint8_t realm[STUN_MAX_REALM_SIZE + 1] = {0}; |
542 | 0 | uint8_t nonce[STUN_MAX_NONCE_SIZE + 1] = {0}; |
543 | 0 | uint8_t upwd[STUN_MAX_PWD_SIZE + 1] = {0}; |
544 | 0 | password_t pwd = {0}; |
545 | 0 | hmackey_t key = {0}; |
546 | |
|
547 | 0 | fuzz_printable_string(Data, Size, 1, uname, sizeof(uname)); |
548 | 0 | fuzz_printable_string(Data, Size, 1 + sizeof(uname), realm, sizeof(realm)); |
549 | 0 | fuzz_printable_string(Data, Size, 1 + sizeof(uname) + sizeof(realm), nonce, sizeof(nonce)); |
550 | 0 | fuzz_printable_string(Data, Size, 1 + sizeof(uname) + sizeof(realm) + sizeof(nonce), upwd, sizeof(upwd)); |
551 | 0 | fuzz_printable_string(Data, Size, 1 + sizeof(uname) + sizeof(realm) + sizeof(nonce) + sizeof(upwd), pwd, sizeof(pwd)); |
552 | |
|
553 | 0 | if (!uname[0]) { |
554 | 0 | memcpy(uname, "fuzzuser", sizeof("fuzzuser")); |
555 | 0 | } |
556 | 0 | if (!realm[0]) { |
557 | 0 | memcpy(realm, "fuzz.realm", sizeof("fuzz.realm")); |
558 | 0 | } |
559 | 0 | if (!nonce[0]) { |
560 | 0 | memcpy(nonce, "fuzznonce", sizeof("fuzznonce")); |
561 | 0 | } |
562 | 0 | if (!upwd[0]) { |
563 | 0 | memcpy(upwd, "fuzzpassword", sizeof("fuzzpassword")); |
564 | 0 | } |
565 | 0 | if (!pwd[0]) { |
566 | 0 | memcpy(pwd, "shortterm", sizeof("shortterm")); |
567 | 0 | } |
568 | |
|
569 | 0 | for (size_t i = 0; i < sizeof(key); ++i) { |
570 | 0 | key[i] = fuzz_byte(Data, Size, 32 + i); |
571 | 0 | } |
572 | |
|
573 | 0 | uint8_t buf[MAX_STUN_MESSAGE_SIZE]; |
574 | 0 | size_t len = 0; |
575 | 0 | stun_init_request_str(STUN_METHOD_BINDING, buf, &len); |
576 | 0 | (void)stun_attr_add_integrity_by_key_str(buf, &len, uname, realm, key, nonce, shatype); |
577 | |
|
578 | 0 | len = 0; |
579 | 0 | stun_init_request_str(STUN_METHOD_ALLOCATE, buf, &len); |
580 | 0 | (void)stun_attr_add_integrity_by_user_str(buf, &len, uname, realm, upwd, nonce, shatype); |
581 | |
|
582 | 0 | len = 0; |
583 | 0 | stun_init_request_str(STUN_METHOD_REFRESH, buf, &len); |
584 | 0 | (void)stun_attr_add_integrity_by_user_short_term_str(buf, &len, uname, pwd, shatype); |
585 | |
|
586 | 0 | len = 0; |
587 | 0 | stun_init_request_str(STUN_METHOD_CHANNEL_BIND, buf, &len); |
588 | 0 | (void)stun_attr_add_integrity_str(TURN_CREDENTIALS_SHORT_TERM, buf, &len, key, pwd, shatype); |
589 | |
|
590 | 0 | len = 0; |
591 | 0 | stun_init_request_str(STUN_METHOD_BINDING, buf, &len); |
592 | 0 | (void)stun_attr_add_integrity_str(TURN_CREDENTIALS_LONG_TERM, buf, &len, key, pwd, shatype); |
593 | 0 | } |
594 | | |
595 | | /* ------------------------------------------------------------------ */ |
596 | | /* libFuzzer entry point — run every harness on each input. */ |
597 | | /* ------------------------------------------------------------------ */ |
598 | 318 | extern int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { |
599 | 318 | harness_integrity_sha1(Data, Size); |
600 | 318 | harness_integrity_multi(Data, Size); |
601 | 318 | harness_attr_iter(Data, Size); |
602 | 318 | harness_attr_add(Data, Size); |
603 | 318 | harness_old_stun(Data, Size); |
604 | 318 | harness_challenge_response_builder(Data, Size); |
605 | 318 | harness_full_addr_parser(Data, Size); |
606 | 318 | harness_integrity_attr_add(Data, Size); |
607 | 318 | return 0; |
608 | 318 | } |