Home

Awesome

INTRODUCTION

The idea comes from scannell's blog, Fuzzing for eBPF JIT bugs in the Linux kernel.

It contains three parts:

QEMU FUZZLIB

This module is mainly used to test the linux kernel. It uses the modified syzkaller script to generate debian buster image file and all other necessary files. The modified script adds a new normal user test without a password.

NOTE: use This create-image.sh to create the buster img: ./create-image.sh --distribution buster

This module provide an interface qemu_fuzzlib_env_setup() for the caller to initialize the fuzzing environment, the prototype of the function is:

extern struct qemu_fuzzlib_env *
qemu_fuzzlib_env_setup(char *user_name, u64 user_id, char *qemu_exec_path,
			char *bzImage_file, char *osimage_file,
			char *host_id_rsa, char *listen_ip, u32 inst_max,
			u32 idle_sec, u32 inst_memsz, u32 inst_core,
			char *env_workdir, char *guest_workdir,
			char *guest_user, char *script_file, char *c_file,
			char *sample_fname, char *fuzz_db,
			int (*db_init)(struct qemu_fuzzlib_env *),
			int (*mutate)(struct qemu_fuzzlib_env *, char *));

After the fuzzing environment is setup, the caller should call qemu_fuzzlib_env_run() to start the fuzzer.

The qemu_fuzzlib_env_run() function generates new sample and put it into an available qemu instance to execute, until no more sample is generated or no available qemu instance found after the idle_sec seconds.

EBPF SAMPLE GENERATOR

We need to focus on just one thing: the mutate() callback. This function is used to generate new sample, in this ebpf fuzzer, to generate new ebpf sample.

The scannell's blog give us a perfect guidance to generate ebpf samples. I recommend you to read the blog first.

In the current implementation, the sample's header and tail are known. We need to generate the sample body, which is filled by ebpf instructions. The instructions do several things:

After all instructions generated, we need to print the instructions and write them to the sample file.

insn_body()

EXCEPTION HANDLER IN THE LINUX KERNEL

The first time I run the fuzzer to trigger cve-2020-8835, the guest frozen: one of the kernel threads runs into an infinite loop. Check this commit: the verifier rewrote original instructions it recognized as dead code with 'goto PC-1'.

This is a good way to detect bugs in the bpf verifier.

What else?

How to run

After compiling the clib and this project, use ./ebpf_fuzzer /path/to/config 0 to startup the fuzzer.

For the bzImage file, make sure the following config options are enabled:

CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y
CONFIG_E1000=y
CONFIG_BINFMT_MISC=y

When the bzImage and buster.img are ready, test the qemu first:

Launch qemu:

/usr/bin/qemu-system-x86_64 -m 2G -smp 2 -kernel /path/to/bzImage -append 'console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0' -drive file=/path/to/buster.img,format=raw -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 -net nic,model=e1000 -enable-kvm -nographic

Communicate with the guest

ssh -q -i /path/to/buster.id_rsa -p 10021 -o 'StrictHostKeyChecking no' test@127.0.0.1 id

An example of the config file:

[
	{
		"version": "general",
		"qemu_exec_path": "/path/to/qemu-system-x86_64",
		"bzImage_path": "/path/to/bzImage",
		"osImage_path": "/path/to/buster.img",
		"rsa_path": "/path/to/buster.id_rsa",
		"idle_sec": "1800",
		"host_ip": "10.0.2.10",
		"instance_nr": "8",
		"instance_memsz": "1",
		"instance_core": "2",
		"env_workdir": "/path/to/fuzzer_workdir",
		"guest_workdir": "/tmp/",
		"guest_user": "test",
		"sample_fname": "test.c",
		"body1_len": "24",
	}
]

The body1_len is used in mutate module, it's the count of instructions to generate in gen_body1(). The larger you give, the lower valid sample rate you will get. Default value is 0x18.

FAQ

Q: When running the fuzzer, the output is 'total: 0'?<br> A: Try to create the buster image with ./create-image.sh --distribution buster. Check issue #1.