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_fragroute.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 fragroute rules-file parser and module chain.
20
 *
21
 * More advisories have come out of this one file than anywhere else in the
22
 * tree:
23
 *
24
 *   GHSA-777w-9599-w8g4  stack overflow in mod_open()'s success-path
25
 *                        diagnostic, from a rules file of a few hundred
26
 *                        perfectly valid one-word directives
27
 *   GHSA-p7xp-4gj2-x56c  OOB write on an *empty* rules file, from
28
 *                        buf[strlen(buf) - 4] underflowing size_t
29
 *   GHSA-27v4-xhfx-g2rx  heap overflow from a negative fragment size, which
30
 *                        passed the "multiple of 8" check because -8 % 8 == 0
31
 *   GHSA-m655-53p4-6qm8  off-by-one in ip_chaff
32
 *   GHSA-v8c4-9w98-9v6v  the same off-by-one in tcp_chaff
33
 *
34
 * Every one of those is reachable from an attacker-influenced rules file and
35
 * needs no crafted packet at all. The pattern is unmistakable: this parser
36
 * accepts text, and nobody had ever fed it anything but valid text.
37
 *
38
 * The target parses the rules and then runs a fixed, well-formed packet
39
 * through the resulting module chain, because several of the bugs above are
40
 * in the modules' apply functions rather than the parser - they need the
41
 * chain to actually execute.
42
 */
43
44
#include "fuzz_common.h"
45
46
#include "defines.h"
47
#include "fragroute/fragroute.h"
48
49
#include <pcap.h>
50
51
/* A minimal, valid IPv4/UDP frame. Deliberately fixed: the rules file is the
52
 * variable under test here, and the pcap target covers packet shapes. */
53
static const uint8_t template_packet[] = {
54
        /* Ethernet: broadcast dst, locally-administered src, IPv4 */
55
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00,
56
        /* IPv4: 20-byte header, total length 0x002e (46), UDP, TTL 1 */
57
        0x45, 0x00, 0x00, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x01, 0x11, 0x00, 0x00,
58
        0x0a, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x02,
59
        /* UDP: sport 1234, dport 5678, length 26 */
60
        0x04, 0xd2, 0x16, 0x2e, 0x00, 0x1a, 0x00, 0x00,
61
        /* payload */
62
        0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
63
        0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41};
64
65
int
66
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
67
0
{
68
0
    char path[FUZZ_PATH_MAX];
69
0
    char errbuf[FRAGROUTE_ERRBUF_LEN];
70
0
    fragroute_t *ctx;
71
0
    uint8_t packet[sizeof(template_packet)];
72
73
    /*
74
     * Cap the input. The parser is line-oriented and the interesting
75
     * behaviour - directive count, argument shape - is all reachable well
76
     * under this; beyond it the fuzzer is just making bigger files.
77
     */
78
0
    if (size > 64 * 1024)
79
0
        return 0;
80
81
0
    fuzz_quiet_stdout(); /* the "print" module dumps every packet */
82
83
    /* fragroute_init() reads a path, as tcprewrite --fragroute does */
84
0
    if (fuzz_write_tempfile(data, size, path, sizeof(path)) != 0)
85
0
        return 0;
86
87
0
    ctx = fragroute_init(1500, DLT_EN10MB, path, errbuf);
88
0
    unlink(path);
89
90
0
    if (ctx == NULL)
91
0
        return 0; /* rejected the rules, which is a valid outcome */
92
93
    /*
94
     * Run a packet through the chain. Several of the advisories above are in
95
     * the modules' apply paths - ip_chaff's off-by-one fires on any ordinary
96
     * IP packet - so parsing alone would miss them.
97
     *
98
     * fragroute_process() writes through its buffer, so hand it a copy.
99
     */
100
0
    memcpy(packet, template_packet, sizeof(packet));
101
0
    if (fragroute_process(ctx, packet, sizeof(packet)) >= 0) {
102
        /*
103
         * fragroute_getfragment() memcpy's into a buffer the *caller* owns -
104
         * it takes char** but only reads the pointer, it does not allocate.
105
         * tcprewrite.c does the same with a MAXPACKET buffer; passing an
106
         * uninitialised pointer here segfaults inside the library, which is
107
         * the harness's fault rather than a finding.
108
         */
109
0
        char *frag = malloc(MAXPACKET);
110
111
0
        if (frag != NULL) {
112
0
            while (fragroute_getfragment(ctx, &frag) > 0)
113
0
                ;
114
0
            free(frag);
115
0
        }
116
0
    }
117
118
0
    fragroute_close(ctx);
119
0
    return 0;
120
0
}