Skip to main content

underhill_mem/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Underhill VM memory management.
5
6#![cfg(target_os = "linux")]
7
8mod init;
9mod mapping;
10mod registrar;
11
12pub use init::BootInit;
13pub use init::Init;
14pub use init::MemoryMappings;
15pub use init::init;
16
17use aarch64defs::rsi::CcaMemPermIndex;
18use cvm_tracing::CVM_ALLOWED;
19use guestmem::GuestMemoryBackingError;
20use guestmem::PAGE_SIZE;
21use guestmem::ranges::PagedRange;
22use hcl::GuestVtl;
23use hcl::ioctl::AcceptPagesError;
24use hcl::ioctl::ApplyVtlProtectionsError;
25use hcl::ioctl::Mshv;
26use hcl::ioctl::MshvHvcall;
27use hcl::ioctl::MshvVtl;
28use hcl::ioctl::snp::SnpPageError;
29use hv1_structs::VtlArray;
30use hvdef::HV_MAP_GPA_PERMISSIONS_ALL;
31use hvdef::HV_MAP_GPA_PERMISSIONS_NONE;
32use hvdef::HV_PAGE_SHIFT;
33use hvdef::HV_PAGE_SIZE;
34use hvdef::HvError;
35use hvdef::HvMapGpaFlags;
36use hvdef::HypercallCode;
37use hvdef::hypercall::AcceptMemoryType;
38use hvdef::hypercall::HostVisibilityType;
39use hvdef::hypercall::HvInputVtl;
40use mapping::GuestMemoryMapping;
41use mapping::GuestValidMemory;
42use memory_range::MemoryRange;
43use parking_lot::Mutex;
44use parking_lot::MutexGuard;
45use registrar::RegisterMemory;
46use std::collections::VecDeque;
47use std::sync::Arc;
48use std::sync::atomic::AtomicBool;
49use thiserror::Error;
50use virt::IsolationType;
51use virt_mshv_vtl::GpnSource;
52use virt_mshv_vtl::ProtectIsolatedMemory;
53use virt_mshv_vtl::TlbFlushLockAccess;
54use vm_topology::memory::MemoryLayout;
55use x86defs::snp::SevRmpAdjust;
56use x86defs::tdx::GpaVmAttributes;
57use x86defs::tdx::GpaVmAttributesMask;
58use x86defs::tdx::TdgMemPageAttrWriteR8;
59use x86defs::tdx::TdgMemPageGpaAttr;
60
61/// Error querying vtl permissions on a page
62#[derive(Debug, Error)]
63pub enum QueryVtlPermissionsError {
64    /// An SNP-specific error
65    #[error("failed to query rmp permissions")]
66    Snp(#[source] SnpPageError),
67}
68
69#[derive(Debug)]
70struct MshvVtlWithPolicy {
71    mshv_vtl: MshvVtl,
72    ignore_registration_failure: bool,
73    shared: bool,
74}
75
76impl RegisterMemory for MshvVtlWithPolicy {
77    fn register_range(&self, range: MemoryRange) -> Result<(), impl 'static + std::error::Error> {
78        match self.mshv_vtl.add_vtl0_memory(range, self.shared) {
79            Ok(()) => Ok(()),
80            // TODO: remove this once the kernel driver tracks registration
81            Err(err) if self.ignore_registration_failure => {
82                tracing::warn!(
83                    CVM_ALLOWED,
84                    error = &err as &dyn std::error::Error,
85                    "registration failure, could be expected"
86                );
87                Ok(())
88            }
89            Err(err) => Err(err),
90        }
91    }
92}
93
94#[derive(Debug, Error)]
95#[error("failed to register memory with kernel")]
96struct RegistrationError;
97
98/// Currently built for hardware CVMs, which only define permissions for VTL
99/// 0 and VTL 1 to express what those VTLs have access to. If this were to
100/// extend to non-hardware CVMs, those would need to define permissions
101/// instead for VTL 2 and VTL 1 to express what the lower VTLs have access
102/// to.
103///
104/// Default VTL memory permissions applied to any mapped memory
105struct DefaultVtlPermissions {
106    vtl0: HvMapGpaFlags,
107    vtl1: Option<HvMapGpaFlags>,
108}
109
110impl DefaultVtlPermissions {
111    fn set(&mut self, vtl: GuestVtl, permissions: HvMapGpaFlags) {
112        match vtl {
113            GuestVtl::Vtl0 => self.vtl0 = permissions,
114            GuestVtl::Vtl1 => self.vtl1 = Some(permissions),
115        }
116    }
117}
118
119/// Represents the vtl permissions on a page for a given isolation type
120#[derive(Copy, Clone)]
121enum GpaVtlPermissions {
122    Vbs(HvMapGpaFlags),
123    Snp(SevRmpAdjust),
124    Tdx(TdgMemPageGpaAttr, TdgMemPageAttrWriteR8),
125    // TODO: CCA: we need to use the 'vtl' and 'protections' below to get the correct index
126    // This implies that we've set up the index list properly, and we just select the right one here
127    Cca(CcaMemPermIndex),
128}
129
130impl GpaVtlPermissions {
131    fn new(isolation: IsolationType, vtl: GuestVtl, protections: HvMapGpaFlags) -> Self {
132        match isolation {
133            IsolationType::None => unreachable!(),
134            IsolationType::Vbs => GpaVtlPermissions::Vbs(protections),
135            IsolationType::Snp => {
136                let mut vtl_permissions = GpaVtlPermissions::Snp(SevRmpAdjust::new());
137                vtl_permissions.set(vtl, protections);
138                vtl_permissions
139            }
140            IsolationType::Tdx => {
141                let mut vtl_permissions =
142                    GpaVtlPermissions::Tdx(TdgMemPageGpaAttr::new(), TdgMemPageAttrWriteR8::new());
143                vtl_permissions.set(vtl, protections);
144                vtl_permissions
145            }
146            IsolationType::Cca => {
147                let mut vtl_permissions = GpaVtlPermissions::Cca(CcaMemPermIndex::default());
148                vtl_permissions.set(vtl, protections);
149                vtl_permissions
150            }
151        }
152    }
153
154    fn set(&mut self, vtl: GuestVtl, protections: HvMapGpaFlags) {
155        match self {
156            GpaVtlPermissions::Vbs(flags) => *flags = protections,
157            GpaVtlPermissions::Snp(rmpadjust) => {
158                *rmpadjust = SevRmpAdjust::new()
159                    .with_enable_read(protections.readable())
160                    .with_enable_write(protections.writable())
161                    .with_enable_user_execute(protections.user_executable())
162                    .with_enable_kernel_execute(protections.kernel_executable())
163                    .with_target_vmpl(match vtl {
164                        GuestVtl::Vtl0 => x86defs::snp::Vmpl::Vmpl2.into(),
165                        GuestVtl::Vtl1 => x86defs::snp::Vmpl::Vmpl1.into(),
166                    });
167            }
168            GpaVtlPermissions::Tdx(attributes, mask) => {
169                let vm_attributes = GpaVmAttributes::new()
170                    .with_valid(true)
171                    .with_read(protections.readable())
172                    .with_write(protections.writable())
173                    .with_kernel_execute(protections.kernel_executable())
174                    .with_user_execute(protections.user_executable());
175
176                let (new_attributes, new_mask) = match vtl {
177                    GuestVtl::Vtl0 => {
178                        let attributes = TdgMemPageGpaAttr::new().with_l2_vm1(vm_attributes);
179                        let mask = TdgMemPageAttrWriteR8::new()
180                            .with_l2_vm1(GpaVmAttributesMask::ALL_CHANGED);
181                        (attributes, mask)
182                    }
183                    GuestVtl::Vtl1 => {
184                        let attributes = TdgMemPageGpaAttr::new().with_l2_vm2(vm_attributes);
185                        let mask = TdgMemPageAttrWriteR8::new()
186                            .with_l2_vm2(GpaVmAttributesMask::ALL_CHANGED);
187                        (attributes, mask)
188                    }
189                };
190
191                *attributes = new_attributes;
192                *mask = new_mask;
193            }
194            GpaVtlPermissions::Cca(_index) => {
195                tracing::debug!("cca: GpaVtlPermissions::set is doing nothing now");
196            }
197        }
198    }
199}
200
201/// Error returned when modifying gpa visibility.
202#[derive(Debug, Error)]
203#[error("failed to modify gpa visibility, elements successfully processed {processed}")]
204pub struct ModifyGpaVisibilityError {
205    source: HvError,
206    processed: usize,
207}
208
209#[derive(Default)]
210struct MemoryAcceptorFlags {
211    tdx_page_release_required: bool,
212}
213
214/// Interface to accept and manipulate lower VTL memory acceptance and page
215/// protections.
216///
217/// FUTURE: this should go away as a separate object once all the logic is moved
218/// into this crate.
219pub struct MemoryAcceptor {
220    mshv_hvcall: MshvHvcall,
221    mshv_vtl: MshvVtl,
222    isolation: IsolationType,
223    flags: MemoryAcceptorFlags,
224}
225
226impl MemoryAcceptor {
227    /// Create a new instance.
228    pub fn new(isolation: IsolationType) -> Result<Self, hcl::ioctl::Error> {
229        let mshv = Mshv::new()?;
230        let mshv_vtl = mshv.create_vtl()?;
231        let mshv_hvcall = MshvHvcall::new()?;
232        mshv_hvcall.set_allowed_hypercalls(&[
233            HypercallCode::HvCallAcceptGpaPages,
234            HypercallCode::HvCallModifySparseGpaPageHostVisibility,
235            HypercallCode::HvCallModifyVtlProtectionMask,
236        ]);
237
238        let mut flags = MemoryAcceptorFlags::default();
239
240        if isolation == IsolationType::Tdx {
241            // Check if TDX Connect is enabled on this TD. If so, page release is required when
242            // unaccepting pages.
243            let config_flags = mshv_vtl.tdx_get_config_flags();
244            if config_flags.tdx_connect() {
245                assert!(
246                    config_flags.page_release(),
247                    "TDX Connect enabled but CONFIG_FLAGS.page_release is not set",
248                );
249
250                flags.tdx_page_release_required = true;
251            }
252        }
253
254        // On boot, VTL 0 should have permissions.
255        Ok(Self {
256            mshv_hvcall,
257            mshv_vtl,
258            isolation,
259            flags,
260        })
261    }
262
263    /// Accept pages for lower VTLs.
264    pub fn accept_lower_vtl_pages(&self, range: MemoryRange) -> Result<(), AcceptPagesError> {
265        match self.isolation {
266            IsolationType::None => unreachable!(),
267            IsolationType::Vbs => self
268                .mshv_hvcall
269                .accept_gpa_pages(range, AcceptMemoryType::RAM),
270            IsolationType::Snp => {
271                self.mshv_vtl
272                    .pvalidate_pages(range, true, false)
273                    .map_err(|err| AcceptPagesError::Snp {
274                        failed_operation: err,
275                        range,
276                    })
277            }
278            IsolationType::Tdx => {
279                let attributes = TdgMemPageGpaAttr::new().with_l2_vm1(GpaVmAttributes::FULL_ACCESS);
280                let mask =
281                    TdgMemPageAttrWriteR8::new().with_l2_vm1(GpaVmAttributesMask::ALL_CHANGED);
282
283                self.mshv_vtl
284                    .tdx_accept_pages(range, Some((attributes, mask)))
285                    .map_err(|err| AcceptPagesError::Tdx { error: err, range })
286            }
287            IsolationType::Cca => {
288                // TODO: CCA: do we need to set RIPAS here?
289                Ok(())
290            }
291        }
292    }
293
294    fn unaccept_lower_vtl_pages(&self, range: MemoryRange) {
295        match self.isolation {
296            IsolationType::None => unreachable!(),
297            IsolationType::Vbs => {
298                // TODO VBS: is there something to do here?
299            }
300            IsolationType::Snp => {
301                // Revoke permissions before unaccepting pages. This is required
302                // because a subsequent page acceptance is not guaranteed to
303                // reset permissions unless the hypervisor executed RMPUPDATE,
304                // which it cannot be trusted to do. We set new permissions
305                // ourselves, but that still leaves open a tiny window where the
306                // guest could access the pages with the old permissions.
307                for lower_vtl in [GuestVtl::Vtl0, GuestVtl::Vtl1] {
308                    self.apply_protections(range, lower_vtl, HV_MAP_GPA_PERMISSIONS_NONE)
309                        .unwrap();
310                }
311                self.mshv_vtl.pvalidate_pages(range, false, false).unwrap()
312            }
313
314            IsolationType::Tdx => {
315                if self.flags.tdx_page_release_required {
316                    self.mshv_vtl
317                        .tdx_release_pages(range)
318                        .unwrap_or_else(|e| panic!("Failed to release memory range {range}: {e}"));
319                }
320            }
321            IsolationType::Cca => {
322                // TODO: CCA: anything to do here?
323            }
324        }
325    }
326
327    /// Tell the host to change the visibility of the given GPAs.
328    pub fn modify_gpa_visibility(
329        &self,
330        host_visibility: HostVisibilityType,
331        gpns: &[u64],
332    ) -> Result<(), ModifyGpaVisibilityError> {
333        self.mshv_hvcall
334            .modify_gpa_visibility(host_visibility, gpns)
335            .map_err(|(e, processed)| ModifyGpaVisibilityError {
336                source: e,
337                processed,
338            })
339    }
340
341    /// Apply the initial protections on lower-vtl memory.
342    ///
343    /// After initialization, the default protections should be applied.
344    pub fn apply_initial_lower_vtl_protections(
345        &self,
346        range: MemoryRange,
347    ) -> Result<(), ApplyVtlProtectionsError> {
348        self.apply_protections(range, GuestVtl::Vtl0, HV_MAP_GPA_PERMISSIONS_ALL)
349    }
350
351    fn apply_protections(
352        &self,
353        range: MemoryRange,
354        vtl: GuestVtl,
355        flags: HvMapGpaFlags,
356    ) -> Result<(), ApplyVtlProtectionsError> {
357        let permissions = GpaVtlPermissions::new(self.isolation, vtl, flags);
358
359        match permissions {
360            GpaVtlPermissions::Vbs(flags) => {
361                // For VBS-isolated VMs, the permissions apply to all lower
362                // VTLs. Therefore VTL 0 cannot set its own permissions.
363                assert_ne!(vtl, GuestVtl::Vtl0);
364
365                self.mshv_hvcall
366                    .modify_vtl_protection_mask(range, flags, HvInputVtl::from(vtl))
367            }
368            GpaVtlPermissions::Snp(rmpadjust) => {
369                // For SNP VMs, the permissions apply to the specified VTL.
370                // Therefore VTL 2 cannot specify its own permissions.
371                self.mshv_vtl
372                    .rmpadjust_pages(range, rmpadjust, false)
373                    .map_err(|err| ApplyVtlProtectionsError::Snp {
374                        failed_operation: err,
375                        range,
376                        permissions: rmpadjust,
377                        vtl: vtl.into(),
378                    })
379            }
380            GpaVtlPermissions::Tdx(attributes, mask) => {
381                // For TDX VMs, the permissions apply to the specified VTL.
382                // Therefore VTL 2 cannot specify its own permissions.
383                self.mshv_vtl
384                    .tdx_set_page_attributes(range, attributes, mask)
385                    .map_err(|err| ApplyVtlProtectionsError::Tdx {
386                        error: err,
387                        range,
388                        permissions: attributes,
389                        vtl: vtl.into(),
390                    })
391            }
392            GpaVtlPermissions::Cca(_index) => {
393                self.mshv_vtl.rsi_set_mem_perm(vtl, &range).map_err(|_err| {
394                    ApplyVtlProtectionsError::Cca {
395                        range,
396                        vtl: vtl.into(),
397                    }
398                })
399            }
400        }
401    }
402}
403
404/// An implementation of [`ProtectIsolatedMemory`] for Underhill VMs.
405pub struct HardwareIsolatedMemoryProtector {
406    // Serves as a lock for synchronizing visibility and page-protection changes.
407    inner: Mutex<HardwareIsolatedMemoryProtectorInner>,
408    layout: MemoryLayout,
409    acceptor: Arc<MemoryAcceptor>,
410    vtl0: Arc<GuestMemoryMapping>,
411    vtl1_protections_enabled: AtomicBool,
412}
413
414struct HardwareIsolatedMemoryProtectorInner {
415    valid_encrypted: Arc<GuestValidMemory>,
416    valid_shared: Arc<GuestValidMemory>,
417    encrypted: Arc<GuestMemoryMapping>,
418    default_vtl_permissions: DefaultVtlPermissions,
419    overlay_pages: VtlArray<Vec<OverlayPage>, 2>,
420    locked_pages: VtlArray<Vec<Box<[u64]>>, 2>,
421}
422
423struct OverlayPage {
424    gpn: u64,
425    previous_permissions: HvMapGpaFlags,
426    overlay_permissions: HvMapGpaFlags,
427    ref_count: u16,
428    gpn_source: GpnSource,
429}
430
431impl HardwareIsolatedMemoryProtector {
432    /// Returns a new instance.
433    ///
434    /// `shared` provides the mapping for shared memory. `vtl0` provides the
435    /// mapping for encrypted memory.
436    pub fn new(
437        valid_encrypted: Arc<GuestValidMemory>,
438        valid_shared: Arc<GuestValidMemory>,
439        encrypted: Arc<GuestMemoryMapping>,
440        vtl0: Arc<GuestMemoryMapping>,
441        layout: MemoryLayout,
442        acceptor: Arc<MemoryAcceptor>,
443    ) -> Self {
444        Self {
445            inner: Mutex::new(HardwareIsolatedMemoryProtectorInner {
446                valid_encrypted,
447                valid_shared,
448                encrypted,
449                // Grant only VTL 0 all permissions. This will be altered
450                // later by VTL 1 enablement and by VTL 1 itself.
451                default_vtl_permissions: DefaultVtlPermissions {
452                    vtl0: HV_MAP_GPA_PERMISSIONS_ALL,
453                    vtl1: None,
454                },
455                overlay_pages: VtlArray::from_fn(|_| Vec::new()),
456                locked_pages: VtlArray::from_fn(|_| Vec::new()),
457            }),
458            layout,
459            acceptor,
460            vtl0,
461            vtl1_protections_enabled: AtomicBool::new(false),
462        }
463    }
464
465    fn apply_protections_with_overlay_handling(
466        &self,
467        range: MemoryRange,
468        target_vtl: GuestVtl,
469        protections: HvMapGpaFlags,
470        inner: &mut MutexGuard<'_, HardwareIsolatedMemoryProtectorInner>,
471    ) -> Result<(), ApplyVtlProtectionsError> {
472        let mut range_queue = VecDeque::new();
473        range_queue.push_back(range);
474
475        'outer: while let Some(range) = range_queue.pop_front() {
476            for overlay_page in inner.overlay_pages[target_vtl].iter_mut() {
477                let overlay_addr = overlay_page.gpn * HV_PAGE_SIZE;
478                if range.contains_addr(overlay_addr) {
479                    // If the overlay page is within the range, update the
480                    // permissions that will be restored when it is unlocked.
481                    overlay_page.previous_permissions = protections;
482                    // And split the range around it.
483                    let (left, right_with_overlay) =
484                        range.split_at_offset(range.offset_of(overlay_addr).unwrap());
485                    let (overlay, right) = right_with_overlay.split_at_offset(HV_PAGE_SIZE);
486                    debug_assert_eq!(overlay.start_4k_gpn(), overlay_page.gpn);
487                    debug_assert_eq!(overlay.len(), HV_PAGE_SIZE);
488                    if !left.is_empty() {
489                        range_queue.push_back(left);
490                    }
491                    if !right.is_empty() {
492                        range_queue.push_back(right);
493                    }
494                    continue 'outer;
495                }
496            }
497            // We can only reach here if the range does not contain any overlay
498            // pages, so now we can apply the protections to the range.
499            self.apply_protections(range, target_vtl, protections, GpnSource::GuestMemory)?
500        }
501
502        Ok(())
503    }
504
505    fn apply_protections(
506        &self,
507        range: MemoryRange,
508        target_vtl: GuestVtl,
509        protections: HvMapGpaFlags,
510        gpn_source: GpnSource,
511    ) -> Result<(), ApplyVtlProtectionsError> {
512        if gpn_source == GpnSource::GuestMemory && target_vtl == GuestVtl::Vtl0 {
513            // Only permissions imposed on VTL 0 guest memory are explicitly tracked
514            self.vtl0.update_permission_bitmaps(range, protections);
515        }
516        self.acceptor
517            .apply_protections(range, target_vtl, protections)
518    }
519
520    /// Get the permissions that the given VTL has to the given GPN.
521    ///
522    /// This function does not check for any protections applied by VTL 2,
523    /// only those applied by lower VTLs.
524    fn query_lower_vtl_permissions(
525        &self,
526        vtl: GuestVtl,
527        gpn: u64,
528    ) -> Result<HvMapGpaFlags, HvError> {
529        if !self.is_in_guest_memory(gpn) {
530            return Err(HvError::OperationDenied);
531        }
532
533        let res = match vtl {
534            GuestVtl::Vtl0 => self
535                .vtl0
536                .query_access_permission(gpn)
537                .unwrap_or(HV_MAP_GPA_PERMISSIONS_ALL),
538            GuestVtl::Vtl1 => HV_MAP_GPA_PERMISSIONS_ALL,
539        };
540
541        Ok(res)
542    }
543
544    fn check_gpn_not_locked(
545        &self,
546        inner: &MutexGuard<'_, HardwareIsolatedMemoryProtectorInner>,
547        vtl: GuestVtl,
548        gpn: u64,
549    ) -> Result<(), HvError> {
550        // Overlay pages have special handling, being locked does not prevent that.
551        // TODO: When uh_mem implements the returning of overlay pages, rather than
552        // requiring them to also be locked through guestmem, the check for overlay
553        // pages can be removed, as locked and overlay pages will be mutually exclusive.
554        if inner.locked_pages[vtl].iter().flatten().any(|x| *x == gpn)
555            && !inner.overlay_pages[vtl].iter().any(|p| p.gpn == gpn)
556        {
557            return Err(HvError::OperationDenied);
558        }
559        Ok(())
560    }
561
562    /// Checks whether the given GPN is present in guest RAM.
563    fn is_in_guest_memory(&self, gpn: u64) -> bool {
564        let gpa = gpn << HV_PAGE_SHIFT;
565        self.layout.ram().iter().any(|r| r.range.contains_addr(gpa))
566    }
567}
568
569impl ProtectIsolatedMemory for HardwareIsolatedMemoryProtector {
570    fn change_host_visibility(
571        &self,
572        vtl: GuestVtl,
573        shared: bool,
574        gpns: &[u64],
575        tlb_access: &mut dyn TlbFlushLockAccess,
576    ) -> Result<(), (HvError, usize)> {
577        let inner = self.inner.lock();
578
579        for &gpn in gpns {
580            // Validate the ranges are RAM.
581            if !self.is_in_guest_memory(gpn) {
582                return Err((HvError::OperationDenied, 0));
583            }
584
585            // Validate they're not locked.
586            self.check_gpn_not_locked(&inner, vtl, gpn)
587                .map_err(|x| (x, 0))?;
588
589            // Don't allow overlay pages to be shared.
590            if shared && inner.overlay_pages[vtl].iter().any(|p| p.gpn == gpn) {
591                return Err((HvError::OperationDenied, 0));
592            }
593        }
594
595        // Filter out the GPNs that are already in the correct state. If the
596        // page is becoming shared, make sure the requesting VTL has read/write
597        // vtl permissions to the page.
598        let orig_gpns = gpns;
599        let mut failed_vtl_permission_index = None;
600        let gpns = gpns
601            .iter()
602            .copied()
603            .enumerate()
604            .take_while(|&(index, gpn)| {
605                if vtl == GuestVtl::Vtl0 && shared && self.vtl1_protections_enabled() {
606                    let permissions = self
607                        .vtl0
608                        .query_access_permission(gpn)
609                        .expect("vtl 1 protections enabled, vtl permissions should be tracked");
610                    if !permissions.readable() || !permissions.writable() {
611                        failed_vtl_permission_index = Some(index);
612                        false
613                    } else {
614                        true
615                    }
616                } else {
617                    true
618                }
619            })
620            .filter_map(|(_, gpn)| {
621                if inner.valid_shared.check_valid(gpn) != shared {
622                    Some(gpn)
623                } else {
624                    None
625                }
626            })
627            .collect::<Vec<_>>();
628
629        tracing::debug!(
630            orig = orig_gpns.len(),
631            len = gpns.len(),
632            first = gpns.first(),
633            shared,
634            "change vis"
635        );
636
637        let ranges = PagedRange::new(0, gpns.len() * PagedRange::PAGE_SIZE, &gpns)
638            .unwrap()
639            .ranges()
640            .map(|r| r.map(|r| MemoryRange::new(r.start..r.end)))
641            .collect::<Result<Vec<_>, _>>()
642            .unwrap(); // Ok to unwrap, we've validated the gpns above.
643
644        // Prevent accesses via the wrong address.
645        let clear_bitmap = if shared {
646            &inner.valid_encrypted
647        } else {
648            &inner.valid_shared
649        };
650
651        for &range in &ranges {
652            if shared && vtl == GuestVtl::Vtl0 {
653                // Accessing these pages through the encrypted mapping is now
654                // invalid. Make sure the VTL bitmaps reflect this. We could
655                // call apply_protections here but that would result in an extra
656                // hardware interaction that we don't need since we're about to
657                // unaccept the pages anyways.
658                self.vtl0
659                    .update_permission_bitmaps(range, HV_MAP_GPA_PERMISSIONS_NONE);
660            }
661
662            clear_bitmap.update_valid(range, false);
663        }
664
665        // There may be other threads concurrently accessing these pages. We
666        // cannot change the page visibility state until these threads have
667        // stopped those accesses. Flush the RCU domain that `guestmem` uses in
668        // order to flush any threads accessing the pages. After this, we are
669        // guaranteed no threads are accessing these pages (unless the pages are
670        // also locked), since no bitmap currently allows access.
671        guestmem::rcu().synchronize_blocking();
672
673        if let IsolationType::Snp = self.acceptor.isolation {
674            // We need to ensure that the guest TLB has been fully flushed since
675            // the unaccept operation is not guaranteed to do so in hardware,
676            // and the hypervisor is also not trusted with TLB hygiene.
677            tlb_access.flush_entire();
678        }
679
680        if shared {
681            // Unaccept the pages so that the hypervisor can reclaim them.
682            for &range in &ranges {
683                self.acceptor.unaccept_lower_vtl_pages(range);
684            }
685        }
686
687        // Ask the hypervisor to update visibility.
688        let host_visibility = if shared {
689            HostVisibilityType::SHARED
690        } else {
691            HostVisibilityType::PRIVATE
692        };
693
694        let (result, ranges) = match self.acceptor.modify_gpa_visibility(host_visibility, &gpns) {
695            Ok(()) => {
696                // All gpns succeeded, so the whole set of ranges should be
697                // processed.
698                (
699                    match failed_vtl_permission_index {
700                        Some(index) => Err((HvError::AccessDenied, index)),
701                        None => Ok(()),
702                    },
703                    ranges,
704                )
705            }
706            Err(err) => {
707                if shared {
708                    // A transition from private to shared should always
709                    // succeed. There is no safe rollback path, so we must
710                    // panic.
711                    panic!(
712                        "the hypervisor refused to transition pages to shared, we cannot safely roll back: {:?}",
713                        err
714                    );
715                }
716
717                // Only some ranges succeeded. Recreate ranges based on which
718                // gpns succeeded, for further processing.
719                let (successful_gpns, failed_gpns) = gpns.split_at(err.processed);
720                let ranges = PagedRange::new(
721                    0,
722                    successful_gpns.len() * PagedRange::PAGE_SIZE,
723                    successful_gpns,
724                )
725                .unwrap()
726                .ranges()
727                .map(|r| r.map(|r| MemoryRange::new(r.start..r.end)))
728                .collect::<Result<Vec<_>, _>>()
729                .expect("previous gpns was already checked");
730
731                // Roll back the cleared bitmap for failed gpns, as they should
732                // be still in their original state of shared.
733                let rollback_ranges =
734                    PagedRange::new(0, failed_gpns.len() * PagedRange::PAGE_SIZE, failed_gpns)
735                        .unwrap()
736                        .ranges()
737                        .map(|r| r.map(|r| MemoryRange::new(r.start..r.end)))
738                        .collect::<Result<Vec<_>, _>>()
739                        .expect("previous gpns was already checked");
740
741                for &range in &rollback_ranges {
742                    clear_bitmap.update_valid(range, true);
743                }
744
745                // Figure out the index of the gpn that failed, in the
746                // pre-filtered list that will be reported back to the caller.
747                let failed_index = orig_gpns
748                    .iter()
749                    .position(|gpn| *gpn == failed_gpns[0])
750                    .expect("failed gpn should be present in the list");
751
752                (Err((err.source, failed_index)), ranges)
753            }
754        };
755
756        if !shared {
757            // Accept the pages so that the guest can use them.
758            for &range in &ranges {
759                self.acceptor
760                    .accept_lower_vtl_pages(range)
761                    .expect("everything should be in a state where we can accept VTL0 pages");
762
763                // For SNP, zero the memory before allowing the guest to access
764                // them. For TDX, this is done by the TDX module. For mshv, this is
765                // done by the hypervisor.
766                if self.acceptor.isolation == IsolationType::Snp {
767                    inner.encrypted.zero_range(range).expect("VTL 2 should have access to lower VTL memory, the page should be accepted, there should be no vtl protections yet.")
768                }
769            }
770        }
771
772        // Allow accesses via the correct address.
773        let set_bitmap = if shared {
774            &inner.valid_shared
775        } else {
776            &inner.valid_encrypted
777        };
778        for &range in &ranges {
779            set_bitmap.update_valid(range, true);
780        }
781
782        if !shared {
783            // Apply vtl protections so that the guest can use them. Any
784            // overlay pages won't be host visible, so just apply the default
785            // protections directly without handling them.
786            for &range in &ranges {
787                // Make sure we reset the permissions bitmaps for VTL 0.
788                self.apply_protections(
789                    range,
790                    GuestVtl::Vtl0,
791                    inner.default_vtl_permissions.vtl0,
792                    GpnSource::GuestMemory,
793                )
794                .expect("should be able to apply default protections");
795
796                if let Some(vtl1_protections) = inner.default_vtl_permissions.vtl1 {
797                    self.apply_protections(
798                        range,
799                        GuestVtl::Vtl1,
800                        vtl1_protections,
801                        GpnSource::GuestMemory,
802                    )
803                    .expect("everything should be in a state where we can apply VTL protections");
804                }
805            }
806        }
807
808        // Return the original result of the underlying page visibility
809        // transition call to the caller.
810        result
811    }
812
813    fn query_host_visibility(
814        &self,
815        gpns: &[u64],
816        host_visibility: &mut [HostVisibilityType],
817    ) -> Result<(), (HvError, usize)> {
818        // Validate the ranges are RAM.
819        for (i, &gpn) in gpns.iter().enumerate() {
820            if !self.is_in_guest_memory(gpn) {
821                return Err((HvError::OperationDenied, i));
822            }
823        }
824
825        let inner = self.inner.lock();
826
827        // Set GPN sharing status in output.
828        for (gpn, host_vis) in gpns.iter().zip(host_visibility.iter_mut()) {
829            *host_vis = if inner.valid_shared.check_valid(*gpn) {
830                HostVisibilityType::SHARED
831            } else {
832                HostVisibilityType::PRIVATE
833            };
834        }
835        Ok(())
836    }
837
838    fn default_vtl0_protections(&self) -> HvMapGpaFlags {
839        self.inner.lock().default_vtl_permissions.vtl0
840    }
841
842    fn change_default_vtl_protections(
843        &self,
844        target_vtl: GuestVtl,
845        vtl_protections: HvMapGpaFlags,
846        tlb_access: &mut dyn TlbFlushLockAccess,
847    ) -> Result<(), HvError> {
848        // Prevent visibility changes while VTL protections are being
849        // applied.
850        //
851        // TODO: This does not need to be synchronized against other
852        // threads performing VTL protection changes; whichever thread
853        // finishes last will control the outcome.
854        let mut inner = self.inner.lock();
855
856        inner
857            .default_vtl_permissions
858            .set(target_vtl, vtl_protections);
859
860        let mut ranges = Vec::new();
861        for ram_range in self.layout.ram().iter() {
862            let mut protect_start = ram_range.range.start();
863            let mut page_count = 0;
864
865            for gpn in
866                ram_range.range.start() / PAGE_SIZE as u64..ram_range.range.end() / PAGE_SIZE as u64
867            {
868                // TODO GUEST VSM: for now, use the encrypted mapping to
869                // find all accepted memory. When lazy acceptance exists,
870                // this should track all pages that have been accepted and
871                // should be used instead.
872                // Also don't attempt to change the permissions of locked pages.
873                if inner.valid_encrypted.check_valid(gpn) {
874                    self.check_gpn_not_locked(&inner, target_vtl, gpn)?;
875                    page_count += 1;
876                } else {
877                    if page_count > 0 {
878                        let end_address = protect_start + (page_count * PAGE_SIZE as u64);
879                        ranges.push(MemoryRange::new(protect_start..end_address));
880                    }
881                    protect_start = (gpn + 1) * PAGE_SIZE as u64;
882                    page_count = 0;
883                }
884            }
885
886            if page_count > 0 {
887                let end_address = protect_start + (page_count * PAGE_SIZE as u64);
888                ranges.push(MemoryRange::new(protect_start..end_address));
889            }
890        }
891
892        for range in ranges {
893            self.apply_protections_with_overlay_handling(
894                range,
895                target_vtl,
896                vtl_protections,
897                &mut inner,
898            )
899            .unwrap();
900        }
901
902        // Flush any threads accessing pages that had their VTL protections
903        // changed.
904        guestmem::rcu().synchronize_blocking();
905
906        // Invalidate the entire VTL 0 TLB to ensure that the new permissions
907        // are observed.
908        tlb_access.flush(GuestVtl::Vtl0);
909        tlb_access.set_wait_for_tlb_locks(target_vtl);
910
911        Ok(())
912    }
913
914    fn change_vtl_protections(
915        &self,
916        target_vtl: GuestVtl,
917        gpns: &[u64],
918        protections: HvMapGpaFlags,
919        tlb_access: &mut dyn TlbFlushLockAccess,
920    ) -> Result<(), (HvError, usize)> {
921        // Prevent visibility changes while VTL protections are being
922        // applied. This does not need to be synchronized against other
923        // threads performing VTL protection changes; whichever thread
924        // finishes last will control the outcome.
925        let inner = self.inner.lock();
926
927        // Validate the ranges are RAM.
928        for &gpn in gpns {
929            if !self.is_in_guest_memory(gpn) {
930                return Err((HvError::OperationDenied, 0));
931            }
932
933            // Validate they're not locked.
934            self.check_gpn_not_locked(&inner, target_vtl, gpn)
935                .map_err(|x| (x, 0))?;
936
937            // Validate they're not overlay pages.
938            if inner.overlay_pages[target_vtl].iter().any(|p| p.gpn == gpn) {
939                return Err((HvError::OperationDenied, 0));
940            }
941        }
942
943        // Protections cannot be applied to a host-visible page
944        if gpns.iter().any(|&gpn| inner.valid_shared.check_valid(gpn)) {
945            return Err((HvError::OperationDenied, 0));
946        }
947
948        let ranges = PagedRange::new(0, gpns.len() * PagedRange::PAGE_SIZE, gpns)
949            .unwrap()
950            .ranges()
951            .map(|r| r.map(|r| MemoryRange::new(r.start..r.end)))
952            .collect::<Result<Vec<_>, _>>()
953            .unwrap(); // Ok to unwrap, we've validated the gpns above.
954
955        for range in ranges {
956            self.apply_protections(range, target_vtl, protections, GpnSource::GuestMemory)
957                .unwrap();
958        }
959
960        // Flush any threads accessing pages that had their VTL protections
961        // changed.
962        guestmem::rcu().synchronize_blocking();
963
964        // Since page protections were modified, we must invalidate the entire
965        // VTL 0 TLB to ensure that the new permissions are observed, and wait for
966        // other CPUs to release all guest mappings before declaring that the VTL
967        // protection change has completed.
968        tlb_access.flush(GuestVtl::Vtl0);
969        tlb_access.set_wait_for_tlb_locks(target_vtl);
970
971        Ok(())
972    }
973
974    fn register_overlay_page(
975        &self,
976        vtl: GuestVtl,
977        gpn: u64,
978        gpn_source: GpnSource,
979        check_perms: HvMapGpaFlags,
980        new_perms: Option<HvMapGpaFlags>,
981        tlb_access: &mut dyn TlbFlushLockAccess,
982    ) -> Result<(), HvError> {
983        let mut inner = self.inner.lock();
984
985        // If the page is already registered as an overlay page, just check
986        // the permissions are adequate. If the permissions requested are
987        // different from the ones already registered just do best effort,
988        // there is no spec-guarantee of which one "wins".
989        if let Some(registered) = inner.overlay_pages[vtl].iter_mut().find(|p| p.gpn == gpn) {
990            let needed_perms = new_perms.unwrap_or(check_perms);
991            if registered.overlay_permissions.into_bits() | needed_perms.into_bits()
992                != registered.overlay_permissions.into_bits()
993            {
994                return Err(HvError::OperationDenied);
995            }
996            registered.ref_count += 1;
997            return Ok(());
998        }
999
1000        let current_perms = match gpn_source {
1001            GpnSource::GuestMemory => {
1002                // Check that the required permissions are present.
1003                let current_perms = self.query_lower_vtl_permissions(vtl, gpn)?;
1004                if current_perms.into_bits() | check_perms.into_bits() != current_perms.into_bits()
1005                {
1006                    return Err(HvError::OperationDenied);
1007                }
1008
1009                // Protections cannot be applied to a host-visible page.
1010                if inner.valid_shared.check_valid(gpn) {
1011                    return Err(HvError::OperationDenied);
1012                }
1013
1014                current_perms
1015            }
1016            GpnSource::Dma => {
1017                if self.is_in_guest_memory(gpn) {
1018                    // DMA memory must not be in guest RAM.
1019                    return Err(HvError::OperationDenied);
1020                }
1021
1022                HV_MAP_GPA_PERMISSIONS_NONE
1023            }
1024        };
1025
1026        // Or a locked page.
1027        self.check_gpn_not_locked(&inner, vtl, gpn)?;
1028
1029        // Everything's validated, change the permissions.
1030        if let Some(new_perms) = new_perms {
1031            self.apply_protections(
1032                MemoryRange::from_4k_gpn_range(gpn..gpn + 1),
1033                vtl,
1034                new_perms,
1035                gpn_source,
1036            )
1037            .map_err(|_| HvError::OperationDenied)?;
1038        }
1039
1040        // Nothing from this point on can fail, so we can safely register the overlay page.
1041        inner.overlay_pages[vtl].push(OverlayPage {
1042            gpn,
1043            previous_permissions: current_perms,
1044            overlay_permissions: new_perms.unwrap_or(current_perms),
1045            ref_count: 1,
1046            gpn_source,
1047        });
1048
1049        // Flush any threads accessing pages that had their VTL protections
1050        // changed.
1051        guestmem::rcu().synchronize_blocking();
1052
1053        // Since page protections were modified, we must invalidate the TLB to
1054        // ensure that the new permissions are observed, and wait for other CPUs
1055        // to release all guest mappings before declaring that the VTL
1056        // protection change has completed.
1057        tlb_access.flush(vtl);
1058        tlb_access.set_wait_for_tlb_locks(vtl);
1059
1060        Ok(())
1061    }
1062
1063    fn unregister_overlay_page(
1064        &self,
1065        vtl: GuestVtl,
1066        gpn: u64,
1067        tlb_access: &mut dyn TlbFlushLockAccess,
1068    ) -> Result<(), HvError> {
1069        let mut inner = self.inner.lock();
1070        let overlay_pages = &mut inner.overlay_pages[vtl];
1071
1072        // Find the overlay page.
1073        let index = overlay_pages
1074            .iter()
1075            .position(|p| p.gpn == gpn)
1076            .ok_or(HvError::OperationDenied)?;
1077
1078        // If this overlay page has been registered multiple times, just
1079        // decrement the reference count and return. We don't implement
1080        // full handling of multiple registrations with different permissions,
1081        // since it's best effort anyways.
1082        if overlay_pages[index].ref_count > 1 {
1083            overlay_pages[index].ref_count -= 1;
1084            return Ok(());
1085        }
1086
1087        // Restore its permissions.
1088        self.apply_protections(
1089            MemoryRange::from_4k_gpn_range(gpn..gpn + 1),
1090            vtl,
1091            overlay_pages[index].previous_permissions,
1092            overlay_pages[index].gpn_source,
1093        )
1094        .map_err(|_| HvError::OperationDenied)?;
1095
1096        // Nothing from this point on can fail, so we can safely unregister the overlay page.
1097        overlay_pages.remove(index);
1098
1099        // Flush any threads accessing pages that had their VTL protections
1100        // changed.
1101        guestmem::rcu().synchronize_blocking();
1102
1103        // Since page protections were modified, we must invalidate the TLB to
1104        // ensure that the new permissions are observed, and wait for other CPUs
1105        // to release all guest mappings before declaring that the VTL
1106        // protection change has completed.
1107        tlb_access.flush(vtl);
1108        tlb_access.set_wait_for_tlb_locks(vtl);
1109        Ok(())
1110    }
1111
1112    fn is_overlay_page(&self, vtl: GuestVtl, gpn: u64) -> bool {
1113        self.inner.lock().overlay_pages[vtl]
1114            .iter()
1115            .any(|p| p.gpn == gpn)
1116    }
1117
1118    fn lock_gpns(&self, vtl: GuestVtl, gpns: &[u64]) -> Result<(), GuestMemoryBackingError> {
1119        // Locking a page multiple times is allowed, so no need to check
1120        // for duplicates.
1121        // We also need to allow locking overlay pages for now.
1122        // TODO: We probably don't want to allow locking overlay pages once
1123        // we return the pointer for them instead of going through guestmem::lock.
1124        // TODO: other preconditions?
1125        self.inner.lock().locked_pages[vtl].push(gpns.to_vec().into_boxed_slice());
1126        Ok(())
1127    }
1128
1129    fn unlock_gpns(&self, vtl: GuestVtl, gpns: &[u64]) {
1130        let mut inner = self.inner.lock();
1131        let locked_pages = &mut inner.locked_pages[vtl];
1132        for (i, w) in locked_pages.iter().enumerate() {
1133            if **w == *gpns {
1134                locked_pages.swap_remove(i);
1135                return;
1136            }
1137        }
1138
1139        // Don't change protections on locked pages to avoid conflicting
1140        // with unregister_overlay_page.
1141        // TODO: Is this the right decision even after we separate overlay and
1142        // locked pages?
1143
1144        panic!("Tried to unlock pages that were not locked");
1145    }
1146
1147    fn set_vtl1_protections_enabled(&self) {
1148        self.vtl1_protections_enabled
1149            .store(true, std::sync::atomic::Ordering::Relaxed);
1150    }
1151
1152    fn vtl1_protections_enabled(&self) -> bool {
1153        self.vtl1_protections_enabled
1154            .load(std::sync::atomic::Ordering::Relaxed)
1155    }
1156}