文章目录

  • 1. Introduction
    • 1-1 Background
  • 2. Injecting network packets — TUN/TAP
    • 2-1 About TUN/TAP
    • 2-2 Employing TUN/TAP
  • 3. Collecting network coverage — KCOV
    • 3-1 About KCOV
    • 3-2 Employing KCOV
  • 4. Integrating into syzkaller
    • 4-1 About syzkaller
    • 4-2 Syscall descriptions
      • (1)Syscalls
      • (2)Resources
      • (3)Types
      • (4)Structures and unions
      • (5)Programs
    • 4-3 Adding a syscall for packet injection
      • (1)Pseudo-syscalls 介绍
      • (2)Pseudo-syscall for TUN/TAP
    • 4-4 Inspecting coverage #1
    • 4-5 escribing packet structure——描述 packet 结构
    • 4-6 Inspecting coverage #2
    • 4-7 Dealing with checksums
    • 4-8 Getting first crashes
    • 4-9 Inspecting coverage #3
    • 4-10 Opening a TCP socket
    • 4-11 Establishing a TCP connection
    • 4-12 Avoiding ARP traffic
    • 4-13 Adding IPv6 support
    • 4-14 Other notable things
  • 5. Found bugs
    • 5-1 Bugs in TUN/TAP
    • 5-2 Remote bugs
      • (1)Showcases
      • (2)Impact
    • 5-3 About that RCE
  • 6. Things to improve
  • 7. Summary
  • 8. Afterword
  • 参考

本文参考一下 Looking for Remote Code Execution bugs in the Linux kernel,学习如何编写syzlang语法的模板。

主要内容:如何远程控制个人Linux或Android设备?发送恶意链接、通过浏览器实现代码执行?针对通讯软件或邮件客户端?但最方便的办法是,直接发送网络包来控制内核。本文介绍了作者如何改进syzkaller来挖掘Linux内核的网络设备的漏洞,并且介绍了syzkaller的一个新的特性——pseudo-syscalls

1. Introduction

1-1 Background

fuzz内核的原理可参考 Ruffling the penguin! How to fuzz the Linux kernel

预期步骤

  • Injecting network packets:向内核注入packet,让内核去正常解析。对于普通fuzzer,直接向机器发包即可,但对于syzkaller架构则还需要调整。
  • Collecting coverage:收集代码覆盖信息。
  • Integrating into syzkaller:将以上两部分整合到syzkaller中。

2. Injecting network packets — TUN/TAP

思路:由于syzkaller的fuzz过程是在VM中进行(包括收集代码覆盖),所以如果从host端向VM内部发包进行测试的话,还涉及到同步输入的问题,很麻烦。最好是通过一个驱动,直接从用户空间取packet并注入到内核网络层,而 TUN/TAP interface 就能做到。

2-1 About TUN/TAP

TUN/TAP介绍:TUN/TAP 为用户空间的程序提供了包接收和包传输功能,可以被看作是P2P或以太网设备,可以直接从用户空间的程序接收包,而不是用物理媒介收包,可以直接向用户程序写,而不是通过物理媒介来发包。TUN/TAP 相当于一个虚拟网卡(virtual Network Interface Card),和真实NIC相比,它能直接从用户程序获取packet,然后内核会来解析这些packet(跟从硬件收包一样)。

2-2 Employing TUN/TAP

设置:设置可以参考 TUN/TAP Demystified,先打开 /dev/tun 并调用 ioctl 进行设置。

#define IFACE "syz_tun"int tun = open("/dev/net/tun", O_RDWR | O_NONBLOCK);struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
// Specify the interface name:
strncpy(ifr.ifr_name, IFACE, IFNAMSIZ);
// Specify the raw TAP mode:
ifr.ifr_flags = IFF_TAP | IFF_NO_PI; // IFF_TAP 打开 TAP模式,这样设备就能在 raw Ethernet frames 层操作,而不是 higher-level protocol frames。 IFF_NO_PI 表示,不要在发往用户空间的 packet 前面追加协议头信息(如 IP version 等, packet 本身已经包含了该信息,避免重复)。// Set the interface name and mode.
ioctl(tun, TUNSETIFF, &ifr);// 设置该接口的 IP/MAC 地址
#define LOCAL_MAC  "aa:aa:aa:aa:aa:aa"
#define LOCAL_IPV4 "172.20.20.170"// Assign MAC and IP addresses.
execute_command("ip link set dev %s address %s", IFACE, LOCAL_MAC);
execute_command("ip addr add %s/24 dev %s", LOCAL_IPV4, IFACE);// 激活接口
// Activate the interface.
execute_command("ip link set %s up", IFACE);

使用:现在可以往 /dev/tun 写任意的 Ethernet frames,内核网络子系统就会来处理。注意,发送的 frame 需包含和接口相同的目的MAC和IP地址,否则接口就会 reject frame。还可以使用 read(tun, ...) 来接收 response。

// Write a packet into TUN/TAP.
write(tun, frame, length);

3. Collecting network coverage — KCOV

3-1 About KCOV

KCOV介绍:KCOV负责收集代码覆盖。KCOV包含两个部分,一是编译器中的插桩部分,在每个基本块中插入回调函数;二是内核中的运行部分,实现了这些回调函数,负责记录基本块的地址,通过 /sys/kernel/debug/kcov 目录来获取这些地址。

注意,KCOV不能一次收集所有内核任务的代码覆盖,只能收集单个用户进程的代码覆盖,这样syzkaller就能保证只收集属于单个 fuzzing input 的syscall的代码覆盖,但是在并发fuzz同一内核时就不可用了。

使用

  • 1.编译内核时配置 CONFIG_KCOV

  • 2.给当前进程设置KCOV,接下来就可以调用 syscall,内核就会将代码覆盖记录到 cover

    int fd = open("/sys/kernel/debug/kcov", ...);
    unsigned long *cover = mmap(NULL, ..., fd, 0);
    ioctl(fd, KCOV_ENABLE, ...);
    

3-2 Employing KCOV

问题:不能直接使用KCOV从包处理代码中收集代码覆盖。

中断:尽管是从用户程序发送的packet,TUN/TAP 并没有在用户程序的上下文中处理packet,而是将packet放入队列,然后内核进程在 NET_RX_SOFTIQ 软中断中处理。由于任何内核任务都有可能处理中断,KCOV 无法收集中断处理函数的代码覆盖。所以通过扩展KCOV来收集软中断的代码覆盖是很困难的。

改进TUN/TAP:对 TUN/TAP 打补丁,直接解析 packet。修改 tun_get_user() 函数,该函数原本负责处理用户空间传过来的 packet,原本该函数会调用 netif_rx_ni()->enqueue_to_backlog() 将 packet 放入队列待处理,打补丁之后,就直接调用 netif_receive_skb() 来处理 packet(只要通过TUN/TAP发送packet,就会在发送进程的上下文中处理packet,KCOV就能收集到代码覆盖了)。 注意,不要开启 CONFIG_4KSTACKS,因为栈空间不够用。

diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index a3ac8636f3ba9..a569e61bc1d9e 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -1286,7 +1286,13 @@ static ssize_t tun_get_user(struct tun_struct *tun, ...skb_probe_transport_header(skb, 0);rxhash = skb_get_hash(skb);
+#ifndef CONFIG_4KSTACKS
+  local_bh_disable();
+  netif_receive_skb(skb);
+  local_bh_enable();
+#elsenetif_rx_ni(skb);
+#endifstats = get_cpu_ptr(tun->pcpu_stats);u64_stats_update_begin(&stats->syncp);

4. Integrating into syzkaller

4-1 About syzkaller

介绍:syzkaller 最支持的系统是Linux和 **BSDs,可以参考原作者 Dmitry Vyukov 在BlueHatIL 2020 的演讲 syzkaller: adventures in continuous coverage-guided kernel fuzzing (video),本文作者发现并利用漏洞的文章 Exploiting the Linux kernel via packet sockets ,其他文档可参见 syzkaller documentation。

4-2 Syscall descriptions

首先需要用syzlang声明式语言来写syscall模板,以下摘录了一段 socket 相关的 syscall 描述(详情可参考 socket_inet / socket_inet_tcp / vnet)

resource sock[fd]
resource sock_in[sock]
resource sock_tcp[sock_in]type sock_port int16be[20000:20004]ipv4_addr [rand_addr   int32be[0x64010100:0x64010102]empty     const[0x0, int32be]loopback const[0x7f000001, int32be]
] [size[4]]sockaddr_in {family  const[AF_INET, int16]port   sock_portaddr   ipv4_addr
} [size[16]]socket$inet_tcp(domain const[AF_INET], type const[SOCK_STREAM],proto const[0]) sock_tcpbind$inet(fd sock_in, addr ptr[in, sockaddr_in], addrlen len[addr])listen(fd sock, backlog int32)

(1)Syscalls

以上描述了3个syscalls,socket$inet_tcp—创建TCP socket,bind$inet—将socket绑定到某个地址和端口,listen—使socket处于监听状态。

Argumentssocket$inet_tcp — 3个常量;bind$inet— 一个 IPv4 socket 文件描述符,一个指向 sockaddr_in 结构的指针,该结构的长度;listen— 一个socket 文件描述符和一个整数。

Variants$ 符号是为了区分syscall及其变体,其参数有不同的生成规则。例如,socket 可用于创建很多类型的socket。syzkaller 定义了一些常见 socket 类型的变体,例如 TCP/UDP:

socket$inet_tcp(domain const[AF_INET], type const[SOCK_STREAM],proto const[0]) sock_tcp
socket$inet_udp(domain const[AF_INET], type const[SOCK_DGRAM],proto const[0]) sock_udp// 其他没有特定描述的 socket 变体
socket(domain flags[socket_domain], type flags[socket_type],proto int32) sock
socket$inet(domain const[AF_INET], type flags[socket_type],proto int32) sock_insocket_domain = AF_UNIX, AF_INET, AF_INET6, AF_NETLINK, ...
socket_type = SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, ...

(2)Resources

表示不同syscall之间的相关性,例如,socket$inet返回 sock_in resource,bind$inet 接收该 resource 作为参数:

socket$inet(domain const[AF_INET], type flags[socket_type],proto int32) sock_in
bind$inet(fd sock_in, addr ptr[in, sockaddr_in], addrlen len[addr])

Inheritance:以上示例定义了3个socket相关的 resource 类型,存在继承关系。

resource sock[fd]
resource sock_in[sock]
resource sock_tcp[sock_in]

当生成程序时,syzkaller更有可能使用syscall定义中的resource类型,也可以使用指定type的父类型或子类型。继承的 resource 是由同一 syscall 的不同变体所返回:

socket(...) sock
socket$inet(...) sock_in
socket$inet_tcp(...) sock_tcp

(3)Types

常见的类型有,const — 常量,int32 — 4-byte整数,flags — 位flag的组合或者 enum 选项,len — 表示某个域成员的长度。

Pointer:指针类型包含其指向的对象类型的信息,例如,bind$inet 调用接收一个指向 sockaddr_in 结构的指针作为第2个参数:

bind$inet(fd sock_in, addr ptr[in, sockaddr_in], addrlen len[addr])

Data-flow:指针类型需要指定数据流方向,表示需要从指向的对象读取还是写入。bind$inet 调用中,in 表示 bind 要从 sockaddr_in 读取数据,所以在执行该调用之前 syzkaller 需要填充该结构的域。

Out pointersout 表示syscall 需要向该结构写入数据,例如,accept$inet 需要将连接对的信息存入第2个参数。

accept$inet(fd sock_in, peer ptr[out, sockaddr_in, opt],peerlen ptr[inout, len[peer, int32]]) sock_in
// 第2个参数中的 opt 表示该指针可选,syzkaller 可以不提供该参数。

syscall可以使用out指针来通过结构域成员来返回 resource,syzkaller 就知道可以将该resource 传递给下一个syscall了。注意,对于 accept$inet 调用,sockaddr_in 不包含resource,因而 out 标记不会有任何作用,syzkaller在执行syscall之前不会填充该域。 inout 标记表示该syscall会读写该指针。

(4)Structures and unions

Structures:以 sockaddr_in 结构为例,包含3个成员,size[16] 表示该结构会被补0到16字节。由于 sock_port 类型也会在其他地方用到,所以该类型需要单独定义,int32be中的 be 表示该整型是大端的,20000:20004 表示该整数的取值范围。

sockaddr_in {family  const[AF_INET, int16]port   sock_portaddr   ipv4_addr
} [size[16]]type sock_port int16be[20000:20004]

Unions:以 ipv4_addr 结构为例,当生成IPv4地址时,syzkaller 从这个 union中选择一个地址。

ipv4_addr [
# Random public addresses 100.1.1.[0-2]:rand_addr   int32be[0x64010100:0x64010102]
# 0.0.0.0:empty     const[0x0, int32be]
# LOCAL_IPV4/REMOTE_IPV4/DEV_IPV4 in executor/common_linux.h:local      ipv4_addr_t[const[170, int8]]remote     ipv4_addr_t[const[187, int8]]dev        ipv4_addr_t[netdev_addr_id]initdev      ipv4_addr_initdev
# 127.0.0.1:loopback    const[0x7f000001, int32be]
# 224.0.0.1:multicast1  const[0xe0000001, int32be]
# 224.0.0.2:multicast2  const[0xe0000002, int32be]
# 255.255.255.255:broadcast const[0xffffffff, int32be]
# 10.1.1.[0-2] can be used for custom things within the image:private       int32be[0xa010100:0xa010102]
] [size[4]]

(5)Programs

当syzkaller生成syscall序列时,会根据参数类型来填充调用参数。示例如下,0x7f0000001000 表示sockaddr_in 结构位于偏移 0x1000 处,{} 中表示该结构的域成员,0x0 表示端口 20000(距离范围的下确界的偏移)。

r0 = socket$inet_tcp(0x2, 0x1, 0x0)
bind$inet(r0, &(0x7f0000001000)={0x2, 0x0, @empty=0x0}, 0x10)
listen(r0, 0x5)

4-3 Adding a syscall for packet injection

问题:如果只用 TUN/TAP ioctl 来写调用模板,可能导致只fuzz 了 TUN/TAP 代码本身,syzkaller 会对ioctl的所有参数进行变异并对ioctl随机排序,很难恰好将 /dev/tun 设置成 TAP 模式(注入packet的前提)。所以目标是使syzkaller 正确初始化 TUN/TAP 并注入packet。

(1)Pseudo-syscalls 介绍

介绍:这是syzkaller一个有用的特性,可参考 seudo-syscalls,可以组合多个syscall。

示例:以 syz_opev_dev$loop 为例(syzkaller中还定义了很多其他的 pseudo syscall,都是以 syz_ 开头),它包含两个部分,一是syzlang描述,二是C实现:

// syzlang 描述, dev表示驱动名, id表示驱动id, flags是open调用的flag
syz_open_dev$loop(dev ptr[in, string["/dev/loop#"]],id intptr, flags flags[open_flags]) fd_loop
// C 实现, 用ID 替换驱动名中的 # 字符, 并用提供的 flag 来打开设备文件
// Pseudo-code.
int syz_open_dev(device, id, flags) {device = device.replace("#", string(id));return open(device, flags);
}

注意,syz_opev_dev$loop 只是 syz_open_dev 的一个变体,所有的变体都有相同的C实现,该C实现的定义位于 syz-executor 的 defined 处。syzkaller 不会改变 pseudo syscall C 代码实现的逻辑。

漏洞示例:某个loop设备中的 found 漏洞对应的 reproducer 中的某段代码如下,参数就是按照 pseudo syscall 来生成的。

r0 = syz_open_dev$loop(&(0x7f0000000140)='/dev/loop#\x00', 0x0, 0x1)
ioctl$LOOP_SET_DIRECT_IO(r0, 0x4c05, 0x0)

(2)Pseudo-syscall for TUN/TAP

通过TUN/TAP 注入packet需要两步,一是将接口设置为 raw TAP 模式,二是通过该接口写 TUN/TAP 文件来发送packet。所以需要添加两个 Pseudo-syscall,不过第一步不需要执行很多次。

TUN/TAP setup:第一步的Pseudo-syscall定义位于 here 代码处,只需全局执行一次(在syz-executor开始时执行1次即可)。

static int tunfd = -1;// This function is indirectly called from syz-executor's main().
static void initialize_tun(void)
{tunfd = open("/dev/net/tun", O_RDWR | O_NONBLOCK);// Call the TUNSETIFF ioctl and do other TUN/TAP setup here.
}

Pseudo-syscall:第二步的Pseudo-syscall叫做 syz_emit_ethernet,参数是 packet 和 length,其C代码实现位于 here ,syz_emit_ethernet 将 packet 写入打开的 /dev/tun。 注意,我们不能在syzlang语法中定义 write$tun 来接受 tunfd 参数,因为 tunfd 不存在于syzlang描述中,而是在 syz-executor 的C代码实现中。

static long syz_emit_ethernet(volatile long a0, volatile long a1)
{uint32 length = a0;char *data = (char *)a1;return write(tunfd, data, length);
}

在对应的syzlang描述中,我们最开始定义 syz_emit_ethernet 接受一段随机数据作为packet。array[int8] 表示一段随机数据,显然随机生成的 packet 不能穿透更深的代码。

syz_emit_ethernet(len len[packet], packet ptr[in, array[int8]])

4-4 Inspecting coverage #1

之前修改了TUN/TAP来注入packet,现在可以开始fuzz了,通过KCOV来收集代码覆盖,不需要额外修改syzkaller。

First run:只允许 syz_emit_ethernet 来进行fuzz,可参考 code coverage report 获取代码覆盖。可以看到fuzzer卡在了 ip_rcv_core(),负责处理接收IP packet 的函数。

粗黑表示已覆盖的基本块,红色表示未覆盖的基本块。由于未经过 __IP_INC_STATS(),说明已通过 if (!skb) 检查;由于未经过 if (iph->ihl < 5 || iph->version != 4),说明 pskb_may_pull() 校验失败,进入 goto inhdr_error 分支。 这说明生成的 packet 过短,不包含 iphdr 结构。

Second run:经过syzkaller的长时间运行,能够通过if (iph->ihl < 5 || iph->version != 4)检查(syzkaller的 comparison operands collection 机制有助于通过这个检查),但是还是会卡住。因此,随机生成 packet 并不可行。

注意,还有一种方法可以分析为什么syzkaller 卡住了,你先获取一个能够 reaches a particular basic block 但是卡住了的程序,然后手动执行—manually,观察内核执行到什么位置,可以往内核代码添加 pr_err() 语句,或者使用 perf-tools 工具或者其他调试工具。

4-5 escribing packet structure——描述 packet 结构

为了能fuzz更深处,需要研究一下packet结构,具体的模板描述可参见 vnet - syzlang descriptions 。作者研读了 RFCs(包含很多互联网协议,如 IPv4, IPv6, TCP, UDP 等)。

Ethernet:修改 syz_emit_ethernet 的第2个参数指向 eth_packet 结构(描述Ethernet frame)。Ethernet frame 包含目的和源MAC地址、可选的 VLAN 标记、EtherType(表示payload中的协议类型)、payload。eth_packet 包含 Ethernet frame 的一些域成员和 eth2_packet payload。

syz_emit_ethernet(len len[packet], packet ptr[in, eth_packet])eth_packet {dst_mac    mac_addrsrc_mac mac_addrvtag    optional[vlan_tag]payload   eth2_packet
} [packed]      // [packed] 表示结构中的成员不需要pad对齐

MAC:如果目标MAC地址是随机生成的,则 TUN/TAP 接口会丢弃这些包,所以作者将 mac_addr 定义成一个union 结构,其中包含选项 LOCAL_MAC 地址(TUN/TAP 的接口地址)。 mac_addr_t[LAST] 表示模板,当用作 mac_addr_t[const[0xaa, int8]] 时,syzlang 编译器会创建 mac_addr_t 结构,并将 LAST 替换成 const[0xaa, int8] (也即将最后一个字节替换成指定字节)。

type mac_addr_t[LAST] {a0    array[const[0xaa, int8], 5]a1   LAST
} [packed]mac_addr [empty       array[const[0x0, int8], 6]
# These match LOCAL_MAC/REMOTE_MAC in executor/common_linux.h:local     mac_addr_t[const[0xaa, int8]]       // 注意: local / remote 这两个mac地址已经在 executor/common_linux.h 中定义过了, 并且在 initialize_tun() 函数中进行的初始化(将本机mac地址修改为 local, 创建邻近路由地址为 remote)remote        mac_addr_t[const[0xbb, int8]]dev        mac_addr_t[netdev_addr_id]broadcast array[const[0xff, int8], 6]multicast    array[const[0xbb, int8], 6]link_local   mac_addr_link_localrandom       array[int8, 6]
]

More EthernetEthernet frame 第2部分的 eth2_packet 结构包含 EtherType 和高层协议(例如,ARP / IPv4 等)。 [varlen] 表示实际生成可执行程序时所选选项的union结构的实际大小,没有这个标记的话,union的大小为最大选项的大小。

eth2_packet [generic eth2_packet_genericarp  eth2_packet_t[ETH_P_ARP, arp_packet]ipv4    eth2_packet_t[ETH_P_IP, ipv4_packet]ipv6    eth2_packet_t[ETH_P_IPV6, ipv6_packet]llc   eth2_packet_t[ETH_P_802_2, llc_packet]llc_tr    eth2_packet_t[ETH_P_TR_802_2, llc_packet]x25    eth2_packet_t[ETH_P_X25, x25_packet]mpls_uc eth2_packet_t[ETH_P_MPLS_UC, mpls_packet]mpls_mc    eth2_packet_t[ETH_P_MPLS_MC, mpls_packet]can    eth2_packet_t[ETH_P_CAN, can_frame]canfd    eth2_packet_t[ETH_P_CANFD, canfd_frame]
] [varlen]type eth2_packet_t[TYPE, PAYLOAD] {etype  const[TYPE, int16be]payload PAYLOAD
} [packed]

IPv4:IPv4 packet 包含 TCP / UDP 或其他payload。

ipv4_packet [generic ipv4_packet_t[flags[ipv4_types, int8], array[int8]]tcp  ipv4_packet_t[const[IPPROTO_TCP, int8], tcp_packet]udp  ipv4_packet_t[const[IPPROTO_UDP, int8], udp_packet]icmp ipv4_packet_t[const[IPPROTO_ICMP, int8], icmp_packet]dccp   ipv4_packet_t[const[IPPROTO_DCCP, int8], dccp_packet]igmp   ipv4_packet_t[const[IPPROTO_IGMP, int8], igmp_packet]gre    ipv4_packet_t[const[IPPROTO_GRE, int8], gre_packet]
] [varlen]// ipv4_packet_t 表示一个结构模板,包含 IPv4 头和高层 payload
type ipv4_packet_t[PROTO, PAYLOAD] {header  ipv4_header[PROTO]payload   PAYLOAD
} [packed]type ipv4_header[PROTO] {ihl      bytesize4[parent, int8:4]version        const[4, int8:4]ecn     int8:2dscp      int8:6total_len len[ipv4_packet_t, int16be]id       int16be[100:104]frag_off    int16be[0:0]ttl     int8protocol    PROTO
# Use a dummy value for the checksum for now:csum       int16src_ip     ipv4_addrdst_ip     ipv4_addroptions        ipv4_options
} [packed]

TCP:最终,一个 TCP packet 包含一个 header 和一个用户层协议数据(用随机字节组成的数组来表示)。

tcp_packet {header   tcp_headerpayload   tcp_payload
} [packed]tcp_header {src_port  sock_portdst_port   sock_port
# Use dummy values for the sequence numbers for now:seq_num     int32ack_num        int32ns     int8:1reserved  const[0, int8:3]data_off    bytesize4[parent, int8:4]flags      flags[tcp_flags, int8]window_size   int16be
# Use a dummy value for the checksum for now:csum       int16urg_ptr        int16beoptions      tcp_options
} [packed]tcp_payload {payload  array[int8]
} [packed]

4-6 Inspecting coverage #2

作者在添加syzlang描述的过程中,不断查看代码覆盖,发现还是会在 ip_rcv_core() 中卡住:

可以看到,覆盖了 __IP_ADD_STATS(),但没有覆盖 ntohs(),所以在这两个语句之间退出,要么是 goto inhdr_error 要么是 goto csum_error,通过观察程序结尾可以发现覆盖了 csum_error:,所以问题出在校验和失败。因为作者在定义 IPv4 packet 时,将校验值定义为一个随机的 int16 值:

type ipv4_header[PROTO] {# ...csum       int16
# ...
} [packed]

4-7 Dealing with checksums

思路:移除校验代码或者加一个配置选项,但是内核主线肯定不接受(我可以自己编译内核时移除啊);只能计算并固化校验值了。

校验分类:有两种类型的校验。

  • IPv4 checksum:这一类属于 Internet Checksum,用于 IPv4 头。 补码和?

    • The checksum field is the 16-bit one’s complement of the one’s complement sum of all 16-bit words in the header. For purposes of computing the checksum, the value of the checksum field is zero.
  • TCP checksum:这一类属于 pseudo-header checksum,用于高层协议,例如TCP,其计算过程更复杂。其计算涉及到 pseudo-headerTCP segment length,其中,pseudo-header 是从 IPv4 头中取值填充而成的,所以这种校验值计算包括了 pseudo-header / TCP header / TCP payload

Integrating checksums:经过很多 series of pull requests,作者为syzlang添加了两种 checksum 类型,使syzkaller的 program generator 计算并嵌入 checksum(新引入了 csum 类型)。

type ipv4_header[PROTO] {# ...csum       csum[parent, inet, int16be]
# ...
} [packed]tcp_header {# ...csum       csum[tcp_packet, pseudo, IPPROTO_TCP, int16be]
# ...
} [packed]

这需要改变syzkaller 填充参数值的方式 — here,syzkaller默认是一个一个填充域成员,但是无法计算checksum(因为checksum必须在其他域成员填充完成后才能计算出来),所以修改了syzkaller 以填充checksum — last。syzkaller 在填充checksum之前需分析所生成的程序—analyze,例如在计算TCP checksum之前需 builds the pseudo-header 。

注意,其他协议(除了TCP/IPv4)也需要计算checksum,详情见vnet.txt。syzkaller中,syz-fuzzersyz-execprog 会把生成的程序序列化 serialize 为指令,这些指令告诉 syz-executor 可以使用哪些值来填充结构filling in structures,何时需要计算校验和calculate checksums,以及调用哪个syscall syscalls to call。syz-prog2c 采用了类似的机制 similar mechanism。 除了checksum,作者还添加了一些定义,例如 big-endian integers int16be, bitfields int8:1, per-process integers proc[20000, 4, int16be],有利于描述网络包。这些新的syzlang特性需要修改Go和C。

4-8 Getting first crashes

First bug:第一个bug其实是 slab-out-of-bounds in sctp_sf_ootb,对应的修复 sctp: validate chunk len before actually using it,但是没有程序可以复现。(PS:CVE-2016-9555 被评为10分,但是其实危害并不大)

Another bug:这个漏洞可以参考 syzbot report,需要 IPv6 支持:

syz_emit_ethernet(0xfdef, &(0x7f00000001c0)={@local={[0xaa, 0xaa, 0xaa, 0xaa, 0xaa], 0xaa},@dev={[0xaa, 0xaa, 0xaa, 0xaa, 0xaa]}, [], {@ipv6={0x86dd, {0x0, 0x6, "50a09c", 0xfdb9, 0x0, 0x0,@remote={0xfe, 0x80, [], 0xbb}, @local={0xfe, 0x80, [], 0xaa},{[], @udp={0x0, 0x0, 0x8}}}}}
}, &(0x7f0000000040))

这个poc会导致内核在 XFRM policy 代码中陷入死循环,报错如下所示,可见程序起源于 drivers/net/tun.c,最后执行到 net/ipv6/xfrm6_policy.c 触发漏洞,漏洞修复于 xfrm6: avoid potential infinite loop in _decode_session6(),不算远程代码执行,但可以远程拒绝服务攻击。

watchdog: BUG: soft lockup - CPU#1 stuck for 134s! [syz-executor738:4553]
Call Trace:_decode_session6+0xc1d/0x14f0 net/ipv6/xfrm6_policy.c:150__xfrm_decode_session+0x71/0x140 net/xfrm/xfrm_policy.c:2368xfrm_decode_session_reverse include/net/xfrm.h:1213 [inline]icmpv6_route_lookup+0x395/0x6e0 net/ipv6/icmp.c:372icmp6_send+0x1982/0x2da0 net/ipv6/icmp.c:551icmpv6_send+0x17a/0x300 net/ipv6/ip6_icmp.c:43ip6_input_finish+0x14e1/0x1a30 net/ipv6/ip6_input.c:305NF_HOOK include/linux/netfilter.h:288 [inline]ip6_input+0xe1/0x5e0 net/ipv6/ip6_input.c:327dst_input include/net/dst.h:450 [inline]ip6_rcv_finish+0x29c/0xa10 net/ipv6/ip6_input.c:71NF_HOOK include/linux/netfilter.h:288 [inline]ipv6_rcv+0xeb8/0x2040 net/ipv6/ip6_input.c:208__netif_receive_skb_core+0x2468/0x3650 net/core/dev.c:4646__netif_receive_skb+0x2c/0x1e0 net/core/dev.c:4711netif_receive_skb_internal+0x126/0x7b0 net/core/dev.c:4785napi_frags_finish net/core/dev.c:5226 [inline]napi_gro_frags+0x631/0xc40 net/core/dev.c:5299tun_get_user+0x3168/0x4290 drivers/net/tun.c:1951tun_chr_write_iter+0xb9/0x154 drivers/net/tun.c:1996call_write_iter include/linux/fs.h:1784 [inline]do_iter_readv_writev+0x859/0xa50 fs/read_write.c:680do_iter_write+0x185/0x5f0 fs/read_write.c:959vfs_writev+0x1c7/0x330 fs/read_write.c:1004do_writev+0x112/0x2f0 fs/read_write.c:1039__do_sys_writev fs/read_write.c:1112 [inline]__se_sys_writev fs/read_write.c:1109 [inline]__x64_sys_writev+0x75/0xb0 fs/read_write.c:1109do_syscall_64+0x1b1/0x800 arch/x86/entry/common.c:287entry_SYSCALL_64_after_hwframe+0x49/0xbe

4-9 Inspecting coverage #3

再次查看代码覆盖,发现卡在了 TCP 模式下。当TCP packet 过来的时候,内核需将它路由到某个在指定端口监听的应用程序,如果找不到该端口,则丢弃该包。

4-10 Opening a TCP socket

解决 4-9 的办法就是,在执行 syz_emit_ethernet 之前,需要 open 和 bind socket。

Enabling syscalls:syzkaller已经实现了一些socket相关的 syscall 描述,只需要在配置文件中设置即可,允许socket$inet_tcp / bind$inet / listen,即可开始fuzz。

Success:之后 syzkaller 就能成功串联起 open socket 和通过 syz_emit_ethernetsend packet。最后发现以下程序成功穿透了 __inet_lookup_skb()

# Create a socket and bind it to a port.
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)# Send a packet to the socket.
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, 0x41424344, 0x41424344, 0x0, 0x0, 0x5, 0x2, 0x0, 0x0, 0x0,{[]}}, {""}}}}
}})

Ports:问题是生成以上程序花费了很长时间,因为最开始,作者使用了随机的 int16be 值来表示端口号,导致syzkaller 很难给 bind$inetsyz_emit_ethernet 生成相同的端口。所以作者限制了端口号的取值:

type sock_port int16be[20000:20004]

4-11 Establishing a TCP connection

现在成功使syzkaller向 open socket 发包,怎么使 fuzzer 完全从外部连接到 TCP socket 呢?

TCP handshake:TCP 握手过程可参考 works。首先fuzzer需要发送 SYN 请求,收到 SYN/ACK,最后发送 ACK。

  • (1)第1个SYN请求需包含序列号 A,可使用任意数值;
  • (2)内核需通过 SYN/ACK 返回该序列号加1,SYN/ACK 回应还需包含一个内核产生的序列号 B;
  • (3)最后发送ACK请求,需包含 A+1 / B+1

syz_emit_ethernet 已经实现了发送 packet ,也即第1步的 SYN 部分,还需要实现接收 SYN/ACK packet,提取序列号,并用在 ACK packet 中。

New pseudo-syscall:实现新的 pseudo-syscall 来负责提取序列号,作者实现了 syz_extract_tcp_res ,可以从 TUN/TAP 接收包、提取seq/ack号,并加上某个值。

struct tcp_resources {uint32 seq;uint32 ack;
};static long syz_extract_tcp_res(volatile long a0, volatile long a1,volatile long a2)
{char data[1000];size_t length = read_tun(&data[0], sizeof(data));if (length < sizeof(struct ethhdr))return -1;struct ethhdr *ethhdr = &data[0];if (ethhdr->h_proto != htons(ETH_P_IP))return -1;if (length < sizeof(struct ethhdr) + sizeof(struct iphdr))return -1;struct iphdr *iphdr = &data[sizeof(struct ethhdr)];if (iphdr->protocol != IPPROTO_TCP)return -1;if (length < sizeof(struct ethhdr) + iphdr->ihl * 4 +sizeof(struct tcphdr))return -1;struct tcphdr *tcphdr = &data[sizeof(struct ethhdr) + iphdr->ihl * 4];struct tcp_resources *res = (struct tcp_resources *)a0;res->seq = htonl((ntohl(tcphdr->seq) + (uint32)a1));res->ack = htonl((ntohl(tcphdr->ack_seq) + (uint32)a2));return 0;
}

说明,本来只需要 seq 号加1即可,但作者加了 a1/a2,使fuzzer能够探索一些异常状态。

以上C代码对应的syzlang描述如下所示,为了简便, seq/ack 号都复用了相同的 tcp_seq_num resource。注意,syz_extract_tcp_res 通过指向struct的 out 指针返回 resource,而不是通过返回值。 syz_extract_tcp_res$synack 是一个 syz_extract_tcp_res 变体,目的是构造正确的TCP连接,将 seq 加1,但是 ack 不变。

resource tcp_seq_num[int32]: 0x41424344tcp_resources {seq    tcp_seq_numack  tcp_seq_num
}# These pseudo-syscalls read a packet from /dev/net/tun and extract TCP
# sequence and acknowledgment numbers from it. They also adds the inc
# arguments to the returned values. This way sequence numbers get incremented.
syz_extract_tcp_res(res ptr[out, tcp_resources], seq_inc int32, ack_inc int32)
syz_extract_tcp_res$synack(res ptr[out, tcp_resources],seq_inc const[1], ack_inc const[0])

TCP header

// 开始将 TCP header 中的 seq/ack 都设置成 int32 类型的值
tcp_header {# ...seq_num        int32ack_num        int32
# ...
} [packed]
// 将 seq/ack 更新为新加入的 resource, 之后 syzkaller 在调用 syz_emit_ethernet 时就会采用 syz_extract_tcp_res 生成的 resource
tcp_header {# ...seq_num        tcp_seq_numack_num      tcp_seq_num
# ...
} [packed]

Connection established:引入 syz_extract_tcp_res,使得syzkaller 能够生成程序来设置socket 并在外部 connect 进去(以下示例可能过时了,需要更新一下)。

# Create a socket and put it into the listening state.
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)# Send a SYN request to the socket externally.
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, 0x41424344, 0x41424344, 0x0, 0x0, 0x5, 0x2, 0x0, 0x0, 0x0,{[]}}, {""}}}}
}})# Receive a SYN/ACK response externally and increment SYN number by 1.
# Ignore 0x41424344, those are defaults if extraction fails.
syz_extract_tcp_res$synack(&(0x7f0000003000)={<r1=>0x41424344, <r2=>0x41424344}, 0x1, 0x0)# Send an ACK to the socket externally.
# Reuse the received sequence numbers, but swap the order.
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"}}}}
}})# Now, the TCP hansdhake is done. Accept the connection on the socket side.
r3 = accept$inet(r0, &(0x7f0000005000)={0x0, 0x0, @multicast1=0x0, [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]},&(0x7f0000006000)=0x10)

以上程序执行后,就能建立连接,可用 netstat 命令查看:

Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:20000           0.0.0.0:*               LISTEN
tcp        2      0 172.20.0.170:20000      172.20.0.187:20001      ESTABLISHED

分析,syzkaller 本来不知道在调用 syz_emit_ethernet 发送 SYN 之后,要调用 syz_extract_tcp_res,但是我们定义的resource 很好的将这两个调用联系在了一起。 注意,udp2 创建连接的过程更简单。

4-12 Avoiding ARP traffic

问题:在创建TCP连接时,syz_extract_tcp_res$synack 没有收到 TCP SYN/ACK 包,而是收到了一个 ARP packet (地址解析协议,当内核收到一个新IP地址发来的包时,需要先解析远程主机的物理地址,才能继续回应)。

Assigned IP:为了避免不必要的 ARP traffic,作者指定了一个IP地址 REMOTE_IPV4,并更新了 TUN/TAP setup 代码,将该地址加入到邻居——add this address to neighbors。以下代码采用了 IP utility 来简化,syzkaller 实际上采用的是 netlink sockets

#define IFACE       "syz_tun"
#define LOCAL_MAC   "aa:aa:aa:aa:aa:aa"
#define REMOTE_MAC  "aa:aa:aa:aa:aa:bb"
#define LOCAL_IPV4  "172.20.20.170"
#define REMOTE_IPV4 "172.20.20.187"// Assign MAC and IP addresses.
execute_command("ip link set dev %s address %s", IFACE, LOCAL_MAC);
execute_command("ip addr add %s/24 dev %s", LOCAL_IPV4, IFACE);// Add a neighbour to avoid unnecessary ARP traffic.
execute_command("ip neigh add %s lladdr %s dev %s nud permanent",REMOTE_IPV4, REMOTE_MAC, IFACE);// Activate the interface.
execute_command("ip link set %s up", IFACE);

作者还将 REMOTE_IPV4 加入到了 ipv4_addr union 结构中 — 参见 here。

type ipv4_addr_t[LAST] {a0   const[0xac, int8]   # 172a1 const[0x14, int8]   # 20a2  const[0x14, int8]   # 20a3  LAST
} [packed]ipv4_addr [
# These match LOCAL_IPV4 and REMOTE_IPV4 in executor/common_linux.h:local       ipv4_addr_t[const[170, int8]]remote     ipv4_addr_t[const[187, int8]]
# random, empty, loopback, ...
] [size[4]]

这样一来,当内核收到从 REMOTE_IPV4 发来的 TCP 请求时,内核已经知道了host的物理地址,跳过ARP请求

4-13 Adding IPv6 support

Extensions:作者扩展了 TUN/TAP setup 代码 —— setting up IPv6 addresses,并将 ipv6_addr 和 ipv6_packet 的定义添加到了网络包描述中。作者还扩展了 checksum 计算代码来支持 IPv6-based pseudo-headers,为了避免 ARP traffic,作者 还将 REMOTE_IPV6 添加到了 ipv6_addr。

为了避免过多的 traffic 流量,作者还关闭了 IPv6 Duplicate Address Detection and Router Solicitation,作者没能关闭 IPv6 MTD。

TCP:以上操作可以使syzkaller在IPv6上建立连接——参见 here。以下示例代码可能也需要更新。

r0 = socket$inet6_tcp(0xa, 0x1, 0x0)
bind$inet6(r0, &(0x7f0000000000)={...}, 0x1c)
listen(r0, 0x5)
syz_emit_ethernet(0x4a, &(0x7f0000001000)={...})
syz_extract_tcp_res$synack(&(0x7f0000002000)={<r1=>0x41424344, <r2=>0x41424344}, 0x1, 0x0)
syz_emit_ethernet(0x4a, &(0x7f0000003000)={..., r2, r1, ...})
r3 = accept$inet6(r0, &(0x7f0000004000)={...}, &(0x7f0000005000)=0x1c)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp6       0      0 :::20001                :::*                    LISTEN
tcp6       0      0 fe80::aa:20001          fe80::bb:20000          ESTABLISHED

IPv6 support 是有限的,参见 6. Things to improve

4-14 Other notable things

syzbot integration:本文功能整合进syzkaller之后,syzbot 一直在挖掘 remote network bus。

Double-sided fuzzing:syzkaller可以从两端来fuzz network,一是从用户空间执行syscall,二是从外部发送网络包。这样就能发现一些有趣的漏洞,当在用户空间建立一个奇怪的socket,然后从外部往该socket发包触发漏洞,参见 5-2 Showcases

Isolation:syzkaller可以在单个VM中发起多个fuzz进程,所以进程之间需要隔离。方法是在每个fuzz进程中使用不同的命名空间——参见 separate network namespace,这样代码覆盖也是隔离的,因为KCOV能搜集到每个进程的代码覆盖。

Packet descriptions:本文展示的包描述只占 vnet.txt 的一部分,还有很大的提升空间,参见 6. Things to improve

Fragmentation:作者曾尝试将分片 packet 发给 TUN/TAP —— commit,但这个功能被禁止了(disabled),因为未知原因 UDP包被 rejected (issue),这就是为什么 syz_emit_ethernet 的 definition 包含第三个参数。

syz_emit_ethernet(len len[packet], packet ptr[in, eth_packet],frags ptr[in, vnet_fragmentation, opt])

KMSAN:Alexander Potapenko 在 MSAN 中添加了 checks 来挖掘信息泄露漏洞(当内核在网络上发送未初始化数据时),但是至今没能挖到 info-leak。

Socket descriptions:作者还优化了现有的 socket 相关的 syscall 描述,并找到了很多漏洞,包括 CVE-2016-9793 / CVE-2017-6074 / CVE-2017-1000112 ,最出名的是 CVE-2017-7308 —— Exploiting the Linux kernel via packet sockets。

5. Found bugs

5-1 Bugs in TUN/TAP

TUN/TAP 本身的漏洞如下,需要修复才能正常 fuzz network。

Bug Fix
KASAN: wild-memory-access Read in skb_copy_ubufs tun: make tun_build_skb() thread safe
WARNING in tun_get_user net-backports: tun: relax check on eth_get_headlen() return value
WARNING in xdp_rxq_info_unreg tun: avoid calling xdp_rxq_info_unreg() twice
WARNING: lock held when returning to user space in tun_get_user tun: add a missing rcu_read_unlock() in error path
KASAN: use-after-free Read in eth_type_trans tun: correct header offsets in napi frags mode

5-2 Remote bugs

Manual:作者手动运行 syzkaller 并发现了一些漏洞。

Bug Fix
net/sctp: slab-out-of-bounds in sctp_sf_ootb [CVE-2016-9555] sctp: validate chunk len before actually using it
net/tcp: null-ptr-deref in __inet_lookup_listener/inet_exact_dif_match net: tcp: check skb is non-NULL for exact match on lookups
net/dccp: null-ptr-deref in dccp_v4_rcv/selinux_socket_sock_rcv_skb dccp: do not release listeners too soon
net/dccp: null-ptr-deref in dccp_parse_options [dccp: fool proof ccid_hc_rt]x_parse_options()
net/icmp: null-ptr-deref in icmp6_send net: handle no dst on skb in icmp6_send
ip6_gre: invalid reads in ip6gre_err() [CVE-2017-5897] ip6_gre: fix ip6gre_err() invalid reads
ipv4: null-ptr-deref in ipv4_pktinfo_prepare [CVE-2017-5970] ipv4: keep skb->dst around in presence of IP options

syzbot:当作者将功能整合到 syzkaller 之后,syzbot 自动报告了一些漏洞。

Bug Fix
inconsistent lock state in sk_clone_lock [repro 2017/08/14 13:35] tcp: fix possible deadlock in TCP stack vs BPF filter
kernel BUG at ./include/linux/skbuff.h [repro 2017/12/22 23:01] esp: Fix GRO when the headers not fully in the linear part of the skb.
possible deadlock in sch_direct_xmit [repro 2018/01/13 18:16] net: use listified RX for handling GRO_NORMAL skbs
general protection fault in arpt_do_table [repro 2018/02/21 23:19] netfilter: add back stackpointer size checks
KASAN: use-after-free Read in ip6_route_me_harder [repro 2018/02/27 16:52] netfilter: use skb_to_full_sk in ip6_route_me_harder
KMSAN: uninit-value in inet_getpeer [repro 2018/04/12 19:01] inetpeer: fix uninit-value in inet_getpeer
BUG: soft lockup in _decode_session6 [repro 2018/05/12 02:19] xfrm6: avoid potential infinite loop in _decode_session6()
WARNING: refcount bug in igmp_start_timer [repro 2018/12/22 21:42] net: use listified RX for handling GRO_NORMAL skbs
KASAN: stack-out-of-bounds Read in gue_err_proto_handler [repro 2019/01/07 18:02] fou6: Prevent unbounded recursion in GUE error handler
KASAN: slab-out-of-bounds Read in tick_sched_handle [repro 2019/01/14 00:40] fou: Prevent unbounded recursion in GUE error handler also with UDP-Lite
general protection fault in xfrmi_decode_session [repro 2019/04/16 19:31] xfrm: Reset secpath in xfrm failure
general protection fault in skb_queue_tail [repro 2019/04/23 13:33] rxrpc: fix race condition in rxrpc_input_packet()
KASAN: slab-out-of-bounds Read in skb_gro_receive [repro 2019/05/01 22:03] [CVE-2019-11683] udp: fix GRO packet of death
BUG: assuming atomic context at net/core/flow_dissector.c [repro 2019/05/13 03:56] flow_dissector: disable preemption around BPF calls
KASAN: use-after-free Read in napi_gro_frags [repro 2019/05/31 10:12] net-gro: fix use-after-free read in napi_gro_frags()
kernel BUG at include/linux/skbuff.h [repro 2019/08/19 23:22] net: ipv6: fix listify ip6_rcv_finish in case of forwarding
KMSAN: uninit-value in inet_ehash_insert [repro 2019/09/30 06:45] net-backports: ipv6: drop incoming packets having a v4mapped source address
general protection fault in ip6_sublist_rcv [repro 2019/10/24 22:21] inet: do not call sublist_rcv on empty list

More bugs:其实还有很多漏洞,本文只展示了一部分,你可以爬取 syzbot dashboard,只要在栈回溯中包含 tun_get_user() 或者 reproducer 中包含 syz_emit_ethernet 则为本文的改进所发现的漏洞。

CVEs:很多漏洞没有CVE,作者为手动找到的漏洞申请了CVE(只要不是 null-pointer-dereference),syzbot 找到的漏洞没有申请CVE,除了这个严重的漏洞——GRO packet of death。

New bugs:可以看到,最近找到的漏洞 (recent bug) 定格在了2019年末,只有提升现有的 packet 描述才有可能发现新的漏洞。

(1)Showcases

来看看本文的改进所带来的一些漏洞发现。

GUE unbounded recursion:这个漏洞只需发送单个包就能导致拒绝服务,类似于之前提到的 XFRM 漏洞,参见 KASAN: slab-out-of-bounds Read in tick_sched_handle。reproducer 如下所示,只展示大致结构,查看详细参数可以点进链接。 这是位于 Generic UDP Encapsulation (GUE) 代码中的内核栈溢出,参见多个patch(patch1 / patch2 / patch3,对IPv4/IPv6分别修复)。

syz_emit_ethernet(0x6a, &(0x7f00000000c0)={..., @icmp={...}, ...})

GRO packet of death:这个漏洞在上面已经提到过了,参见 KASAN: slab-out-of-bounds Read in skb_gro_receive,reproducer 如下所示。 open 并 bind UDP socket,通过 UDP_GRO 选项来开启 Generic Receive Offload (GRO),从外部发包后触发漏洞,patch参见 udp: fix GRO packet of death。 该漏洞不确定能不能导致远程crash。

r0 = socket$inet(0x2, 0x2, 0x0)
bind(r0, &(0x7f0000000080)={...}, 0x7c)
setsockopt$inet_udp_int(r0, 0x11, 0x68, ...)
syz_emit_ethernet(0x2a, &(0x7f00000000c0)={..., @udp={...}, ...})

TCP vs BPF dead-lock:参见 inconsistent lock state in sk_clone_lock,reproducer 如下所示,设置 TCP socket,创建外部连接,同时安装 BPF filter,漏洞是由 TCP ACK handler 和 BPF filter 导致的死锁。patch 参见tcp: fix possible deadlock in TCP stack vs BPF filter。

r0 = socket$inet_tcp(0x2, 0x1, 0x0)
bind$inet(r0, &(0x7f0000001000)={...}, 0x10)
listen(r0, 0x8)
syz_emit_ethernet(0x3a, &(0x7f0000002000)={..., @tcp={...}, ...})
syz_extract_tcp_res(&(0x7f0000017000)={<r1=>0x42424242, <r2=>0x42424242}, ...)
setsockopt$SO_ATTACH_FILTER(r0, 0x1, 0x1a, &(0x7f0000017000-0x10)={...}, 0x10)
syz_emit_ethernet(0x36, &(0x7f0000004000)={..., @tcp={..., r2, r1, ...}, ...})

IPv6 routed hard:参见 KASAN: use-after-free Read in ip6_route_me_harder ,reproducer 如下所示。这个漏洞体现了 IPv6 support,先添加 IPv6 netfilter rule,设置 IPv6 TCP socket,然后发送 packet。添加 netfilter rule 是不需要额外的socket的,但是syzkaller并不知道。 patch 参见 netfilter: use skb_to_full_sk in ip6_route_me_harder —— Ipv6 ,patch-IPv4

r0 = socket$inet6(0xa, 0x2, 0x0)
setsockopt$IP6T_SO_SET_REPLACE(r0, ...)
r1 = socket$inet6(0xa, 0x1, 0x0)
bind$inet6(r1, &(0x7f0000000640)={...}, 0x1c)
listen(r1, 0x2)
syz_emit_ethernet(0x4a, &(0x7f0000000100)={..., @ipv6={...}, ...})

(2)Impact

没有一个漏洞是很危险的,少数能导致远程拒绝服务,没有可以远程代码执行的。有一些内存破坏漏洞,如果没有信息泄露漏洞,则很难利用。KMSAN没有发现任何漏洞。

5-3 About that RCE

Bug:发现一个危险的内核栈溢出漏洞,但是只影响这个特定版本的内核,因为它有定制的网络协议扩展(导致漏洞)。这个栈溢出可以覆盖size和content,通常 Stack Protector 机制会阻止这类线性栈溢出的利用,除非能够提前泄露canary。

Suprise:作者关闭 KASAN 后尝试复现该漏洞,发现 Stack Protector 失效了,该内核版本还禁用了KASLR,所以能构造ROP提权,问题是你很难获取一个非公开的 kernel binary。这个漏洞和 CVE-2022-0435 很相似(exploit)。

6. Things to improve

More protocols:需要完善模板来支持更多协议,例如 SCTF,可以参见 vnet.txt 中的 TODO 部分。

IPv6 support:IPv6 的支持很有限,例如对 IPv6 Extension Headers 的支持很有限,头部很特殊(参见unusual way),只有扩展现有的 syzlang 语法才能描述—— next_header 域成员必须指定下一个header的类型,syz_extract_tcp_res 不能处理这些header (参见 doesn’t handle)。 另一个有效的改变就是,关闭IPv6 spam 以更好的隔离 syzkaller 程序,但这需要修改 syzkaller 和内核本身。

Better resources:优化 resource 可以使syzkaller更好的组装 syscall,生成有效的程序。例如,如果要支持SCTP协议,你需要扩展 syzlang 来允许将 SCTP cookies 作为 resource,并添加类似于 syz_extract_tcp_res 这样的 pseudo-syscallsyz_sctp_extract_res。 可以参见 4-11 Establishing a TCP connection 节。

Check KMSAN:KMSAN 在其他子系统找到很多 info-leak,但是在网络子系统中没找到,可能是没有正确检查 network buffer。

Reading code:如果你想深挖本文的代码,可以从最初的 pull request 开始学习,内容更详细具体,然后你需要阅读syzkaller 源码,主要代码位于 initialize_netdevices()syz_emit_ethernet() (参见 implementations 和 vnet.txt)。

Exercise:作者在构造TCP连接展示了 the programs 来作为测试,这些基于老版本的syzkaller,现在不管用了。现在需要新的程序进行测试,可以采用fuzz生成或者手动写。 syzkaller 把这类程序称为 runtests,可以用于检测模板描述是否有效。 在fuzz指定网络协议时,需要限制 syzkaller 只fuzz相关模块:添加导向性的 pseudo-syscall 变体,在packet描述中注释不需要的 payload,关闭常量变异。可以参考下 syzkaller 相关的 tips。

7. Summary

Injecting network packets:利用 TUN/TAP 来将 packet 注入到内核。

Collecting coverage:采用KCOV来收集代码覆盖,并修改 TUN/TAP 代码来使KCOV能够收集到网络包解析代码中的代码覆盖。

Integrating into syzkaller:将以上两点整合到 syzkaller 中。作者研究了 syzkaller 的工作原理,如何写 syscall 描述,如何加入 pseudo-syscalls,然后如何一步步改进,使syzkaller能够探索到更深的代码。

Found bugs:作者最后列出了一些发现过的漏洞,还在一个特定版本的内核中发现RCE。

8. Afterword

Motivation:external_fuzzing_network.txt 已经公开很久了,但是一直没有进行记录。写这篇文章也是为了激励大家来进行相关研究,例如挖掘 remote bugs。

参考

Looking for Remote Code Execution bugs in the Linux kernel

syzlang语法编写案例学习 —— Looking for Remote Code Execution bugs in the Linux kernel相关推荐

  1. CVE-2014-6321 MS14-066 Microsoft Schannel Remote Code Execution Vulnerability Analysis

    目录 1. 漏洞的起因 2. 漏洞原理分析 3. 漏洞的影响范围 4. 漏洞的利用场景 5. 漏洞的POC.测试方法 6. 漏洞的修复Patch情况 7. 如何避免此类漏洞继续出现 1. 漏洞的起因 ...

  2. ue编辑器c语言语法高亮文件,再见UE之VS Code初体验---实现远端Linux C开发配置,ue初体验...

    再见UE之VS Code初体验---实现远端Linux C开发配置,ue初体验 一.引言 笔者以前使用的远端开发工具为 Xshell5 + Xftp5 + Source Insight4++Uedit ...

  3. php图片写入带问号_php-fpm Remote Code Execution 分析(CVE-2019-11043)

    漏洞简介 国外安全研究员 Andrew Danau发现向服务器请求的URL中包含%0a 符号时,服务返回异常,疑似存在漏洞. Nginx+php-fpm的环境中,若Nginx上的fastcgi_spl ...

  4. 软件工程案例学习-图书管理系统

    软件工程案例学习-图书管理系统 文档编号:LMS_1                                                                           ...

  5. JavaScript 基础语法循环案例练习

    JavaScript 基础语法循环案例练习 文章目录 JavaScript 基础语法循环案例练习 前言 一.早午晚安喵喵喵 二.判断奇偶数 三.班长请客 四.1-100跳过个位为3的数求和 五.第几个 ...

  6. 通过脚本案例学习shell(五) 通过创建DNS脚本一步一步教你将一个普通脚本规范到一个生产环境脚本...

    通过脚本案例学习shell(五) 通过创建DNS脚本一步一步教你将一个普通脚本规范到一个生产环境脚本   版权声明: 本文遵循"署名非商业性使用相同方式共享 2.5 中国大陆"协议 ...

  7. hadoop之mapreduce教程+案例学习(二)

    第3章 MapReduce框架原理 目录 第3章 MapReduce框架原理 3.1 InputFormat数据输入 3.1.1 切片与MapTask并行度决定机制 3.1.2 Job提交流程源码和切 ...

  8. hadoop之mapreduce教程+案例学习(一)

    第1章 MapReduce概述 目录 第1章 MapReduce概述 1.1 MapReduce定义 MapReduce是一个分布式运算程序的编程框架,是用户开发"基于Hadoop的数据分析 ...

  9. 三菱PLC项目案例学习之PLC控制伺服或步进电机带动丝运行

    三菱PLC项目案例学习之PLC控制伺服或步进电机带动丝运行案例. 器件:三菱FX1SPLC,威纶通触摸屏,48步进驱动器,伺服电机,丝杆滑台等. 控制方式:PLC发脉冲给步进驱动器控制步进电机带动丝杆 ...

  10. Unity2D案例学习——背包系统制作(ScriptableObject)

    00 简介 该学习案例来自b站up主M_Studio的系列视频背包系统,此篇博客为案例学习笔记 项目概览 在该项目中, 01 项目基础内容的搭建 系列视频的讲解中不包括基础场景的搭建以及角色移动控制的 ...

最新文章

  1. 事件ID 5775 NETLOGON
  2. linux qt小型计算器,Qt实现一个简单的计算器
  3. css提取页面元素唯一性_一日一技:爬虫如何正确从网页中提取伪元素?
  4. windows 改变文件大小 函数_手写 bind call apply 方法 与 实现节流防抖函数
  5. 005 vim程序编辑器
  6. POJ 2409 Let it Bead (Polya定理)
  7. c语言将链表写入二进制文件_通过逐级遍历将二进制树转换为单链表的C程序
  8. 新技术层出不穷,一入前端深似海!
  9. Mr.J-- jQuery学习笔记(十五)--实现页面的对联广告
  10. 开源 非开源_打破开源中的怪胎刻板印象
  11. 原创专栏:谈谈我对评分模型的理解
  12. 乾颐堂现任明教教主(2014年课程)TCPIP协议详解卷一 第八节课笔记
  13. win7用html做桌面,win7系统怎么制作主题桌面 win7系统制作主题桌面方法
  14. STM32F030C8T6 STM32F307VCT6 STM32F107VCT6结构图( block diagram)位置是在数据手册中
  15. 【安全知识分享】PPTX|精益安全管理(130页)(附下载)
  16. 电脑使用 Dolby Audio提示将耳机或扬声器插入系统的音频插孔,以启用Dolby Audio
  17. 金山终端安全系统任意文件上传漏洞附poc(新鲜趁热)
  18. 计算机清理方法,电脑清理方法 电脑要如何清理
  19. Vagrant 手册之同步目录 - 基本用法
  20. 信工三行代码——红色底蕴与编程技术的火花

热门文章

  1. MAL II,怀槐凝集素II(MAL II)
  2. python+selenium+request实行全自动12306抢票和购票
  3. HDU 5651 xiaoxin juju needs help 组合数
  4. Unity学习笔记(六)——顶点动画
  5. STM32F7 架构和资源分析
  6. 【Linux】/bin/sh、/bin/bash、/bin/dash、init=/sysroot/bin/bash(sh) 、init=/bin/bash(sh) 的区别
  7. 让GitHub沸腾的阿里内部神作Java并发原理JDK源码手册,现已开源
  8. 推荐10款笔者一直在使用的Chrome提效插件
  9. cocos 躲避球游戏(3) --群组碰撞
  10. iview form表单验证手机号