Skip to main content

igvmfilegen/vp_context_builder/
snp.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! SNP VP context builder.
5
6use crate::vp_context_builder::VpContextBuilder;
7use crate::vp_context_builder::VpContextPageState;
8use crate::vp_context_builder::VpContextState;
9use hvdef::Vtl;
10use igvm_defs::PAGE_SIZE_4K;
11use loader::importer::BootPageAcceptance;
12use loader::importer::SegmentRegister;
13use loader::importer::TableRegister;
14use loader::importer::X86Register;
15use loader::paravisor::HCL_SECURE_VTL;
16use std::fmt::Debug;
17use x86defs::X64_EFER_SVME;
18use x86defs::snp::SevSelector;
19use x86defs::snp::SevVmsa;
20use zerocopy::FromZeros;
21use zerocopy::IntoBytes;
22
23/// The interrupt injection type to use for the highest vmpl's VMSA.
24#[derive(Debug, Copy, Clone, PartialEq, Eq)]
25pub enum InjectionType {
26    /// Normal.
27    Normal,
28    /// Restricted injection.
29    Restricted,
30}
31
32/// The secure AVIC.
33#[derive(Debug, Copy, Clone, PartialEq, Eq)]
34pub enum SecureAvic {
35    /// Offload AVIC to the hardware.
36    Enabled,
37    /// The paravisor emulates APIC.
38    Disabled,
39}
40
41#[derive(Debug, Copy, Clone)]
42enum SnpContextKind {
43    Standard,
44    LinuxDirect { c_bit_mask: u64 },
45}
46
47fn table_register(reg: TableRegister) -> SevSelector {
48    SevSelector {
49        limit: reg.limit.into(),
50        base: reg.base,
51        ..FromZeros::new_zeroed()
52    }
53}
54
55fn segment_register(reg: SegmentRegister) -> SevSelector {
56    SevSelector {
57        limit: reg.limit,
58        base: reg.base,
59        selector: reg.selector,
60        attrib: (reg.attributes & 0xff) | ((reg.attributes >> 4) & 0xf00),
61    }
62}
63
64/// A hardware SNP VP context, that is imported as a VMSA.
65#[derive(Debug)]
66pub struct SnpHardwareContext {
67    /// If an assembly stub to accept the lower 1mb should be imported as page
68    /// data.
69    accept_lower_1mb: bool,
70    /// The page number to import this vp context at.
71    page_number: Option<u64>,
72    /// The VMSA for this VP.
73    vmsa: SevVmsa,
74    kind: SnpContextKind,
75}
76
77impl SnpHardwareContext {
78    /// Create a new SNP VP context builder.
79    ///
80    /// `enlightened_uefi` specifies if UEFI is enlightened. This will result in
81    /// [`VpContextBuilder::finalize`] generating additional trampoline code for
82    /// UEFI running without a paravisor, along with setting different fields in
83    /// the `SEV_FEATURES` register.
84    ///
85    /// `injection_type` specifies the injection type for the highest enabled
86    /// VMPL.
87    ///
88    /// Only the highest VTL will have a VMSA generated, with lower VTLs being
89    /// imported with the VBS format as page data.
90    pub fn new(
91        vtl: Vtl,
92        enlightened_uefi: bool,
93        shared_gpa_boundary: u64,
94        injection_type: InjectionType,
95        secure_avic: SecureAvic,
96    ) -> Self {
97        let mut vmsa: SevVmsa = FromZeros::new_zeroed();
98
99        // Fill in reset values that are needed for consistency.
100        vmsa.efer = X64_EFER_SVME;
101
102        // Fill in boilerplate fields of the vmsa
103        vmsa.sev_features.set_snp(true);
104        vmsa.sev_features.set_vtom(true);
105        vmsa.virtual_tom = shared_gpa_boundary;
106        vmsa.sev_features.set_debug_swap(true);
107
108        if enlightened_uefi {
109            // Enlightened UEFI requires SevFeatureRestrictInjection to be set, in order
110            // to receive #HV interrupts.
111            assert_eq!(injection_type, InjectionType::Restricted);
112            vmsa.sev_features.set_restrict_injection(true);
113        } else {
114            // Lower VTLs like VTL0 images (UEFI) are SevFeatureAlternateInjection,
115            // while VTL2 (HCL) is SevFeatureRestrictInjection
116            // Additionally, set the BTB isolation and Prevent Host IBS property for
117            // VTL2. VTL2 is responsible for setting this property on any additional
118            // VMSAs.
119            if vtl < HCL_SECURE_VTL {
120                vmsa.sev_features
121                    .set_alternate_injection(injection_type == InjectionType::Restricted);
122                if injection_type == InjectionType::Normal {
123                    vmsa.sev_features
124                        .set_secure_avic(secure_avic == SecureAvic::Enabled);
125                    vmsa.sev_features
126                        .set_guest_intercept_control(secure_avic == SecureAvic::Enabled);
127                }
128            } else {
129                vmsa.sev_features
130                    .set_restrict_injection(injection_type == InjectionType::Restricted);
131                vmsa.sev_features.set_snp_btb_isolation(true);
132                vmsa.sev_features.set_ibpb_on_entry(true);
133                vmsa.sev_features.set_prevent_host_ibs(true);
134                vmsa.sev_features.set_vmsa_reg_prot(true);
135                vmsa.sev_features.set_vtom(false);
136                vmsa.sev_features
137                    .set_secure_avic(secure_avic == SecureAvic::Enabled);
138                vmsa.virtual_tom = 0;
139            }
140        }
141
142        // Configure the hardware reset value for XFEM.  The HCL will execute XSETBV if it needs
143        // additional XSAVE support.
144        vmsa.xcr0 = 0x1; // Maps to LegacyX87 bit
145
146        SnpHardwareContext {
147            accept_lower_1mb: enlightened_uefi,
148            page_number: None,
149            vmsa,
150            kind: SnpContextKind::Standard,
151        }
152    }
153
154    /// Create a VMSA builder for a Linux-direct guest using the C-bit model.
155    pub fn new_linux_direct(c_bit_mask: u64, injection_type: InjectionType) -> Self {
156        let mut vmsa: SevVmsa = FromZeros::new_zeroed();
157        vmsa.efer = X64_EFER_SVME;
158        vmsa.sev_features.set_snp(true);
159        vmsa.sev_features
160            .set_restrict_injection(injection_type == InjectionType::Restricted);
161        vmsa.xcr0 = x86defs::xsave::XFEATURE_X87;
162        vmsa.rflags = u64::from(x86defs::RFlags::at_reset());
163        vmsa.dr6 = 0xffff_0ff0;
164        vmsa.dr7 = 0x400;
165        vmsa.tr = segment_register(SegmentRegister {
166            base: 0,
167            limit: 0xffff,
168            selector: 0,
169            attributes: 0x8b,
170        });
171        vmsa.ldtr = segment_register(SegmentRegister {
172            base: 0,
173            limit: 0xffff,
174            selector: 0,
175            attributes: 0x82,
176        });
177        vmsa.idtr = table_register(TableRegister {
178            base: 0,
179            limit: 0xffff,
180        });
181        vmsa.x87_fcw = x86defs::xsave::INIT_FCW;
182        vmsa.mxcsr = x86defs::xsave::DEFAULT_MXCSR;
183
184        Self {
185            accept_lower_1mb: false,
186            page_number: None,
187            vmsa,
188            kind: SnpContextKind::LinuxDirect { c_bit_mask },
189        }
190    }
191
192    /// Returns the VMSA under construction.
193    #[cfg(test)]
194    pub fn vmsa(&self) -> &SevVmsa {
195        &self.vmsa
196    }
197}
198
199impl VpContextBuilder for SnpHardwareContext {
200    type Register = X86Register;
201
202    fn import_vp_register(&mut self, register: X86Register) {
203        match register {
204            X86Register::Gdtr(reg) => self.vmsa.gdtr = table_register(reg),
205            X86Register::Idtr(reg) => match self.kind {
206                SnpContextKind::Standard => panic!("Idtr not allowed for SNP"),
207                SnpContextKind::LinuxDirect { .. } => self.vmsa.idtr = table_register(reg),
208            },
209            X86Register::Ds(reg) => self.vmsa.ds = segment_register(reg),
210            X86Register::Es(reg) => self.vmsa.es = segment_register(reg),
211            X86Register::Fs(reg) => self.vmsa.fs = segment_register(reg),
212            X86Register::Gs(reg) => self.vmsa.gs = segment_register(reg),
213            X86Register::Ss(reg) => self.vmsa.ss = segment_register(reg),
214            X86Register::Cs(reg) => self.vmsa.cs = segment_register(reg),
215            X86Register::Tr(reg) => self.vmsa.tr = segment_register(reg),
216            X86Register::Cr0(reg) => {
217                self.vmsa.cr0 = match self.kind {
218                    SnpContextKind::Standard => reg,
219                    SnpContextKind::LinuxDirect { .. } => reg | x86defs::X64_CR0_ET,
220                }
221            }
222            X86Register::Cr3(reg) => {
223                self.vmsa.cr3 = match self.kind {
224                    SnpContextKind::Standard => reg,
225                    SnpContextKind::LinuxDirect { c_bit_mask } => reg | c_bit_mask,
226                }
227            }
228            X86Register::Cr4(reg) => {
229                self.vmsa.cr4 = match self.kind {
230                    SnpContextKind::Standard => reg,
231                    SnpContextKind::LinuxDirect { .. } => reg | x86defs::X64_CR4_MCE,
232                }
233            }
234            X86Register::Efer(reg) => {
235                // All SEV guests require EFER.SVME for the VMSA to be valid.
236                self.vmsa.efer = reg | X64_EFER_SVME;
237            }
238            X86Register::Pat(reg) => self.vmsa.pat = reg,
239            X86Register::Rbp(reg) => self.vmsa.rbp = reg,
240            X86Register::Rip(reg) => self.vmsa.rip = reg,
241            X86Register::Rsi(reg) => self.vmsa.rsi = reg,
242            X86Register::Rsp(reg) => match self.kind {
243                SnpContextKind::Standard => panic!("rsp not allowed for SNP"),
244                SnpContextKind::LinuxDirect { .. } => self.vmsa.rsp = reg,
245            },
246            X86Register::R8(reg) => self.vmsa.r8 = reg,
247            X86Register::R9(reg) => self.vmsa.r9 = reg,
248            X86Register::R10(reg) => self.vmsa.r10 = reg,
249            X86Register::R11(reg) => self.vmsa.r11 = reg,
250            X86Register::R12(reg) => self.vmsa.r12 = reg,
251            X86Register::Rflags(reg) => match self.kind {
252                SnpContextKind::Standard => panic!("rflags not allowed for SNP"),
253                SnpContextKind::LinuxDirect { .. } => self.vmsa.rflags = reg,
254            },
255
256            X86Register::MtrrDefType(_)
257            | X86Register::MtrrPhysBase0(_)
258            | X86Register::MtrrPhysMask0(_)
259            | X86Register::MtrrPhysBase1(_)
260            | X86Register::MtrrPhysMask1(_)
261            | X86Register::MtrrPhysBase2(_)
262            | X86Register::MtrrPhysMask2(_)
263            | X86Register::MtrrPhysBase3(_)
264            | X86Register::MtrrPhysMask3(_)
265            | X86Register::MtrrPhysBase4(_)
266            | X86Register::MtrrPhysMask4(_)
267            | X86Register::MtrrFix64k00000(_)
268            | X86Register::MtrrFix16k80000(_)
269            | X86Register::MtrrFix4kE0000(_)
270            | X86Register::MtrrFix4kE8000(_)
271            | X86Register::MtrrFix4kF0000(_)
272            | X86Register::MtrrFix4kF8000(_) => {
273                tracing::warn!(?register, "Ignoring MTRR register for SNP.")
274            }
275        }
276    }
277
278    fn set_vp_context_memory(&mut self, page_base: u64) {
279        assert!(self.page_number.is_none(), "only allowed to set vmsa once");
280        self.page_number = Some(page_base);
281    }
282
283    fn finalize(&mut self, state: &mut Vec<VpContextState>) {
284        let Some(page_number) = self.page_number else {
285            return;
286        };
287
288        // If no paravisor is present, then generate a trampoline page to perform
289        // validation of the low 1 MB of memory.  This is expected by UEFI and
290        // normally performed by the HCL, but must be done in a trampoline if no
291        // HCL is present.
292        if self.accept_lower_1mb {
293            let mut trampoline_page = vec![0u8; PAGE_SIZE_4K as usize];
294
295            // Since this page is discarded immediately after it executes, it can
296            // be placed anywhere in memory.  GPA page zero is a convenient unused
297            // location.
298            trampoline_page[..8].copy_from_slice(self.vmsa.rip.as_bytes());
299
300            // Place a breakpoint at the front of the page to force a triple fault
301            // in case of early failure.
302            let break_offset = size_of::<u64>();
303            trampoline_page[break_offset] = 0xCC;
304
305            // Set RIP to the trampoline page.
306            let mut byte_offset = break_offset + 1;
307            self.vmsa.rip = byte_offset as u64;
308
309            let copy_instr =
310                |trampoline_page: &mut Vec<u8>, byte_offset, instruction: &[u8]| -> usize {
311                    trampoline_page[byte_offset..byte_offset + instruction.len()]
312                        .copy_from_slice(instruction);
313                    byte_offset + instruction.len()
314                };
315
316            // mov esi, 01000h
317            byte_offset = copy_instr(
318                &mut trampoline_page,
319                byte_offset,
320                &[0xBE, 0x00, 0x10, 0x00, 0x00],
321            );
322
323            // mov ebx, 0100000h
324            byte_offset = copy_instr(
325                &mut trampoline_page,
326                byte_offset,
327                &[0xBB, 0x00, 0x00, 0x10, 0x00],
328            );
329
330            // xor ecx, ecx
331            byte_offset = copy_instr(&mut trampoline_page, byte_offset, &[0x33, 0xC9]);
332
333            // mov edx, 1
334            byte_offset = copy_instr(
335                &mut trampoline_page,
336                byte_offset,
337                &[0xBA, 0x01, 0x00, 0x00, 0x00],
338            );
339
340            // L1:
341            let jump_offset = byte_offset;
342
343            // mov eax, esi
344            byte_offset = copy_instr(&mut trampoline_page, byte_offset, &[0x8B, 0xC6]);
345
346            // pvalidate
347            byte_offset = copy_instr(&mut trampoline_page, byte_offset, &[0xF2, 0x0F, 0x01, 0xFF]);
348
349            // jc Break
350            byte_offset = copy_instr(&mut trampoline_page, byte_offset, &[0x72]);
351            byte_offset += 1;
352            trampoline_page[byte_offset - 1] = (break_offset as u8).wrapping_sub(byte_offset as u8);
353
354            // test rax, rax
355            byte_offset = copy_instr(&mut trampoline_page, byte_offset, &[0x48, 0x85, 0xC0]);
356
357            // jnz Break
358            byte_offset = copy_instr(&mut trampoline_page, byte_offset, &[0x75]);
359            byte_offset += 1;
360            trampoline_page[byte_offset - 1] = (break_offset as u8).wrapping_sub(byte_offset as u8);
361
362            // add esi, 01000h
363            byte_offset = copy_instr(
364                &mut trampoline_page,
365                byte_offset,
366                &[0x81, 0xC6, 0x00, 0x10, 0x00, 0x00],
367            );
368
369            // cmp esi, ebx
370            byte_offset = copy_instr(&mut trampoline_page, byte_offset, &[0x3B, 0xF3]);
371
372            // jb L1
373            byte_offset = copy_instr(&mut trampoline_page, byte_offset, &[0x72]);
374            byte_offset += 1;
375            trampoline_page[byte_offset - 1] = (jump_offset as u8).wrapping_sub(byte_offset as u8);
376
377            // jmp [0]
378            byte_offset = copy_instr(&mut trampoline_page, byte_offset, &[0xFF, 0x25]);
379            let relative_offset: u32 = 0u32.wrapping_sub(byte_offset as u32 + 4);
380            trampoline_page[byte_offset..byte_offset + 4]
381                .copy_from_slice(relative_offset.as_bytes());
382
383            state.push(VpContextState::Page(VpContextPageState {
384                page_base: 0,
385                page_count: 1,
386                acceptance: BootPageAcceptance::Exclusive,
387                data: trampoline_page,
388            }));
389        }
390
391        state.push(VpContextState::Page(VpContextPageState {
392            page_base: page_number,
393            page_count: 1,
394            acceptance: BootPageAcceptance::VpContext,
395            data: self.vmsa.as_bytes().to_vec(),
396        }));
397    }
398}