Skip to content

Commit

Permalink
fuzzing: add filesystem fuzzer
Browse files Browse the repository at this point in the history
Add a new harness to fuzz the filesystem implementation by issuing
random operations such as creating, opening and closing files and
reading and writing from them.

Signed-off-by: Carlos López <[email protected]>
  • Loading branch information
00xc committed Oct 11, 2023
1 parent 5e89918 commit 1d592ab
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 0 deletions.
15 changes: 15 additions & 0 deletions fuzz/Cargo.lock

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

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

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

[dependencies.svsm]
Expand All @@ -31,3 +32,9 @@ name = "acpi"
path = "fuzz_targets/acpi.rs"
test = false
doc = false

[[bin]]
name = "fs"
path = "fuzz_targets/fs.rs"
test = false
doc = false
229 changes: 229 additions & 0 deletions fuzz/fuzz_targets/fs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2023 SUSE LLC
//
// Author: Carlos López <[email protected]>

#![no_main]

use arbitrary::Arbitrary;
use core::hint::black_box;
use libfuzzer_sys::fuzz_target;
use std::rc::Rc;
use std::sync::OnceLock;
use svsm::fs::FileHandle;
use svsm::fs::{create, create_all, initialize_fs, list_dir, mkdir, open, uninitialize_fs, unlink};
use svsm::locking::LockGuard;
use svsm::mm::alloc::setup_test_root_mem;

const ROOT_MEM_SIZE: usize = 0x10000;
const MAX_READ_SIZE: usize = 4096 * 8;
const MAX_WRITE_SIZE: usize = 4096 * 8;
const WRITE_BYTE: u8 = 0x0;
const POISON_BYTE: u8 = 0xaf;

#[derive(Arbitrary, Debug)]
enum FsAction<'a> {
Create(&'a str),
CreateNamed(usize),
CreateAll(&'a str),
CreateAllNamed(usize),
Open(&'a str),
OpenNamed(usize),
Close(usize),
Unlink(&'a str),
UnlinkNamed(usize),
Mkdir(&'a str),
MkdirNamed(usize),
ListDir(&'a str),
ListDirNamed(usize),
Read(usize, usize),
Write(usize, usize),
Seek(usize, usize),
Truncate(usize, usize),
}

fn get_idx<T>(v: &[T], idx: usize) -> Option<usize> {
if !v.is_empty() {
Some(idx % v.len())
} else {
None
}
}

#[derive(Debug)]
struct Handle {
fd: FileHandle,
name: Rc<str>,
}

impl Handle {
fn new(fd: FileHandle, name: Rc<str>) -> Self {
Self { fd, name }
}
}

static MEM: OnceLock<LockGuard<()>> = OnceLock::new();

fuzz_target!(|actions: Vec<FsAction>| {
let _mem = MEM.get_or_init(|| setup_test_root_mem(ROOT_MEM_SIZE));

let mut files = Vec::<Handle>::new();
let mut aux_buf = vec![POISON_BYTE; MAX_READ_SIZE.max(MAX_WRITE_SIZE)];

initialize_fs();

for action in actions.into_iter() {
match action {
FsAction::Create(name) => {
if let Ok(fh) = create(name) {
files.push(Handle::new(fh, Rc::from(name)));
}
}
FsAction::CreateNamed(idx) => {
let Some(idx) = get_idx(&files, idx) else {
continue;
};
let file = &files[idx];
if let Ok(fh) = create(&file.name) {
files.push(Handle::new(fh, file.name.clone()));
}
}
FsAction::CreateAll(name) => {
if let Ok(fh) = create_all(name) {
files.push(Handle::new(fh, Rc::from(name)));
}
}
FsAction::CreateAllNamed(idx) => {
let Some(idx) = get_idx(&files, idx) else {
continue;
};
let file = &files[idx];
if let Ok(fh) = create_all(&file.name) {
files.push(Handle::new(fh, file.name.clone()));
}
}
FsAction::Open(name) => {
if let Ok(fh) = open(name) {
files.push(Handle::new(fh, Rc::from(name)));
}
}
FsAction::OpenNamed(idx) => {
let Some(idx) = get_idx(&files, idx) else {
continue;
};
let file = &files[idx];
if let Ok(fh) = open(&file.name) {
files.push(Handle::new(fh, file.name.clone()));
}
}
FsAction::Close(idx) => {
if let Some(idx) = get_idx(&files, idx) {
let _ = files.swap_remove(idx);
}
}
FsAction::Unlink(name) => {
let _ = black_box(unlink(name));
}
FsAction::UnlinkNamed(idx) => {
let Some(idx) = get_idx(&files, idx) else {
continue;
};
let file = &files[idx];
let _ = black_box(unlink(&file.name));
}
FsAction::Mkdir(name) => {
let _ = black_box(mkdir(name));
}
FsAction::MkdirNamed(idx) => {
let Some(idx) = get_idx(&files, idx) else {
continue;
};
let name = &files[idx].name;
let _ = black_box(mkdir(name));
}
FsAction::ListDir(name) => {
let _ = black_box(list_dir(name));
}
FsAction::ListDirNamed(idx) => {
let Some(idx) = get_idx(&files, idx) else {
continue;
};
let name = &files[idx].name;
let _ = black_box(list_dir(name));
}
FsAction::Read(idx, len) => {
let Some(idx) = get_idx(&files, idx) else {
continue;
};

// Prepare the destination buffer
let len = len % MAX_READ_SIZE;
let buf = &mut aux_buf[..len];

// Read some bytes
let Ok(num) = files[idx].fd.read(buf) else {
// No partial reads allowed if we got an error
assert!(aux_buf.iter().all(|c| *c == POISON_BYTE));
continue;
};

// Sanity check the bytes and reset the buffer
assert!(num <= len);
let (read, rest) = aux_buf.split_at_mut(num);
assert!(read.iter().all(|c| *c == WRITE_BYTE));
assert!(rest.iter().all(|c| *c == POISON_BYTE));
read.fill(POISON_BYTE);
}
FsAction::Write(idx, len) => {
let Some(idx) = get_idx(&files, idx) else {
continue;
};

// Save the current position
let file = &files[idx];
let start_pos = file.fd.position();

// Prepare the source buffer
let len = len % MAX_WRITE_SIZE;
let (buf, rest) = aux_buf.split_at_mut(len);
buf.fill(WRITE_BYTE);

let Ok(num) = file.fd.write(buf) else {
assert!(buf.iter().all(|c| *c == WRITE_BYTE));
assert!(rest.iter().all(|c| *c == POISON_BYTE));
buf.fill(POISON_BYTE);
continue;
};

// Reset the buffer and the file position
buf.fill(POISON_BYTE);
file.fd.seek(start_pos);

// Read back the bytes and sanity check them
assert!(num <= len);
let (read, rest) = aux_buf.split_at_mut(num);
let nread = file.fd.read(read).unwrap();
assert_eq!(num, nread);
assert!(read.iter().all(|c| *c == WRITE_BYTE));
assert!(rest.iter().all(|c| *c == POISON_BYTE));

// Reset the buffer
read.fill(POISON_BYTE);
}
FsAction::Seek(idx, pos) => {
if let Some(idx) = get_idx(&files, idx) {
let file = &files[idx];
file.fd.seek(pos);
}
}
FsAction::Truncate(idx, off) => {
if let Some(idx) = get_idx(&files, idx) {
let _ = black_box(files[idx].fd.truncate(off));
}
}
}
}

uninitialize_fs();
});

0 comments on commit 1d592ab

Please sign in to comment.