一个简单、通用的内核模糊测试工具。
本项目是 2024 全国大学生系统能力大赛操作系统设计赛 OS 功能挑战赛道(以下简称“比赛”)的参赛作品的组成部分。
flicker 是一个基于 LibAFL 和 LibAFL QEMU 的内核模糊测试工具,能够帮助用户检测内核中的潜在漏洞。flicker 主要由两部分构成:
-
fuzzer:运行在 host 机器上,负责生成包含若干系统调用的测例,传递给 harness 执行,监控崩溃、超时等异常状况。执行结束后,分析执行过程的覆盖率、所需时间等信息,由此进一步改变测例的参数,以期提高目标程序的覆盖率。
-
harness:运行在 QEMU 的 guest OS 上,负责接受前者生成的测例,解析后发出系统调用。
flicker 的主要特点有:
-
简单易用:只需描述系统调用、编写简单的 harness、配置运行方式,即可开始测试。
-
通用:支持任何可由 syzlang 描述的系统调用接口,以及任何底层框架支持的架构(包括 x86、ARM、RISC-V 等)。
-
高效:参考 syzkaller 设计具有类型信息的结构化测例,并实现相应的生成和变异(mutation)算法,同时充分利用 LibAFL 和 LibAFL QEMU 提供的 API 实现测试流程,提高测试效率。
关于更详细的结构设计、技术细节等信息,请参考比赛文档。
源代码位于 src/
目录下,主要包含 2 个部分:
-
库(
lib.rs
):负责生成/变异测试用例。提供了面向 LibAFL 的组件SyscallInput
、SyscallGenerator
和 4 种SyscallMutator
,分别是系统调用测试用例的载体、系统调用测试用例的生成器和变异器。其具体实现又由以下几个部分组成:-
Syscall
:表示系统调用信息的结构体,每个Syscall
包含若干表示参数字段的Field
,不同类型的Field
实现了GenerateArg
和MutateArg
两个 trait,能够根据自身信息生成参数或变异特定参数。相关代码主要在program/syscall
目录下。 -
Call
:表示生成的系统调用,包含了具体数据,去除了冗余的类型信息。每个Call
包含若干Arg
,不同类型的Arg
实现了ToExecByte
trait,能够将数据序列化成 harness 可以解析的序列。相关代码主要在program/call.rs
中。 -
Context
:记录生成/变异过程中产生的信息。相关代码主要在program/context.rs
中。
-
-
可执行程序(
main.rs
):负责具体的测试流程。其工作流程如下:-
初始化:读取 syzlang 系统调用描述文件和常数文件,形成
Syscall
结构体。 -
生成测例:
Generator
生成初始测例。 -
执行:从测例库(corpus)中选取测例,运行测试,监控状态。
-
优化测例:根据覆盖率、运行时间等反馈信息判断当前测例的价值,对测例进行变异。
-
-
corpus/
:有价值的测例,其中corpus/init
存放用户设置的初始化测试用例,corpus/gen
存放生成的测试用例。 -
crashes/
:能够产生异常的测例(在 LibAFL 中也被称为 solution)。 -
desc/
:syzlang 描述的系统调用信息。 -
kernel/
:内核源码。建议每个内核单独建立一个目录(如kernel/rCore-Tutorial-v3
),方便管理。 -
makefiles/
:配置如何运行 fuzzer 的 Makefile,例如准备工作、启动参数等,参见添加内核一节。建议每个内核单独编写一个 Makefile(如makefiles/rCore-Tutorial-v3.toml
),方便管理。 -
target/
:编译生成的文件。
已在 Ubuntu 24.04 上测试,可能至少需要 Ubuntu 22.04 或更高版本。
-
安装 LLVM(以 15 为例,请参考 LibAFL 推荐的 LLVM 版本):
sudo apt-get install llvm-15 llvm-15-dev
编译本项目时,可能需要通过环境变量
LLVM_CONFIG
指定 LLVM 版本,或通过LLVM_CONFIG_PATH
指定 LLVM 路径。 -
本项目需要编译经 LibAFL 改动的 QEMU。安装 ninja、glib-2.0、libclang 等编译 QEMU 所需依赖:
sudo apt-get install ninja libglib2.0-dev libclang-dev
glib-2.0 的版本需要
>=2.66.0
。编译 QEMU 的过程中可能会提示需要安装 tomli。 -
安装 cargo-make:
cargo install cargo-make
-
使用 syzlang 描述待测的系统调用。可参考 syzkaller 关于 syzlang 的说明文档及 syzkaller 仓库中的系统调用描述文件,也可参考
desc/
目录下的描述文件。 -
编写 harness。
-
测例解析与执行:fuzzer 与 harness 之间存在一套序列化/反序列化测例的协议,可参考
src/program/call.rs
中ToExecBytes
的实现。对于不关心具体细节的用户,建议使用本项目的辅助工具 syscall2struct 将描述文件中的每个待测系统调用转换为一个 Rust 结构体,通过 serde 的Deserialize
、Serialize
trait 实现系统调用数据的序列化/反序列化,并通过该库的MakeSyscall
或MakeSyscallMut
trait 实现执行系统调用的逻辑。 -
实现 harness:为内核添加一个用户程序,根据 LibAFL QEMU 的接口,首先调用 start 命令,之后从缓冲区依次读取测例、解析、执行,最后调用 end 命令。可参考已有示例实现。其中,解析过程的具体实现需要 postcard 的支持;对于 Rust 编写的内核,libafl_qemu_cmd 提供了 LibAFL QEMU 接口的 Rust 版本。
-
-
使用基于 cargo-make 的 Makefile 配置运行方法。
-
添加 Makefile:在
makefiles/
目录下为待测内核新建一个 Makefile,如makefiles/rCore-Tutorial-v3.toml
。 -
配置 Makefile:
Makefile.toml
是所有 Makefile 的基础模板,内容包括:-
环境变量:编译选项、文件路径、LibAFL 相关配置等。
-
任务:如下图所示,其中方角矩形是已实现的任务,圆角矩形是未具体实现的任务。
flowchart TD R(run) --> B[build] B[build] --> B1[build_fuzzer] B --> B2(build_kernel) C[clean] ---> C1[clean_fuzzer] C ---> C2(clean_kernel)
因此,新的 Makefile 应至少包括以下内容:
extending = "../Makefile.toml" [env] KERNEL_NAME = "..." KERNEL_DIR = "..." [tasks.build_kernel] command = "..." args = ["..."] [tasks.clean_kernel] command = "..." args = ["..."] [tasks.run] command = "..." args = ["..."]
亦可修改环境变量、已有任务或添加新的任务。可参考
makefiles/
目录下的示例。 -
-
Makefile 配置完成后,可通过 cargo make
编译或直接运行 fuzzer,如:
cargo make --makefile path/to/makefile.toml build
cargo make --makefile path/to/makefile.toml run
注意需要先通过 --makefile
导入 Makefile,再指定任务,保证环境变量正确加载。
flicker 还提供了测例复现功能,请参考 makefiles/Alien.toml
中的 reproduce
任务。
-
更新 LibAFL 依赖。
-
支持更多 syzlang 类型,如
array
、struct
、union
等。 -
完善测例生成算法,提高测试效率,如
resource
类型的生成、变异。
-
LibAFL book:https://aflplus.plus/libafl-book/libafl.html
-
LibAFL paper:https://dl.acm.org/doi/abs/10.1145/3548606.3560602
-
LibAFL QEMU paper:https://hal.science/hal-04500872/