Skip to content

Commit

Permalink
fuzz test archive round trip
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasf committed Mar 25, 2024
1 parent 03d664b commit f74eaf8
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 0 deletions.
4 changes: 4 additions & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
113 changes: 113 additions & 0 deletions fuzz/Cargo.lock

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

19 changes: 19 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "ar-fuzz"
publish = false
edition = "2021"

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"
ar = { path = ".." }
arbitrary = { version = "1.3.2", features = ["derive"] }

[workspace]
members = ["."] # Let fuzz suite not interfere with workspaces

[[bin]]
name = "roundtrip"
path = "fuzz_targets/roundtrip.rs"
127 changes: 127 additions & 0 deletions fuzz/fuzz_targets/roundtrip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#![no_main]

use arbitrary::{Arbitrary, Unstructured};
use libfuzzer_sys::fuzz_target;
use std::io::Read as _;

#[derive(Clone, Default, Debug, Eq, PartialEq, Arbitrary)]
struct Model {
entries: Vec<Entry>,
}

#[derive(Clone, Debug, Eq, PartialEq, Arbitrary)]
struct Entry {
header: Header,
data: Vec<u8>,
}

#[derive(Clone, Debug, Eq, PartialEq, Arbitrary)]
struct Header {
identifier: Identifier,
mtime: Timestamp,
uid: Uid,
gid: Uid,
mode: Mode,
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct Identifier(Vec<u8>);

impl Arbitrary<'_> for Identifier {
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
// TODO: Reject invalid identifiers at creation time:
// - Long identifiers
// - Invalid ASCII or invalid UTF-8?
// - Containing NUL
// - Rules for '/'?
// - Rules for spaces?
String::arbitrary(u).map(|mut v| {
v.retain(|ch| ch != '\0' && ch != '/' && ch != ' ');
while v.len() > 16 {
v.pop();
}
Identifier(v.into_bytes())
})
}
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct Mode(u32);

impl Arbitrary<'_> for Mode {
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
u32::arbitrary(u).map(|v| Mode(v & 0o7777))
}
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct Timestamp(u64);

impl Arbitrary<'_> for Timestamp {
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
u.int_in_range(0..=999_999_999_999).map(Timestamp) // TODO: Deal with entire range
}
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct Uid(u32);

impl Arbitrary<'_> for Uid {
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
u.int_in_range(0..=999_999).map(Uid) // TODO: Deal with entire range (#29)
}
}

fuzz_target!(|data: &[u8]| {
let mut u = Unstructured::new(data);
let model = Model::arbitrary(&mut u).expect("make arbitrary model");

if model.entries.is_empty() {
return; // Builder does not do anything unless there is at least one entry
}

let mut buffer = Vec::new();

let mut builder = ar::Builder::new(&mut buffer);
for entry in &model.entries {
let mut header = ar::Header::new(
entry.header.identifier.0.clone(),
entry.data.len() as u64,
);
header.set_mtime(entry.header.mtime.0);
header.set_uid(entry.header.uid.0);
header.set_gid(entry.header.gid.0);
header.set_mode(entry.header.mode.0);
if let Err(err) = builder.append(&header, &mut &entry.data[..]) {
panic!("append entry: {err} with {model:?}"); // Or just return if invalid input
}
}

let mut rountripped = Model::default();
let mut reader = &buffer[..];
let mut archive = ar::Archive::new(&mut reader);
while let Some(entry) = archive.next_entry() {
let mut entry = match entry {
Ok(entry) => entry,
Err(err) => panic!("read entry: {err} with {model:?}"),
};
rountripped.entries.push(Entry {
header: Header {
identifier: Identifier(entry.header().identifier().to_vec()),
mtime: Timestamp(entry.header().mtime()),
uid: Uid(entry.header().uid()),
gid: Uid(entry.header().gid()),
mode: Mode(entry.header().mode()),
},
data: {
let mut data = Vec::new();
if let Err(err) = entry.read_to_end(&mut data) {
panic!("read entry data: {err} with {model:?}");
}
data
},
})
}

assert_eq!(model, rountripped);
});

0 comments on commit f74eaf8

Please sign in to comment.