Skip to content

Architectural cache side channel fuzzer from the research paper "ExfilState: Automated Discovery of Timer-Free Cache Side Channels on ARM CPUs".

License

Notifications You must be signed in to change notification settings

cispa/ExfilState

Repository files navigation

ExfilState — Architectural Cache Side Channel Fuzzer

This repository contains the architectural side channel fuzzer from the research paper "ExfilState: Automated Discovery of Timer-Free Cache Side Channels on ARM CPUs".

Note

Find the artifacts for the paper here.

Note

This repository relies on Git LFS to handle large files. Please install it before cloning the repository. If you cloned without having LFS installed or have other problems with LFS files run: git lfs install && git lfs pull.

The framework largely depends on Nix for package retrieval. Refer to the setup section for instructions on how to install and load dependencies.

Fuzzer

The fuzzer is primarily implemented in client/src/exfilstate.c and client/src/lib/sc_fuzzing_lib.h. It can be build by calling:

build-client --no-auto-map-mem --vector --seq-len 100 --target exfilstate --out exfilstate

This produces a static binary exfilstate in the CWD.

The fuzzer can just be copied to a machine and run. It also supports Android (e.g., via Termux). The fuzzer first finds which instructions it should use and then starts a campaign. The results are saved in exfilstate-results relative to the CWD.

Typical output looks like this:

exfilstate-results
|-- 4108d034
|   `-- 04_04
|       `-- 073_097_1_2_00315_00020.yaml
|-- 4108d092
|   |-- 00_00
|   |   |-- 100_100_1_2_00007_00001.yaml
|   |   `-- 100_100_1_2_00333_00016.yaml
|   `-- 11_11
|       |-- 077_099_1_2_00419_00024.yaml
|       |-- 078_088_1_3_00091_00007.yaml
|       |-- 082_096_1_4_00256_00015.yaml
|       |-- 082_098_1_2_00006_00002.yaml
|       |-- 082_099_1_2_00251_00012.yaml
|       |-- 082_099_1_2_00338_00019.yaml
|       |-- 088_094_1_2_00250_00014.yaml
|       |-- 089_100_1_2_00007_00003.yaml
|-- client.yaml
|-- cpuinfo
|-- instruction_list_0
|-- instruction_list_1
|-- instruction_list_2
|-- instruction_list_3
|-- instruction_list_4
|-- instruction_list_5
|-- possible
|-- progress_worker_0
|-- progress_worker_1
|-- progress_worker_2
|-- progress_worker_3
|-- progress_worker_4
`-- progress_worker_5

The instruction lists and progress logs for each worker (core) and the reproducers stored in the directories named after the MIDR (the microarchitecture's identifier).

The name of the reproducer can be read as follows:

11_11/077_099_1_2_00419_00024.yaml
  • 11_11/ → Signal number of architectural outcomes. Here SEGV vs. SEGV.
  • 077 → Best recorded bit-error rate (%) in covert channel.
  • 099 → F-score.
  • 1 → Number of differences (n_diffs).
  • 2 → Reduced sequence length (seq_len).
  • 00419 → Seconds since start.
  • 00024 → Counter (unique reproducer index).

The results can be copied off the machine after a fuzzing run.

To quickly skim over reproducers run:

for dir in exfilstate-results/*/*/; do find "$dir" -maxdepth 1 -type f | head -n 1; done | xargs bat --style=header --line-range :80

Reproducers

Reproducer files (yaml) can be compiled to static binaries for quick reproduction. The framework provides two main ways to use these reproducers:

  • repro-runner: A statically compiled binary that can load reproducers and re-execute them. Good for quick testing, without compilation.
  • C reproducers: C programs which can be modified and statically compiled. Better for debugging a reproducer.

The reproducer binaries automatically pin to the correct microarchitecture, but inform about further actions to take. They follow a similar command line interface pattern:

Usage: repro-runner/repro [core N | midr 0x.... | uarch NAME]

Arguments:
  core N       Use logical core index N (decimal).
  midr 0x....  Use cores with this MIDR (hex or decimal).
  uarch NAME   Use microarch by name (e.g., Cortex-X4).

Repro-runner

The repro-runner can be compiled like this (ensure that you match the arguments of fuzzer compilation):

build-client --no-auto-map-mem --vector --seq-len 100 --target repro-runner --out repro-runner

You can then run and reproduce reproducers (yaml) using:

./repro-runner exfilstate-results/4108d034/04_04/073_097_1_2_00315_00020.yaml

Statically Compiled C Reproducers

The framework provides multiple commands for making a C program out of a reproducer file and compiling that to a static binary.

  1. init-repro-template produces a C program that can be compiled and run:
init-repro-template exfilstate-results/4108d034/04_04/073_097_1_2_00315_00020.yaml

Creates a file exfilstate-results/4108d034/04_04/073_097_1_2_00315_00020.c.

  1. build-repro compiles such C programs into static binaries:
build-repro exfilstate-results/4108d034/04_04/073_097_1_2_00315_00020.c

This produces a static binary exfilstate-results,4108d034,04_04,073_097_1_2_00315_00020.

  1. build-repro-run-on allows for quickly building and running on a machine using ssh:
build-repro-run-on exfilstate-results/4108d034/04_04/073_097_1_2_00315_00020.c -- <ssh/hostname>
  1. init-build-run-on allows for quickly initializing, building and running on a machine using ssh:
init-build-run-on exfilstate-results/4108d034/04_04/073_097_1_2_00315_00020.yaml -- -- <ssh/hostname>

For all these scripts the following flags can be used:

  • --orig-seq: Use the original bytes as sequence and not assemble the recovered assembly instructions.
  • --no-sig: Build the reproducer without signal handling, i.e., plain. This is not that useful for the reproducers generated by exfilstate.

Setup

The framework uses Nix to easily provide the required packages for the various parts. Nix is a declarative package manager, i.e., needed packages can be specified in a file, locked via a lock file and then loaded reproducibly. Each directory contains a flake.nix or shell.nix, specifying the packages and environment. Nix can be installed on a large range of Linux distributions. If your distribution is not supported you can fall back to the Docker container from the artifact repo.

Installation

  1. Please install Nix. We recommend the multi-user installation if root privileges are available.

  2. We further use an experimental Nix feature called Flakes. This feature needs to be enabled by running the following command:

    mkdir -p ~/.config/nix && echo 'experimental-features = nix-command flakes' > ~/.config/nix/nix.conf
  3. Quickly verify that Nix Flakes work by running:

    nix run 'nixpkgs#hello'

    This should print "Hello, world!".

  4. For automatically loading the environment when you navigate to this repository we use direnv. With Nix already installed it can be simply installed by running:

    nix profile add 'nixpkgs#direnv'

    For other package managers refer to this page.

  5. Please also setup your shell to automatically load the environment as specified here.

Loading environment & Verification

Run direnv allow to load the environment. Note that this can take a while because we customize packages. Once the environment is loaded, it is cached, so loading will be instant afterwards. This should expand FRAMEWORK_ROOT and load the needed packages.

To verify the setup, run:

echo $FRAMEWORK_ROOT # expected: /absolute/path/to/the/repository
echo $CC             # expected: aarch64-unknown-linux-gnu-gcc

About

Architectural cache side channel fuzzer from the research paper "ExfilState: Automated Discovery of Timer-Free Cache Side Channels on ARM CPUs".

Resources

License

Stars

Watchers

Forks