hyperlight_host/hypervisor/
mod.rs1use log::LevelFilter;
18use tracing::{Span, instrument};
19
20use crate::error::HyperlightError::ExecutionCanceledByHost;
21use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
22use crate::metrics::METRIC_GUEST_CANCELLATION;
23use crate::{HyperlightError, Result, log_then_return, new_error};
24
25#[cfg(any(kvm, mshv, target_os = "windows"))]
27pub mod fpu;
28pub mod handlers;
30#[cfg(mshv)]
32pub mod hyperv_linux;
33#[cfg(target_os = "windows")]
34pub(crate) mod hyperv_windows;
36
37#[cfg(gdb)]
39pub(crate) mod gdb;
40
41#[cfg(kvm)]
42pub mod kvm;
44#[cfg(target_os = "windows")]
45pub(crate) mod surrogate_process;
47#[cfg(target_os = "windows")]
48pub(crate) mod surrogate_process_manager;
50#[cfg(target_os = "windows")]
52pub(crate) mod windows_hypervisor_platform;
53#[cfg(target_os = "windows")]
55pub(crate) mod wrappers;
56
57#[cfg(crashdump)]
58pub(crate) mod crashdump;
59
60use std::fmt::Debug;
61use std::str::FromStr;
62#[cfg(any(kvm, mshv))]
63use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
64use std::sync::{Arc, Mutex};
65#[cfg(any(kvm, mshv))]
66use std::time::Duration;
67
68#[cfg(gdb)]
69use gdb::VcpuStopReason;
70
71#[cfg(gdb)]
72use self::handlers::{DbgMemAccessHandlerCaller, DbgMemAccessHandlerWrapper};
73use self::handlers::{
74 MemAccessHandlerCaller, MemAccessHandlerWrapper, OutBHandlerCaller, OutBHandlerWrapper,
75};
76use crate::mem::ptr::RawPtr;
77
78cfg_if::cfg_if! {
79 if #[cfg(feature = "init-paging")] {
80 pub(crate) const CR4_PAE: u64 = 1 << 5;
81 pub(crate) const CR4_OSFXSR: u64 = 1 << 9;
82 pub(crate) const CR4_OSXMMEXCPT: u64 = 1 << 10;
83 pub(crate) const CR0_PE: u64 = 1;
84 pub(crate) const CR0_MP: u64 = 1 << 1;
85 pub(crate) const CR0_ET: u64 = 1 << 4;
86 pub(crate) const CR0_NE: u64 = 1 << 5;
87 pub(crate) const CR0_WP: u64 = 1 << 16;
88 pub(crate) const CR0_AM: u64 = 1 << 18;
89 pub(crate) const CR0_PG: u64 = 1 << 31;
90 pub(crate) const EFER_LME: u64 = 1 << 8;
91 pub(crate) const EFER_LMA: u64 = 1 << 10;
92 pub(crate) const EFER_SCE: u64 = 1;
93 pub(crate) const EFER_NX: u64 = 1 << 11;
94 }
95}
96
97pub enum HyperlightExit {
100 #[cfg(gdb)]
101 Debug(VcpuStopReason),
103 Halt(),
105 IoOut(u16, Vec<u8>, u64, u64),
107 Mmio(u64),
109 AccessViolation(u64, MemoryRegionFlags, MemoryRegionFlags),
111 Cancelled(),
113 Unknown(String),
115 Retry(),
117}
118
119pub(crate) trait Hypervisor: Debug + Sync + Send {
121 #[allow(clippy::too_many_arguments)]
124 fn initialise(
125 &mut self,
126 peb_addr: RawPtr,
127 seed: u64,
128 page_size: u32,
129 outb_handle_fn: OutBHandlerWrapper,
130 mem_access_fn: MemAccessHandlerWrapper,
131 guest_max_log_level: Option<LevelFilter>,
132 #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
133 ) -> Result<()>;
134
135 fn dispatch_call_from_host(
143 &mut self,
144 dispatch_func_addr: RawPtr,
145 outb_handle_fn: OutBHandlerWrapper,
146 mem_access_fn: MemAccessHandlerWrapper,
147 #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
148 ) -> Result<()>;
149
150 fn handle_io(
152 &mut self,
153 port: u16,
154 data: Vec<u8>,
155 rip: u64,
156 instruction_length: u64,
157 outb_handle_fn: OutBHandlerWrapper,
158 ) -> Result<()>;
159
160 fn run(&mut self) -> Result<HyperlightExit>;
162
163 fn get_memory_access_violation(
166 &self,
167 gpa: usize,
168 mem_regions: &[MemoryRegion],
169 access_info: MemoryRegionFlags,
170 ) -> Option<HyperlightExit> {
171 let region = mem_regions
173 .iter()
174 .find(|region| region.guest_region.contains(&gpa));
175
176 if let Some(region) = region {
177 if !region.flags.contains(access_info)
178 || region.flags.contains(MemoryRegionFlags::STACK_GUARD)
179 {
180 return Some(HyperlightExit::AccessViolation(
181 gpa as u64,
182 access_info,
183 region.flags,
184 ));
185 }
186 }
187 None
188 }
189
190 fn interrupt_handle(&self) -> Arc<dyn InterruptHandle>;
192
193 fn get_max_log_level(&self) -> u32 {
195 let val = std::env::var("RUST_LOG").unwrap_or_default();
204
205 let level = if val.contains("hyperlight_guest") {
206 val.split(',')
207 .find(|s| s.contains("hyperlight_guest"))
208 .unwrap_or("")
209 .split('=')
210 .nth(1)
211 .unwrap_or("")
212 } else if val.contains("hyperlight_host") {
213 val.split(',')
214 .find(|s| s.contains("hyperlight_host"))
215 .unwrap_or("")
216 .split('=')
217 .nth(1)
218 .unwrap_or("")
219 } else {
220 val.split(',').find(|s| !s.contains("=")).unwrap_or("")
222 };
223
224 log::info!("Determined guest log level: {}", level);
225 LevelFilter::from_str(level).unwrap_or(LevelFilter::Error) as u32
228 }
229
230 fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor;
232
233 #[cfg(crashdump)]
234 fn crashdump_context(&self) -> Result<Option<crashdump::CrashDumpContext>>;
235
236 #[cfg(gdb)]
237 fn handle_debug(
239 &mut self,
240 _dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
241 _stop_reason: VcpuStopReason,
242 ) -> Result<()> {
243 unimplemented!()
244 }
245}
246
247pub struct VirtualCPU {}
249
250impl VirtualCPU {
251 #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
253 pub fn run(
254 hv: &mut dyn Hypervisor,
255 outb_handle_fn: Arc<Mutex<dyn OutBHandlerCaller>>,
256 mem_access_fn: Arc<Mutex<dyn MemAccessHandlerCaller>>,
257 #[cfg(gdb)] dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
258 ) -> Result<()> {
259 loop {
260 match hv.run() {
261 #[cfg(gdb)]
262 Ok(HyperlightExit::Debug(stop_reason)) => {
263 if let Err(e) = hv.handle_debug(dbg_mem_access_fn.clone(), stop_reason) {
264 log_then_return!(e);
265 }
266 }
267
268 Ok(HyperlightExit::Halt()) => {
269 break;
270 }
271 Ok(HyperlightExit::IoOut(port, data, rip, instruction_length)) => {
272 hv.handle_io(port, data, rip, instruction_length, outb_handle_fn.clone())?
273 }
274 Ok(HyperlightExit::Mmio(addr)) => {
275 #[cfg(crashdump)]
276 crashdump::generate_crashdump(hv)?;
277
278 mem_access_fn
279 .clone()
280 .try_lock()
281 .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
282 .call()?;
283
284 log_then_return!("MMIO access address {:#x}", addr);
285 }
286 Ok(HyperlightExit::AccessViolation(addr, tried, region_permission)) => {
287 #[cfg(crashdump)]
288 crashdump::generate_crashdump(hv)?;
289
290 #[cfg(gdb)]
293 let _ = hv.handle_debug(dbg_mem_access_fn.clone(), VcpuStopReason::Crash);
294
295 if region_permission.intersects(MemoryRegionFlags::STACK_GUARD) {
296 return Err(HyperlightError::StackOverflow());
297 }
298 log_then_return!(HyperlightError::MemoryAccessViolation(
299 addr,
300 tried,
301 region_permission
302 ));
303 }
304 Ok(HyperlightExit::Cancelled()) => {
305 metrics::counter!(METRIC_GUEST_CANCELLATION).increment(1);
308 log_then_return!(ExecutionCanceledByHost());
309 }
310 Ok(HyperlightExit::Unknown(reason)) => {
311 #[cfg(crashdump)]
312 crashdump::generate_crashdump(hv)?;
313 #[cfg(gdb)]
316 let _ = hv.handle_debug(dbg_mem_access_fn.clone(), VcpuStopReason::Crash);
317
318 log_then_return!("Unexpected VM Exit {:?}", reason);
319 }
320 Ok(HyperlightExit::Retry()) => continue,
321 Err(e) => {
322 #[cfg(crashdump)]
323 crashdump::generate_crashdump(hv)?;
324 #[cfg(gdb)]
327 let _ = hv.handle_debug(dbg_mem_access_fn.clone(), VcpuStopReason::Crash);
328
329 return Err(e);
330 }
331 }
332 }
333
334 Ok(())
335 }
336}
337
338pub trait InterruptHandle: Debug + Send + Sync {
340 fn kill(&self) -> bool;
350
351 #[cfg(gdb)]
361 fn kill_from_debugger(&self) -> bool;
362
363 fn dropped(&self) -> bool;
365}
366
367#[cfg(any(kvm, mshv))]
368#[derive(Debug)]
369pub(super) struct LinuxInterruptHandle {
370 running: AtomicU64,
387 tid: AtomicU64,
390 cancel_requested: AtomicBool,
398 #[cfg(gdb)]
403 debug_interrupt: AtomicBool,
404 dropped: AtomicBool,
406 retry_delay: Duration,
408 sig_rt_min_offset: u8,
410}
411
412#[cfg(any(kvm, mshv))]
413impl LinuxInterruptHandle {
414 const RUNNING_BIT: u64 = 1 << 63;
415 const MAX_GENERATION: u64 = Self::RUNNING_BIT - 1;
416
417 fn set_running_and_increment_generation(&self) -> std::result::Result<u64, u64> {
419 self.running
420 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |raw| {
421 let generation = raw & !Self::RUNNING_BIT;
422 if generation == Self::MAX_GENERATION {
423 return Some(Self::RUNNING_BIT);
425 }
426 Some((generation + 1) | Self::RUNNING_BIT)
427 })
428 }
429
430 fn clear_running_bit(&self) -> u64 {
432 self.running
433 .fetch_and(!Self::RUNNING_BIT, Ordering::Relaxed)
434 }
435
436 fn get_running_and_generation(&self) -> (bool, u64) {
437 let raw = self.running.load(Ordering::Relaxed);
438 let running = raw & Self::RUNNING_BIT != 0;
439 let generation = raw & !Self::RUNNING_BIT;
440 (running, generation)
441 }
442
443 fn send_signal(&self) -> bool {
444 let signal_number = libc::SIGRTMIN() + self.sig_rt_min_offset as libc::c_int;
445 let mut sent_signal = false;
446 let mut target_generation: Option<u64> = None;
447
448 loop {
449 let (running, generation) = self.get_running_and_generation();
450
451 if !running {
452 break;
453 }
454
455 match target_generation {
456 None => target_generation = Some(generation),
457 Some(expected) if expected != generation => break,
459 _ => {}
460 }
461
462 log::info!("Sending signal to kill vcpu thread...");
463 sent_signal = true;
464 unsafe {
465 libc::pthread_kill(self.tid.load(Ordering::Relaxed) as _, signal_number);
466 }
467 std::thread::sleep(self.retry_delay);
468 }
469
470 sent_signal
471 }
472}
473
474#[cfg(any(kvm, mshv))]
475impl InterruptHandle for LinuxInterruptHandle {
476 fn kill(&self) -> bool {
477 self.cancel_requested.store(true, Ordering::Relaxed);
478
479 self.send_signal()
480 }
481 #[cfg(gdb)]
482 fn kill_from_debugger(&self) -> bool {
483 self.debug_interrupt.store(true, Ordering::Relaxed);
484 self.send_signal()
485 }
486 fn dropped(&self) -> bool {
487 self.dropped.load(Ordering::Relaxed)
488 }
489}
490
491#[cfg(all(test, any(target_os = "windows", kvm)))]
492pub(crate) mod tests {
493 use std::sync::{Arc, Mutex};
494
495 use hyperlight_testing::dummy_guest_as_string;
496
497 use super::handlers::{MemAccessHandler, OutBHandler};
498 #[cfg(gdb)]
499 use crate::hypervisor::DbgMemAccessHandlerCaller;
500 use crate::mem::ptr::RawPtr;
501 use crate::sandbox::uninitialized::GuestBinary;
502 #[cfg(any(crashdump, gdb))]
503 use crate::sandbox::uninitialized::SandboxRuntimeConfig;
504 use crate::sandbox::uninitialized_evolve::set_up_hypervisor_partition;
505 use crate::sandbox::{SandboxConfiguration, UninitializedSandbox};
506 use crate::{Result, is_hypervisor_present, new_error};
507
508 #[cfg(gdb)]
509 struct DbgMemAccessHandler {}
510
511 #[cfg(gdb)]
512 impl DbgMemAccessHandlerCaller for DbgMemAccessHandler {
513 fn read(&mut self, _offset: usize, _data: &mut [u8]) -> Result<()> {
514 Ok(())
515 }
516
517 fn write(&mut self, _offset: usize, _data: &[u8]) -> Result<()> {
518 Ok(())
519 }
520
521 fn get_code_offset(&mut self) -> Result<usize> {
522 Ok(0)
523 }
524 }
525
526 #[test]
527 fn test_initialise() -> Result<()> {
528 if !is_hypervisor_present() {
529 return Ok(());
530 }
531
532 let outb_handler: Arc<Mutex<OutBHandler>> = {
533 let func: Box<dyn FnMut(u16, u32) -> Result<()> + Send> =
534 Box::new(|_, _| -> Result<()> { Ok(()) });
535 Arc::new(Mutex::new(OutBHandler::from(func)))
536 };
537 let mem_access_handler = {
538 let func: Box<dyn FnMut() -> Result<()> + Send> = Box::new(|| -> Result<()> { Ok(()) });
539 Arc::new(Mutex::new(MemAccessHandler::from(func)))
540 };
541 #[cfg(gdb)]
542 let dbg_mem_access_handler = Arc::new(Mutex::new(DbgMemAccessHandler {}));
543
544 let filename = dummy_guest_as_string().map_err(|e| new_error!("{}", e))?;
545
546 let config: SandboxConfiguration = Default::default();
547 #[cfg(any(crashdump, gdb))]
548 let rt_cfg: SandboxRuntimeConfig = Default::default();
549 let sandbox =
550 UninitializedSandbox::new(GuestBinary::FilePath(filename.clone()), Some(config))?;
551 let (_hshm, mut gshm) = sandbox.mgr.build();
552 let mut vm = set_up_hypervisor_partition(
553 &mut gshm,
554 &config,
555 #[cfg(any(crashdump, gdb))]
556 &rt_cfg,
557 )?;
558 vm.initialise(
559 RawPtr::from(0x230000),
560 1234567890,
561 4096,
562 outb_handler,
563 mem_access_handler,
564 None,
565 #[cfg(gdb)]
566 dbg_mem_access_handler,
567 )
568 }
569}