diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 0ae7f85a2..1a427afa8 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -179,8 +179,11 @@ dependencies = [ "block_util", "cloud-hypervisor", "devices", + "epoll", "libc", "libfuzzer-sys", + "micro_http", + "once_cell", "qcow", "seccompiler", "vhdx", @@ -189,6 +192,7 @@ dependencies = [ "vm-device", "vm-memory", "vm-virtio", + "vmm", "vmm-sys-util", ] @@ -444,7 +448,7 @@ dependencies = [ [[package]] name = "micro_http" version = "0.1.0" -source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#9517a300370a158a7af0996b7eebf040d171e1a4" +source = "git+https://github.com/firecracker-microvm/micro-http?branch=main#863b0370ba7e57f7df5b908ada9e5b44809ccae9" dependencies = [ "libc", "vmm-sys-util", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 700df9a5f..76a09ebea 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -11,13 +11,17 @@ cargo-fuzz = true [dependencies] block_util = { path = "../block_util" } devices = { path = "../devices" } +epoll = "4.3.1" libc = "0.2.126" libfuzzer-sys = "0.4.3" +micro_http = { git = "https://github.com/firecracker-microvm/micro-http", branch = "main" } +once_cell = "1.13.0" qcow = { path = "../qcow" } seccompiler = "0.2.0" vhdx = { path = "../vhdx" } virtio-devices = { path = "../virtio-devices" } virtio-queue = "0.5.0" +vmm = { path = "../vmm" } vmm-sys-util = "0.10.0" vm-memory = "0.8.0" vm-device = { path = "../vm-device" } @@ -47,6 +51,12 @@ path = "fuzz_targets/cmos.rs" test = false doc = false +[[bin]] +name = "http_api" +path = "fuzz_targets/http_api.rs" +test = false +doc = false + [[bin]] name = "qcow" path = "fuzz_targets/qcow.rs" @@ -64,4 +74,3 @@ name = "vhdx" path = "fuzz_targets/vhdx.rs" test = false doc = false - diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs new file mode 100644 index 000000000..7e24a82a2 --- /dev/null +++ b/fuzz/fuzz_targets/http_api.rs @@ -0,0 +1,196 @@ +// Copyright © 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +#![no_main] +use libfuzzer_sys::fuzz_target; +use micro_http::Request; +use once_cell::sync::Lazy; +use std::os::unix::io::AsRawFd; +use std::sync::mpsc::{channel, Receiver}; +use std::thread; +use vmm::api::{http::*, ApiRequest, ApiResponsePayload}; +use vmm::{EpollContext, EpollDispatch}; +use vmm_sys_util::eventfd::EventFd; + +// Need to be ordered for test case reproducibility +static ROUTES: Lazy>> = Lazy::new(|| { + let mut keys: Vec<&String> = HTTP_ROUTES.routes.keys().collect(); + keys.sort(); + keys.iter() + .map(|k| HTTP_ROUTES.routes.get(*k).unwrap()) + .collect() +}); + +fuzz_target!(|bytes| { + if bytes.len() < 2 { + return; + } + + let route = ROUTES[bytes[0] as usize % ROUTES.len()]; + if let Some(request) = generate_request(&bytes[1..]) { + let exit_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let api_evt = EventFd::new(libc::EFD_NONBLOCK).unwrap(); + let (api_sender, api_receiver) = channel(); + + let http_receiver_thread = { + let exit_evt = exit_evt.try_clone().unwrap(); + let api_evt = api_evt.try_clone().unwrap(); + thread::Builder::new() + .name("http_receiver".to_string()) + .spawn(move || { + http_receiver_stub(exit_evt, api_evt, api_receiver); + }) + .unwrap() + }; + + route.handle_request(&request, api_evt, api_sender); + exit_evt.write(1).ok(); + http_receiver_thread.join().unwrap(); + }; +}); + +fn generate_request(bytes: &[u8]) -> Option { + let req_method = match bytes[0] % 5 { + 0 => "GET", + 1 => "PUT", + 2 => "PATCH", + 3 => "POST", + _ => "INVALID", + }; + let request_line = format!("{} http://localhost/home HTTP/1.1\r\n", req_method); + + let req_body = &bytes[1..]; + let request = if req_body.len() > 0 { + [ + format!("{}Content-Length: {}\r\n", request_line, req_body.len()).as_bytes(), + req_body, + ] + .concat() + } else { + format!("{}\r\n", request_line).as_bytes().to_vec() + }; + + Request::try_from(&request, None).ok() +} + +fn http_receiver_stub(exit_evt: EventFd, api_evt: EventFd, api_receiver: Receiver) { + let mut epoll = EpollContext::new().unwrap(); + epoll.add_event(&exit_evt, EpollDispatch::Exit).unwrap(); + epoll.add_event(&api_evt, EpollDispatch::Api).unwrap(); + + let epoll_fd = epoll.as_raw_fd(); + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); 2]; + let num_events; + loop { + num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) { + Ok(num_events) => num_events, + Err(e) => match e.raw_os_error() { + Some(libc::EAGAIN) | Some(libc::EINTR) => continue, + _ => panic!("Unexpected epoll::wait error!"), + }, + }; + + break; + } + + for event in events.iter().take(num_events) { + let dispatch_event: EpollDispatch = event.data.into(); + match dispatch_event { + EpollDispatch::Exit => { + break; + } + EpollDispatch::Api => { + for _ in 0..api_evt.read().unwrap() { + let api_request = api_receiver.recv().unwrap(); + match api_request { + ApiRequest::VmCreate(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmDelete(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmBoot(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmShutdown(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmReboot(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmInfo(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmmPing(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmPause(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmResume(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmSnapshot(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmRestore(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmmShutdown(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmResize(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmResizeZone(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmAddDevice(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmAddUserDevice(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmRemoveDevice(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmAddDisk(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmAddFs(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmAddPmem(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmAddNet(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmAddVdpa(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmAddVsock(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmCounters(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmReceiveMigration(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmSendMigration(_, sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + ApiRequest::VmPowerButton(sender) => { + sender.send(Ok(ApiResponsePayload::Empty)).unwrap(); + } + } + } + } + _ => { + panic!("Unexpected Epoll event"); + } + } + } +}