Coverage Report

Created: 2026-06-13 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}