BRF复现

BRF复现

BRF

简介

BRF: Fuzzing the eBPF Runtime 是一个针对ebpf runtime组件的模糊测试工具。它旨在解决syzkaller对ebpf模糊测试低效的问题,从程序语义、程序依赖、程序执行三个要点出发,生成了有高verifier通过率、以及丰富语义(即包含了一些列helper与map操作)的C程序,并与syzkaller结合,对ebpf进行模糊测试,大大提升了代码覆盖率,并发现了6个新的ebpf漏洞。

仓库地址:https://github.com/trusslab/brf

环境搭建

基础复现流程

BRF仓库的README文件提供了详细的复现流程,可以直接按照这个流程走下来。不过在这个过程中,我仍然遇到了一些难以解决的问题。

BRF编译问题

在make BRF源代码时,会产生报错:

make_error

这个错误产生的原因是libbpf组件没有被正确地配置,我为此提交了一个PR,解决的办法也很简单,下载最新的libbpf并安装即可:

1
2
3
4
5
git clone https://github.com/libbpf/libbpf.git
cd libbpf/src
make
make install
make install_uapi_headers

C代码编译问题

按照BRF的配置,生成器生成的ebpf C代码会在$BRF_WORKDIR下保存,并被挂载到Qemu中,使用clang-16进行编译,生成.o文件。但是实际运行过程中,我发现编译过程并没有被正确执行,可执行文件没有被生成,fuzz流程陷入停滞。

手动启动Qemu后,我尝试在虚拟机内用clang-16编译C文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
qemu-system-x86_64 \
        -m 2G \
        -smp 2 \
        -kernel $KERNEL/arch/x86/boot/bzImage \
        -append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0" \
        -drive file=$IMAGE/bookworm.img,format=raw \
        -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
        -net nic,model=e1000 \
	    -virtfs local,path=$KERNEL,mount_tag=host0,security_model=mapped,id=host0 \
        -virtfs local,path=$BRF_WORKDIR,mount_tag=brf,security_model=mapped,id=brf \
        -enable-kvm \
        -nographic \
        -pidfile vm.pid \
        2>&1 | tee vm.log

mkdir /mnt/brf_work_dir
mount -t 9p -o trans=virtio,version=9p2000.L brf /mnt/brf_work_dir


clang-16 -g -O2 -D__TARGET_ARCH_x86 -I/usr/include/$(uname -m)-linux-gnu -Wno-compare-distinct-pointer-types -Wno-int-conversion -target bpf -mcpu=v3 -c ./prog_1869aed7db619f2f.c -o test.o

发现确实编译存在问题,似乎也是libbpf的配置问题,但是在前面的基础流程中明明已经安装了libbpf和头文件。不过为了解决这个问题,我还是尝试安装了libbpf-dev

1
apt install libbpf-dev

成功解决了这个问题。

C代码语法错误问题

我发现在生成的C文件中有一些匪夷所思的语法错误,在生成char *类型的变量时,正确的定义方式应该类似于char v[8]=...,但实际上变量的定义方式却是char %!p(string=v)[8]=...

c_grammar

源码中关于这一部分代码的内容在 prog/brf_legacy.go#L1308 中:

1
2
	a.Name = fmt.Sprintf("v%d", p.VarId)
	a.Prepare = fmt.Sprintf("	char %p[%d] = {};\n", a.Name, varSize)

看起来也并没有什么不妥,为什么会生成这样语法的代码暂时未可知,我的解决办法简单粗暴,修改源代码,识别这样的错误语法并进行修正。

prog/brf_prog.go 中,writeCSource函数会将生成好的C代码写入文件,我们只需在它写入文件之前将代码字符串改为正确的就可以。我添加了一个函数fixCharError,它使用正则表达式匹配错误代码,并修改为正确的格式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import (
        "encoding/gob"
        "fmt"
        "os"
        "time"
        "regexp"
)

func fixCharError(content string) string {
        fmt.Printf("I am trying to fix this: %s",content)
        pattern := regexp.MustCompile(`%!p\(string=(\w+)\)\[(\d+)\]`)

        // 执行替换操作并返回结果
        newContent:=pattern.ReplaceAllString(content, "$1[$2]")
        fmt.Printf("I fix this: %s\n",newContent)
        return newContent
}

func (p *BpfProg) writeCSource() error {
        var progSrc string

        if (p.UseTestSrc) {
                progSrc = testSrc
        } else {
                progSrc = p.genCSource()
        }
        progSrc = fixCharError(progSrc)
        f, err := os.Create(p.BasePath + ".c")
        if err != nil {
                return err
        }
        defer f.Close()

        _, err = f.WriteString(progSrc)
        return err
}

Qemu内存大小问题

解决了上述问题之后,按理来说,应该可以正常运行并编译生成可执行文件了,但是虚拟机仍然不断产生崩溃,观察了log之后,发现是虚拟机内存不够导致的,修改一下config文件就可以解决啦!把mem2048修改为4096

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
        "target": "linux/amd64",
        "http": "127.0.0.1:56741",
        "workdir": "$SYZKALLER/workdir/bookworm",
        "kernel_obj": "$KERNEL",
        "image": "$IMAGE/bookworm.img",
        "sshkey": "$IMAGE/bookworm.id_rsa",
        "syzkaller": "$SYZKALLER",
        "procs": 8,
        "type": "qemu",
        "vm": {
                "count": 4,
                "kernel": "$KERNEL/arch/x86/boot/bzImage",
                "cpu": 2,
                "mem": 4096,
		"brf_workdir": "$BRF_WORKDIR"
        }
}

运行效果

解决了上述问题以后,BRF可以正常跑起来了,在$BRF_WORKDIR目录下也可以看到正常的C代码以及可执行文件了,可喜可贺。

这里我使用的commit是 047d1c9a3e47722ae94afb9f0247a1057c323df3

brf_web_1

不过相信聪明的你也已经发现了,为什么coverage是0?

这是因为在这一个 commit 中,WorkQueue loop相关的代码被注释掉了,triageInput函数不会被执行,也就不会收集覆盖率以及扩充语料库。

从commit的注释"TEST: disable mutation"似乎可以看出,这个提交只是为了测试brf生成程序以及执行的能力,那我们转移到上一个commit a1f51c55c2c01b5959cfbfc9e44559da5d48551c 再运行试试。

转移到这个commit再运行以后会发现,BRF在给系统调用syz_bpf_prog_load等传递参数时,由于参数(形如:/workdir/prog_xxx.c)的值会触发syzkaller中对escapingFilename的检查,因此我们需要将相关的检查都注释掉,程序才可以正常运行。

经过这样一系列的操作之后,我们终于可以看到一个正常运行的BRF:

覆盖率

brf_cover

语料库

brf_corpus

Licensed under CC BY-NC-SA 4.0
Last updated on Jun 14, 2025 00:00 UTC
View: Loading