cloud-hypervisor/fuzz/fuzz_targets/http_api.rs
Philipp Schuster e50a641126 devices: add debug-console device
This commit adds the debug-console (or debugcon) device to CHV. It is a
very simple device on I/O port 0xe9 supported by QEMU and BOCHS. It is
meant for printing information as easy as possible, without any
necessary configuration from the guest at all.

It is primarily interesting to OS/kernel and firmware developers as they
can produce output as soon as the guest starts without any configuration
of a serial device or similar. Furthermore, a kernel hacker might use
this device for information of type B whereas information of type A are
printed to the serial device.

This device is not used by default by Linux, Windows, or any other
"real" OS, but only by toy kernels and during firmware development.

In the CLI, it can be configured similar to --console or --serial with
the --debug-console parameter.

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
2024-01-25 10:25:14 -08:00

316 lines
9.1 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")),
firmware: None,
cmdline: None,
initramfs: None,
#[cfg(feature = "igvm")]
igvm: None,
}),
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,
},
#[cfg(target_arch = "x86_64")]
debug_console: DebugConsoleConfig::default(),
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");
}
}
}
}