/src/pjsip/tests/fuzz/fuzz-rtp.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 <stdio.h> |
19 | | #include <stdint.h> |
20 | | #include <stdlib.h> |
21 | | |
22 | | #include <pjlib.h> |
23 | | #include <pjmedia.h> |
24 | | #include <pjmedia/rtp.h> |
25 | | #include <pjmedia/jbuf.h> |
26 | | |
27 | | /* |
28 | | * Minimum input length calculation: |
29 | | * - Bytes 0-4: Session config (PT + SSRC) |
30 | | * - Bytes 5-16: First RTP packet for decode_rtp test (12 bytes) |
31 | | * - Bytes 17-28: Second RTP packet for decode_rtp2 test (12 bytes) |
32 | | * - Bytes 29-35: Encode test parameters (7 bytes) |
33 | | * - Bytes 36+: Multi-packet simulation |
34 | | * |
35 | | * Minimum: 5 config + 12 decode1 + 12 decode2 + 7 encode = 36 bytes |
36 | | */ |
37 | 22.9k | #define kMinInputLength 36 |
38 | 11.3k | #define kMaxInputLength 5120 |
39 | | |
40 | | /* For RTP session testing with randomized memory pool */ |
41 | | pj_pool_factory *mem; |
42 | | |
43 | | /* Fixed Input byte offsets */ |
44 | 782 | #define OFFSET_PT 0 /* Byte 0: Payload Type (7 bits) */ |
45 | 3.12k | #define OFFSET_SSRC 1 /* Bytes 1-4: SSRC (32 bits) */ |
46 | 2.34k | #define OFFSET_DECODE1 5 /* Bytes 5-16: First packet for decode_rtp test (12 bytes) */ |
47 | 2.34k | #define OFFSET_DECODE2 17 /* Bytes 17-28: Second packet for decode_rtp2 test (12 bytes) */ |
48 | 6.25k | #define OFFSET_ENCODE 29 /* Bytes 29-35: Encode parameters (7 bytes) */ |
49 | 1.56k | #define OFFSET_MULTI_PKT 36 /* Bytes 36+: Multiple packet simulation */ |
50 | | |
51 | | /* |
52 | | * Extract RTP session configuration from fuzzer input |
53 | | */ |
54 | | static void setup_rtp_session_config(const uint8_t *data, |
55 | | uint8_t *pt, |
56 | | uint32_t *ssrc) |
57 | 782 | { |
58 | | /* Payload Type: 7 bits (0-127) */ |
59 | 782 | *pt = data[OFFSET_PT] & 0x7F; |
60 | | |
61 | | /* SSRC: 32-bit synchronization source identifier */ |
62 | 782 | *ssrc = ((uint32_t)data[OFFSET_SSRC] << 24) | |
63 | 782 | ((uint32_t)data[OFFSET_SSRC + 1] << 16) | |
64 | 782 | ((uint32_t)data[OFFSET_SSRC + 2] << 8) | |
65 | 782 | ((uint32_t)data[OFFSET_SSRC + 3]); |
66 | 782 | } |
67 | | |
68 | | /* |
69 | | * Test basic RTP packet decoding (pjmedia_rtp_decode_rtp) |
70 | | * Tests: header parsing, payload extraction, session state updates |
71 | | */ |
72 | | static void test_decode_basic(pjmedia_rtp_session *session, |
73 | | const uint8_t *data, size_t size) |
74 | 782 | { |
75 | 782 | pj_status_t status; |
76 | 782 | const pjmedia_rtp_hdr *hdr; |
77 | 782 | const void *payload; |
78 | 782 | unsigned payloadlen; |
79 | 782 | pjmedia_rtp_status seq_st; |
80 | | |
81 | 782 | if (size < OFFSET_DECODE1 + 12) |
82 | 0 | return; |
83 | | |
84 | 782 | status = pjmedia_rtp_decode_rtp(session, data + OFFSET_DECODE1, |
85 | 782 | (int)(size - OFFSET_DECODE1), |
86 | 782 | &hdr, &payload, &payloadlen); |
87 | | |
88 | 782 | if (status == PJ_SUCCESS && hdr != NULL) { |
89 | | /* Update session state - tests sequence number tracking */ |
90 | 432 | pjmedia_rtp_session_update(session, hdr, &seq_st); |
91 | | |
92 | | /* Test update with check_pt disabled */ |
93 | 432 | pjmedia_rtp_session_update2(session, hdr, NULL, PJ_FALSE); |
94 | 432 | } |
95 | 782 | } |
96 | | |
97 | | /* |
98 | | * Test extended RTP packet decoding (pjmedia_rtp_decode_rtp2) |
99 | | * Tests: extension header parsing, detailed header info extraction |
100 | | */ |
101 | | static void test_decode_extended(pjmedia_rtp_session *session, |
102 | | const uint8_t *data, size_t size) |
103 | 782 | { |
104 | 782 | pj_status_t status; |
105 | 782 | pjmedia_rtp_dec_hdr dec_hdr; |
106 | 782 | const pjmedia_rtp_hdr *hdr; |
107 | 782 | const void *payload; |
108 | 782 | unsigned payloadlen; |
109 | 782 | pjmedia_rtp_status seq_st; |
110 | | |
111 | 782 | if (size < OFFSET_DECODE2 + 12) |
112 | 0 | return; |
113 | | |
114 | 782 | status = pjmedia_rtp_decode_rtp2(session, data + OFFSET_DECODE2, |
115 | 782 | (int)(size - OFFSET_DECODE2), |
116 | 782 | &hdr, &dec_hdr, &payload, &payloadlen); |
117 | | |
118 | 782 | if (status == PJ_SUCCESS && hdr != NULL) { |
119 | 380 | pjmedia_rtp_session_update(session, hdr, &seq_st); |
120 | | |
121 | | /* Access extension header if present */ |
122 | 380 | if (dec_hdr.ext_hdr && dec_hdr.ext && dec_hdr.ext_len > 0) { |
123 | 39 | pj_ntohs(dec_hdr.ext_hdr->profile_data); |
124 | 39 | pj_ntohs(dec_hdr.ext_hdr->length); |
125 | 39 | } |
126 | 380 | } |
127 | 782 | } |
128 | | |
129 | | /* |
130 | | * Test RTP packet encoding (pjmedia_rtp_encode_rtp) |
131 | | * Tests: header generation, roundtrip encode->decode |
132 | | */ |
133 | | static void test_encode_roundtrip(pjmedia_rtp_session *session, |
134 | | const uint8_t *data, size_t size) |
135 | 782 | { |
136 | 782 | pj_status_t status; |
137 | 782 | const void *rtphdr = NULL; |
138 | 782 | int hdrlen = 0; |
139 | 782 | const pjmedia_rtp_hdr *hdr; |
140 | 782 | const void *payload; |
141 | 782 | unsigned payloadlen; |
142 | | |
143 | 782 | if (size < OFFSET_ENCODE + 7) |
144 | 0 | return; |
145 | | |
146 | | /* Extract encode parameters from fuzzer input */ |
147 | 782 | pj_bool_t marker = (data[OFFSET_ENCODE] & 0x80) ? PJ_TRUE : PJ_FALSE; |
148 | 782 | int pt_encode = data[OFFSET_ENCODE + 1] & 0x7F; |
149 | 782 | int payload_len = data[OFFSET_ENCODE + 2]; |
150 | 782 | uint32_t ts = ((uint32_t)data[OFFSET_ENCODE + 3] << 24) | |
151 | 782 | ((uint32_t)data[OFFSET_ENCODE + 4] << 16) | |
152 | 782 | ((uint32_t)data[OFFSET_ENCODE + 5] << 8) | |
153 | 782 | ((uint32_t)data[OFFSET_ENCODE + 6]); |
154 | | |
155 | | /* Encode RTP header */ |
156 | 782 | status = pjmedia_rtp_encode_rtp(session, pt_encode, marker, |
157 | 782 | payload_len, ts, &rtphdr, &hdrlen); |
158 | | |
159 | 782 | if (status == PJ_SUCCESS && rtphdr != NULL && hdrlen > 0) { |
160 | | /* Roundtrip test: decode what we just encoded */ |
161 | 583 | pjmedia_rtp_status seq_st; |
162 | 583 | status = pjmedia_rtp_decode_rtp(session, rtphdr, hdrlen, |
163 | 583 | &hdr, &payload, &payloadlen); |
164 | 583 | if (status == PJ_SUCCESS && hdr != NULL) { |
165 | 583 | pjmedia_rtp_session_update(session, hdr, &seq_st); |
166 | 583 | } |
167 | 583 | } |
168 | 782 | } |
169 | | |
170 | | /* |
171 | | * Test multiple packet simulation with jitter buffer |
172 | | * Tests: sequence number handling, packet loss detection, jitter buffer reordering |
173 | | */ |
174 | | static void test_multiple_packets(pjmedia_rtp_session *session, |
175 | | const uint8_t *data, size_t size, |
176 | | pj_pool_t *pool) |
177 | 782 | { |
178 | 782 | pj_status_t status; |
179 | 782 | const pjmedia_rtp_hdr *hdr; |
180 | 782 | const void *payload; |
181 | 782 | unsigned payloadlen; |
182 | 782 | pjmedia_rtp_status seq_st; |
183 | 782 | size_t offset = OFFSET_MULTI_PKT; |
184 | 782 | int packet_num = 0; |
185 | 782 | pjmedia_jbuf *jb = NULL; |
186 | | |
187 | 782 | if (size <= OFFSET_MULTI_PKT) |
188 | 204 | return; |
189 | | |
190 | | /* Jitter buffer configuration */ |
191 | 578 | unsigned frame_size = 160; |
192 | 578 | unsigned ptime = 20; |
193 | 578 | unsigned max_count = 50; |
194 | 578 | unsigned prefetch = 10; |
195 | | |
196 | | /* Create jitter buffer with fixed, realistic parameters */ |
197 | 578 | pj_str_t jb_name = pj_str("fuzz-jb"); |
198 | 578 | status = pjmedia_jbuf_create(pool, &jb_name, frame_size, ptime, max_count, &jb); |
199 | 578 | if (status != PJ_SUCCESS) { |
200 | 0 | return; |
201 | 0 | } |
202 | | |
203 | | /* Configure jitter buffer prefetch (initial delay) using adaptive mode */ |
204 | 578 | pjmedia_jbuf_set_adaptive(jb, prefetch, 5, max_count); |
205 | | |
206 | | /* Process up to 16 packets to test more complex scenarios */ |
207 | 3.67k | while (offset + 1 + 12 < size && packet_num < 16) { |
208 | | /* First byte: packet size (12-200 bytes) */ |
209 | 3.18k | uint8_t pkt_size = data[offset]; |
210 | 3.18k | if (pkt_size < 12) pkt_size = 12; |
211 | 3.18k | if (pkt_size > 200) pkt_size = 200; |
212 | | |
213 | 3.18k | offset++; |
214 | | |
215 | 3.18k | if (offset + pkt_size > size) |
216 | 85 | break; |
217 | | |
218 | | /* Decode packet */ |
219 | 3.10k | status = pjmedia_rtp_decode_rtp(session, data + offset, pkt_size, |
220 | 3.10k | &hdr, &payload, &payloadlen); |
221 | 3.10k | if (status == PJ_SUCCESS && hdr != NULL) { |
222 | | /* Update RTP session state */ |
223 | 1.79k | pjmedia_rtp_session_update(session, hdr, &seq_st); |
224 | | |
225 | | /* Test different jitter buffer operations based on fuzzer input */ |
226 | 1.79k | int seq = pj_ntohs(hdr->seq); |
227 | | |
228 | | /* Use packet number to decide which JB operation to test */ |
229 | 1.79k | if (payloadlen > 0 && (packet_num & 1)) { |
230 | | /* Put frame using basic API */ |
231 | 541 | pjmedia_jbuf_put_frame(jb, payload, payloadlen, seq); |
232 | 1.25k | } else if (payloadlen > 0) { |
233 | | /* Put frame using extended API with bit_info */ |
234 | 661 | pj_uint32_t bit_info = payloadlen * 8; |
235 | 661 | pj_bool_t discarded = PJ_FALSE; |
236 | 661 | pjmedia_jbuf_put_frame2(jb, payload, payloadlen, bit_info, seq, &discarded); |
237 | 661 | } |
238 | | |
239 | | /* Periodically test jitter buffer reset */ |
240 | 1.79k | if (packet_num == 5) { |
241 | 89 | pjmedia_jbuf_reset(jb); |
242 | 89 | } |
243 | | |
244 | | /* Test jitter buffer state query */ |
245 | 1.79k | if (packet_num % 4 == 0) { |
246 | 646 | pjmedia_jb_state jb_state; |
247 | 646 | pjmedia_jbuf_get_state(jb, &jb_state); |
248 | 646 | } |
249 | | |
250 | | /* Exercise jitter buffer GET operations to drive dequeue path */ |
251 | 1.79k | if (packet_num > prefetch && packet_num % 3 == 0) { |
252 | 111 | pjmedia_jb_state jb_state; |
253 | 111 | pjmedia_jbuf_get_state(jb, &jb_state); |
254 | | |
255 | | /* Only call GET if there are frames in the buffer */ |
256 | 111 | if (jb_state.size > 0) { |
257 | 91 | char frame_type; |
258 | 91 | pj_size_t frame_size_out = jb_state.frame_size; |
259 | 91 | pj_uint32_t bit_info; |
260 | 91 | pj_uint32_t ts; |
261 | 91 | int seq_out; |
262 | 91 | char out_buf[512]; |
263 | | |
264 | | /* Test get_frame3 (most comprehensive API) */ |
265 | 91 | pjmedia_jbuf_get_frame3(jb, out_buf, &frame_size_out, |
266 | 91 | &frame_type, &bit_info, |
267 | 91 | &ts, &seq_out); |
268 | 91 | } |
269 | 111 | } |
270 | 1.79k | } |
271 | | |
272 | 3.10k | offset += pkt_size; |
273 | 3.10k | packet_num++; |
274 | 3.10k | } |
275 | | |
276 | 578 | pjmedia_jbuf_destroy(jb); |
277 | 578 | } |
278 | | |
279 | | /* |
280 | | * Main RTP fuzzing test function |
281 | | */ |
282 | | void rtp_test(const uint8_t *data, size_t size) |
283 | 782 | { |
284 | 782 | pj_status_t status; |
285 | 782 | pj_pool_t *pool; |
286 | 782 | pjmedia_rtp_session session; |
287 | 782 | uint8_t pt; |
288 | 782 | uint32_t ssrc; |
289 | | |
290 | | /* Extract session configuration from fuzzer input */ |
291 | 782 | setup_rtp_session_config(data, &pt, &ssrc); |
292 | | |
293 | | /* Create memory pool */ |
294 | 782 | pool = pj_pool_create(mem, "rtp_test", 4000, 4000, NULL); |
295 | 782 | if (!pool) |
296 | 0 | return; |
297 | | |
298 | | /* Initialize RTP session with fuzzer-controlled PT and SSRC */ |
299 | 782 | status = pjmedia_rtp_session_init(&session, pt, ssrc); |
300 | 782 | if (status != PJ_SUCCESS) { |
301 | 0 | pj_pool_release(pool); |
302 | 0 | return; |
303 | 0 | } |
304 | | |
305 | | /* Test basic decoding */ |
306 | 782 | test_decode_basic(&session, data, size); |
307 | | |
308 | | /* Test extended decoding with header extensions */ |
309 | 782 | test_decode_extended(&session, data, size); |
310 | | |
311 | | /* Test encode path and roundtrip */ |
312 | 782 | test_encode_roundtrip(&session, data, size); |
313 | | |
314 | | /* Test multiple packet scenarios with jitter buffer */ |
315 | 782 | test_multiple_packets(&session, data, size, pool); |
316 | | |
317 | 782 | pj_pool_release(pool); |
318 | 782 | } |
319 | | |
320 | | extern int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) |
321 | 11.4k | { |
322 | 11.4k | pj_caching_pool caching_pool; |
323 | | |
324 | 11.4k | if (Size < kMinInputLength || Size > kMaxInputLength) { |
325 | 365 | return 1; |
326 | 365 | } |
327 | | |
328 | | /* Init */ |
329 | 11.1k | pj_init(); |
330 | 11.1k | pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0); |
331 | 11.1k | pj_log_set_level(0); |
332 | | |
333 | | /* Configure the global blocking pool */ |
334 | 11.1k | mem = &caching_pool.factory; |
335 | | |
336 | | /* Fuzz RTP */ |
337 | 11.1k | rtp_test(Data, Size); |
338 | | |
339 | | /* Cleanup */ |
340 | 11.1k | pj_caching_pool_destroy(&caching_pool); |
341 | | |
342 | 11.1k | return 0; |
343 | 11.4k | } |