mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2024-09-17 20:44:55 +00:00
4ca18c082e
Uses of the old ApiRequest enum conflated two different concerns: identifying an API request endpoint, and storing data for an API request. This led to ApiRequest values being passed around with junk data just to communicate a request type, which forced all API request body types to implement Default, which in some cases doesn't make any sense — what's the "default" path for a vhost-user socket? The nonsensical Default values have led to tests relying on being able to use nonsensical data, which is an impediment to adding better validation for these types. Rather than having API request types be represented by an enum, which has to carry associated body data everywhere it's used, it makes more sense to represent API request types as trait objects. These can have an associated type for the type of the request body, and this makes it possible to pass API request types and data around as siblings in a type-safe way without forcing them into a single value even where it doesn't make sense. Trait objects also give us dynamic dispatch, which lets us get rid of several large match blocks. To keep it possible to fuzz the HTTP API, all the Vmm methods called by the HTTP API are pulled out into a trait, so the fuzzer can provide its own stub implementation of the VMM. Signed-off-by: Alyssa Ross <hi@alyssa.is>
310 lines
8.9 KiB
Rust
310 lines
8.9 KiB
Rust
// 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::path::PathBuf;
|
|
use std::sync::mpsc::{channel, Receiver};
|
|
use std::sync::{Arc, Mutex};
|
|
use std::thread;
|
|
use vm_migration::MigratableError;
|
|
use vmm::api::{
|
|
http::*, ApiRequest, RequestHandler, VmInfoResponse, VmReceiveMigrationData,
|
|
VmSendMigrationData, VmmPingResponse,
|
|
};
|
|
use vmm::config::RestoreConfig;
|
|
use vmm::vm::{Error as VmError, VmState};
|
|
use vmm::vm_config::*;
|
|
use vmm::{EpollContext, EpollDispatch};
|
|
use vmm_sys_util::eventfd::EventFd;
|
|
|
|
// Need to be ordered for test case reproducibility
|
|
static ROUTES: Lazy<Vec<&Box<dyn EndpointHandler + Sync + Send>>> =
|
|
Lazy::new(|| HTTP_ROUTES.routes.values().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<Request> {
|
|
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()
|
|
}
|
|
|
|
struct StubApiRequestHandler;
|
|
|
|
impl RequestHandler for StubApiRequestHandler {
|
|
fn vm_create(&mut self, _: Arc<Mutex<VmConfig>>) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_boot(&mut self) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_pause(&mut self) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_resume(&mut self) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_snapshot(&mut self, _: &str) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_restore(&mut self, _: RestoreConfig) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
|
|
fn vm_coredump(&mut self, _: &str) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_shutdown(&mut self) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_reboot(&mut self) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_info(&self) -> Result<VmInfoResponse, VmError> {
|
|
Ok(VmInfoResponse {
|
|
config: Arc::new(Mutex::new(VmConfig {
|
|
cpus: CpusConfig {
|
|
boot_vcpus: 1,
|
|
max_vcpus: 1,
|
|
topology: None,
|
|
kvm_hyperv: false,
|
|
max_phys_bits: 46,
|
|
affinity: None,
|
|
features: CpuFeatures::default(),
|
|
},
|
|
memory: MemoryConfig {
|
|
size: 536_870_912,
|
|
mergeable: false,
|
|
hotplug_method: HotplugMethod::Acpi,
|
|
hotplug_size: None,
|
|
hotplugged_size: None,
|
|
shared: false,
|
|
hugepages: false,
|
|
hugepage_size: None,
|
|
prefault: false,
|
|
zones: None,
|
|
thp: true,
|
|
},
|
|
payload: Some(PayloadConfig {
|
|
kernel: Some(PathBuf::from("/path/to/kernel")),
|
|
..Default::default()
|
|
}),
|
|
rate_limit_groups: None,
|
|
disks: None,
|
|
net: None,
|
|
rng: RngConfig {
|
|
src: PathBuf::from("/dev/urandom"),
|
|
iommu: false,
|
|
},
|
|
balloon: None,
|
|
fs: None,
|
|
pmem: None,
|
|
serial: ConsoleConfig {
|
|
file: None,
|
|
mode: ConsoleOutputMode::Null,
|
|
iommu: false,
|
|
socket: None,
|
|
},
|
|
console: ConsoleConfig {
|
|
file: None,
|
|
mode: ConsoleOutputMode::Tty,
|
|
iommu: false,
|
|
socket: None,
|
|
},
|
|
devices: None,
|
|
user_devices: None,
|
|
vdpa: None,
|
|
vsock: None,
|
|
pvpanic: false,
|
|
iommu: false,
|
|
#[cfg(target_arch = "x86_64")]
|
|
sgx_epc: None,
|
|
numa: None,
|
|
watchdog: false,
|
|
#[cfg(feature = "guest_debug")]
|
|
gdb: false,
|
|
platform: None,
|
|
tpm: None,
|
|
preserved_fds: None,
|
|
})),
|
|
state: VmState::Running,
|
|
memory_actual_size: 0,
|
|
device_tree: None,
|
|
})
|
|
}
|
|
|
|
fn vmm_ping(&self) -> VmmPingResponse {
|
|
VmmPingResponse {
|
|
build_version: String::new(),
|
|
version: String::new(),
|
|
pid: 0,
|
|
features: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn vm_delete(&mut self) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vmm_shutdown(&mut self) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_resize(&mut self, _: Option<u8>, _: Option<u64>, _: Option<u64>) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_resize_zone(&mut self, _: String, _: u64) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_add_device(&mut self, _: DeviceConfig) -> Result<Option<Vec<u8>>, VmError> {
|
|
Ok(None)
|
|
}
|
|
|
|
fn vm_add_user_device(&mut self, _: UserDeviceConfig) -> Result<Option<Vec<u8>>, VmError> {
|
|
Ok(None)
|
|
}
|
|
|
|
fn vm_remove_device(&mut self, _: String) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_add_disk(&mut self, _: DiskConfig) -> Result<Option<Vec<u8>>, VmError> {
|
|
Ok(None)
|
|
}
|
|
|
|
fn vm_add_fs(&mut self, _: FsConfig) -> Result<Option<Vec<u8>>, VmError> {
|
|
Ok(None)
|
|
}
|
|
|
|
fn vm_add_pmem(&mut self, _: PmemConfig) -> Result<Option<Vec<u8>>, VmError> {
|
|
Ok(None)
|
|
}
|
|
|
|
fn vm_add_net(&mut self, _: NetConfig) -> Result<Option<Vec<u8>>, VmError> {
|
|
Ok(None)
|
|
}
|
|
|
|
fn vm_add_vdpa(&mut self, _: VdpaConfig) -> Result<Option<Vec<u8>>, VmError> {
|
|
Ok(None)
|
|
}
|
|
|
|
fn vm_add_vsock(&mut self, _: VsockConfig) -> Result<Option<Vec<u8>>, VmError> {
|
|
Ok(None)
|
|
}
|
|
|
|
fn vm_counters(&mut self) -> Result<Option<Vec<u8>>, VmError> {
|
|
Ok(None)
|
|
}
|
|
|
|
fn vm_power_button(&mut self) -> Result<(), VmError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_receive_migration(&mut self, _: VmReceiveMigrationData) -> Result<(), MigratableError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn vm_send_migration(&mut self, _: VmSendMigrationData) -> Result<(), MigratableError> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn http_receiver_stub(exit_evt: EventFd, api_evt: EventFd, api_receiver: Receiver<ApiRequest>) {
|
|
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();
|
|
api_request(&mut StubApiRequestHandler).unwrap();
|
|
}
|
|
}
|
|
_ => {
|
|
panic!("Unexpected Epoll event");
|
|
}
|
|
}
|
|
}
|
|
}
|