sev/launch/
snp.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! An implementation of the SEV-SNP launch process as a type-state machine.
4//! This ensures (at compile time) that the right steps are called in the
5//! right order.
6
7#[cfg(target_os = "linux")]
8use crate::{
9    error::FirmwareError,
10    firmware::guest::GuestPolicy,
11    launch::linux::{ioctl::*, shared::*, snp::*},
12};
13
14use std::{marker::PhantomData, os::unix::io::AsRawFd, result::Result};
15
16use bitflags::bitflags;
17
18#[cfg(feature = "serde")]
19use serde::{Deserialize, Serialize};
20
21/// Launcher type-state that indicates a brand new launch.
22pub struct New;
23
24/// Launcher type-state that indicates a SNP in-progress.
25pub struct Started;
26
27/// Facilitates the correct execution of the SEV launch process.
28pub struct Launcher<T, U: AsRawFd, V: AsRawFd> {
29    vm_fd: U,
30    sev: V,
31    state: PhantomData<T>,
32}
33
34impl<T, U: AsRawFd, V: AsRawFd> AsRef<U> for Launcher<T, U, V> {
35    /// Give access to the vm fd to create vCPUs or such.
36    fn as_ref(&self) -> &U {
37        &self.vm_fd
38    }
39}
40
41impl<T, U: AsRawFd, V: AsRawFd> AsMut<U> for Launcher<T, U, V> {
42    /// Give access to the vm fd to create vCPUs or such.
43    fn as_mut(&mut self) -> &mut U {
44        &mut self.vm_fd
45    }
46}
47
48impl<U: AsRawFd, V: AsRawFd> Launcher<New, U, V> {
49    /// Begin the SEV-SNP launch process by creating a Launcher and issuing the
50    /// KVM_SNP_INIT ioctl.
51    pub fn new(vm_fd: U, sev: V) -> Result<Self, FirmwareError> {
52        let mut launcher = Launcher {
53            vm_fd,
54            sev,
55            state: PhantomData,
56        };
57
58        let init = Init2::init_default_snp();
59
60        let mut cmd = Command::from(&launcher.sev, &init);
61
62        INIT2
63            .ioctl(&mut launcher.vm_fd, &mut cmd)
64            .map_err(|_| cmd.encapsulate())?;
65
66        Ok(launcher)
67    }
68
69    /// Initialize the flow to launch a guest.
70    pub fn start(mut self, start: Start) -> Result<Launcher<Started, U, V>, FirmwareError> {
71        let launch_start = LaunchStart::from(start);
72        let mut cmd = Command::from(&self.sev, &launch_start);
73
74        SNP_LAUNCH_START
75            .ioctl(&mut self.vm_fd, &mut cmd)
76            .map_err(|_| cmd.encapsulate())?;
77
78        let launcher = Launcher {
79            vm_fd: self.vm_fd,
80            sev: self.sev,
81            state: PhantomData,
82        };
83
84        Ok(launcher)
85    }
86}
87
88impl<U: AsRawFd, V: AsRawFd> Launcher<Started, U, V> {
89    /// Encrypt guest SNP data.
90    pub fn update_data(
91        &mut self,
92        mut update: Update,
93        gpa: u64,
94        gpa_len: u64,
95    ) -> Result<(), FirmwareError> {
96        loop {
97            let launch_update_data = LaunchUpdate::from(update);
98            let mut cmd = Command::from(&self.sev, &launch_update_data);
99
100            // Register the encryption region
101            KvmEncRegion::new(update.uaddr).register(&mut self.vm_fd)?;
102
103            // Set memory attributes to private
104            KvmSetMemoryAttributes::new(gpa, gpa_len, KVM_MEMORY_ATTRIBUTE_PRIVATE)
105                .set_attributes(&mut self.vm_fd)?;
106
107            // Perform the SNP_LAUNCH_UPDATE ioctl call
108            match SNP_LAUNCH_UPDATE.ioctl(&mut self.vm_fd, &mut cmd) {
109                Ok(_) => {
110                    // Check if the entire range has been processed
111                    if launch_update_data.len == 0 {
112                        break;
113                    }
114
115                    // Update the `update` object with the remaining range
116                    update.start_gfn = launch_update_data.start_gfn;
117                    update.uaddr = unsafe {
118                        std::slice::from_raw_parts(
119                            launch_update_data.uaddr as *const u8,
120                            launch_update_data.len as usize,
121                        )
122                    };
123                }
124                Err(e) if e.raw_os_error() == Some(libc::EAGAIN) => {
125                    // Retry the operation if `-EAGAIN` is returned
126                    continue;
127                }
128                Err(_) => {
129                    // Handle other errors
130                    return Err(cmd.encapsulate());
131                }
132            }
133        }
134
135        Ok(())
136    }
137
138    /// Complete the SNP launch process.
139    pub fn finish(mut self, finish: Finish) -> Result<(U, V), FirmwareError> {
140        let launch_finish = LaunchFinish::from(finish);
141        let mut cmd = Command::from(&self.sev, &launch_finish);
142
143        SNP_LAUNCH_FINISH
144            .ioctl(&mut self.vm_fd, &mut cmd)
145            .map_err(|_| cmd.encapsulate())?;
146
147        Ok((self.vm_fd, self.sev))
148    }
149}
150
151/// Encapsulates the various data needed to begin the launch process.
152#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
153#[derive(Default, Clone, Debug, PartialEq, Eq)]
154pub struct Start {
155    /// Describes a policy that the AMD Secure Processor will enforce.
156    pub(crate) policy: GuestPolicy,
157
158    /// Hypervisor provided value to indicate guest OS visible workarounds.The format is hypervisor defined.
159    pub(crate) gosvw: [u8; 16],
160
161    /// Indicates that this launch flow is launching an IMI for the purpose of guest-assisted migration.
162    pub(crate) flags: u16,
163}
164
165impl Start {
166    /// Encapsulate all data needed for the SNP_LAUNCH_START ioctl.
167    pub fn new(policy: GuestPolicy, gosvw: [u8; 16]) -> Self {
168        Self {
169            policy,
170            gosvw,
171            flags: 0,
172        }
173    }
174}
175
176/// Encoded page types for a launch update. See Table 58 of the SNP Firmware
177/// specification for further details.
178#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
179#[derive(Copy, Clone, Debug, PartialEq, Eq)]
180#[repr(C)]
181#[non_exhaustive]
182pub enum PageType {
183    /// A normal data page.
184    Normal = 0x1,
185
186    /// A VMSA page.
187    Vmsa = 0x2,
188
189    /// A page full of zeroes.
190    Zero = 0x3,
191
192    /// A page that is encrypted but not measured
193    Unmeasured = 0x4,
194
195    /// A page for the firmware to store secrets for the guest.
196    Secrets = 0x5,
197
198    /// A page for the hypervisor to provide CPUID function values.
199    Cpuid = 0x6,
200}
201
202/// Encapsulates the various data needed to begin the update process.
203#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
204#[derive(Copy, Clone, Debug, PartialEq, Eq)]
205pub struct Update<'a> {
206    /// guest start frame number.
207    pub(crate) start_gfn: u64,
208
209    /// The userspace of address of the encrypted region.
210    pub(crate) uaddr: &'a [u8],
211
212    /// Encoded page type.
213    pub(crate) page_type: PageType,
214}
215
216impl<'a> Update<'a> {
217    /// Encapsulate all data needed for the SNP_LAUNCH_UPDATE ioctl.
218    pub fn new(start_gfn: u64, uaddr: &'a [u8], page_type: PageType) -> Self {
219        Self {
220            start_gfn,
221            uaddr,
222            page_type,
223        }
224    }
225}
226
227bitflags! {
228    #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
229    /// VMPL permission masks.
230    pub struct VmplPerms: u8 {
231        /// Page is readable by the VMPL.
232        const READ = 1;
233
234        /// Page is writeable by the VMPL.
235        const WRITE = 1 << 1;
236
237        /// Page is executable by the VMPL in CPL3.
238        const EXECUTE_USER = 1 << 2;
239
240        /// Page is executable by the VMPL in CPL2, CPL1, and CPL0.
241        const EXECUTE_SUPERVISOR = 1 << 3;
242    }
243}
244
245#[cfg(feature = "serde")]
246impl Serialize for VmplPerms {
247    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
248    where
249        S: serde::Serializer,
250    {
251        serializer.serialize_u8(self.bits())
252    }
253}
254
255#[cfg(feature = "serde")]
256impl<'de> Deserialize<'de> for VmplPerms {
257    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
258    where
259        D: serde::Deserializer<'de>,
260    {
261        let bits = u8::deserialize(deserializer)?;
262        Ok(VmplPerms::from_bits_truncate(bits))
263    }
264}
265
266/// Encapsulates the data needed to complete a guest launch.
267#[derive(Copy, Clone, Debug, PartialEq, Eq)]
268pub struct Finish<'a, 'b> {
269    /// The userspace address of the encrypted region.
270    pub(crate) id_block: Option<&'a [u8]>,
271
272    /// The userspace address of the authentication information of the ID block.
273    pub(crate) id_auth: Option<&'b [u8]>,
274
275    /// Opaque host-supplied data to describe the guest. The firmware does not interpret this
276    /// value.
277    pub(crate) host_data: [u8; KVM_SEV_SNP_FINISH_DATA_SIZE],
278}
279
280impl<'a, 'b> Finish<'a, 'b> {
281    /// Encapsulate all data needed for the SNP_LAUNCH_FINISH ioctl.
282    pub fn new(
283        id_block: Option<&'a [u8]>,
284        id_auth: Option<&'b [u8]>,
285        host_data: [u8; KVM_SEV_SNP_FINISH_DATA_SIZE],
286    ) -> Self {
287        Self {
288            id_block,
289            id_auth,
290            host_data,
291        }
292    }
293}