Skip to content

Commit

Permalink
fuzzing: initial fuzzing implementation
Browse files Browse the repository at this point in the history
Add fuzzing to the COCONUT SVSM project via cargo-fuzz. This commit
adds the base infrastructure for fuzzing, as well as two harnesses
for testing the fw_meta and ACPI table interfaces respectively.

The fuzzing harnesses, much like regular tests, are built as
userspace binaries, which means we need to enable some conditional
compilation, namely disabling the SVSM allocator in favor of the
standard Rust allocator, as well as enabling certain helper test
functions.

This works towards issue coconut-svsm#34.

Signed-off-by: Carlos López <[email protected]>
  • Loading branch information
00xc committed Oct 3, 2023
1 parent 7e356a9 commit 8b70f44
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 14 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
150 changes: 150 additions & 0 deletions fuzz/Cargo.lock

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

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

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"

[dependencies.svsm]
path = ".."

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[profile.release]
debug = 1

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

[[bin]]
name = "acpi"
path = "fuzz_targets/acpi.rs"
test = false
doc = false
4 changes: 4 additions & 0 deletions fuzz/acpi-dict.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"etc/acpi/rsdp"
"etc/acpi/tables"
"APIC"
"\x00\x00\x00\x14"
65 changes: 65 additions & 0 deletions fuzz/fuzz_targets/acpi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2023 SUSE LLC
//
// Author: Carlos López <[email protected]>

#![no_main]

use core::num::NonZeroUsize;
use libfuzzer_sys::{fuzz_target, Corpus};
use std::cell::Cell;
use std::hint::black_box;
use svsm::acpi::tables::load_acpi_cpu_info;
use svsm::fw_cfg::FwCfg;
use svsm::io::IOPort;

/// A structure that emulates port I/O from a libfuzzer input.
#[derive(Clone, Debug)]
struct FuzzIo<'a> {
data: &'a [u8],
len: NonZeroUsize,
pos: Cell<usize>,
}

impl<'a> FuzzIo<'a> {
/// Create a new [`FuzzIo`] instance. Returns [`None`] if the input is
/// empty.
fn new(data: &'a [u8]) -> Option<Self> {
let len = NonZeroUsize::new(data.len())?;
let pos = Cell::new(0);
Some(Self { data, len, pos })
}
}

impl IOPort for FuzzIo<'_> {
fn outb(&self, _port: u16, _value: u8) {}
fn outw(&self, _port: u16, _value: u16) {}

fn inb(&self, _port: u16) -> u8 {
let pos = self.pos.get();
let val = unsafe { *self.data.get_unchecked(pos) };
self.pos.set((pos + 1) % self.len);
val
}

fn inw(&self, port: u16) -> u16 {
let mut buf = [0u8; 2];
buf[0] = self.inb(port);
buf[1] = self.inb(port);
u16::from_le_bytes(buf)
}
}

fuzz_target!(|data: &[u8]| -> Corpus {
let Some(io) = FuzzIo::new(data) else {
return Corpus::Reject;
};
let fwcfg = FwCfg::new(&io);

if let Ok(info) = load_acpi_cpu_info(&fwcfg) {
let _ = black_box(info);
}

Corpus::Keep
});
27 changes: 27 additions & 0 deletions fuzz/fuzz_targets/fw_meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2023 SUSE LLC
//
// Author: Carlos López <[email protected]>

#![no_main]

use libfuzzer_sys::{fuzz_target, Corpus};
use std::hint::black_box;
use svsm::address::VirtAddr;
use svsm::fw_meta::parse_fw_meta_data;
use svsm::types::PAGE_SIZE;

fuzz_target!(|data: &[u8]| -> Corpus {
if data.len() != PAGE_SIZE {
return Corpus::Reject;
}

let addr = VirtAddr::from(data.as_ptr());
let fw_meta = parse_fw_meta_data(addr);
if let Ok(meta) = fw_meta {
let _ = black_box(meta);
}

Corpus::Keep
});
6 changes: 3 additions & 3 deletions src/fs/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl SvsmFs {
self.root = Some(root.clone());
}

#[cfg(test)]
#[cfg(any(test, fuzzing))]
fn uninitialize(&mut self) {
self.root = None;
}
Expand All @@ -138,8 +138,8 @@ pub fn initialize_fs() {
}
}

#[cfg(test)]
fn uninitialize_fs() {
#[cfg(any(test, fuzzing))]
pub fn uninitialize_fs() {
unsafe {
FS_ROOT.uninitialize();
}
Expand Down
10 changes: 5 additions & 5 deletions src/mm/address_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// Author: Joerg Roedel <[email protected]>

#[cfg(test)]
#[cfg(any(test, fuzzing))]
use crate::address::Address;
use crate::address::{PhysAddr, VirtAddr};
use crate::utils::immut_after_init::ImmutAfterInitCell;
Expand All @@ -30,7 +30,7 @@ pub fn init_kernel_mapping_info(vstart: VirtAddr, vend: VirtAddr, pstart: PhysAd
.expect("Already initialized kernel mapping info");
}

#[cfg(not(test))]
#[cfg(not(any(test, fuzzing)))]
pub fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
if vaddr < KERNEL_MAPPING.virt_start || vaddr >= KERNEL_MAPPING.virt_end {
panic!("Invalid physical address {:#018x}", vaddr);
Expand All @@ -41,7 +41,7 @@ pub fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
KERNEL_MAPPING.phys_start + offset
}

#[cfg(not(test))]
#[cfg(not(any(test, fuzzing)))]
pub fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
let size: usize = KERNEL_MAPPING.virt_end - KERNEL_MAPPING.virt_start;
if paddr < KERNEL_MAPPING.phys_start || paddr >= KERNEL_MAPPING.phys_start + size {
Expand All @@ -53,12 +53,12 @@ pub fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
KERNEL_MAPPING.virt_start + offset
}

#[cfg(test)]
#[cfg(any(test, fuzzing))]
pub fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
PhysAddr::from(vaddr.bits())
}

#[cfg(test)]
#[cfg(any(test, fuzzing))]
pub fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
VirtAddr::from(paddr.bits())
}
Expand Down
12 changes: 6 additions & 6 deletions src/mm/alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,8 +1266,8 @@ unsafe impl GlobalAlloc for SvsmAllocator {
}
}

#[cfg_attr(not(any(test, doctest)), global_allocator)]
#[cfg_attr(any(test, doctest), allow(dead_code))]
#[cfg_attr(not(any(test, doctest, fuzzing)), global_allocator)]
#[cfg_attr(any(test, doctest, fuzzing), allow(dead_code))]
static mut ALLOCATOR: SvsmAllocator = SvsmAllocator::new();

pub fn root_mem_init(pstart: PhysAddr, vstart: VirtAddr, page_count: usize) {
Expand All @@ -1286,13 +1286,13 @@ pub fn root_mem_init(pstart: PhysAddr, vstart: VirtAddr, page_count: usize) {
.expect("Failed to initialize SLAB_PAGE_SLAB");
}

#[cfg(test)]
#[cfg(any(test, fuzzing))]
static TEST_ROOT_MEM_LOCK: SpinLock<()> = SpinLock::new(());

#[cfg(test)]
#[cfg(any(test, fuzzing))]
use crate::locking::LockGuard;

#[cfg(test)]
#[cfg(any(test, fuzzing))]
// Allocate a memory region from the standard Rust allocator and pass it to
// root_mem_init().
pub fn setup_test_root_mem(size: usize) -> LockGuard<'static, ()> {
Expand All @@ -1317,7 +1317,7 @@ pub fn setup_test_root_mem(size: usize) -> LockGuard<'static, ()> {
lock
}

#[cfg(test)]
#[cfg(any(test, fuzzing))]
// Undo the setup done from setup_test_root_mem().
pub fn destroy_test_root_mem(lock: LockGuard<'static, ()>) {
extern crate alloc;
Expand Down

0 comments on commit 8b70f44

Please sign in to comment.