Skip to main content

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 std::sync::atomic::AtomicU32;
28use std::sync::atomic::Ordering;
29use thiserror::Error;
30use x86defs::snp::SevAvicPage;
31use x86defs::snp::SevRmpAdjust;
32use x86defs::snp::SevVmsa;
33
34/// Runner backing for SNP partitions.
35pub struct Snp<'a> {
36    vmsa: VtlArray<&'a UnsafeCell<SevVmsa>, 2>,
37    avic_pages: VtlArray<&'a UnsafeCell<SevAvicPage>, 2>,
38}
39
40/// A synthetic timer count write handled by the kernel.
41pub struct SnpStimer0Update {
42    /// The new synthetic timer count.
43    pub count: u64,
44    /// The reference time when the count was programmed.
45    pub programmed_ref_time: u64,
46    /// Whether the kernel timer expired before returning to user mode.
47    pub expired: bool,
48}
49
50/// Error returned by failing SNP operations.
51#[derive(Debug, Error)]
52#[expect(missing_docs)]
53pub enum SnpError {
54    #[error("operating system error")]
55    Os(#[source] nix::Error),
56    #[error("isa error {0:?}")]
57    Isa(u32),
58}
59
60/// Error returned by failing SNP page operations.
61#[derive(Debug, Error)]
62#[expect(missing_docs)]
63pub enum SnpPageError {
64    #[error("pvalidate failed")]
65    Pvalidate(#[source] SnpError),
66    #[error("rmpadjust failed")]
67    Rmpadjust(#[source] SnpError),
68    #[error("rmpquery failed")]
69    Rmpquery(#[source] SnpError),
70}
71
72impl MshvVtl {
73    /// Execute the pvalidate instruction on the specified memory range.
74    ///
75    /// The range must not be mapped in the kernel as RAM.
76    pub fn pvalidate_pages(
77        &self,
78        range: MemoryRange,
79        validate: bool,
80        terminate_on_failure: bool,
81    ) -> Result<(), SnpPageError> {
82        tracing::debug!(%range, validate, terminate_on_failure, "pvalidate");
83        // SAFETY: TODO SNP FUTURE: we are passing parameters as the kernel requires.
84        // For defense in depth it could be useful to prevent usermode from changing
85        // visibility of a VTL2 kernel page in the kernel.
86        let ret = unsafe {
87            hcl_pvalidate_pages(
88                self.file.as_raw_fd(),
89                &mshv_pvalidate {
90                    start_pfn: range.start() / HV_PAGE_SIZE,
91                    page_count: (range.end() - range.start()) / HV_PAGE_SIZE,
92                    validate: validate as u8,
93                    terminate_on_failure: terminate_on_failure as u8,
94                    ram: 0,
95                    padding: [0; 1],
96                },
97            )
98            .map_err(SnpError::Os)
99            .map_err(SnpPageError::Pvalidate)?
100        };
101
102        if ret != 0 {
103            return Err(SnpPageError::Pvalidate(SnpError::Isa(ret as u32)));
104        }
105
106        Ok(())
107    }
108
109    /// Execute the rmpadjust instruction on the specified memory range.
110    ///
111    /// The range must not be mapped in the kernel as RAM.
112    pub fn rmpadjust_pages(
113        &self,
114        range: MemoryRange,
115        value: SevRmpAdjust,
116        terminate_on_failure: bool,
117    ) -> Result<(), SnpPageError> {
118        // SAFETY: TODO SNP FUTURE: For defense in depth it could be useful to prevent
119        // usermode from changing permissions of a VTL2 kernel page in the kernel.
120        let ret = unsafe {
121            hcl_rmpadjust_pages(
122                self.file.as_raw_fd(),
123                &mshv_rmpadjust {
124                    start_pfn: range.start() / HV_PAGE_SIZE,
125                    page_count: (range.end() - range.start()) / HV_PAGE_SIZE,
126                    value: value.into(),
127                    terminate_on_failure: terminate_on_failure as u8,
128                    ram: 0,
129                    padding: Default::default(),
130                },
131            )
132            .map_err(SnpError::Os)
133            .map_err(SnpPageError::Rmpadjust)?
134        };
135
136        if ret != 0 {
137            return Err(SnpPageError::Rmpadjust(SnpError::Isa(ret as u32)));
138        }
139
140        Ok(())
141    }
142
143    /// Gets the current vtl permissions for a page.
144    /// Note: only supported on Genoa+
145    pub fn rmpquery_page(&self, gpa: u64, vtl: GuestVtl) -> Result<SevRmpAdjust, SnpPageError> {
146        let page_count = 1u64;
147        let mut flags = [u64::from(SevRmpAdjust::new().with_target_vmpl(match vtl {
148            GuestVtl::Vtl0 => 2,
149            GuestVtl::Vtl1 => 1,
150        })); 1];
151
152        let mut page_size = [0; 1];
153        let mut pages_processed = 0u64;
154
155        debug_assert!(flags.len() == page_count as usize);
156        debug_assert!(page_size.len() == page_count as usize);
157
158        let query = mshv_rmpquery {
159            start_pfn: gpa / HV_PAGE_SIZE,
160            page_count,
161            terminate_on_failure: 0,
162            ram: 0,
163            padding: Default::default(),
164            flags: flags.as_mut_ptr(),
165            page_size: page_size.as_mut_ptr(),
166            pages_processed: &mut pages_processed,
167        };
168
169        // SAFETY: the input query is the correct type for this ioctl
170        unsafe {
171            hcl_rmpquery_pages(self.file.as_raw_fd(), &query)
172                .map_err(SnpError::Os)
173                .map_err(SnpPageError::Rmpquery)?;
174        }
175
176        assert!(pages_processed <= page_count);
177
178        Ok(SevRmpAdjust::from(flags[0]))
179    }
180}
181
182impl<'a> super::private::BackingPrivate<'a> for Snp<'a> {
183    fn new(vp: &'a HclVp, sidecar: Option<&SidecarVp<'_>>, _hcl: &Hcl) -> Result<Self, NoRunner> {
184        assert!(sidecar.is_none());
185        let super::BackingState::Snp {
186            vtl0_apic_page,
187            vtl1_apic_page,
188            vmsa,
189        } = &vp.backing
190        else {
191            return Err(NoRunner::MismatchedIsolation);
192        };
193
194        // SAFETY: The mapping is held for the appropriate lifetime, and the
195        // APIC page is never accessed as any other type, or by any other location.
196        let vtl1_apic_page = unsafe { &*vtl1_apic_page.base().cast() };
197
198        Ok(Self {
199            avic_pages: [vtl0_apic_page.as_ref(), vtl1_apic_page].into(),
200            vmsa: vmsa.each_ref().map(|mp| mp.as_ref()),
201        })
202    }
203
204    fn try_set_reg(
205        _runner: &mut ProcessorRunner<'a, Self>,
206        _vtl: GuestVtl,
207        _name: HvRegisterName,
208        _value: HvRegisterValue,
209    ) -> bool {
210        false
211    }
212
213    fn must_flush_regs_on(_runner: &ProcessorRunner<'a, Self>, _name: HvRegisterName) -> bool {
214        false
215    }
216
217    fn try_get_reg(
218        _runner: &ProcessorRunner<'a, Self>,
219        _vtl: GuestVtl,
220        _name: HvRegisterName,
221    ) -> Option<HvRegisterValue> {
222        None
223    }
224
225    fn flush_register_page(_runner: &mut ProcessorRunner<'a, Self>) {}
226}
227
228impl<'a> ProcessorRunner<'a, Snp<'a>> {
229    fn snp_context_ptr(&self) -> *mut crate::protocol::snp_vp_context {
230        // This is an SNP partition, so the architecture context union is
231        // interpreted as an SNP context.
232        // SAFETY: `self.run` points to a mapped run page for this VP.
233        unsafe { (&raw mut (*self.run.get()).context).cast() }
234    }
235
236    /// Publishes the current STIMER0 configuration to the kernel.
237    pub fn set_stimer0_config(&mut self, config: Option<u64>) {
238        // SAFETY: The kernel does not access these fields while the run ioctl
239        // is not active. The flags remain atomic for the timer callback.
240        unsafe {
241            let context = self.snp_context_ptr();
242            let flags = &*((&raw mut (*context).stimer0_flags).cast::<AtomicU32>());
243            if let Some(config) = config {
244                (&raw mut (*context).stimer0_config).write(config);
245                flags.fetch_or(
246                    crate::protocol::MSHV_VTL_SNP_STIMER0_CONFIG_VALID,
247                    Ordering::Release,
248                );
249            } else {
250                flags.fetch_and(
251                    !crate::protocol::MSHV_VTL_SNP_STIMER0_CONFIG_VALID,
252                    Ordering::Release,
253                );
254            }
255        }
256    }
257
258    /// Takes a synthetic timer count write handled by the kernel.
259    pub fn take_stimer0_update(&mut self) -> Option<SnpStimer0Update> {
260        // SAFETY: The kernel cancels its timer before returning from the run
261        // ioctl and publishes state before setting KERNEL_UPDATE.
262        unsafe {
263            let context = self.snp_context_ptr();
264            let flags = &*((&raw mut (*context).stimer0_flags).cast::<AtomicU32>());
265            let value = flags.fetch_and(
266                !(crate::protocol::MSHV_VTL_SNP_STIMER0_KERNEL_UPDATE
267                    | crate::protocol::MSHV_VTL_SNP_STIMER0_EXPIRED),
268                Ordering::AcqRel,
269            );
270            if value & crate::protocol::MSHV_VTL_SNP_STIMER0_KERNEL_UPDATE == 0 {
271                return None;
272            }
273
274            Some(SnpStimer0Update {
275                count: (&raw const (*context).stimer0_count).read(),
276                programmed_ref_time: (&raw const (*context).stimer0_programmed_ref_time).read(),
277                expired: value & crate::protocol::MSHV_VTL_SNP_STIMER0_EXPIRED != 0,
278            })
279        }
280    }
281
282    /// Gets a reference to the VMSA and backing state of a VTL
283    pub fn vmsa(&self, vtl: GuestVtl) -> VmsaWrapper<'_, &SevVmsa> {
284        // SAFETY: the VMSA will not be concurrently accessed by the processor
285        // while this VP is in VTL2.
286        let vmsa = unsafe { &*self.state.vmsa[vtl].get() };
287
288        VmsaWrapper::new(vmsa, &self.hcl.snp_register_bitmap)
289    }
290
291    /// Gets a mutable reference to the VMSA and backing state of a VTL.
292    pub fn vmsa_mut(&mut self, vtl: GuestVtl) -> VmsaWrapper<'_, &mut SevVmsa> {
293        // SAFETY: the VMSA will not be concurrently accessed by the processor
294        // while this VP is in VTL2.
295        let vmsa = unsafe { &mut *self.state.vmsa[vtl].get() };
296
297        VmsaWrapper::new(vmsa, &self.hcl.snp_register_bitmap)
298    }
299
300    /// Returns the VMSAs for [VTL0, VTL1].
301    pub fn vmsas_mut(&mut self) -> [VmsaWrapper<'_, &mut SevVmsa>; 2] {
302        self.state
303            .vmsa
304            .each_mut()
305            .map(|vmsa| {
306                // SAFETY: the VMSA will not be concurrently accessed by the processor
307                // while this VP is in VTL2.
308                let vmsa = unsafe { &mut *vmsa.get() };
309
310                VmsaWrapper::new(vmsa, &self.hcl.snp_register_bitmap)
311            })
312            .into_inner()
313    }
314
315    /// Gets a PFN of the VTL0 secure AVIC page.
316    /// TODO: Maybe there is a better way other than passing `cpu_index` here.
317    pub fn secure_avic_vtl0_pfn(&self, cpu_index: u32) -> u64 {
318        self.hcl.secure_avic_vtl0_pfn(cpu_index)
319    }
320
321    /// Gets a reference to the secure AVIC page for the given VTL.
322    pub fn secure_avic_page(&self, vtl: GuestVtl) -> &SevAvicPage {
323        // SAFETY: the APIC pages will not be concurrently accessed by the processor
324        // while this VP is in VTL2.
325        unsafe { &*self.state.avic_pages[vtl].get() }
326    }
327
328    /// Gets a mutable reference to the secure AVIC page for the given VTL.
329    pub fn secure_avic_page_mut(&mut self, vtl: GuestVtl) -> &mut SevAvicPage {
330        // SAFETY: the AVIC pages will not be concurrently accessed by the processor
331        // while this VP is in VTL2.
332        unsafe { &mut *self.state.avic_pages[vtl].get() }
333    }
334
335    /// Gets a mutable reference to the secure AVIC page for the given VTL.
336    pub fn secure_avic_page_vmsa_mut(
337        &mut self,
338        vtl: GuestVtl,
339    ) -> (&mut SevAvicPage, VmsaWrapper<'_, &mut SevVmsa>) {
340        // SAFETY: the AVIC pages will not be concurrently accessed by the processor
341        // while this VP is in VTL2.
342        let avic_page = unsafe { &mut *self.state.avic_pages[vtl].get() };
343        let vmsa = self.vmsa_mut(vtl);
344
345        (avic_page, vmsa)
346    }
347
348    /// Gets a mutable reference to the secure AVIC page and the proxy_irr_exit field
349    pub fn secure_avic_page_proxy_irr_exit_vtl0_mut(
350        &mut self,
351    ) -> (&mut SevAvicPage, &mut [u32; 8]) {
352        // SAFETY: the AVIC pages will not be concurrently accessed by the processor
353        // while this VP is in VTL2.
354        let avic_page = unsafe { &mut *self.state.avic_pages[GuestVtl::Vtl0].get() };
355        // SAFETY: The `proxy_irr_exit` field of the run page will not be concurrently updated.
356        let proxy_irr_vtl0 = unsafe { &mut (*self.run.get()).proxy_irr_exit };
357
358        (avic_page, proxy_irr_vtl0)
359    }
360}