hyperlight_host/hypervisor/
mod.rs

1/*
2Copyright 2024 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use tracing::{instrument, Span};
18
19use crate::error::HyperlightError::ExecutionCanceledByHost;
20use crate::hypervisor::metrics::HypervisorMetric::NumberOfCancelledGuestExecutions;
21use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
22use crate::{int_counter_inc, log_then_return, new_error, HyperlightError, Result};
23
24/// Util for handling x87 fpu state
25#[cfg(any(kvm, mshv, target_os = "windows"))]
26pub mod fpu;
27/// Handlers for Hypervisor custom logic
28pub mod handlers;
29/// HyperV-on-linux functionality
30#[cfg(mshv)]
31pub mod hyperv_linux;
32#[cfg(target_os = "windows")]
33/// Hyperv-on-windows functionality
34pub(crate) mod hyperv_windows;
35pub(crate) mod hypervisor_handler;
36
37/// GDB debugging support
38#[cfg(gdb)]
39mod gdb;
40
41/// Driver for running in process instead of using hypervisor
42#[cfg(inprocess)]
43pub mod inprocess;
44#[cfg(kvm)]
45/// Functionality to manipulate KVM-based virtual machines
46pub mod kvm;
47/// Metric definitions for Hypervisor module.
48mod metrics;
49#[cfg(target_os = "windows")]
50/// Hyperlight Surrogate Process
51pub(crate) mod surrogate_process;
52#[cfg(target_os = "windows")]
53/// Hyperlight Surrogate Process
54pub(crate) mod surrogate_process_manager;
55/// WindowsHypervisorPlatform utilities
56#[cfg(target_os = "windows")]
57pub(crate) mod windows_hypervisor_platform;
58/// Safe wrappers around windows types like `PSTR`
59#[cfg(target_os = "windows")]
60pub(crate) mod wrappers;
61
62#[cfg(crashdump)]
63pub(crate) mod crashdump;
64
65use std::fmt::Debug;
66use std::sync::{Arc, Mutex};
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::hypervisor::hypervisor_handler::HypervisorHandler;
77use crate::mem::ptr::RawPtr;
78
79pub(crate) const CR4_PAE: u64 = 1 << 5;
80pub(crate) const CR4_OSFXSR: u64 = 1 << 9;
81pub(crate) const CR4_OSXMMEXCPT: u64 = 1 << 10;
82pub(crate) const CR0_PE: u64 = 1;
83pub(crate) const CR0_MP: u64 = 1 << 1;
84pub(crate) const CR0_ET: u64 = 1 << 4;
85pub(crate) const CR0_NE: u64 = 1 << 5;
86pub(crate) const CR0_WP: u64 = 1 << 16;
87pub(crate) const CR0_AM: u64 = 1 << 18;
88pub(crate) const CR0_PG: u64 = 1 << 31;
89pub(crate) const EFER_LME: u64 = 1 << 8;
90pub(crate) const EFER_LMA: u64 = 1 << 10;
91pub(crate) const EFER_SCE: u64 = 1;
92pub(crate) const EFER_NX: u64 = 1 << 11;
93
94/// These are the generic exit reasons that we can handle from a Hypervisor the Hypervisors run method is responsible for mapping from
95/// the hypervisor specific exit reasons to these generic ones
96pub enum HyperlightExit {
97    #[cfg(gdb)]
98    /// The vCPU has exited due to a debug event
99    Debug(VcpuStopReason),
100    /// The vCPU has halted
101    Halt(),
102    /// The vCPU has issued a write to the given port with the given value
103    IoOut(u16, Vec<u8>, u64, u64),
104    /// The vCPU has attempted to read or write from an unmapped address
105    Mmio(u64),
106    /// The vCPU tried to access memory but was missing the required permissions
107    AccessViolation(u64, MemoryRegionFlags, MemoryRegionFlags),
108    /// The vCPU execution has been cancelled
109    Cancelled(),
110    /// The vCPU has exited for a reason that is not handled by Hyperlight
111    Unknown(String),
112    /// The operation should be retried, for example this can happen on Linux where a call to run the CPU can return EAGAIN
113    Retry(),
114}
115
116/// A common set of hypervisor functionality
117///
118/// Note: a lot of these structures take in an `Option<HypervisorHandler>`.
119/// This is because, if we are coming from the C API, we don't have a HypervisorHandler and have
120/// to account for the fact the Hypervisor was set up beforehand.
121pub(crate) trait Hypervisor: Debug + Sync + Send {
122    /// Initialise the internally stored vCPU with the given PEB address and
123    /// random number seed, then run it until a HLT instruction.
124    #[allow(clippy::too_many_arguments)]
125    fn initialise(
126        &mut self,
127        peb_addr: RawPtr,
128        seed: u64,
129        page_size: u32,
130        outb_handle_fn: OutBHandlerWrapper,
131        mem_access_fn: MemAccessHandlerWrapper,
132        hv_handler: Option<HypervisorHandler>,
133        #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
134    ) -> Result<()>;
135
136    /// Dispatch a call from the host to the guest using the given pointer
137    /// to the dispatch function _in the guest's address space_.
138    ///
139    /// Do this by setting the instruction pointer to `dispatch_func_addr`
140    /// and then running the execution loop until a halt instruction.
141    ///
142    /// Returns `Ok` if the call succeeded, and an `Err` if it failed
143    fn dispatch_call_from_host(
144        &mut self,
145        dispatch_func_addr: RawPtr,
146        outb_handle_fn: OutBHandlerWrapper,
147        mem_access_fn: MemAccessHandlerWrapper,
148        hv_handler: Option<HypervisorHandler>,
149        #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
150    ) -> Result<()>;
151
152    /// Handle an IO exit from the internally stored vCPU.
153    fn handle_io(
154        &mut self,
155        port: u16,
156        data: Vec<u8>,
157        rip: u64,
158        instruction_length: u64,
159        outb_handle_fn: OutBHandlerWrapper,
160    ) -> Result<()>;
161
162    /// Run the vCPU
163    fn run(&mut self) -> Result<HyperlightExit>;
164
165    /// Returns a Some(HyperlightExit::AccessViolation(..)) if the given gpa doesn't have
166    /// access its corresponding region. Returns None otherwise, or if the region is not found.
167    fn get_memory_access_violation(
168        &self,
169        gpa: usize,
170        mem_regions: &[MemoryRegion],
171        access_info: MemoryRegionFlags,
172    ) -> Option<HyperlightExit> {
173        // find the region containing the given gpa
174        let region = mem_regions
175            .iter()
176            .find(|region| region.guest_region.contains(&gpa));
177
178        if let Some(region) = region {
179            if !region.flags.contains(access_info)
180                || region.flags.contains(MemoryRegionFlags::STACK_GUARD)
181            {
182                return Some(HyperlightExit::AccessViolation(
183                    gpa as u64,
184                    access_info,
185                    region.flags,
186                ));
187            }
188        }
189        None
190    }
191
192    /// Get the logging level to pass to the guest entrypoint
193    fn get_max_log_level(&self) -> u32 {
194        log::max_level() as u32
195    }
196
197    /// get a mutable trait object from self
198    fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor;
199
200    /// Get the partition handle for WHP
201    #[cfg(target_os = "windows")]
202    fn get_partition_handle(&self) -> windows::Win32::System::Hypervisor::WHV_PARTITION_HANDLE;
203
204    #[cfg(crashdump)]
205    fn get_memory_regions(&self) -> &[MemoryRegion];
206
207    #[cfg(gdb)]
208    /// handles the cases when the vCPU stops due to a Debug event
209    fn handle_debug(
210        &mut self,
211        _dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
212        _stop_reason: VcpuStopReason,
213    ) -> Result<()> {
214        unimplemented!()
215    }
216}
217
218/// A virtual CPU that can be run until an exit occurs
219pub struct VirtualCPU {}
220
221impl VirtualCPU {
222    /// Run the given hypervisor until a halt instruction is reached
223    #[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
224    pub fn run(
225        hv: &mut dyn Hypervisor,
226        hv_handler: Option<HypervisorHandler>,
227        outb_handle_fn: Arc<Mutex<dyn OutBHandlerCaller>>,
228        mem_access_fn: Arc<Mutex<dyn MemAccessHandlerCaller>>,
229        #[cfg(gdb)] dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
230    ) -> Result<()> {
231        loop {
232            match hv.run() {
233                #[cfg(gdb)]
234                Ok(HyperlightExit::Debug(stop_reason)) => {
235                    if let Err(e) = hv.handle_debug(dbg_mem_access_fn.clone(), stop_reason) {
236                        log_then_return!(e);
237                    }
238                }
239
240                Ok(HyperlightExit::Halt()) => {
241                    break;
242                }
243                Ok(HyperlightExit::IoOut(port, data, rip, instruction_length)) => {
244                    hv.handle_io(port, data, rip, instruction_length, outb_handle_fn.clone())?
245                }
246                Ok(HyperlightExit::Mmio(addr)) => {
247                    #[cfg(crashdump)]
248                    crashdump::crashdump_to_tempfile(hv)?;
249
250                    mem_access_fn
251                        .clone()
252                        .try_lock()
253                        .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
254                        .call()?;
255
256                    log_then_return!("MMIO access address {:#x}", addr);
257                }
258                Ok(HyperlightExit::AccessViolation(addr, tried, region_permission)) => {
259                    #[cfg(crashdump)]
260                    crashdump::crashdump_to_tempfile(hv)?;
261
262                    if region_permission.intersects(MemoryRegionFlags::STACK_GUARD) {
263                        return Err(HyperlightError::StackOverflow());
264                    }
265                    log_then_return!(HyperlightError::MemoryAccessViolation(
266                        addr,
267                        tried,
268                        region_permission
269                    ));
270                }
271                Ok(HyperlightExit::Cancelled()) => {
272                    // Shutdown is returned when the host has cancelled execution
273                    // After termination, the main thread will re-initialize the VM
274                    if let Some(hvh) = hv_handler {
275                        // If hvh is None, then we are running from the C API, which doesn't use
276                        // the HypervisorHandler
277                        hvh.set_running(false);
278                        #[cfg(target_os = "linux")]
279                        hvh.set_run_cancelled(true);
280                    }
281                    int_counter_inc!(&NumberOfCancelledGuestExecutions);
282                    log_then_return!(ExecutionCanceledByHost());
283                }
284                Ok(HyperlightExit::Unknown(reason)) => {
285                    #[cfg(crashdump)]
286                    crashdump::crashdump_to_tempfile(hv)?;
287
288                    log_then_return!("Unexpected VM Exit {:?}", reason);
289                }
290                Ok(HyperlightExit::Retry()) => continue,
291                Err(e) => {
292                    #[cfg(crashdump)]
293                    crashdump::crashdump_to_tempfile(hv)?;
294
295                    return Err(e);
296                }
297            }
298        }
299
300        Ok(())
301    }
302}
303
304#[cfg(all(test, any(target_os = "windows", kvm)))]
305pub(crate) mod tests {
306    use std::path::Path;
307    use std::sync::{Arc, Mutex};
308    use std::time::Duration;
309
310    use hyperlight_testing::dummy_guest_as_string;
311
312    #[cfg(gdb)]
313    use super::handlers::DbgMemAccessHandlerWrapper;
314    use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper};
315    use crate::hypervisor::hypervisor_handler::{
316        HvHandlerConfig, HypervisorHandler, HypervisorHandlerAction,
317    };
318    use crate::mem::ptr::RawPtr;
319    use crate::sandbox::uninitialized::GuestBinary;
320    use crate::sandbox::{SandboxConfiguration, UninitializedSandbox};
321    use crate::{new_error, Result};
322
323    pub(crate) fn test_initialise(
324        outb_hdl: OutBHandlerWrapper,
325        mem_access_hdl: MemAccessHandlerWrapper,
326        #[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
327    ) -> Result<()> {
328        let filename = dummy_guest_as_string().map_err(|e| new_error!("{}", e))?;
329        if !Path::new(&filename).exists() {
330            return Err(new_error!(
331                "test_initialise: file {} does not exist",
332                filename
333            ));
334        }
335
336        let sandbox =
337            UninitializedSandbox::new(GuestBinary::FilePath(filename.clone()), None, None, None)?;
338        let (hshm, gshm) = sandbox.mgr.build();
339        drop(hshm);
340
341        let hv_handler_config = HvHandlerConfig {
342            outb_handler: outb_hdl,
343            mem_access_handler: mem_access_hdl,
344            #[cfg(gdb)]
345            dbg_mem_access_handler: dbg_mem_access_fn,
346            seed: 1234567890,
347            page_size: 4096,
348            peb_addr: RawPtr::from(0x230000),
349            dispatch_function_addr: Arc::new(Mutex::new(None)),
350            max_init_time: Duration::from_millis(
351                SandboxConfiguration::DEFAULT_MAX_INITIALIZATION_TIME as u64,
352            ),
353            max_exec_time: Duration::from_millis(
354                SandboxConfiguration::DEFAULT_MAX_EXECUTION_TIME as u64,
355            ),
356            max_wait_for_cancellation: Duration::from_millis(
357                SandboxConfiguration::DEFAULT_MAX_WAIT_FOR_CANCELLATION as u64,
358            ),
359        };
360
361        let mut hv_handler = HypervisorHandler::new(hv_handler_config);
362
363        // call initialise on the hypervisor implementation with specific values
364        // for PEB (process environment block) address, seed and page size.
365        //
366        // these values are not actually used, they're just checked inside
367        // the dummy guest, and if they don't match these values, the dummy
368        // guest issues a write to an invalid memory address, which in turn
369        // fails this test.
370        //
371        // in this test, we're not actually testing whether a guest can issue
372        // memory operations, call functions, etc... - we're just testing
373        // whether we can configure the shared memory region, load a binary
374        // into it, and run the CPU to completion (e.g., a HLT interrupt)
375
376        hv_handler.start_hypervisor_handler(
377            gshm,
378            #[cfg(gdb)]
379            None,
380        )?;
381
382        hv_handler.execute_hypervisor_handler_action(HypervisorHandlerAction::Initialise)
383    }
384}