Skip to main content

vmi_driver_xen/
lib.rs

1//! VMI driver for Xen hypervisor.
2
3mod arch;
4mod convert;
5mod core;
6
7use std::{
8    cell::RefCell,
9    collections::HashMap,
10    os::fd::AsRawFd as _,
11    time::{Duration, Instant},
12};
13
14use vmi_core::{
15    Architecture, Gfn, MemoryAccess, MemoryAccessOptions, VcpuId, View, VmiDriver, VmiError,
16    VmiEvent, VmiEventResponse, VmiInfo, VmiMappedPage,
17    driver::{
18        VmiEventControl, VmiQueryProtection, VmiQueryRegisters, VmiRead, VmiSetProtection,
19        VmiSetRegisters, VmiViewControl, VmiVmControl, VmiWrite,
20    },
21};
22pub use xen::XenDomainId; // Only `libxen` type in `VmiXenDriver`'s public API.
23use xen::{
24    XenAltP2M, XenAltP2MView, XenControl, XenDeviceModel, XenDomain, XenDomainInfo,
25    XenEventChannelPort, XenForeignMemory, XenForeignMemoryProtection, XenMonitor, XenStore,
26    ctrl::VmEventRing,
27};
28
29pub use self::arch::ArchAdapter;
30use self::convert::{FromExt, IntoExt, TryFromExt};
31
32struct XenView {
33    view: XenAltP2MView,
34    default_access: xen::MemoryAccess,
35}
36
37fn is_missing_altp2m_entry(err: &xen::XenError) -> bool {
38    matches!(
39        err,
40        xen::XenError::Io(inner) if inner.raw_os_error() == Some(libc::ESRCH)
41    )
42}
43
44/// VMI driver for Xen hypervisor.
45pub struct VmiXenDriver<Arch>
46where
47    Arch: ArchAdapter,
48{
49    pub(crate) domain: XenDomain<Arch::XenArch>,
50    pub(crate) devicemodel: XenDeviceModel,
51    pub(crate) monitor: XenMonitor,
52    pub(crate) altp2m: XenAltP2M,
53    pub(crate) evtchn: XenEventChannelPort,
54    pub(crate) foreign_memory: XenForeignMemory,
55    pub(crate) info: XenDomainInfo,
56
57    pub(crate) ring: RefCell<VmEventRing>,
58    pub(crate) views: RefCell<HashMap<u16, XenView>>,
59    pub(crate) event_processing_overhead: RefCell<Duration>,
60}
61
62impl<Arch> Drop for VmiXenDriver<Arch>
63where
64    Arch: ArchAdapter,
65{
66    fn drop(&mut self) {
67        let max_memkb = self.info.max_pages * Arch::PAGE_SIZE / 1024;
68
69        let _ = self.domain.set_max_mem(max_memkb);
70        let _ = self.monitor.emul_unimplemented(false);
71        let _ = self.monitor.inguest_pagefault(false);
72    }
73}
74
75impl<Arch> VmiXenDriver<Arch>
76where
77    Arch: ArchAdapter,
78{
79    /// Creates a new VMI driver for Xen hypervisor.
80    pub fn new(domain_id: XenDomainId) -> Result<Self, VmiError> {
81        let xc = XenControl::new().map_err(VmiError::driver)?;
82        let domain = xc.domain(domain_id).map_err(VmiError::driver)?;
83        domain.set_max_mem(u64::MAX).map_err(VmiError::driver)?;
84
85        let devicemodel = domain.device_model().map_err(VmiError::driver)?;
86        let (monitor, ring) = domain.monitor().map_err(VmiError::driver)?;
87        let altp2m = domain.altp2m().map_err(VmiError::driver)?;
88        let evtchn = monitor.channel().map_err(VmiError::driver)?;
89        let foreign_memory = XenForeignMemory::new().map_err(VmiError::driver)?;
90        let info = domain.info().map_err(VmiError::driver)?;
91
92        monitor.inguest_pagefault(true).map_err(VmiError::driver)?;
93        monitor.emul_unimplemented(true).map_err(VmiError::driver)?;
94
95        Ok(Self {
96            domain,
97            devicemodel,
98            monitor,
99            altp2m,
100            evtchn,
101            foreign_memory,
102            info,
103            ring: RefCell::new(ring),
104            views: RefCell::new(HashMap::new()),
105            event_processing_overhead: RefCell::new(Duration::ZERO),
106        })
107    }
108
109    /// Creates a new VMI driver from environment variables.
110    ///
111    /// The first of the following variables that is **set** selects the target
112    /// domain.
113    ///
114    /// - `VMI_XEN_DOMAIN`: interpreted as a numeric domain ID, falling back to
115    ///   a domain name if the value is not numeric.
116    /// - `VMI_XEN_DOMAIN_ID`: interpreted as a numeric domain ID.
117    /// - `VMI_XEN_DOMAIN_NAME`: interpreted as a domain name.
118    ///
119    /// Returns `Ok(None)` if none of the variables are set, or if the selected
120    /// variable does not resolve to a domain.
121    pub fn try_from_env() -> Result<Option<Self>, VmiError> {
122        fn resolve_domain(domain_name: &str) -> Result<Option<XenDomainId>, VmiError> {
123            XenStore::new()
124                .map_err(VmiError::driver)?
125                .domain_id_from_name(domain_name)
126                .map_err(VmiError::driver)
127        }
128
129        if let Ok(var) = std::env::var("VMI_XEN_DOMAIN") {
130            if let Ok(domain_id) = var.parse() {
131                return Ok(Some(Self::new(XenDomainId(domain_id))?));
132            }
133
134            if let Some(domain_id) = resolve_domain(&var)? {
135                return Ok(Some(Self::new(domain_id)?));
136            }
137        }
138        else if let Ok(var) = std::env::var("VMI_XEN_DOMAIN_ID") {
139            match var.parse() {
140                Ok(domain_id) => return Ok(Some(Self::new(XenDomainId(domain_id))?)),
141                Err(err) => tracing::error!(%err, var, "invalid value for VMI_XEN_DOMAIN_ID"),
142            }
143        }
144        else if let Ok(var) = std::env::var("VMI_XEN_DOMAIN_NAME")
145            && let Some(domain_id) = resolve_domain(&var)?
146        {
147            return Ok(Some(Self::new(domain_id)?));
148        }
149
150        Ok(None)
151    }
152}
153
154impl<Arch> VmiDriver for VmiXenDriver<Arch>
155where
156    Arch: ArchAdapter,
157{
158    type Architecture = Arch;
159
160    fn info(&self) -> Result<VmiInfo, VmiError> {
161        Ok(VmiInfo {
162            page_size: Arch::PAGE_SIZE,
163            page_shift: Arch::PAGE_SHIFT,
164            max_gfn: Gfn::new(self.domain.maximum_gpfn().map_err(VmiError::driver)?),
165            vcpus: self.info.max_vcpu_id + 1,
166        })
167    }
168}
169
170impl<Arch> VmiRead for VmiXenDriver<Arch>
171where
172    Arch: ArchAdapter,
173{
174    fn read_page(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError> {
175        let page = self
176            .foreign_memory
177            .map(
178                self.domain.id(),
179                XenForeignMemoryProtection::READ,
180                &[u64::from(gfn)],
181                None,
182            )
183            .map_err(VmiError::driver)?;
184
185        Ok(VmiMappedPage::new(page))
186    }
187}
188
189impl<Arch> VmiWrite for VmiXenDriver<Arch>
190where
191    Arch: ArchAdapter,
192{
193    fn write_page(&self, gfn: Gfn, offset: u64, content: &[u8]) -> Result<VmiMappedPage, VmiError> {
194        let mut page = self
195            .foreign_memory
196            .map(
197                self.domain.id(),
198                XenForeignMemoryProtection::WRITE,
199                &[u64::from(gfn)],
200                None,
201            )
202            .map_err(VmiError::driver)?;
203
204        let offset = offset as usize;
205        if offset + content.len() > Arch::PAGE_SIZE as usize {
206            return Err(VmiError::OutOfBounds);
207        }
208
209        page[offset..offset + content.len()].copy_from_slice(content);
210
211        Ok(VmiMappedPage::new(page))
212    }
213}
214
215impl<Arch> VmiQueryProtection for VmiXenDriver<Arch>
216where
217    Arch: ArchAdapter,
218{
219    fn memory_access(&self, gfn: Gfn, view: View) -> Result<MemoryAccess, VmiError> {
220        Ok(self.memory_access_with_options(gfn, view)?.0)
221    }
222
223    fn memory_access_with_options(
224        &self,
225        gfn: Gfn,
226        view: View,
227    ) -> Result<(MemoryAccess, MemoryAccessOptions), VmiError> {
228        // As of Xen 4.22, xc_altp2m_get_mem_access() reports ESRCH for a valid
229        // GFN whose entry has not been materialized in a lazy altp2m view.
230        // Ideally, Xen would return the view's effective default access in
231        // this case. Until it does, validate the GFN through the domain p2m,
232        // discard its unrelated access value and fall back to the default
233        // access recorded for the view. Materialized Xen modes are decoded
234        // into both permissions and options.
235
236        if view.0 == 0 {
237            let xen_access = self
238                .domain
239                .get_mem_access(gfn.0)
240                .map_err(VmiError::driver)?;
241
242            let access = MemoryAccess::from_ext(xen_access);
243            let options = MemoryAccessOptions::from_ext(xen_access);
244
245            tracing::trace!(%gfn, %view, ?xen_access, "get memory access");
246
247            return Ok((access, options));
248        }
249
250        let views = self.views.borrow();
251        let xen_view = match views.get(&view.0) {
252            Some(view) => view,
253            None => return Err(VmiError::ViewNotFound),
254        };
255
256        let xen_access = match xen_view.view.get_mem_access(gfn.0) {
257            Ok(xen_access) => xen_access,
258            Err(err) if is_missing_altp2m_entry(&err) => {
259                // Confirm the GFN exists in the base p2m before treating ESRCH
260                // as a lazy, unmaterialized altp2m entry.
261                self.domain
262                    .get_mem_access(gfn.0)
263                    .map_err(VmiError::driver)?;
264
265                let xen_access = xen_view.default_access;
266
267                let access = MemoryAccess::from_ext(xen_access);
268                let options = MemoryAccessOptions::from_ext(xen_access);
269
270                tracing::trace!(%gfn, %view, ?xen_access, "get memory access (default)");
271
272                return Ok((access, options));
273            }
274            Err(err) => return Err(VmiError::driver(err)),
275        };
276
277        let access = MemoryAccess::from_ext(xen_access);
278        let options = MemoryAccessOptions::from_ext(xen_access);
279
280        tracing::trace!(%gfn, %view, ?xen_access, "get memory access");
281
282        Ok((access, options))
283    }
284}
285
286impl<Arch> VmiSetProtection for VmiXenDriver<Arch>
287where
288    Arch: ArchAdapter,
289{
290    fn set_memory_access(
291        &self,
292        gfn: Gfn,
293        view: View,
294        access: MemoryAccess,
295    ) -> Result<(), VmiError> {
296        tracing::trace!(%gfn, %view, %access, "set memory access");
297
298        if view.0 == 0 {
299            return self
300                .domain
301                .set_mem_access(gfn.into(), access.into_ext())
302                .map_err(VmiError::driver);
303        }
304
305        match self.views.borrow().get(&view.0) {
306            Some(view) => view
307                .view
308                .set_mem_access(gfn.into(), access.into_ext())
309                .map_err(VmiError::driver),
310            None => Err(VmiError::ViewNotFound),
311        }
312    }
313
314    fn set_memory_access_with_options(
315        &self,
316        gfn: Gfn,
317        view: View,
318        access: MemoryAccess,
319        options: MemoryAccessOptions,
320    ) -> Result<(), VmiError> {
321        tracing::trace!(%gfn, %view, %access, "set memory access");
322
323        let mut xen_access = access.into_ext();
324
325        if options.contains(MemoryAccessOptions::IGNORE_PAGE_WALK_UPDATES) {
326            if access != MemoryAccess::R {
327                return Err(VmiError::NotSupported);
328            }
329
330            xen_access = xen::MemoryAccess::R_PW;
331        }
332
333        if view.0 == 0 {
334            return self
335                .domain
336                .set_mem_access(gfn.into(), xen_access)
337                .map_err(VmiError::driver);
338        }
339
340        match self.views.borrow().get(&view.0) {
341            Some(view) => view
342                .view
343                .set_mem_access(gfn.into(), xen_access)
344                .map_err(VmiError::driver),
345            None => Err(VmiError::ViewNotFound),
346        }
347    }
348}
349
350impl<Arch> VmiQueryRegisters for VmiXenDriver<Arch>
351where
352    Arch: ArchAdapter,
353{
354    fn registers(&self, vcpu: VcpuId) -> Result<Arch::Registers, VmiError> {
355        Arch::registers(self, vcpu)
356    }
357}
358
359impl<Arch> VmiSetRegisters for VmiXenDriver<Arch>
360where
361    Arch: ArchAdapter,
362{
363    fn set_registers(&self, vcpu: VcpuId, registers: Arch::Registers) -> Result<(), VmiError> {
364        Arch::set_registers(self, vcpu, registers)
365    }
366}
367
368impl<Arch> VmiViewControl for VmiXenDriver<Arch>
369where
370    Arch: ArchAdapter,
371{
372    fn default_view(&self) -> View {
373        View(0)
374    }
375
376    fn create_view(&self, default_access: MemoryAccess) -> Result<View, VmiError> {
377        let default_access = default_access.into_ext();
378
379        let view = self
380            .altp2m
381            .create_view(default_access)
382            .map_err(VmiError::driver)?;
383
384        let id = view.id();
385        self.views.borrow_mut().insert(
386            id,
387            XenView {
388                view,
389                default_access,
390            },
391        );
392
393        Ok(View(id))
394    }
395
396    fn destroy_view(&self, view: View) -> Result<(), VmiError> {
397        if view.0 == 0 {
398            return Ok(());
399        }
400
401        match self.views.borrow_mut().remove(&view.0) {
402            // View is destroyed automatically when it goes out of scope
403            Some(_view) => Ok(()),
404            None => Err(VmiError::ViewNotFound),
405        }
406    }
407
408    fn switch_to_view(&self, view: View) -> Result<(), VmiError> {
409        if view.0 == 0 {
410            return self.altp2m.reset_view().map_err(VmiError::driver);
411        }
412
413        match self.views.borrow().get(&view.0) {
414            Some(view) => view.view.switch().map_err(VmiError::driver),
415            None => Err(VmiError::ViewNotFound),
416        }
417    }
418
419    fn change_view_gfn(&self, view: View, old_gfn: Gfn, new_gfn: Gfn) -> Result<(), VmiError> {
420        if view.0 == 0 {
421            return Ok(());
422        }
423
424        match self.views.borrow().get(&view.0) {
425            // WARNING: This will change access permissions of the GFN!
426            Some(view) => view
427                .view
428                .change_gfn(old_gfn.into(), new_gfn.into())
429                .map_err(VmiError::driver),
430            None => Err(VmiError::ViewNotFound),
431        }
432    }
433
434    fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError> {
435        if view.0 == 0 {
436            return Ok(());
437        }
438
439        match self.views.borrow().get(&view.0) {
440            // WARNING: This will change access permissions of the GFN!
441            Some(view) => view
442                .view
443                .change_gfn(gfn.into(), u64::MAX)
444                .map_err(VmiError::driver),
445            None => Err(VmiError::ViewNotFound),
446        }
447    }
448}
449
450impl<Arch> VmiEventControl for VmiXenDriver<Arch>
451where
452    Arch: ArchAdapter,
453{
454    fn monitor_enable(&self, option: Arch::EventMonitor) -> Result<(), VmiError> {
455        Arch::monitor_enable(self, option)
456    }
457
458    fn monitor_disable(&self, option: Arch::EventMonitor) -> Result<(), VmiError> {
459        Arch::monitor_disable(self, option)
460    }
461
462    fn events_pending(&self) -> usize {
463        self.ring.borrow().unconsumed_requests()
464    }
465
466    fn event_processing_overhead(&self) -> Duration {
467        *self.event_processing_overhead.borrow()
468    }
469
470    fn wait_for_event(
471        &self,
472        timeout: Duration,
473        mut handler: impl FnMut(&VmiEvent<Arch>) -> VmiEventResponse<Arch>,
474    ) -> Result<(), VmiError> {
475        let mut fds = [libc::pollfd {
476            fd: self.evtchn.as_raw_fd(),
477            events: libc::POLLIN | libc::POLLERR,
478            revents: 0,
479        }];
480
481        let timeout = timeout
482            .as_millis()
483            .try_into()
484            .map_err(|_| VmiError::InvalidTimeout)?;
485
486        #[rustfmt::skip]
487        let poll_result = unsafe {
488            libc::poll(
489                fds.as_mut_ptr() as _,
490                fds.len() as _,
491                timeout
492            )
493        };
494
495        match poll_result {
496            0 => return Err(VmiError::Timeout),
497            -1 => return Err(VmiError::Io(std::io::Error::last_os_error())),
498            _ => (),
499        }
500
501        struct OverheadGuard<'a, Arch>
502        where
503            Arch: ArchAdapter,
504        {
505            driver: &'a VmiXenDriver<Arch>,
506            start: Instant,
507        }
508
509        impl<'a, Arch> OverheadGuard<'a, Arch>
510        where
511            Arch: ArchAdapter,
512        {
513            fn new(driver: &'a VmiXenDriver<Arch>) -> Self {
514                Self {
515                    driver,
516                    start: Instant::now(),
517                }
518            }
519        }
520
521        impl<Arch> Drop for OverheadGuard<'_, Arch>
522        where
523            Arch: ArchAdapter,
524        {
525            fn drop(&mut self) {
526                let elapsed = Instant::now().duration_since(self.start);
527                *self.driver.event_processing_overhead.borrow_mut() += elapsed;
528            }
529        }
530
531        self.evtchn.wait().map_err(VmiError::driver)?;
532
533        {
534            let _overhead_guard = OverheadGuard::new(self);
535
536            while self.ring.borrow().has_unconsumed_requests() {
537                let mut event = self.ring.borrow_mut().get_request();
538                Arch::process_event(self, &mut event, &mut handler)?;
539                self.ring.borrow_mut().put_response(event);
540            }
541        }
542
543        self.evtchn.notify().map_err(VmiError::driver)?;
544
545        Ok(())
546    }
547}
548
549impl<Arch> VmiVmControl for VmiXenDriver<Arch>
550where
551    Arch: ArchAdapter,
552{
553    fn pause(&self) -> Result<(), VmiError> {
554        self.domain.pause().map_err(VmiError::driver)
555    }
556
557    fn resume(&self) -> Result<(), VmiError> {
558        self.domain.unpause().map_err(VmiError::driver)
559    }
560
561    fn allocate_gfn(&self) -> Result<Gfn, VmiError> {
562        let gfn = Gfn::new(self.domain.maximum_gpfn().map_err(VmiError::driver)?) + 1;
563        self.allocate_gfn_at(gfn)?;
564        Ok(gfn)
565    }
566
567    fn allocate_gfn_at(&self, gfn: Gfn) -> Result<(), VmiError> {
568        self.domain
569            .populate_physmap_exact(0, 0, &[gfn.into()])
570            .map_err(VmiError::driver)
571    }
572
573    fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError> {
574        self.domain
575            .decrease_reservation_exact(0, &[gfn.into()])
576            .map_err(VmiError::driver)
577    }
578
579    fn inject_interrupt(&self, vcpu: VcpuId, interrupt: Arch::Interrupt) -> Result<(), VmiError> {
580        Arch::inject_interrupt(self, vcpu, interrupt)
581    }
582
583    fn reset_state(&self) -> Result<(), VmiError> {
584        Arch::reset_state(self)
585    }
586}
587
588#[cfg(test)]
589mod tests {
590    use super::is_missing_altp2m_entry;
591
592    #[test]
593    fn identifies_missing_altp2m_entries() {
594        let missing = xen::XenError::Io(std::io::Error::from_raw_os_error(libc::ESRCH));
595        let invalid = xen::XenError::Io(std::io::Error::from_raw_os_error(libc::EINVAL));
596
597        assert!(is_missing_altp2m_entry(&missing));
598        assert!(!is_missing_altp2m_entry(&invalid));
599        assert!(!is_missing_altp2m_entry(&xen::XenError::Other(
600            "other error"
601        )));
602    }
603}