hcl/ioctl/
snp.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Backing for SNP partitions.
5
6use super::Hcl;
7use super::HclVp;
8use super::MshvVtl;
9use super::NoRunner;
10use super::ProcessorRunner;
11use super::hcl_pvalidate_pages;
12use super::hcl_rmpadjust_pages;
13use super::hcl_rmpquery_pages;
14use super::mshv_pvalidate;
15use super::mshv_rmpadjust;
16use super::mshv_rmpquery;
17use crate::GuestVtl;
18use crate::vmsa::VmsaWrapper;
19use hv1_structs::VtlArray;
20use hvdef::HV_PAGE_SIZE;
21use hvdef::HvRegisterName;
22use hvdef::HvRegisterValue;
23use memory_range::MemoryRange;
24use sidecar_client::SidecarVp;
25use std::cell::UnsafeCell;
26use std::os::fd::AsRawFd;
27use thiserror::Error;
28use x86defs::snp::SevRmpAdjust;
29use x86defs::snp::SevVmsa;
30
31/// Runner backing for SNP partitions.
32pub struct Snp<'a> {
33    vmsa: VtlArray<&'a UnsafeCell<SevVmsa>, 2>,
34}
35
36/// Error returned by failing SNP operations.
37#[derive(Debug, Error)]
38#[expect(missing_docs)]
39pub enum SnpError {
40    #[error("operating system error")]
41    Os(#[source] nix::Error),
42    #[error("isa error {0:?}")]
43    Isa(u32),
44}
45
46/// Error returned by failing SNP page operations.
47#[derive(Debug, Error)]
48#[expect(missing_docs)]
49pub enum SnpPageError {
50    #[error("pvalidate failed")]
51    Pvalidate(#[source] SnpError),
52    #[error("rmpadjust failed")]
53    Rmpadjust(#[source] SnpError),
54    #[error("rmpquery failed")]
55    Rmpquery(#[source] SnpError),
56}
57
58impl MshvVtl {
59    /// Execute the pvalidate instruction on the specified memory range.
60    ///
61    /// The range must not be mapped in the kernel as RAM.
62    pub fn pvalidate_pages(
63        &self,
64        range: MemoryRange,
65        validate: bool,
66        terminate_on_failure: bool,
67    ) -> Result<(), SnpPageError> {
68        tracing::debug!(%range, validate, terminate_on_failure, "pvalidate");
69        // SAFETY: TODO SNP FUTURE: we are passing parameters as the kernel requires.
70        // For defense in depth it could be useful to prevent usermode from changing
71        // visibility of a VTL2 kernel page in the kernel.
72        let ret = unsafe {
73            hcl_pvalidate_pages(
74                self.file.as_raw_fd(),
75                &mshv_pvalidate {
76                    start_pfn: range.start() / HV_PAGE_SIZE,
77                    page_count: (range.end() - range.start()) / HV_PAGE_SIZE,
78                    validate: validate as u8,
79                    terminate_on_failure: terminate_on_failure as u8,
80                    ram: 0,
81                    padding: [0; 1],
82                },
83            )
84            .map_err(SnpError::Os)
85            .map_err(SnpPageError::Pvalidate)?
86        };
87
88        if ret != 0 {
89            return Err(SnpPageError::Pvalidate(SnpError::Isa(ret as u32)));
90        }
91
92        Ok(())
93    }
94
95    /// Execute the rmpadjust instruction on the specified memory range.
96    ///
97    /// The range must not be mapped in the kernel as RAM.
98    pub fn rmpadjust_pages(
99        &self,
100        range: MemoryRange,
101        value: SevRmpAdjust,
102        terminate_on_failure: bool,
103    ) -> Result<(), SnpPageError> {
104        // SAFETY: TODO SNP FUTURE: For defense in depth it could be useful to prevent
105        // usermode from changing permissions of a VTL2 kernel page in the kernel.
106        let ret = unsafe {
107            hcl_rmpadjust_pages(
108                self.file.as_raw_fd(),
109                &mshv_rmpadjust {
110                    start_pfn: range.start() / HV_PAGE_SIZE,
111                    page_count: (range.end() - range.start()) / HV_PAGE_SIZE,
112                    value: value.into(),
113                    terminate_on_failure: terminate_on_failure as u8,
114                    ram: 0,
115                    padding: Default::default(),
116                },
117            )
118            .map_err(SnpError::Os)
119            .map_err(SnpPageError::Rmpadjust)?
120        };
121
122        if ret != 0 {
123            return Err(SnpPageError::Rmpadjust(SnpError::Isa(ret as u32)));
124        }
125
126        Ok(())
127    }
128
129    /// Gets the current vtl permissions for a page.
130    /// Note: only supported on Genoa+
131    pub fn rmpquery_page(&self, gpa: u64, vtl: GuestVtl) -> Result<SevRmpAdjust, SnpPageError> {
132        let page_count = 1u64;
133        let mut flags = [u64::from(SevRmpAdjust::new().with_target_vmpl(match vtl {
134            GuestVtl::Vtl0 => 2,
135            GuestVtl::Vtl1 => 1,
136        })); 1];
137
138        let mut page_size = [0; 1];
139        let mut pages_processed = 0u64;
140
141        debug_assert!(flags.len() == page_count as usize);
142        debug_assert!(page_size.len() == page_count as usize);
143
144        let query = mshv_rmpquery {
145            start_pfn: gpa / HV_PAGE_SIZE,
146            page_count,
147            terminate_on_failure: 0,
148            ram: 0,
149            padding: Default::default(),
150            flags: flags.as_mut_ptr(),
151            page_size: page_size.as_mut_ptr(),
152            pages_processed: &mut pages_processed,
153        };
154
155        // SAFETY: the input query is the correct type for this ioctl
156        unsafe {
157            hcl_rmpquery_pages(self.file.as_raw_fd(), &query)
158                .map_err(SnpError::Os)
159                .map_err(SnpPageError::Rmpquery)?;
160        }
161
162        assert!(pages_processed <= page_count);
163
164        Ok(SevRmpAdjust::from(flags[0]))
165    }
166}
167
168impl<'a> super::private::BackingPrivate<'a> for Snp<'a> {
169    fn new(vp: &'a HclVp, sidecar: Option<&SidecarVp<'_>>, _hcl: &Hcl) -> Result<Self, NoRunner> {
170        assert!(sidecar.is_none());
171        let super::BackingState::Snp { vmsa } = &vp.backing else {
172            return Err(NoRunner::MismatchedIsolation);
173        };
174
175        Ok(Self {
176            vmsa: vmsa.each_ref().map(|mp| mp.as_ref()),
177        })
178    }
179
180    fn try_set_reg(
181        _runner: &mut ProcessorRunner<'a, Self>,
182        _vtl: GuestVtl,
183        _name: HvRegisterName,
184        _value: HvRegisterValue,
185    ) -> bool {
186        false
187    }
188
189    fn must_flush_regs_on(_runner: &ProcessorRunner<'a, Self>, _name: HvRegisterName) -> bool {
190        false
191    }
192
193    fn try_get_reg(
194        _runner: &ProcessorRunner<'a, Self>,
195        _vtl: GuestVtl,
196        _name: HvRegisterName,
197    ) -> Option<HvRegisterValue> {
198        None
199    }
200
201    fn flush_register_page(_runner: &mut ProcessorRunner<'a, Self>) {}
202}
203
204impl<'a> ProcessorRunner<'a, Snp<'a>> {
205    /// Gets a reference to the VMSA and backing state of a VTL
206    pub fn vmsa(&self, vtl: GuestVtl) -> VmsaWrapper<'_, &SevVmsa> {
207        // SAFETY: the VMSA will not be concurrently accessed by the processor
208        // while this VP is in VTL2.
209        let vmsa = unsafe { &*self.state.vmsa[vtl].get() };
210
211        VmsaWrapper::new(vmsa, &self.hcl.snp_register_bitmap)
212    }
213
214    /// Gets a mutable reference to the VMSA and backing state of a VTL.
215    pub fn vmsa_mut(&mut self, vtl: GuestVtl) -> VmsaWrapper<'_, &mut SevVmsa> {
216        // SAFETY: the VMSA will not be concurrently accessed by the processor
217        // while this VP is in VTL2.
218        let vmsa = unsafe { &mut *self.state.vmsa[vtl].get() };
219
220        VmsaWrapper::new(vmsa, &self.hcl.snp_register_bitmap)
221    }
222
223    /// Returns the VMSAs for [VTL0, VTL1].
224    pub fn vmsas_mut(&mut self) -> [VmsaWrapper<'_, &mut SevVmsa>; 2] {
225        self.state
226            .vmsa
227            .each_mut()
228            .map(|vmsa| {
229                // SAFETY: the VMSA will not be concurrently accessed by the processor
230                // while this VP is in VTL2.
231                let vmsa = unsafe { &mut *vmsa.get() };
232
233                VmsaWrapper::new(vmsa, &self.hcl.snp_register_bitmap)
234            })
235            .into_inner()
236    }
237}