Skip to content

Commit

Permalink
Add initial honggfuzz fuzzing infrastructure
Browse files Browse the repository at this point in the history
This initially includes fuzzers for the AVB and boot image parsers. The
initial input corpus are the same test files we use for the round trip
tests.

Issue: #160

Signed-off-by: Andrew Gunnerson <[email protected]>
  • Loading branch information
chenxiaolong committed Sep 27, 2023
1 parent 11d19de commit 7f5c043
Show file tree
Hide file tree
Showing 22 changed files with 256 additions and 5 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ jobs:
uses: Swatinem/rust-cache@v2

- name: Clippy
run: cargo clippy --release --workspace --features static
run: cargo xtask wswrap -v -- clippy --release --features static

- name: Build
run: cargo build --release --workspace --features static
run: cargo xtask wswrap -v -- build --release --features static

- name: Tests
run: cargo test --release --workspace --features static
run: cargo xtask wswrap -v -- test --release --features static

- name: Archive documentation
uses: actions/upload-artifact@v3
Expand Down
50 changes: 50 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
default-members = ["avbroot"]
members = ["avbroot", "e2e", "xtask"]
members = ["avbroot", "e2e", "fuzz", "xtask"]
resolver = "2"

[workspace.package]
Expand Down
4 changes: 4 additions & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/hfuzz_target/
HONGGFUZZ.REPORT.TXT
*.honggfuzz.cov
*.fuzz
13 changes: 13 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "fuzz"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
avbroot = { path = "../avbroot" }
honggfuzz = "0.5.55"
53 changes: 53 additions & 0 deletions fuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Fuzzing

While avbroot's parsers are all memory-safe, it is still possible for panics or crashes to occur, for example due to excessive memory allocation, integer overflow, or division by zero. Fuzzing helps to identify these issues by randomizing inputs in a way that tries to increase code coverage.

## Running the fuzzers

1. Install the cargo honggfuzz commands.

```bash
cargo install honggfuzz
```

2. Pick a fuzz target to run. A fuzz target is the name of the source file in [`src/bin/`](./src/bin) without the `.rs` extension.

The list of targets can be queried programmatically with:

```bash
cargo read-manifest | jq -r '.targets[].name'
```

3. Run the fuzzer.

```bash
cargo hfuzz run <fuzz target>
```

This will run forever until it is manually killed. At the top of the screen, a summary section like the following is shown:

```
Iterations : 31,243 [31.24k]
Mode [1/3] : Feedback Driven Dry Run [2486/4085]
Target : hfuzz_target/x86_64-unknown-linux-gnu/release/bootimage
Threads : 8, CPUs: 16, CPU%: 800% [50%/CPU]
Speed : 36,126/sec [avg: 31,243]
Crashes : 53 [unique: 1, blocklist: 0, verified: 0]
Timeouts : 0 [1 sec]
Corpus Size : 1,424, max: 24,576 bytes, init: 4,085 files
Cov Update : 0 days 00 hrs 00 mins 00 secs ago
Coverage : edge: 897/224,621 [0%] pc: 2 cmp: 34,736
```

When a crash occurs, the `Crashes` counter will increment and the input data that triggered the crash will be written to `hfuzz_workspace/<fuzz target>/*.fuzz`. New files are only written for unique crashes.

4. If a crash occurs, run the following command to trigger the crash in a debugger.

```bash
cargo hfuzz run-debug <fuzz target> \
hfuzz_workspace/<fuzz_target>/<input file>.fuzz
```

This defaults to using `rust-lldb`. To use `rust-gdb` instead, set the `HFUZZ_DEBUGGER` environment variable to `rust-gdb`.

Alternatively, just feed the input file to the appropriate avbroot command directly (eg. `avbroot boot info -i hfuzz_workspace/<fuzz_target>/<input file>.fuzz` for boot images).
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/avb/input/vbmeta_appended_hash.img
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/avb/input/vbmeta_root.img
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/bootimage/input/boot_v0.img
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/bootimage/input/boot_v1.img
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/bootimage/input/boot_v2.img
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/bootimage/input/boot_v3.img
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/bootimage/input/boot_v4.img
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/bootimage/input/boot_v4_vts.img
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/bootimage/input/vendor_v3.img
1 change: 1 addition & 0 deletions fuzz/hfuzz_workspace/bootimage/input/vendor_v4.img
13 changes: 13 additions & 0 deletions fuzz/src/bin/avb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use std::io::Cursor;

use avbroot::format::avb;
use honggfuzz::fuzz;

fn main() {
loop {
fuzz!(|data: &[u8]| {
let reader = Cursor::new(data);
let _ = avb::load_image(reader);
});
}
}
13 changes: 13 additions & 0 deletions fuzz/src/bin/bootimage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use std::io::Cursor;

use avbroot::{format::bootimage::BootImage, stream::FromReader};
use honggfuzz::fuzz;

fn main() {
loop {
fuzz!(|data: &[u8]| {
let reader = Cursor::new(data);
let _ = BootImage::from_reader(reader);
});
}
}
11 changes: 11 additions & 0 deletions xtask/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,14 @@ cargo xtask modules -a
```

See the main [`README.md`](../README.md#avbroot-magisk-modules) for more details.

## Platform specific `--workspace`-like wrapper

Cargo currently doesn't support conditionally including workspace members on certain platforms (https://github.com/rust-lang/cargo/issues/5220 and https://github.com/rust-lang/cargo/issues/6179). Since honggfuzz does not support Windows, building with `--workspace` will fail on Windows.

The `wswrap` wrapper works around this by running Cargo subcommands with `-p <member>` for each member supported by the platform.

```bash
cargo xtask wswrap -- build --release
# Replaces: cargo build --release --workspace
```
5 changes: 4 additions & 1 deletion xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
mod changelog;
mod module;
mod version;
mod wswrap;

use anyhow::Result;
use clap::{Parser, Subcommand};

use crate::{module::ModulesCli, version::SetVersionCli};
use crate::{module::ModulesCli, version::SetVersionCli, wswrap::WswrapCli};

const WORKSPACE_DIR: &str = env!("CARGO_WORKSPACE_DIR");

Expand All @@ -21,6 +22,7 @@ fn main() -> Result<()> {
Command::SetVersion(c) => version::set_version_subcommand(&c),
Command::Modules(c) => module::modules_subcommand(&c),
Command::UpdateChangelog => changelog::update_changelog_subcommand(),
Command::Wswrap(c) => wswrap::wswrap_subcommand(&c),
}
}

Expand All @@ -30,6 +32,7 @@ pub enum Command {
Modules(ModulesCli),
/// Update links in CHANGELOG.md.
UpdateChangelog,
Wswrap(WswrapCli),
}

#[derive(Debug, Parser)]
Expand Down
80 changes: 80 additions & 0 deletions xtask/src/wswrap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: 2023 Andrew Gunnerson
* SPDX-License-Identifier: GPL-3.0-only
*/

use std::{env, fs, path::Path, process::Command};

use anyhow::{anyhow, bail, Result};
use clap::Parser;
use toml_edit::Document;

use crate::WORKSPACE_DIR;

fn get_members() -> Result<Vec<String>> {
let path = Path::new(WORKSPACE_DIR).join("Cargo.toml");
let data = fs::read_to_string(&path)?;

let document: Document = data.parse()?;
let members = document["workspace"]["members"]
.as_array()
.ok_or_else(|| anyhow!("Cargo.toml has no workspace members"))?;

let mut result = vec![];

for member in members {
let member = member
.as_str()
.ok_or_else(|| anyhow!("Member is not a string: {member:?}"))?;

result.push(member.to_owned());
}

Ok(result)
}

pub fn wswrap_subcommand(cli: &WswrapCli) -> Result<()> {
let cargo = env::var_os("CARGO").ok_or_else(|| anyhow!("CARGO is not set"))?;
let members = get_members()?;

let mut command = Command::new(cargo);
command.args(&cli.args);

for member in members {
#[cfg(windows)]
if member == "fuzz" {
continue;
}

command.arg("-p");
command.arg(member);
}

if cli.verbose {
eprintln!("Running: {command:?}");
}

let mut child = command.spawn()?;
let status = child.wait()?;

if !status.success() {
bail!("{command:?} failed with {status}");
}

Ok(())
}

/// Run a Cargo command against all workspace members compatible with the target
/// platform.
///
/// This is meant to replace the use of `cargo <subcommand> --workspace`. This
/// currently only filters out the `fuzz` member on Windows.
#[derive(Debug, Parser)]
pub struct WswrapCli {
/// Display command before running.
#[arg(short, long)]
verbose: bool,

/// Full Cargo command.
args: Vec<String>,
}

0 comments on commit 7f5c043

Please sign in to comment.