diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000000..1a45eee776 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock new file mode 100644 index 0000000000..e2bd515772 --- /dev/null +++ b/fuzz/Cargo.lock @@ -0,0 +1,150 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "packit" +version = "0.1.0" +source = "git+https://github.com/coconut-svsm/packit#fffebdc18a3f559f0a01425b17cf41b1c249fbe0" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "svsm" +version = "0.1.0" +dependencies = [ + "bitflags", + "log", + "packit", +] + +[[package]] +name = "svsm-fuzz" +version = "0.0.0" +dependencies = [ + "libfuzzer-sys", + "svsm", +] + +[[package]] +name = "syn" +version = "2.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "zerocopy" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20707b61725734c595e840fb3704378a0cd2b9c74cc9e6e20724838fc6a1e2f9" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56097d5b91d711293a42be9289403896b68654625021732067eac7a4ca388a1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000000..8c3d168b6b --- /dev/null +++ b/fuzz/Cargo.toml @@ -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 diff --git a/fuzz/acpi-dict.txt b/fuzz/acpi-dict.txt new file mode 100644 index 0000000000..c20a7e0bf4 --- /dev/null +++ b/fuzz/acpi-dict.txt @@ -0,0 +1,4 @@ +"etc/acpi/rsdp" +"etc/acpi/tables" +"APIC" +"\x00\x00\x00\x14" diff --git a/fuzz/fuzz_targets/acpi.rs b/fuzz/fuzz_targets/acpi.rs new file mode 100644 index 0000000000..0bc1aa81eb --- /dev/null +++ b/fuzz/fuzz_targets/acpi.rs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2023 SUSE LLC +// +// Author: Carlos López + +#![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, +} + +impl<'a> FuzzIo<'a> { + /// Create a new [`FuzzIo`] instance. Returns [`None`] if the input is + /// empty. + fn new(data: &'a [u8]) -> Option { + 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 +}); diff --git a/fuzz/fuzz_targets/fw_meta.rs b/fuzz/fuzz_targets/fw_meta.rs new file mode 100644 index 0000000000..80414b3896 --- /dev/null +++ b/fuzz/fuzz_targets/fw_meta.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright (c) 2023 SUSE LLC +// +// Author: Carlos López + +#![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 = unsafe { parse_fw_meta_data(addr) }; + if let Ok(meta) = fw_meta { + let _ = black_box(meta); + } + + Corpus::Keep +}); diff --git a/src/fs/filesystem.rs b/src/fs/filesystem.rs index 616f79764c..097e7924ce 100644 --- a/src/fs/filesystem.rs +++ b/src/fs/filesystem.rs @@ -110,7 +110,7 @@ impl SvsmFs { self.root = Some(root.clone()); } - #[cfg(test)] + #[cfg(any(test, fuzzing))] fn uninitialize(&mut self) { self.root = None; } @@ -134,8 +134,8 @@ pub fn initialize_fs() { } } -#[cfg(test)] -fn uninitialize_fs() { +#[cfg(any(test, fuzzing))] +pub fn uninitialize_fs() { unsafe { FS_ROOT.uninitialize(); } diff --git a/src/mm/address_space.rs b/src/mm/address_space.rs index 1df5d8d7c0..9636c06ed5 100644 --- a/src/mm/address_space.rs +++ b/src/mm/address_space.rs @@ -4,7 +4,7 @@ // // Author: Joerg Roedel -#[cfg(test)] +#[cfg(any(test, fuzzing))] use crate::address::Address; use crate::address::{PhysAddr, VirtAddr}; use crate::utils::immut_after_init::ImmutAfterInitCell; @@ -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); @@ -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 { @@ -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()) } diff --git a/src/mm/alloc.rs b/src/mm/alloc.rs index 98cfc30278..31a1f35aec 100644 --- a/src/mm/alloc.rs +++ b/src/mm/alloc.rs @@ -6,7 +6,7 @@ use crate::address::{Address, PhysAddr, VirtAddr}; use crate::error::SvsmError; -#[cfg(test)] +#[cfg(any(test, fuzzing))] use crate::locking::LockGuard; use crate::locking::SpinLock; use crate::mm::virt_to_phys; @@ -1189,7 +1189,7 @@ impl SvsmAllocator { /// Resets the internal state. This is equivalent to reassigning `self` /// with `Self::new()`. - #[cfg(test)] + #[cfg(any(test, fuzzing))] fn reset(&self) { *self.slab_size_32.lock() = Slab::new(32); *self.slab_size_64.lock() = Slab::new(64); @@ -1283,7 +1283,7 @@ 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))] /// A global lock on global memory. Should only be acquired via /// [`TestRootMem::setup()`]. static TEST_ROOT_MEM_LOCK: SpinLock<()> = SpinLock::new(()); @@ -1291,11 +1291,11 @@ static TEST_ROOT_MEM_LOCK: SpinLock<()> = SpinLock::new(()); #[cfg(test)] pub const DEFAULT_TEST_MEMORY_SIZE: usize = 16usize * 1024 * 1024; -#[cfg(test)] +#[cfg(any(test, fuzzing))] /// A dummy struct to acquire a lock over global memory pub struct TestRootMem<'a>(LockGuard<'a, ()>); -#[cfg(test)] +#[cfg(any(test, fuzzing))] impl TestRootMem<'_> { /// Acquire a lock on global memory and initialize it #[must_use = "memory guard must be held for the whole test"] @@ -1322,7 +1322,7 @@ impl TestRootMem<'_> { } } -#[cfg(test)] +#[cfg(any(test, fuzzing))] impl Drop for TestRootMem<'_> { /// Destroy the global memory before dropping the lock over it fn drop(&mut self) {