-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
3 changed files
with
252 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
// 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::mm::alloc::TestRootMem; | ||
|
||
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 | ||
} | ||
} | ||
|
||
/// A handle for a file that also holds its original name | ||
#[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<TestRootMem> = OnceLock::new(); | ||
|
||
fuzz_target!(|actions: Vec<FsAction>| { | ||
// Initialize memory only once | ||
let _mem = MEM.get_or_init(|| TestRootMem::setup(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(); | ||
}); |