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