Coverage Report

Created: 2026-10-28 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pjsip/tests/fuzz/fuzz-fmtp.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
19
/*
20
 * Fuzz target for the SDP "fmtp" (format parameters) parser in
21
 * pjmedia/src/pjmedia/stream_common.c.
22
 *
23
 * An SDP "a=fmtp:<pt> <params>" line carries codec-specific parameters that
24
 * arrive from a remote, untrusted peer (e.g. H.264 profile-level-id, Opus
25
 * minptime/useinbandfec, AMR octet-align/mode-set). pjmedia tokenises that
26
 * raw parameter string into name/value pairs. None of this is exercised by
27
 * any existing harness, so stream_common.c is reported at 0% coverage.
28
 *
29
 * The harness drives the fmtp code from both ends:
30
 *
31
 *   1. pjmedia_stream_info_parse_fmtp_data() is fed the fuzz input directly
32
 *      as a raw parameter string. This keeps the tokeniser reachable from
33
 *      unstructured input, without depending on the input being valid SDP.
34
 *
35
 *   2. pjmedia_stream_info_parse_fmtp() is driven from a parsed SDP session,
36
 *      once per (media, format) pair. This additionally covers the attribute
37
 *      lookup and pjmedia_sdp_attr_get_fmtp(), which is how the parameter
38
 *      string is reached in production.
39
 */
40
#include <stdint.h>
41
#include <stdlib.h>
42
43
#include <pjlib.h>
44
#include <pjmedia.h>
45
46
#include <pjmedia/sdp.h>
47
#include <pjmedia/stream_common.h>
48
49
static pj_caching_pool caching_pool;
50
static pj_pool_factory *mem;
51
52
/* Sink for parsed results, so that the name/value ranges handed back by the
53
 * parser are actually read. Without this the compiler is free to discard the
54
 * accesses and ASan never validates the returned pj_str_t ranges.
55
 */
56
static volatile unsigned long g_sink;
57
58
static void consume_fmtp(const pjmedia_codec_fmtp *fmtp)
59
9.94k
{
60
9.94k
    unsigned long sum = 0;
61
9.94k
    unsigned i;
62
63
30.5k
    for (i = 0; i < fmtp->cnt && i < PJMEDIA_CODEC_MAX_FMTP_CNT; ++i) {
64
20.5k
        const pj_str_t *name = &fmtp->param[i].name;
65
20.5k
        const pj_str_t *val = &fmtp->param[i].val;
66
20.5k
        pj_ssize_t j;
67
68
285k
        for (j = 0; j < name->slen; ++j)
69
264k
            sum += (unsigned char)name->ptr[j];
70
988k
        for (j = 0; j < val->slen; ++j)
71
968k
            sum += (unsigned char)val->ptr[j];
72
20.5k
    }
73
74
9.94k
    g_sink += sum;
75
9.94k
}
76
77
/* Feed the input straight to the parameter tokeniser.
78
 *
79
 * pjmedia_stream_info_parse_fmtp_data() takes an explicit slen and never
80
 * relies on NUL termination, so the fuzz input is passed through without a
81
 * copy.
82
 */
83
static void fuzz_fmtp_data(pj_pool_t *pool, const uint8_t *data, size_t size)
84
2.54k
{
85
2.54k
    pjmedia_codec_fmtp fmtp;
86
2.54k
    pj_str_t str;
87
88
2.54k
    pj_bzero(&fmtp, sizeof(fmtp));
89
2.54k
    str.ptr = (char *)data;
90
2.54k
    str.slen = (pj_ssize_t)size;
91
92
2.54k
    if (pjmedia_stream_info_parse_fmtp_data(pool, &str, &fmtp) == PJ_SUCCESS)
93
2.54k
        consume_fmtp(&fmtp);
94
2.54k
}
95
96
/* Reach the fmtp parser the way production code does: via an SDP media
97
 * description and its payload types.
98
 */
99
static void fuzz_fmtp_sdp(pj_pool_t *pool, const uint8_t *data, size_t size)
100
2.54k
{
101
2.54k
    pjmedia_sdp_session *sdp;
102
2.54k
    char *buf;
103
2.54k
    unsigned mi;
104
105
    /* pjmedia_sdp_parse() scans in place and retains pointers into the
106
     * buffer, so it needs a writable, NUL-terminated copy.
107
     */
108
2.54k
    buf = (char *)pj_pool_alloc(pool, size + 1);
109
2.54k
    if (!buf)
110
0
        return;
111
2.54k
    pj_memcpy(buf, data, size);
112
2.54k
    buf[size] = '\0';
113
114
2.54k
    if (pjmedia_sdp_parse(pool, buf, size, &sdp) != PJ_SUCCESS)
115
1.12k
        return;
116
117
3.09k
    for (mi = 0; mi < sdp->media_count; ++mi) {
118
1.67k
        const pjmedia_sdp_media *m = sdp->media[mi];
119
1.67k
        unsigned ai, fi;
120
121
1.67k
        if (!m)
122
0
            continue;
123
124
        /* Once per declared format: exercises the "fmtp" attribute lookup,
125
         * pjmedia_sdp_attr_get_fmtp() and the tokeniser.
126
         */
127
8.62k
        for (fi = 0; fi < m->desc.fmt_count && fi < PJMEDIA_MAX_SDP_FMT; ++fi) {
128
6.95k
            pjmedia_codec_fmtp fmtp;
129
6.95k
            unsigned long pt;
130
131
6.95k
            pt = pj_strtoul(&m->desc.fmt[fi]);
132
133
6.95k
            pj_bzero(&fmtp, sizeof(fmtp));
134
6.95k
            if (pjmedia_stream_info_parse_fmtp(pool, m, (unsigned)pt,
135
6.95k
                                               &fmtp) == PJ_SUCCESS)
136
6.38k
                consume_fmtp(&fmtp);
137
6.95k
        }
138
139
        /* Also convert every fmtp attribute directly, so that attributes
140
         * whose format does not appear in the m= line are still parsed.
141
         */
142
12.1k
        for (ai = 0; ai < m->attr_count; ++ai) {
143
10.4k
            pjmedia_sdp_attr *attr = m->attr[ai];
144
10.4k
            pjmedia_sdp_fmtp sdp_fmtp;
145
146
10.4k
            if (!attr || pj_strcmp2(&attr->name, "fmtp") != 0)
147
8.62k
                continue;
148
149
1.80k
            if (pjmedia_sdp_attr_get_fmtp(attr, &sdp_fmtp) == PJ_SUCCESS) {
150
1.01k
                pjmedia_codec_fmtp fmtp;
151
152
1.01k
                pj_bzero(&fmtp, sizeof(fmtp));
153
1.01k
                if (pjmedia_stream_info_parse_fmtp_data(pool,
154
1.01k
                                                        &sdp_fmtp.fmt_param,
155
1.01k
                                                        &fmtp) == PJ_SUCCESS)
156
1.01k
                    consume_fmtp(&fmtp);
157
1.01k
            }
158
1.80k
        }
159
1.67k
    }
160
1.41k
}
161
162
int LLVMFuzzerInitialize(int *argc, char ***argv)
163
2
{
164
2
    PJ_UNUSED_ARG(argc);
165
2
    PJ_UNUSED_ARG(argv);
166
167
    /* Silence logging before pj_init(), which itself logs at level 4. */
168
2
    pj_log_set_level(0);
169
170
    /* Initialise PJLIB once for the whole process. Doing this per iteration
171
     * would bump PJLIB's internal init counter on every execution without a
172
     * matching pj_shutdown().
173
     */
174
2
    if (pj_init() != PJ_SUCCESS)
175
0
        return 1;
176
177
2
    pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0);
178
2
    mem = &caching_pool.factory;
179
180
2
    return 0;
181
2
}
182
183
extern int
184
LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
185
8.67k
{
186
8.67k
    pj_pool_t *pool;
187
188
8.67k
    pool = pj_pool_create(mem, "fuzz-fmtp", 2000, 2000, NULL);
189
8.67k
    if (!pool)
190
0
        return 0;
191
192
8.67k
    fuzz_fmtp_data(pool, Data, Size);
193
8.67k
    fuzz_fmtp_sdp(pool, Data, Size);
194
195
8.67k
    pj_pool_release(pool);
196
8.67k
    return 0;
197
8.67k
}