/src/pjsip/tests/fuzz/fuzz-ice.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2026 Teluu Inc. (http://www.teluu.com) |
3 | | * |
4 | | * This program is free software; you can redistribute it and/or modify |
5 | | * it under the terms of the GNU General Public License as published by |
6 | | * the Free Software Foundation; either version 2 of the License, or |
7 | | * (at your option) any later version. |
8 | | * |
9 | | * This program is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU General Public License |
15 | | * along with this program; if not, write to the Free Software |
16 | | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
17 | | */ |
18 | | #include <stdint.h> |
19 | | #include <string.h> |
20 | | #include <pjlib.h> |
21 | | #include <pjnath.h> |
22 | | |
23 | 92 | #define POOL_SIZE 8192 |
24 | 22 | #define MAX_CANDIDATES 8 |
25 | | |
26 | | /* ICE callbacks - stateless stubs */ |
27 | | static void on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, |
28 | | void *pkt, pj_size_t size, |
29 | | const pj_sockaddr_t *src_addr, unsigned src_addr_len) |
30 | 0 | { |
31 | 0 | (void)ice_st; (void)comp_id; (void)pkt; (void)size; |
32 | 0 | (void)src_addr; (void)src_addr_len; |
33 | 0 | } |
34 | | |
35 | | static void on_ice_complete(pj_ice_strans *ice_st, pj_ice_strans_op op, |
36 | | pj_status_t status) |
37 | 46 | { |
38 | 46 | (void)ice_st; (void)op; (void)status; |
39 | 46 | } |
40 | | |
41 | | /* Parse fuzzer input into remote candidates */ |
42 | | static unsigned parse_candidates(const uint8_t *data, size_t size, |
43 | | pj_ice_sess_cand *cands, unsigned max_cands) |
44 | 11 | { |
45 | 11 | unsigned count = 0; |
46 | 11 | size_t offset = 0; |
47 | | |
48 | | /* Need at least 12 bytes per candidate */ |
49 | 96 | while (offset + 12 <= size && count < max_cands) { |
50 | 85 | pj_ice_sess_cand *cand = &cands[count]; |
51 | 85 | pj_bzero(cand, sizeof(*cand)); |
52 | | |
53 | | /* Candidate type from fuzzer */ |
54 | 85 | cand->type = (pj_ice_cand_type)(data[offset] % PJ_ICE_CAND_TYPE_MAX); |
55 | 85 | cand->comp_id = 1; |
56 | 85 | cand->transport_id = 0; |
57 | 85 | cand->local_pref = 65535; |
58 | | |
59 | | /* Priority from fuzzer (4 bytes) - cast to avoid UB */ |
60 | 85 | cand->prio = ((pj_uint32_t)data[offset+1] << 24) | |
61 | 85 | ((pj_uint32_t)data[offset+2] << 16) | |
62 | 85 | ((pj_uint32_t)data[offset+3] << 8) | |
63 | 85 | (pj_uint32_t)data[offset+4]; |
64 | | |
65 | | /* Foundation - simple static string */ |
66 | 85 | cand->foundation = pj_str("candidate"); |
67 | | |
68 | | /* IP address from fuzzer (4 bytes) */ |
69 | 85 | pj_sockaddr_in *addr = (pj_sockaddr_in*)&cand->addr; |
70 | 85 | pj_bzero(addr, sizeof(*addr)); |
71 | 85 | addr->sin_family = pj_AF_INET(); |
72 | 85 | pj_memcpy(&addr->sin_addr, data + offset + 5, 4); |
73 | 85 | addr->sin_port = pj_htons(((pj_uint16_t)data[offset+9] << 8) | |
74 | 85 | (pj_uint16_t)data[offset+10]); |
75 | | |
76 | | /* Base/related address */ |
77 | 85 | pj_memcpy(&cand->base_addr, &cand->addr, sizeof(cand->addr)); |
78 | 85 | pj_memcpy(&cand->rel_addr, &cand->addr, sizeof(cand->addr)); |
79 | 85 | count++; |
80 | 85 | offset += 12; |
81 | 85 | } |
82 | 11 | return count; |
83 | 11 | } |
84 | | |
85 | | extern int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) |
86 | 55 | { |
87 | | /* Initialize pjlib once */ |
88 | 55 | static int pj_initialized = 0; |
89 | 55 | pj_caching_pool caching_pool; |
90 | 55 | pj_ioqueue_t *ioqueue = NULL; |
91 | 55 | pj_timer_heap_t *timer_heap = NULL; |
92 | 55 | pj_stun_config stun_cfg; |
93 | 55 | pj_ice_strans *ice_st = NULL; |
94 | 55 | pj_pool_t *pool = NULL; |
95 | 55 | pj_pool_t *env_pool = NULL; |
96 | 55 | int i; |
97 | | |
98 | 55 | if (Size < 30) |
99 | 9 | return 0; |
100 | | |
101 | 46 | if (!pj_initialized) { |
102 | 1 | pj_init(); |
103 | 1 | pj_initialized = 1; |
104 | 1 | } |
105 | | |
106 | | /* Create caching pool for this iteration */ |
107 | 46 | pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0); |
108 | 46 | pj_log_set_level(0); |
109 | | |
110 | | /* Create environment pool for ioqueue and timer_heap */ |
111 | 46 | env_pool = pj_pool_create(&caching_pool.factory, "ice_env", 4096, 4096, NULL); |
112 | 46 | if (!env_pool) goto cleanup; |
113 | | |
114 | | /* Create ioqueue and timer heap for this iteration */ |
115 | 46 | if (pj_ioqueue_create(env_pool, 16, &ioqueue) != PJ_SUCCESS) |
116 | 0 | goto cleanup; |
117 | 46 | if (pj_timer_heap_create(env_pool, 64, &timer_heap) != PJ_SUCCESS) |
118 | 0 | goto cleanup; |
119 | | |
120 | 46 | pj_stun_config_init(&stun_cfg, &caching_pool.factory, 0, ioqueue, timer_heap); |
121 | | |
122 | | /* Create work pool */ |
123 | 46 | pool = pj_pool_create(&caching_pool.factory, "ice_fuzz", POOL_SIZE, POOL_SIZE, NULL); |
124 | 46 | if (!pool) goto cleanup; |
125 | | |
126 | 46 | pj_ice_strans_cb ice_cb; |
127 | 46 | pj_ice_strans_cfg ice_cfg; |
128 | | |
129 | | /* Setup callbacks */ |
130 | 46 | pj_bzero(&ice_cb, sizeof(ice_cb)); |
131 | 46 | ice_cb.on_rx_data = on_rx_data; |
132 | 46 | ice_cb.on_ice_complete = on_ice_complete; |
133 | | |
134 | | /* Configure ICE transport */ |
135 | 46 | pj_ice_strans_cfg_default(&ice_cfg); |
136 | 46 | ice_cfg.stun_cfg = stun_cfg; |
137 | 46 | ice_cfg.af = pj_AF_INET(); |
138 | 46 | ice_cfg.opt.aggressive = PJ_FALSE; |
139 | | |
140 | | /* Create ICE stream transport */ |
141 | 46 | if (pj_ice_strans_create("fuzz", &ice_cfg, 1, NULL, &ice_cb, &ice_st) != PJ_SUCCESS) |
142 | 0 | goto cleanup; |
143 | | |
144 | | /* Initialize ICE session with credentials from fuzzer */ |
145 | 46 | pj_str_t local_ufrag, local_pwd; |
146 | 46 | char ufrag_buf[16], pwd_buf[32]; |
147 | | |
148 | 46 | size_t ufrag_len = (Data[0] % 8) + 4; |
149 | 46 | size_t pwd_len = (Data[1] % 16) + 16; |
150 | | |
151 | 46 | if (ufrag_len + pwd_len > Size - 10) { |
152 | 19 | ufrag_len = 4; |
153 | 19 | pwd_len = 16; |
154 | 19 | } |
155 | | |
156 | 46 | pj_memcpy(ufrag_buf, Data + 2, ufrag_len); |
157 | 46 | ufrag_buf[ufrag_len] = '\0'; |
158 | 46 | pj_memcpy(pwd_buf, Data + 2 + ufrag_len, pwd_len); |
159 | 46 | pwd_buf[pwd_len] = '\0'; |
160 | | |
161 | 46 | local_ufrag = pj_str(ufrag_buf); |
162 | 46 | local_pwd = pj_str(pwd_buf); |
163 | | |
164 | | /* Role from fuzzer */ |
165 | 46 | pj_ice_sess_role role = (Data[ufrag_len + pwd_len + 2] & 1) ? |
166 | 20 | PJ_ICE_SESS_ROLE_CONTROLLING : |
167 | 46 | PJ_ICE_SESS_ROLE_CONTROLLED; |
168 | | |
169 | 46 | if (pj_ice_strans_init_ice(ice_st, role, &local_ufrag, &local_pwd) != PJ_SUCCESS) |
170 | 0 | goto cleanup; |
171 | | |
172 | | /* Parse remote candidates from fuzzer input */ |
173 | 46 | size_t cand_offset = ufrag_len + pwd_len + 10; |
174 | 46 | pj_ice_sess_cand remote_cands[MAX_CANDIDATES]; |
175 | 46 | unsigned remote_count = 0; |
176 | | |
177 | 46 | if (cand_offset + 24 < Size) { |
178 | 11 | remote_count = parse_candidates(Data + cand_offset, Size - cand_offset, |
179 | 11 | remote_cands, MAX_CANDIDATES); |
180 | 11 | } |
181 | | |
182 | | /* Start negotiation with remote candidates */ |
183 | 46 | if (remote_count > 0) { |
184 | 11 | pj_str_t remote_ufrag = pj_str("remote"); |
185 | 11 | pj_str_t remote_pwd = pj_str("remotepassword"); |
186 | | |
187 | 11 | if (Size > cand_offset + 50) { |
188 | 11 | size_t rem_ufrag_len = (Data[cand_offset] % 8) + 4; |
189 | 11 | size_t rem_pwd_len = (Data[cand_offset+1] % 16) + 16; |
190 | | |
191 | 11 | static char remote_ufrag_buf[16], remote_pwd_buf[32]; |
192 | 11 | if (rem_ufrag_len < sizeof(remote_ufrag_buf) && |
193 | 11 | rem_pwd_len < sizeof(remote_pwd_buf) && |
194 | 11 | cand_offset + rem_ufrag_len + rem_pwd_len < Size) { |
195 | 11 | pj_memcpy(remote_ufrag_buf, Data + cand_offset + 2, rem_ufrag_len); |
196 | 11 | remote_ufrag_buf[rem_ufrag_len] = '\0'; |
197 | 11 | pj_memcpy(remote_pwd_buf, Data + cand_offset + 2 + rem_ufrag_len, rem_pwd_len); |
198 | 11 | remote_pwd_buf[rem_pwd_len] = '\0'; |
199 | 11 | remote_ufrag = pj_str(remote_ufrag_buf); |
200 | 11 | remote_pwd = pj_str(remote_pwd_buf); |
201 | 11 | } |
202 | 11 | } |
203 | | |
204 | 11 | pj_ice_strans_start_ice(ice_st, &remote_ufrag, &remote_pwd, |
205 | 11 | remote_count, remote_cands); |
206 | 11 | } |
207 | | |
208 | | /* Process STUN messages from remaining fuzzer input */ |
209 | 46 | size_t stun_offset = cand_offset + (remote_count * 12) + 20; |
210 | 46 | if (stun_offset + 20 < Size) { |
211 | 10 | const uint8_t *stun_data = Data + stun_offset; |
212 | 10 | size_t stun_len = Size - stun_offset; |
213 | | |
214 | 10 | pj_stun_msg *stun_msg = NULL; |
215 | 10 | if (pj_stun_msg_decode(pool, stun_data, stun_len, |
216 | 10 | PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET, |
217 | 10 | &stun_msg, NULL, NULL) == PJ_SUCCESS && stun_msg) { |
218 | |
|
219 | 0 | pj_sockaddr_in src_addr; |
220 | 0 | pj_sockaddr_in_init(&src_addr, NULL, 0); |
221 | 0 | if (remote_count > 0 && remote_cands[0].addr.addr.sa_family == pj_AF_INET()) { |
222 | 0 | pj_memcpy(&src_addr, &remote_cands[0].addr, sizeof(src_addr)); |
223 | 0 | } |
224 | |
|
225 | 0 | pj_ice_strans_sendto2(ice_st, 1, stun_data, stun_len, |
226 | 0 | (pj_sockaddr_t*)&src_addr, sizeof(src_addr)); |
227 | 0 | } |
228 | 10 | } |
229 | | |
230 | | /* Poll timer heap to drive state machine */ |
231 | 276 | for (i = 0; i < 5; i++) { |
232 | 230 | pj_time_val timeout = {0, 100}; |
233 | 230 | pj_timer_heap_poll(timer_heap, &timeout); |
234 | 230 | } |
235 | | |
236 | | /* Test state queries */ |
237 | 46 | pj_ice_strans_get_state(ice_st); |
238 | 46 | pj_ice_strans_has_sess(ice_st); |
239 | 46 | pj_ice_strans_sess_is_running(ice_st); |
240 | 46 | pj_ice_strans_sess_is_complete(ice_st); |
241 | | |
242 | | /* Enumerate candidates */ |
243 | 46 | if (remote_count > 0) { |
244 | 11 | unsigned cand_count = MAX_CANDIDATES; |
245 | 11 | pj_ice_sess_cand cands[MAX_CANDIDATES]; |
246 | 11 | pj_ice_strans_enum_cands(ice_st, 1, &cand_count, cands); |
247 | 11 | pj_ice_sess_cand def_cand; |
248 | 11 | pj_ice_strans_get_def_cand(ice_st, 1, &def_cand); |
249 | 11 | } |
250 | | |
251 | 46 | pj_ice_strans_stop_ice(ice_st); |
252 | | |
253 | 46 | cleanup: |
254 | 46 | if (ice_st) |
255 | 46 | pj_ice_strans_destroy(ice_st); |
256 | 46 | if (pool) |
257 | 46 | pj_pool_release(pool); |
258 | 46 | if (timer_heap) |
259 | 46 | pj_timer_heap_destroy(timer_heap); |
260 | 46 | if (ioqueue) |
261 | 46 | pj_ioqueue_destroy(ioqueue); |
262 | 46 | if (env_pool) |
263 | 46 | pj_pool_release(env_pool); |
264 | | |
265 | 46 | pj_caching_pool_destroy(&caching_pool); |
266 | | |
267 | 46 | return 0; |
268 | 46 | } |