/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 | } |