Coverage Report

Created: 2026-08-13 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tcpreplay/test/fuzz/fuzz_pcap.c
Line
Count
Source
1
/*
2
 *   Copyright (c) 2026 Fred Klassen <tcpreplay.dev at gmail dot com> - AppNeta by Broadcom
3
 *
4
 *   The Tcpreplay Suite of tools is free software: you can redistribute it
5
 *   and/or modify it under the terms of the GNU General Public License as
6
 *   published by the Free Software Foundation, either version 3 of the
7
 *   License, or with the authors permission any later version.
8
 *
9
 *   The Tcpreplay Suite 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 the Tcpreplay Suite.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
/*
19
 * Fuzz target: the packet header parsers, driven from a whole pcap file.
20
 *
21
 * This is the surface that reads untrusted input in every tool - tcpprep,
22
 * tcprewrite, tcpreplay and tcpcapinfo all start by opening a capture
23
 * somebody else produced - and it is where most of the 2026 advisories
24
 * landed:
25
 *
26
 *   GHSA-jj65-mrgg-f5fx  heap over-read walking IPv6 extension headers
27
 *                        (get_layer4_v6); reported against 4.4.4, marked
28
 *                        patched, still reproducing on 4.5.4
29
 *   GHSA-5q26-7fxx-v8fh  heap OOB in ARP rewriting, from trusting the
30
 *                        packet's own ar_hln/ar_pln
31
 *   GHSA-m6w7-8497-g9c9  heap over-read via --pktlen, where the on-the-wire
32
 *                        length exceeds what was captured
33
 *   GHSA-ww62-mxv7-pg55  SEGV in the DLT_JUNIPER_ETHER decoder
34
 *
35
 * The fuzzer drives the pcap through libpcap exactly as the tools do, then
36
 * runs each packet through the shared header parsers. The DLT comes from the
37
 * file, so the input controls which decode path is taken and the whole DLT
38
 * plugin matrix is reachable from one target.
39
 *
40
 * The capture is fed through fmemopen() rather than a temp file: this runs
41
 * millions of times and the filesystem would dominate.
42
 */
43
44
#include "fuzz_common.h"
45
46
#include "common/get.h"
47
#include "defines.h"
48
49
#include <pcap.h>
50
51
int
52
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
53
1.45k
{
54
1.45k
    FILE *fp;
55
1.45k
    pcap_t *pcap;
56
1.45k
    char errbuf[PCAP_ERRBUF_SIZE];
57
1.45k
    struct pcap_pkthdr *pkthdr;
58
1.45k
    const u_char *pktdata;
59
1.45k
    int datalink;
60
1.45k
    int rc;
61
1.45k
    unsigned int packets = 0;
62
63
    /* a pcap file header is 24 bytes; below that there is nothing to open */
64
1.45k
    if (size < 24)
65
8
        return 0;
66
67
    /*
68
     * fmemopen wants a writable pointer but libpcap only reads. Casting away
69
     * const on the fuzzer's buffer is not on - copy it.
70
     */
71
1.44k
    void *copy = malloc(size);
72
1.44k
    if (copy == NULL)
73
0
        return 0;
74
1.44k
    memcpy(copy, data, size);
75
76
1.44k
    fp = fmemopen(copy, size, "rb");
77
1.44k
    if (fp == NULL) {
78
0
        free(copy);
79
0
        return 0;
80
0
    }
81
82
1.44k
    pcap = pcap_fopen_offline(fp, errbuf);
83
1.44k
    if (pcap == NULL) {
84
        /* malformed header: libpcap rejected it, which is the correct outcome */
85
263
        fclose(fp);
86
263
        free(copy);
87
263
        return 0;
88
263
    }
89
    /* from here on the pcap_t owns fp and closes it in pcap_close() - closing
90
     * it again here is a double free, and it is the harness that would be
91
     * wrong, not libpcap */
92
93
1.18k
    datalink = pcap_datalink(pcap);
94
95
22.6k
    while ((rc = pcap_next_ex(pcap, &pkthdr, &pktdata)) == 1) {
96
21.4k
        uint16_t ethertype;
97
21.4k
        uint32_t l2len, l2offset, vlan_offset;
98
99
        /*
100
         * Cap the work per input. A crafted pcap can claim an enormous packet
101
         * count, and the fuzzer's time is better spent on new shapes than on
102
         * one pathological file.
103
         */
104
21.4k
        if (++packets > 512)
105
5
            break;
106
107
21.4k
        if (pkthdr->caplen == 0)
108
8.63k
            continue;
109
110
        /*
111
         * The parsers take an end pointer computed from caplen. Deliberately
112
         * use caplen, not len: the gap between them is what GHSA-m6w7-8497-g9c9
113
         * was about, and a harness that papered over it would hide the bug
114
         * class it exists to find.
115
         */
116
12.7k
        if (get_l2len_protocol(pktdata,
117
12.7k
                               pkthdr->caplen,
118
12.7k
                               datalink,
119
12.7k
                               &ethertype,
120
12.7k
                               &l2len,
121
12.7k
                               &l2offset,
122
12.7k
                               &vlan_offset) < 0)
123
2.10k
            continue;
124
125
10.6k
        if (l2len > pkthdr->caplen)
126
0
            continue;
127
128
10.6k
        switch (ethertype) {
129
3.65k
        case ETHERTYPE_IP: {
130
3.65k
            ipv4_hdr_t *ip_hdr = (ipv4_hdr_t *)(pktdata + l2len);
131
132
3.65k
            if (pkthdr->caplen < l2len + sizeof(ipv4_hdr_t))
133
269
                break;
134
135
3.38k
            (void)get_layer4_v4(ip_hdr, pktdata + pkthdr->caplen - 1);
136
3.38k
            break;
137
3.65k
        }
138
139
3.12k
        case ETHERTYPE_IP6: {
140
3.12k
            ipv6_hdr_t *ip6_hdr = (ipv6_hdr_t *)(pktdata + l2len);
141
142
3.12k
            if (pkthdr->caplen < l2len + sizeof(ipv6_hdr_t))
143
89
                break;
144
145
            /* GHSA-jj65-mrgg-f5fx */
146
3.03k
            (void)get_layer4_v6(ip6_hdr, pktdata + pkthdr->caplen - 1);
147
3.03k
            (void)get_ipv6_l4proto(ip6_hdr, pktdata + pkthdr->caplen - 1);
148
3.03k
            break;
149
3.12k
        }
150
151
3.90k
        default:
152
3.90k
            break;
153
10.6k
        }
154
10.6k
    }
155
156
1.18k
    pcap_close(pcap);
157
1.18k
    free(copy);
158
1.18k
    return 0;
159
1.18k
}