/src/libcoap/tests/oss-fuzz/proxy_target.c
Line | Count | Source |
1 | | /* Proxy fuzzer for libcoap. |
2 | | * |
3 | | * Drives coap_proxy_forward_request() with a fuzzer-controlled request PDU. |
4 | | * The proxy is configured forward-dynamic so the upstream target comes from |
5 | | * the request's Proxy-Uri or Proxy-Scheme+Uri-Host+Uri-Port options — the |
6 | | * attacker-controllable parsing surface inside coap_proxy.c. |
7 | | * |
8 | | * This harness drives: |
9 | | * - coap_get_uri_proxy_scheme_info (Proxy-Scheme + Uri-Host + Uri-Port) |
10 | | * - coap_split_proxy_uri (full URI string in Proxy-Uri) |
11 | | * - coap_verify_proxy_scheme_supported |
12 | | * - coap_proxy_get_session / coap_proxy_get_add_list_entry |
13 | | * - coap_proxy_get_ongoing_session (address resolution, session spawn) |
14 | | * - coap_proxy_forward_request_lkd (option mangling, PDU rewrite, send) |
15 | | * - coap_proxy_log_entry |
16 | | * - coap_proxy_release / coap_proxy_remove_association on cleanup |
17 | | * |
18 | | * Outbound UDP sockets to 127.0.0.1:0 quietly drop frames; no listener is |
19 | | * needed because the proxy doesn't block waiting for the upstream response. |
20 | | */ |
21 | | |
22 | | #include "coap3/coap_internal.h" |
23 | | |
24 | | #include <stdint.h> |
25 | | #include <stdlib.h> |
26 | | #include <string.h> |
27 | | |
28 | | static int |
29 | 0 | proxy_fuzz_event_handler(coap_session_t *session, const coap_event_t event) { |
30 | 0 | (void)session; |
31 | 0 | (void)event; |
32 | 0 | return 0; |
33 | 0 | } |
34 | | |
35 | | /* Required by coap_resource_proxy_uri_init2; we never forward a real response |
36 | | * back to a client so the handler body just sets a code and returns. */ |
37 | | static void |
38 | | proxy_fuzz_handler(coap_resource_t *resource, coap_session_t *session, |
39 | | const coap_pdu_t *request, const coap_string_t *query, |
40 | 0 | coap_pdu_t *response) { |
41 | 0 | (void)resource; |
42 | 0 | (void)session; |
43 | 0 | (void)request; |
44 | 0 | (void)query; |
45 | 0 | response->code = COAP_RESPONSE_CODE(205); |
46 | 0 | } |
47 | | |
48 | | /* Bounded cursor for slicing option values out of the fuzz buffer. */ |
49 | | typedef struct { |
50 | | const uint8_t *data; |
51 | | size_t size; |
52 | | size_t pos; |
53 | | } proxy_cursor_t; |
54 | | |
55 | | /* Take up to `max` bytes off the cursor. Returns the actual length written to |
56 | | * *out_len. May return zero. */ |
57 | | static const uint8_t * |
58 | 184 | proxy_take(proxy_cursor_t *c, size_t max, size_t *out_len) { |
59 | 184 | size_t avail = c->size > c->pos ? c->size - c->pos : 0; |
60 | 184 | size_t take = avail < max ? avail : max; |
61 | 184 | const uint8_t *p = c->data + c->pos; |
62 | 184 | c->pos += take; |
63 | 184 | *out_len = take; |
64 | 184 | return p; |
65 | 184 | } |
66 | | |
67 | | int |
68 | 32 | LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |
69 | | /* Need: mode byte + method byte + at least a few option-payload bytes. */ |
70 | 32 | if (size < 8) |
71 | 4 | return 0; |
72 | | |
73 | 28 | coap_context_t *ctx = NULL; |
74 | 28 | coap_session_t *req_session = NULL; |
75 | 28 | coap_resource_t *resource = NULL; |
76 | 28 | coap_pdu_t *request = NULL; |
77 | 28 | coap_pdu_t *response = NULL; |
78 | 28 | coap_address_t addr; |
79 | | |
80 | 28 | const uint8_t mode = data[0]; |
81 | 28 | const uint8_t method_byte = data[1]; |
82 | | |
83 | 28 | coap_startup(); |
84 | 28 | coap_set_log_level(COAP_LOG_EMERG); |
85 | 28 | coap_dtls_set_log_level(COAP_LOG_EMERG); |
86 | 28 | coap_debug_set_packet_loss("100%"); |
87 | 28 | coap_debug_set_packet_fail("100%"); |
88 | | |
89 | 28 | ctx = coap_new_context(NULL); |
90 | 28 | if (!ctx) |
91 | 0 | goto cleanup; |
92 | 28 | coap_register_event_handler(ctx, proxy_fuzz_event_handler); |
93 | | |
94 | | /* Pick a proxy type from mode bits 2-4. Cover all three base types (static, |
95 | | * dynamic, reverse) crossed with the option-controlling bits that are |
96 | | * meaningful for each, per coap_proxy.h: |
97 | | * 0: FWD_STATIC — needs pre-populated server_list entry |
98 | | * 1: FWD_STATIC | STRIP — static, strip proxy options on forward |
99 | | * 2: FWD_DYNAMIC — pulls target from request's Proxy-* options |
100 | | * 3: FWD_DYNAMIC | STRIP — same, strip proxy options (forces re-walk) |
101 | | * 4: FWD_DYNAMIC | MCAST — allow multicast upstream resolution |
102 | | * 5: FWD_DYNAMIC | DYN_DEFINED — no auto-added dynamic upstreams |
103 | | * 6: REV — reverse proxy, also uses server_list entry |
104 | | * 7: REV | STRIP — reverse, strip proxy options |
105 | | * STRIP is the more interesting code path because it forces an option re-walk. |
106 | | */ |
107 | 28 | static const coap_proxy_t types[8] = { |
108 | 28 | COAP_PROXY_FWD_STATIC, |
109 | 28 | COAP_PROXY_FWD_STATIC | COAP_PROXY_BIT_STRIP, |
110 | 28 | COAP_PROXY_FWD_DYNAMIC, |
111 | 28 | COAP_PROXY_FWD_DYNAMIC | COAP_PROXY_BIT_STRIP, |
112 | 28 | COAP_PROXY_FWD_DYNAMIC | COAP_PROXY_BIT_MCAST, |
113 | 28 | COAP_PROXY_FWD_DYNAMIC | COAP_PROXY_DYN_DEFINED, |
114 | 28 | COAP_PROXY_REV, |
115 | 28 | COAP_PROXY_REV | COAP_PROXY_BIT_STRIP, |
116 | 28 | }; |
117 | 28 | coap_proxy_t selected_type = types[(mode >> 2) & 0x07]; |
118 | | |
119 | | /* Backing storage for a static server_list entry. coap_proxy_get_session |
120 | | * memcpys the entry into server_use, so stack storage is fine. */ |
121 | 28 | coap_proxy_server_t static_entry; |
122 | 28 | memset(&static_entry, 0, sizeof(static_entry)); |
123 | 28 | static_entry.uri.scheme = COAP_URI_SCHEME_COAP; |
124 | 28 | static_entry.uri.host.s = (const uint8_t *)"127.0.0.1"; |
125 | 28 | static_entry.uri.host.length = 9; |
126 | 28 | static_entry.uri.port = 0; /* Picks something the OS will quietly drop. */ |
127 | | |
128 | 28 | coap_proxy_server_list_t server_list; |
129 | 28 | memset(&server_list, 0, sizeof(server_list)); |
130 | 28 | server_list.type = selected_type; |
131 | | /* Match on the base type only (COAP_PROXY_NEW_MASK), so the STRIP/MCAST/ |
132 | | * DYN_DEFINED option bits don't defeat the comparison — both FWD_STATIC and |
133 | | * REV (with or without those bits) need a pre-populated upstream entry. */ |
134 | 28 | coap_proxy_t base_type = selected_type & COAP_PROXY_NEW_MASK; |
135 | 28 | if (base_type == COAP_PROXY_FWD_STATIC || base_type == COAP_PROXY_REV) { |
136 | 14 | server_list.entry = &static_entry; |
137 | 14 | server_list.entry_count = 1; |
138 | 14 | } |
139 | | |
140 | | /* Build the "incoming client" session. We use UDP-to-loopback because UDP |
141 | | * doesn't connect — coap_new_client_session returns a fully-formed session |
142 | | * with no real network handshake. We won't actually transmit on it. */ |
143 | 28 | coap_address_init(&addr); |
144 | 28 | addr.addr.sin.sin_family = AF_INET; |
145 | 28 | addr.addr.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
146 | 28 | addr.addr.sin.sin_port = htons(5683); |
147 | 28 | req_session = coap_new_client_session(ctx, NULL, &addr, COAP_PROTO_UDP); |
148 | 28 | if (!req_session) |
149 | 0 | goto cleanup; |
150 | 28 | req_session->state = COAP_SESSION_STATE_ESTABLISHED; |
151 | | |
152 | | /* The proxy code uses `resource` to track which proxy URI resource the |
153 | | * request came in on. Use the official proxy-URI resource builder so flags |
154 | | * like `is_proxy_uri` are set, but with no host_name_list — we don't enforce |
155 | | * the host filter here. */ |
156 | 28 | resource = coap_resource_proxy_uri_init2(proxy_fuzz_handler, 0, NULL, 0); |
157 | 28 | if (!resource) |
158 | 0 | goto cleanup; |
159 | 28 | coap_add_resource(ctx, resource); |
160 | | |
161 | | /* Build the request PDU. */ |
162 | 28 | const coap_pdu_code_t methods[8] = { |
163 | 28 | COAP_REQUEST_CODE_GET, COAP_REQUEST_CODE_POST, |
164 | 28 | COAP_REQUEST_CODE_PUT, COAP_REQUEST_CODE_DELETE, |
165 | 28 | COAP_REQUEST_CODE_FETCH, COAP_REQUEST_CODE_PATCH, |
166 | 28 | COAP_REQUEST_CODE_IPATCH, COAP_REQUEST_CODE_GET, |
167 | 28 | }; |
168 | 28 | coap_pdu_type_t pdu_type = (mode & 0x01) ? COAP_MESSAGE_NON : COAP_MESSAGE_CON; |
169 | 28 | size_t max_pdu = coap_session_max_pdu_size(req_session); |
170 | 28 | request = coap_pdu_init(pdu_type, methods[method_byte & 0x07], |
171 | 28 | coap_new_message_id(req_session), max_pdu); |
172 | 28 | if (!request) |
173 | 0 | goto cleanup; |
174 | | |
175 | | /* Deterministic 4-byte token derived from the input keeps option-tracking |
176 | | * predictable across iterations without consuming the cursor's payload. */ |
177 | 28 | const uint8_t token[4] = { data[2], data[3], data[4], data[5] }; |
178 | 28 | coap_add_token(request, sizeof(token), token); |
179 | | |
180 | | /* Add options in monotonically increasing number order, since |
181 | | * coap_add_option doesn't reorder. Numbers below from coap_option.h: |
182 | | * IF_MATCH=1 URI_HOST=3 URI_PORT=7 URI_PATH=11 PROXY_URI=35 |
183 | | * PROXY_SCHEME=39 SIZE1=60 ... |
184 | | */ |
185 | 28 | proxy_cursor_t c = { data + 6, size - 6, 0 }; |
186 | | |
187 | | /* URI-Host (option 3). Pull a small slice — the proxy uses this together |
188 | | * with Proxy-Scheme to compute the upstream target. */ |
189 | 28 | { |
190 | 28 | size_t n; |
191 | 28 | const uint8_t *p = proxy_take(&c, 32, &n); |
192 | 28 | if (n) |
193 | 28 | coap_add_option(request, COAP_OPTION_URI_HOST, n, p); |
194 | 28 | } |
195 | | |
196 | | /* URI-Port (option 7). 2 bytes max, encodes as a CoAP varint internally. */ |
197 | 28 | { |
198 | 28 | size_t n; |
199 | 28 | const uint8_t *p = proxy_take(&c, 2, &n); |
200 | 28 | if (n) |
201 | 17 | coap_add_option(request, COAP_OPTION_URI_PORT, n, p); |
202 | 28 | } |
203 | | |
204 | | /* URI-Path (option 11) — a couple of segments to drive option-walk code. */ |
205 | 84 | for (int i = 0; i < 2; i++) { |
206 | 56 | size_t n; |
207 | 56 | const uint8_t *p = proxy_take(&c, 16, &n); |
208 | 56 | if (n) |
209 | 33 | coap_add_option(request, COAP_OPTION_URI_PATH, n, p); |
210 | 56 | } |
211 | | |
212 | | /* Submode (bits 5-6 of mode) selects which proxy target option(s) we set: |
213 | | * 0: Proxy-Scheme only — drives coap_get_uri_proxy_scheme_info |
214 | | * 1: Proxy-Uri only — drives coap_split_proxy_uri |
215 | | * 2: both — proxy code prefers Proxy-Uri but parses both |
216 | | * 3: neither — exercises the "404 no proxy info" path |
217 | | */ |
218 | 28 | uint8_t submode = (mode >> 5) & 0x03; |
219 | | |
220 | 28 | if (submode == 0 || submode == 2) { |
221 | | /* Proxy-Scheme value (option 39). The parser at coap_proxy.c:222-249 |
222 | | * branches on opt_len 4/5/7/8/9 to identify "coap" / "coaps" / "coap+ws" |
223 | | * / "coap+tcp" / "coaps+tcp" — we want the fuzzer to discover those. */ |
224 | 14 | size_t n; |
225 | 14 | const uint8_t *p = proxy_take(&c, 9, &n); |
226 | 14 | if (n) |
227 | 6 | coap_add_option(request, COAP_OPTION_PROXY_SCHEME, n, p); |
228 | 14 | } |
229 | 28 | if (submode == 1 || submode == 2) { |
230 | | /* Proxy-Uri value (option 35). Up to 1034 bytes per RFC 7252 — we cap at |
231 | | * 256 to leave room for payload. The parser is coap_split_proxy_uri. */ |
232 | 19 | size_t n; |
233 | 19 | const uint8_t *p = proxy_take(&c, 256, &n); |
234 | 19 | if (n) |
235 | 12 | coap_add_option(request, COAP_OPTION_PROXY_URI, n, p); |
236 | 19 | } |
237 | | |
238 | | /* Optional Observe option (bit 7 of mode, 4 bytes max). Triggers the proxy's |
239 | | * observe-cache logic if a proxy_response_cb were registered. */ |
240 | 28 | if ((mode >> 7) & 0x01) { |
241 | 11 | size_t n; |
242 | 11 | const uint8_t *p = proxy_take(&c, 4, &n); |
243 | 11 | if (n) |
244 | 6 | coap_add_option(request, COAP_OPTION_OBSERVE, n, p); |
245 | 11 | } |
246 | | |
247 | | /* Remaining bytes become the request body. */ |
248 | 28 | { |
249 | 28 | size_t n; |
250 | 28 | const uint8_t *p = proxy_take(&c, c.size > c.pos ? c.size - c.pos : 0, &n); |
251 | 28 | if (n) |
252 | 14 | coap_add_data(request, n, p); |
253 | 28 | } |
254 | | |
255 | | /* Response PDU the proxy fills in (with an error code on failure). */ |
256 | 28 | response = coap_pdu_init(COAP_MESSAGE_ACK, 0, |
257 | 28 | coap_new_message_id(req_session), max_pdu); |
258 | 28 | if (!response) |
259 | 0 | goto cleanup; |
260 | | |
261 | | /* The single API call we're here to fuzz. */ |
262 | 28 | coap_proxy_forward_request(req_session, request, response, resource, NULL, |
263 | 28 | &server_list); |
264 | | |
265 | 28 | cleanup: |
266 | 28 | if (request) |
267 | 28 | coap_delete_pdu(request); |
268 | 28 | if (response) |
269 | 28 | coap_delete_pdu(response); |
270 | | /* Don't release req_session manually — coap_free_context iterates |
271 | | * ctx->sessions and releases each (same idiom as the other harnesses). */ |
272 | 28 | if (ctx) |
273 | 28 | coap_free_context(ctx); |
274 | 28 | coap_cleanup(); |
275 | 28 | return 0; |
276 | 28 | } |