External network fuzzing for Linux kernel

syzkaller has support for external fuzzing of the network stack. This is achieved by using the TUN/TAP interface. It allows to set up a virtual network interface and send packets to the kernel as they are being received from an external network. This triggers the same paths as a real packet delivered through a real network interface (except for the driver layer).

You need to enable the CONFIG_TUN kernel config to enable external network fuzzing. See initialize_tun() in executor/common_linux.h for the exact way the virtual interface is set up.

The template descriptions can be found in sys/linux/vnet.txt. At this moment there are 2 fake syscalls: syz_emit_ethernet and syz_extract_tcp_res. The first one externally sends a packet through the virtual interface. The second one tries to externally receive a packet back and parse TCP sequence numbers from it for use in subseqent packets. There are many protocols or protocol extensions that are not described yet, so the additions are welcome!

Since fuzzing may be done in mutiple executor proccesses within the same VM instance, we need a way to isolate the virtual networks for different executors. Right now this is done by creating one virtual interface per executor and assigning different MAC, IPv4 and IPv6 addresses to each of these interfaces. Then the template descriptions make use of the proc type to generate proper addresses for each executor.

Since many network protocols require checksum fields to be embedded into packets, there‘s a support for describing such fields. There’s a csum type, which right now supports two different kinds of checksumming: the Internet checksum: csum[parent, inet, int16be], and TCP-like pseudo header checksum: csum[tcp_packet, pseudo, IPPROTO_TCP, int16be]. The checksums are computed and embedded right before emitting a packet though the virtual interface. There's also a nice feature: when syzkaller generates a C reproducer, it generates code to compute checksums in runtime as well.

By using syz_emit_ethernet and syz_extract_tcp_res the following syzkaller program is able to establish a TCP connection over IPv4:

mmap(&(0x7f0000000000/0x10000)=nil, (0x10000), 0x3, 0x32, 0xffffffffffffffff, 0x0)
r0 = socket$inet_tcp(0x2, 0x1, 0x0)
bind$inet(r0, &(0x7f0000001000)={0x2, 0x0, @empty=0x0, [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]}, 0x10)
listen(r0, 0x5)
syz_emit_ethernet(0x36, &(0x7f0000002000)={@local={[0xaa, 0xaa, 0xaa, 0xaa, 0xaa], 0x0}, @random="4c6112cc15d8", [], {{0x800, @ipv4={{0x5, 0x4, 0x0, 0x0, 0x28, 0x0, 0x0, 0x0, 0x6, 0x0, @remote={0xac, 0x14, 0x0, 0xbb}, @local={0xac, 0x14, 0x0, 0xaa}, {[]}}, @tcp={{0x1, 0x0, 0x42424242, 0x42424242, 0x0, 0x0, 0x5, 0x2, 0x0, 0x0, 0x0, {[]}}, {""}}}}}})
syz_extract_tcp_res(&(0x7f0000003000)={<r1=>0x42424242, <r2=>0x42424242}, 0x1, 0x0)
syz_emit_ethernet(0x38, &(0x7f0000004000)={@local={[0xaa, 0xaa, 0xaa, 0xaa, 0xaa], 0x0}, @remote={[0xbb, 0xbb, 0xbb, 0xbb, 0xbb], 0x0}, [], {{0x800, @ipv4={{0x5, 0x4, 0x0, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x6, 0x0, @remote={0xac, 0x14, 0x0, 0xbb}, @local={0xac, 0x14, 0x0, 0xaa}, {[]}}, @tcp={{0x1, 0x0, r2, r1, 0x0, 0x0, 0x5, 0x10, 0x0, 0x0, 0x0, {[]}}, {"0c10"}}}}}})
r3 = accept$inet(r0, &(0x7f0000005000)={0x0, 0x0, @multicast1=0x0, [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]}, &(0x7f0000006000)=0x10)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:20000           0.0.0.0:*               LISTEN      5477/a.out
tcp        2      0 172.20.0.170:20000      172.20.0.187:20001      ESTABLISHED 5477/a.out

Similar program for IPv6:

mmap(&(0x7f0000000000/0x10000)=nil, (0x10000), 0x3, 0x32, 0xffffffffffffffff, 0x0)
r0 = socket$inet6_tcp(0xa, 0x1, 0x0)
bind$inet6(r0, &(0x7f0000000000)={0xa, 0x1, 0x0, @empty={[0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]}, 0x0}, 0x1c)
listen(r0, 0x5)
syz_emit_ethernet(0x4a, &(0x7f0000001000)={@local={[0xaa, 0xaa, 0xaa, 0xaa, 0xaa], 0x0}, @random="de895db1468d", [], {{0x86dd, @ipv6={0x0, 0x6, "a228af", 0x14, 0x6, 0x0, @remote={0xfe, 0x80, [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], 0x0, 0xbb}, @local={0xfe, 0x80, [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], 0x0, 0xaa}, {[], @tcp={{0x0, 0x1, 0x42424242, 0x42424242, 0x0, 0x0, 0x5, 0x2, 0x0, 0x0, 0x0, {[]}}, {""}}}}}}})
syz_extract_tcp_res(&(0x7f0000002000)={<r1=>0x42424242, <r2=>0x42424242}, 0x1, 0x0)
syz_emit_ethernet(0x4a, &(0x7f0000003000)={@local={[0xaa, 0xaa, 0xaa, 0xaa, 0xaa], 0x0}, @random="de895db1468d", [], {{0x86dd, @ipv6={0x0, 0x6, "a228af", 0x14, 0x6, 0x0, @remote={0xfe, 0x80, [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], 0x0, 0xbb}, @local={0xfe, 0x80, [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], 0x0, 0xaa}, {[], @tcp={{0x0, 0x1, r2, r1, 0x0, 0x0, 0x5, 0x10, 0x0, 0x0, 0x0, {[]}}, {""}}}}}}})
r3 = accept$inet6(r0, &(0x7f0000004000)={0x0, 0x0, 0x0, @empty={[0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]}, 0x0}, &(0x7f0000005000)=0x1c)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp6       0      0 :::20001                :::*                    LISTEN      5527/a.out
tcp6       0      0 fe80::aa:20001          fe80::bb:20000          ESTABLISHED 5527/a.out