From f919224c44b9828208a3cce79b93183df8ca4fb4 Mon Sep 17 00:00:00 2001 From: Andrey Konovalov Date: Tue, 16 May 2017 16:14:58 +0200 Subject: sys, executor: extract tcp sequence numbers from /dev/net/tun This commit adds a new pseudo syscall syz_extract_tcp_res, that reads a packet from /dev/net/tun and extracts tcp sequence numbers to be used in subsequent packets. As a result this syzkaller program: 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)={0x42424242, 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) established a TCP connection: 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)={0x42424242, 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 --- executor/common.h | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 141 insertions(+), 11 deletions(-) (limited to 'executor/common.h') diff --git a/executor/common.h b/executor/common.h index c264d7b70..c32341f57 100644 --- a/executor/common.h +++ b/executor/common.h @@ -20,10 +20,15 @@ #include #include -#include -#include #include #include + +#include +#include +#include +#include +#include +#include #include #include @@ -184,7 +189,11 @@ static void install_segv_handler() *(type*)(addr) = new_val; \ } -#ifdef __NR_syz_emit_ethernet +#if defined(__NR_syz_emit_ethernet) || defined(__NR_syz_extract_tcp_res) +#define SYZ_TUN_ENABLE +#endif + +#ifdef SYZ_TUN_ENABLE static void vsnprintf_check(char* str, size_t size, const char* format, va_list args) { int rv; @@ -225,6 +234,8 @@ static void execute_command(const char* format, ...) int tunfd = -1; +#define SYZ_TUN_MAX_PACKET_SIZE (64 << 10) + // sysgen knowns about this constant (maxPids) #define MAX_PIDS 32 #define ADDR_MAX_LEN 32 @@ -235,8 +246,8 @@ int tunfd = -1; #define LOCAL_IPV4 "172.20.%d.170" #define REMOTE_IPV4 "172.20.%d.187" -#define LOCAL_IPV6 "fd00::%02hxaa" -#define REMOTE_IPV6 "fd00::%02hxbb" +#define LOCAL_IPV6 "fe80::%02hxaa" +#define REMOTE_IPV6 "fe80::%02hxbb" static void initialize_tun(uint64_t pid) { @@ -244,7 +255,7 @@ static void initialize_tun(uint64_t pid) fail("tun: no more than %d executors", MAX_PIDS); int id = pid; - tunfd = open("/dev/net/tun", O_RDWR); + tunfd = open("/dev/net/tun", O_RDWR | O_NONBLOCK); if (tunfd == -1) fail("tun: can't open /dev/net/tun"); @@ -273,12 +284,19 @@ static void initialize_tun(uint64_t pid) char remote_ipv6[ADDR_MAX_LEN]; snprintf_check(remote_ipv6, sizeof(remote_ipv6), REMOTE_IPV6, id); + // Disable IPv6 DAD, otherwise the address remains unusable until DAD completes. + execute_command("sysctl -w net.ipv6.conf.%s.accept_dad=0", iface); + + // Disable IPv6 router solicitation to prevent IPv6 spam. + execute_command("sysctl -w net.ipv6.conf.%s.router_solicitations=0", iface); + // There seems to be no way to disable IPv6 MTD to prevent more IPv6 spam. + execute_command("ip link set dev %s address %s", iface, local_mac); execute_command("ip addr add %s/24 dev %s", local_ipv4, iface); execute_command("ip -6 addr add %s/120 dev %s", local_ipv6, iface); execute_command("ip neigh add %s lladdr %s dev %s nud permanent", remote_ipv4, remote_mac, iface); execute_command("ip -6 neigh add %s lladdr %s dev %s nud permanent", remote_ipv6, remote_mac, iface); - execute_command("ip link set %s up", iface); + execute_command("ip link set dev %s up", iface); } static void setup_tun(uint64_t pid, bool enable_tun) @@ -287,16 +305,124 @@ static void setup_tun(uint64_t pid, bool enable_tun) initialize_tun(pid); } +void debug_dump_data(const char* data, int length) +{ + int i; + for (i = 0; i < length; i++) { + debug("%02hx ", (uint8_t)data[i] & (uint8_t)0xff); + if (i % 16 == 15) + debug("\n"); + } + if (i % 16 != 0) + debug("\n"); +} +#endif // SYZ_TUN_ENABLE + +#ifdef __NR_syz_emit_ethernet static uintptr_t syz_emit_ethernet(uintptr_t a0, uintptr_t a1) { + // syz_emit_ethernet(len len[packet], packet ptr[in, eth_packet]) + if (tunfd < 0) return (uintptr_t)-1; int64_t length = a0; char* data = (char*)a1; + debug_dump_data(data, length); return write(tunfd, data, length); } -#endif // __NR_syz_emit_ethernet +#endif + +#ifdef __NR_syz_extract_tcp_res +// Can't include , since it causes +// conflicts due to some structs redefinition. +struct ipv6hdr { + __u8 priority : 4, + version : 4; + __u8 flow_lbl[3]; + + __be16 payload_len; + __u8 nexthdr; + __u8 hop_limit; + + struct in6_addr saddr; + struct in6_addr daddr; +}; + +struct tcp_resources { + int32_t seq; + int32_t ack; +}; + +int read_tun(char* data, int size) +{ + int rv = read(tunfd, data, size); + if (rv < 0) { + if (errno == EAGAIN) + return -1; + fail("tun: read failed with %d, errno: %d", rv, errno); + } + return rv; +} + +void flush_tun() +{ + char data[SYZ_TUN_MAX_PACKET_SIZE]; + while (read_tun(&data[0], sizeof(data)) != -1) + ; +} + +static uintptr_t syz_extract_tcp_res(uintptr_t a0, uintptr_t a1, uintptr_t a2) +{ + // syz_extract_tcp_res(res ptr[out, tcp_resources], seq_inc int32, ack_inc int32) + + if (tunfd < 0) + return (uintptr_t)-1; + + char data[SYZ_TUN_MAX_PACKET_SIZE]; + int rv = read_tun(&data[0], sizeof(data)); + if (rv == -1) + return (uintptr_t)-1; + size_t length = rv; + debug_dump_data(data, length); + + struct tcphdr* tcphdr; + + if (length < sizeof(struct ethhdr)) + return (uintptr_t)-1; + struct ethhdr* ethhdr = (struct ethhdr*)&data[0]; + + if (ethhdr->h_proto == htons(ETH_P_IP)) { + if (length < sizeof(struct ethhdr) + sizeof(struct iphdr)) + return (uintptr_t)-1; + struct iphdr* iphdr = (struct iphdr*)&data[sizeof(struct ethhdr)]; + if (iphdr->protocol != IPPROTO_TCP) + return (uintptr_t)-1; + if (length < sizeof(struct ethhdr) + iphdr->ihl * 4 + sizeof(struct tcphdr)) + return (uintptr_t)-1; + tcphdr = (struct tcphdr*)&data[sizeof(struct ethhdr) + iphdr->ihl * 4]; + } else { + if (length < sizeof(struct ethhdr) + sizeof(struct ipv6hdr)) + return (uintptr_t)-1; + struct ipv6hdr* ipv6hdr = (struct ipv6hdr*)&data[sizeof(struct ethhdr)]; + // TODO: parse and skip extension headers. + if (ipv6hdr->nexthdr != IPPROTO_TCP) + return (uintptr_t)-1; + if (length < sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + sizeof(struct tcphdr)) + return (uintptr_t)-1; + tcphdr = (struct tcphdr*)&data[sizeof(struct ethhdr) + sizeof(struct ipv6hdr)]; + } + + struct tcp_resources* res = (struct tcp_resources*)a0; + NONFAILING(res->seq = htonl((ntohl(tcphdr->seq) + (uint32_t)a1))); + NONFAILING(res->ack = htonl((ntohl(tcphdr->ack_seq) + (uint32_t)a2))); + + debug("extracted seq: %08x\n", res->seq); + debug("extracted ack: %08x\n", res->ack); + + return 0; +} +#endif struct csum_inet { uint32_t acc; @@ -469,6 +595,10 @@ static uintptr_t execute_syscall(int nr, uintptr_t a0, uintptr_t a1, uintptr_t a case __NR_syz_emit_ethernet: return syz_emit_ethernet(a0, a1); #endif +#ifdef __NR_syz_extract_tcp_res + case __NR_syz_extract_tcp_res: + return syz_extract_tcp_res(a0, a1, a2); +#endif #ifdef __NR_syz_kvm_setup_cpu case __NR_syz_kvm_setup_cpu: return syz_kvm_setup_cpu(a0, a1, a2, a3, a4, a5, a6, a7); @@ -530,7 +660,7 @@ static int do_sandbox_none(int executor_pid, bool enable_tun) return pid; sandbox_common(); -#ifdef __NR_syz_emit_ethernet +#ifdef SYZ_TUN_ENABLE setup_tun(executor_pid, enable_tun); #endif @@ -547,7 +677,7 @@ static int do_sandbox_setuid(int executor_pid, bool enable_tun) return pid; sandbox_common(); -#ifdef __NR_syz_emit_ethernet +#ifdef SYZ_TUN_ENABLE setup_tun(executor_pid, enable_tun); #endif @@ -608,7 +738,7 @@ static int namespace_sandbox_proc(void* arg) if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid)) fail("write of /proc/self/gid_map failed"); -#ifdef __NR_syz_emit_ethernet +#ifdef SYZ_TUN_ENABLE // For sandbox namespace we setup tun after initializing uid mapping, // otherwise ip commands fail. setup_tun(epid, etun); -- cgit mrf-deployment