kAFL Fuzzing with Userspace Stimulus

October 20, 2022 ยท View on GitHub

This approach focuses on runtime activity of the Linux kernel. In contrast to boot time fuzzing, a 'stimulus' is required to trigger various kernel activity, which in turn may lead to reading untrusted input.

The approach the same kAFL agent as in boot time fuzzing to control the actual input injection, intercepting kernel panic etc. This way, we only need to supply a relevant stimulus program, and a loader script to initialize the agent and execute the stimulus as desired.

The below examples use Busybox or Buildroot to create small VM images. It is possible to use more common Linux distributions but the longer boot times and RAM size will impact fuzzer startup time and ability to fuzz many VMs in parallel.

TODO:

  • virtio-blk is not yet supported, so we are limited to initramfs
  • busybox image not really used here. Maybe simplify and use busybox scripts only as dummy initrd in boot time fuzzing (to trigger ABORT hypercall on userspace entry)

Busybox RootFS

A rootfs based on Busybox is easy to build and small. It is good for simple userspace apps that can be compiled as static or added together with their dependencies (check ldd).

The following steps create, bless and package an initrd based on busybox:

$BKC_ROOT/bkc/kafl/userspace/gen_initrd.sh  initrd.rootfs
$BKC_ROOT/bkc/kafl/userspace/bless_initrd.sh initrd.rootfs
./initrd.rootfs/build.sh initrd.cpio.gz

Blessing means we add a loader.sh init script that will contact the host kAFL fuzzer to download and execute a file init.sh. This is used to bootstrap any desired userspace setup inside the guest (see below).

In the simplest case, we can use this to trigger an "abort" hypercall as soon as Linux reaches userspace. This is useful in combination with boot time fuzzing, where we don't expect to reach userspace and should raise a corresponding error:

cat > $BKC_ROOT/sharedir/init.sh << HERE
#!/bin/sh
/fuzz/habort "Error: guest execution reached userspace!"
HERE

The sharedir folder is automatically picked up by the fuzz.sh launcher and init.sh is executed as the default loader inside the guest.

Buildroot RootFS

Buildroot is a framework for building small root filesystems. It allows to include some standard tools and custom packages on top of busybox.

The provided example configuration sets the correct target platform, initramfs output format, and selects some basic Linux tools to use as stimulus (lspci, fio, stress-ng,..). The patches include a newer version of stress-ng and add the perf_fuzzer tool.

Quick install:

1. Download latest stable buildroot environment:

BUILDROOT_VERSION="buildroot-2021.11"
URL="https://buildroot.org/downloads/$BUILDROOT_VERSION.tar.gz"

wget $URL
tar xzf $(basename $URL)

2. Apply patches for newer stress-ng package and adding perf_fuzzer tool:

cd $BUILDROOT_VERSION
git init .; git add .; git commit -m "vanilla $BUILDROOT_VERSION"
git am $BKC_ROOT/bkc/kafl/userspace/buildroot/0001-upgrade-to-stress-ng-0.13.05.patch
git am $BKC_ROOT/bkc/kafl/userspace/buildroot/0002-new-package-perf_fuzzer.patch

3. Apply / customize configuration and build. Initial build may take a while.

cp $BKC_ROOT/bkc/kafl/userspace/buildroot/buildroot.config .config
cp $BKC_ROOT/bkc/kafl/userspace/buildroot/busybox.config package/busybox/.config
#make menuconfig            # optional
#make busybox-menuconfig    # optional
make source                 # download packages up front
make -j $(nproc)

4. Bless the Buildroot rootfs

The provided bless_initrd.sh can be used again to update the buildroot init flow and inject a loader.sh early on, which will in turn attempt to download and execute a init.sh from the host kAFL sharedir.

Bless and rebuild the image, then copy or link it for use by fuzz.sh:

cd $BKC_ROOT
./bkc/kafl/userspace/bless_initrd.sh $BUILDROOT_VERSION/output/target/
make -C $BUILDROOT_VERSION
ln -sf  $BUILDROOT_VERSION/output/images/rootfs.cpio.gz $BKC_ROOT/initrd.cpio.gz

Sharedir Setup

The kAFL sharedir feature offers an OS-independent interface for downloading files from a host directory provided using the kafl_fuzz.py --sharedir option.

By default, the stage 1 loader.sh uses hget to request and execute an init.sh script as the 2nd stage and perform actual harness initialization. This allows to define the userpace harness or stimulus setup completely based on files provided in the host-side sharedir.

A basic sharedir can be build from existing bkc/templates like this:

$BKC_ROOT/bkc/kafl/userspace/gen_sharedir.sh $BKC_ROOT/sharedir

To run the userspace harness, make sure the TDX guest kernel has the kAFL agent activated with harness set to NONE. This disables any boot-time harnesses and allows to boot through to the userspace loader.sh. Recommended settings:

cd $LINUX_GUEST
./scripts/config -e CONFIG_TDX_FUZZ_KAFL
./scripts/config -e CONFIG_TDX_FUZZ_KAFL_DEBUGFS
./scripts/config -e CONFIG_TDX_FUZZ_KAFL_SKIP_CPUID
./scripts/config -d CONFIG_TDX_FUZZ_KAFL_SKIP_PARAVIRT_REWRITE
./scripts/config -e CONFIG_TDX_FUZZ_HARNESS_NONE

The provided sharedir scripts analyze payloads using Linux gcov + trace events:

cd $LINUX_GUEST
./scripts/config -e CONFIG_GCOV_KERNEL
./scripts/config -e CONFIG_GCOV_PROFILE_ALL
./scripts/config -d GCOV_PROFILE_FTRACE
./scripts/config -e CONFIG_FTRACE
./scripts/config -e CONFIG_TRACING
./scripts/config -e CONFIG_EVENT_TRACING
#./scripts/config -e CONFIG_BOOTTIME_TRACING

Also recommend to disable tdx_fuzz event for the tdg_fuzz_err() hook, otherwise there will be even more timer-based input events that we don't care about:

diff --git a/arch/x86/kernel/kafl-agent.c b/arch/x86/kernel/kafl-agent.c
index e31113b30014..425d211aa95f 100644
--- a/arch/x86/kernel/kafl-agent.c
+++ b/arch/x86/kernel/kafl-agent.c
@@ -434,7 +434,7 @@ bool tdg_fuzz_err(enum tdg_fuzz_loc type)
 {
        if (!fuzz_enabled || !fuzz_tderror) {
                // for filtering stimulus payloads, raise a trace event with
size=0 here
-               trace_tdx_fuzz((u64)__builtin_return_address(0), 0, 1, 1, type);
+               //trace_tdx_fuzz((u64)__builtin_return_address(0), 0, 1, 1,
                type);
                return false;
        }


And we seem to require a small patch for the guest kernel to boot with ftrace enabled:

diff --git a/arch/x86/kernel/tdx.c b/arch/x86/kernel/tdx.c
index 380fd05fe38a..81a6b5ec9874 100644
--- a/arch/x86/kernel/tdx.c
+++ b/arch/x86/kernel/tdx.c
@@ -96,10 +96,10 @@ static u64 _trace_tdx_hypercall(u64 fn, u64 r12, u64 r13, u64 r14, u64 r15,
 {
        u64 err;

-       trace_tdx_hypercall_enter_rcuidle(fn, r12, r13, r14, r15);
+       //trace_tdx_hypercall_enter_rcuidle(fn, r12, r13, r14, r15);
        err = _tdx_hypercall(fn, r12, r13, r14, r15, out);
-       trace_tdx_hypercall_exit_rcuidle(err, out->r11, out->r12, out->r13,
-                                        out->r14, out->r15);
+       //trace_tdx_hypercall_exit_rcuidle(err, out->r11, out->r12, out->r13,
+       //                               out->r14, out->r15);

        return err;
 }

Before building the final kernel, now is also a good time to generate a smatch report for your current kernel. See the first part of below "Stimulus Evaluation" section. Then continue to build the kernel as usual using make -j <N>.

Finally, review the launcher script bkc/kafl/fuzz.sh to ensure it picks up the correct version of bzImage, initrd and sharedir. Once the loader.sh starts properly, modifications should be mostly limited to the sharedir folder:

cd ~/tdx
ln -s bkc/kafl/fuzz.sh
ln -sf buildroot-2021.08/output/images/rootfs.cpio.gz initrd.cpio.gz
./fuzz.sh linux-guest -p 1 --sharedir ~/sharedir

Fuzzing complex user stimulus like lspci or ping may suffer from significant non-determinism. Current best known option is to disable -funky and use --log_crashes. Also ensure you set a sufficient timeout window, e.g. -t 2 -ts 1. The new --kickstart option tends to work very well for building an initial corpus. Also increase the VM RAM to at least 1G and look out for 'out of memory' errors in the crash logs.

TODO: Describe and provide sample/template scripts for both, stimulus evaluation and currently recommended fuzz cases

Stimulus Evaluation

Produce a smatch report for your kernel. The below helper will download and build smatch as well as the guest-audit repo if not present, then run smatch on the kernel to produce '~/tdx/linux-guest/smatch_warns.txt'. This file is also picked up by fuzz.sh and placed in the workdir/target/ folder for later use.

cd ~/tdx
./bkc/kafl/gen_smatch_warns.sh linux-guest

Finally, the below script will inspect the gcov/lcov traces produced by kAFL userspace launcher (in sharedir/init.sh):

pip3 install gcovr
./bkc/kafl/userspace/minimize_stimulus.sh /path/to/workdir

It decompresses the individual payloads placed in workdir/dump/, inspects the trace log, and performs gcov + smatch match analysis for any payload with new tdx_fuzz events.

You can re-run the script whenever new results are uploaded to the dump/ folder. Previously seen stacks are kept in dump/seen/.