While many parts of batctl are rather simple, tcpdump is one of the most complex parts - which unfortunately is also dealing all the time with potentially harmful input. It is therefore a good idea to perform some tests to figure out how bad the current state of the code is. The findings will be presented here - including some information how other people can reproduce these problems.
With afl++, it is possible to fuzz batctl tcpdump and find parsing errors (easier). But it needs an entry point to actually send data to. So for simplicity, a fuzzme subcommand was added which just gets new data from afl++ and then runs the main ethernet parsing function.
diff --git a/split_pcap.py b/split_pcap.py new file mode 100755 index 0000000000000000000000000000000000000000..11a1f5ce8ec60fac141693b0449d5c3955f9ad28 --- /dev/null +++ b/split_pcap.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +import sys + +from hashlib import sha256 +from scapy.utils import rdpcap + + +def main(): + for pcap in sys.argv[1:]: + packets = rdpcap(pcap) + for packet in packets: + m = sha256() + m.update(packet.load) + fname = m.hexdigest() + with open(fname, "wb") as f: + f.write(packet.load) + + +if __name__ == "__main__": + main() diff --git a/tcpdump.c b/tcpdump.c index 5e7c76c69bd192d7485958aafabc0e9264b41b90..d340af986f99bdf2e8e6dae0d91a641bc80e82a2 100644 --- a/tcpdump.c +++ b/tcpdump.c @@ -1556,3 +1556,41 @@ static int tcpdump(struct state *state __maybe_unused, int argc, char **argv)
COMMAND(SUBCOMMAND, tcpdump, "td", 0, NULL, "<interface> \ttcpdump layer 2 traffic on the given interface"); + +__AFL_FUZZ_INIT(); + +static int fuzzme(struct state *state __maybe_unused, int argc, char **argv) +{ + dump_level = dump_level_all; + +#ifdef __AFL_HAVE_MANUAL_CONTROL + __AFL_INIT(); +#endif + + unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; + while (__AFL_LOOP(10000)) { + int len = __AFL_FUZZ_TESTCASE_LEN; + + /* safety check from tcpdump */ + if ((size_t)len < sizeof(struct ether_header)) + continue; + + /* move into new buffer to allow ASAN to detect invalid memory access */ + unsigned char *p = malloc(len); + if (!p) + continue; + + memcpy(p, buf, len); + + /* function under test */ + parse_eth_hdr(p, len, 0, 0); + + /* drop buffer from asan */ + free(p); + } + + return 0; +} + +COMMAND(SUBCOMMAND, fuzzme, "fz", 0, NULL,
To build the fuzzing test, it is necessary to build batctl slightly differently:
make clean export AFL_USE_ASAN=1; CC=afl-clang-fast make V=s
And the some input files (containing raw ethernet fames have to be generated from existing pcaps):
mkdir in cd in ../split_pcap.py ~/wireshark-batman-adv/tests/* cd ..
And then multiple afl++ fuzzer instances can be started.
if [ -z "${STY}" ]; then echo "must be started inside a screen session" >&2 exit 1 fi
for i in $(seq 1 $(nproc)); do start_mode=-M [ "${i}" = "1" ] || start_mode=-S screen afl-fuzz "${start_mode}" "fuzzer${i}" -i in -o out ./batctl fuzzme done
The crashes can then be analyzed further by sending them to the fuzzme subcommand:
./batctl fuzzme < out/fuzzer1/crashes/id:000000,sig:06,src:000528,time:12,execs:23992,op:havoc,rep:8
Signed-off-by: Sven Eckelmann sven@narfation.org --- Sven Eckelmann (6): batctl: tcpdump: Fix missing sanity check for batman-adv header batctl: tcpdump: Add missing throughput header length check batctl: tcpdump: Fix IPv4 header length check batctl: tcpdump: Add missing ICMPv6 Neighbor Advert length check batctl: tcpdump: Add missing ICMPv6 Neighbor Solicit length check batctl: tcpdump: Fix ICMPv4 inner IPv4 header length check
tcpdump.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) --- base-commit: a57de3183e67ec27cf96f1761e69d542e6dfac03 change-id: 20240127-tcpdump_fuzzing-736774906f60
Best regards,