diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 60eed5f29..642209cc3 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -7,7 +7,7 @@ name = "acpi_tables" version = "0.1.0" source = "git+https://github.com/rust-vmm/acpi_tables?branch=main#849d5950196f66dd10f2b2606d8fe8c7cb39ec24" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -67,9 +67,9 @@ checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "arc-swap" @@ -195,13 +195,16 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" name = "cloud-hypervisor-fuzz" version = "0.0.0" dependencies = [ + "arbitrary", "block", "devices", "epoll", + "hypervisor", "libc", "libfuzzer-sys", "linux-loader", "micro_http", + "mshv-bindings", "net_util", "once_cell", "seccompiler", @@ -441,10 +444,12 @@ dependencies = [ "byteorder", "cfg-if", "concat-idents", + "iced-x86", "kvm-bindings", "kvm-ioctls", "libc", "log", + "mshv-bindings", "serde", "serde_with", "thiserror", @@ -453,6 +458,15 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "iced-x86" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b" +dependencies = [ + "lazy_static", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -507,7 +521,7 @@ checksum = "fa4933174d0cc4b77b958578cd45784071cc5ae212c2d78fbd755aaaa6dfa71a" dependencies = [ "serde", "vmm-sys-util", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -533,6 +547,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.169" @@ -596,6 +616,20 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "mshv-bindings" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0cb5031f3243a7459b7c13d960d25420980874eebda816db24ce6077e21d43" +dependencies = [ + "libc", + "num_enum", + "serde", + "serde_derive", + "vmm-sys-util", + "zerocopy 0.8.14", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -704,7 +738,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -1228,7 +1262,7 @@ dependencies = [ "vm-migration", "vm-virtio", "vmm-sys-util", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -1415,7 +1449,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +dependencies = [ + "zerocopy-derive 0.8.14", ] [[package]] @@ -1428,3 +1471,14 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zerocopy-derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 947035fac..09aae4c92 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,16 +10,20 @@ cargo-fuzz = true [features] igvm = [] +mshv_emulator = ["hypervisor/mshv_emulator"] pvmemcontrol = [] [dependencies] +arbitrary = "1.4.1" block = { path = "../block" } devices = { path = "../devices" } epoll = "4.3.3" +hypervisor = { path = "../hypervisor", features = ["mshv_emulator"] } libc = "0.2.155" libfuzzer-sys = "0.4.7" linux-loader = { version = "0.13.0", features = ["bzimage", "elf", "pe"] } micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } +mshv-bindings = "0.3.2" net_util = { path = "../net_util" } once_cell = "1.19.0" seccompiler = "0.4.0" @@ -131,3 +135,10 @@ doc = false name = "watchdog" path = "fuzz_targets/watchdog.rs" test = false + +[[bin]] +doc = false +name = "x86emul" +path = "fuzz_targets/x86emul.rs" +required-features = ["mshv_emulator"] +test = false diff --git a/fuzz/fuzz_targets/x86emul.rs b/fuzz/fuzz_targets/x86emul.rs new file mode 100644 index 000000000..2b70e1e36 --- /dev/null +++ b/fuzz/fuzz_targets/x86emul.rs @@ -0,0 +1,153 @@ +// Copyright © 2025 Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#![no_main] + +use hypervisor::arch::emulator::{PlatformEmulator, PlatformError}; +use hypervisor::arch::x86::emulator::{Emulator, EmulatorCpuState}; +use hypervisor::arch::x86::{DescriptorTable, SegmentRegister, SpecialRegisters}; +use hypervisor::StandardRegisters; +use libfuzzer_sys::{fuzz_target, Corpus}; + +#[derive(Debug)] +struct EmulatorContext { + state: EmulatorCpuState, + memory: [u8; 8], +} + +impl PlatformEmulator for EmulatorContext { + type CpuState = EmulatorCpuState; + + fn read_memory(&self, _gva: u64, data: &mut [u8]) -> Result<(), PlatformError> { + data.copy_from_slice(&self.memory[..data.len()]); + Ok(()) + } + + fn write_memory(&mut self, _gva: u64, _data: &[u8]) -> Result<(), PlatformError> { + // Discard writes + Ok(()) + } + + fn cpu_state(&self, _cpu_id: usize) -> Result { + Ok(self.state.clone()) + } + + fn set_cpu_state(&self, _cpu_id: usize, _state: Self::CpuState) -> Result<(), PlatformError> { + // Ignore + Ok(()) + } + + fn fetch(&self, _ip: u64, _data: &mut [u8]) -> Result<(), PlatformError> { + // The fuzzer already provides 16 bytes of data, we don't need to fetch anything + panic!("fetch should not be called"); + } +} + +fuzz_target!(|bytes: &[u8]| -> Corpus { + let (mut ctx, insn) = match generate_context_and_instruction(bytes) { + Ok((ctx, insn)) => (ctx, insn), + Err(_) => return Corpus::Reject, + }; + + let mut e = Emulator::new(&mut ctx); + + if e.emulate_first_insn(0, &insn).is_err() { + return Corpus::Reject; + } + + Corpus::Keep +}); + +// Helper functions to generate structures from fuzzer input below + +fn generate_segment_register( + u: &mut arbitrary::Unstructured<'_>, +) -> arbitrary::Result { + Ok(SegmentRegister { + base: u.arbitrary()?, + limit: u.arbitrary()?, + selector: u.arbitrary()?, + avl: u.arbitrary()?, + dpl: u.arbitrary()?, + db: u.arbitrary()?, + g: u.arbitrary()?, + l: u.arbitrary()?, + present: u.arbitrary()?, + s: u.arbitrary()?, + type_: u.arbitrary()?, + unusable: u.arbitrary()?, + }) +} + +fn generate_descriptor_table( + u: &mut arbitrary::Unstructured<'_>, +) -> arbitrary::Result { + Ok(DescriptorTable { + base: u.arbitrary()?, + limit: u.arbitrary()?, + }) +} + +fn generate_context_and_instruction( + bytes: &[u8], +) -> arbitrary::Result<(EmulatorContext, [u8; 16])> { + let mut u = arbitrary::Unstructured::new(bytes); + + let mut regs = mshv_bindings::StandardRegisters { + rax: u.arbitrary()?, + rbx: u.arbitrary()?, + rcx: u.arbitrary()?, + rdx: u.arbitrary()?, + rsi: u.arbitrary()?, + rdi: u.arbitrary()?, + rsp: u.arbitrary()?, + rbp: u.arbitrary()?, + r8: u.arbitrary()?, + r9: u.arbitrary()?, + r10: u.arbitrary()?, + r11: u.arbitrary()?, + r12: u.arbitrary()?, + r13: u.arbitrary()?, + r14: u.arbitrary()?, + r15: u.arbitrary()?, + rip: u.arbitrary()?, + rflags: u.arbitrary()?, + }; + + // Cap RCX to avoid looping for too long for reps instructions. + regs.rcx &= 0xFFFFu64; + + let regs = StandardRegisters::Mshv(regs); + + let sregs = SpecialRegisters { + cs: generate_segment_register(&mut u)?, + ds: generate_segment_register(&mut u)?, + es: generate_segment_register(&mut u)?, + fs: generate_segment_register(&mut u)?, + gs: generate_segment_register(&mut u)?, + ss: generate_segment_register(&mut u)?, + tr: generate_segment_register(&mut u)?, + ldt: generate_segment_register(&mut u)?, + gdt: generate_descriptor_table(&mut u)?, + idt: generate_descriptor_table(&mut u)?, + cr0: u.arbitrary()?, + cr2: u.arbitrary()?, + cr3: u.arbitrary()?, + cr4: u.arbitrary()?, + cr8: u.arbitrary()?, + efer: u.arbitrary()?, + apic_base: u.arbitrary()?, + interrupt_bitmap: u.arbitrary()?, + }; + + let memory = u.arbitrary::<[u8; 8]>()?; + let insn = u.arbitrary::<[u8; 16]>()?; + + let ctx = EmulatorContext { + state: EmulatorCpuState { regs, sregs }, + memory, + }; + + Ok((ctx, insn)) +}