Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add fuzzer target #366

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions fuzz/Cargo.lock

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

9 changes: 8 additions & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2021"
cargo-fuzz = true

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

[dependencies.backhand]
path = "../backhand"
Expand Down Expand Up @@ -38,5 +38,12 @@ path = "fuzz_targets/raw.rs"
test = false
doc = false

[[bin]]
name = "write_read_write"
path = "fuzz_targets/write_read_write.rs"
test = false
doc = false


[features]
xz-static = ["backhand/xz-static"]
159 changes: 159 additions & 0 deletions fuzz/fuzz_targets/write_read_write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#![no_main]

use std::io::Cursor;
use std::path::Path;

use backhand::{FilesystemReader, FilesystemWriter, NodeHeader};

use libfuzzer_sys::arbitrary::{self, Arbitrary, Result, Unstructured};
use libfuzzer_sys::fuzz_target;

#[derive(Debug, Default)]
struct Header(NodeHeader);

impl Arbitrary<'_> for Header {
fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
Ok(Self(NodeHeader {
permissions: u.arbitrary()?,
uid: u.arbitrary()?,
gid: u.arbitrary()?,
mtime: u.arbitrary()?,
}))
}

#[inline]
fn size_hint(_depth: usize) -> (usize, Option<usize>) {
(14, Some(14))
}
}

fn consume_path<'a>(u: &mut Unstructured<'a>, size: usize) -> &'a Path {
// limit Paths to 255
let bytes = u.bytes(size.min(255)).unwrap();
use std::os::unix::ffi::*;
let os_str = std::ffi::OsStr::from_bytes(bytes);
Path::new(os_str)
}

// NOTE don't use the PathBuf implementation of Arbitary, because it rely on the
// &str implementation. This is a problem because it don't have a size limit
// and we want to also have paths made of non-utf8 bytes
#[derive(Debug)]
struct MyPath<'a>(&'a Path);
impl<'a> Arbitrary<'a> for MyPath<'a> {
#[cfg(unix)]
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
let size = u.arbitrary_len::<u8>()?;
Ok(MyPath(consume_path(u, size)))
}

fn arbitrary_take_rest(mut u: Unstructured<'a>) -> Result<Self> {
let size = u.len();
Ok(MyPath(consume_path(&mut u, size)))
}

#[inline]
fn size_hint(_depth: usize) -> (usize, Option<usize>) {
(0, None)
}
}

#[derive(Debug)]
struct MyData<'a>(&'a [u8]);
impl<'a> Arbitrary<'a> for MyData<'a> {
#[cfg(unix)]
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
// limit the file size to 10, for speed...
let size = u.arbitrary_len::<u8>()?.min(10);
Ok(MyData(u.bytes(size)?))
}

fn arbitrary_take_rest(mut u: Unstructured<'a>) -> Result<Self> {
let size = u.len();
Ok(MyData(u.bytes(size)?))
}

#[inline]
fn size_hint(_depth: usize) -> (usize, Option<usize>) {
(0, None)
}
}

#[derive(Debug, Arbitrary)]
enum Node<'a> {
File { path: MyPath<'a>, header: Header, data: MyData<'a> },
Dir { path: MyPath<'a>, header: Header },
Symlink { src: MyPath<'a>, header: Header, dst: MyPath<'a> },
CharDev { file: MyPath<'a>, header: Header, device_num: u32 },
BlockDev { file: MyPath<'a>, header: Header, device_num: u32 },
}

impl<'a> Node<'a> {
fn path(&self) -> &'a Path {
match self {
Node::File { path, .. }
| Node::Dir { path, .. }
| Node::Symlink { src: path, .. }
| Node::CharDev { file: path, .. }
| Node::BlockDev { file: path, .. } => path.0,
}
}
}

#[derive(Debug, Arbitrary)]
struct Squashfs<'a> {
time: u32,
nodes: Vec<Node<'a>>,
}

impl<'a> Squashfs<'a> {
fn into_writer(&'a self) -> FilesystemWriter<'static, 'static, 'a> {
let mut fs = FilesystemWriter::default();
// NOTE no compression to make it fast
fs.set_compressor(
backhand::FilesystemCompressor::new(backhand::compression::Compressor::None, None)
.unwrap(),
);
fs.set_time(self.time);

for node in self.nodes.iter() {
if let Some(parent) = node.path().parent() {
let _ = fs.push_dir_all(parent, NodeHeader::default());
}
// ignore errors from push_* functions
let _ = match &node {
Node::File { path, header, data } => {
fs.push_file(Cursor::new(data.0), path.0, header.0)
}
Node::Dir { path, header } => fs.push_dir(path.0, header.0),
Node::Symlink { src, header, dst } => fs.push_symlink(dst.0, src.0, header.0),
Node::CharDev { file, header, device_num } => {
fs.push_char_device(*device_num, file.0, header.0)
}
Node::BlockDev { file, header, device_num } => {
fs.push_block_device(*device_num, file.0, header.0)
}
};
}
fs
}
}

fuzz_target!(|input: Squashfs| {
// step 1: generate a squashfs file from the random input
let mut file_1 = Vec::new();
let _ = input.into_writer().write(Cursor::new(&mut file_1)).unwrap();

// step 2: parse the generated file
// all files create using FilesystemWriter should be valid
let fs_reader = FilesystemReader::from_reader(Cursor::new(&file_1)).unwrap();

// step 3: use the parsed file to generate other file
let mut squashfs_2 = FilesystemWriter::from_fs_reader(&fs_reader).unwrap();
let mut file_2 = Vec::new();
let _ = squashfs_2.write(Cursor::new(&mut file_2)).unwrap();

// step 4: verify parsed data
// both generated files need to be equal
assert_eq!(file_1, file_2);
});