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