Skip to main content

virt/x86/
snp.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! SEV-SNP launch helpers.
5//!
6//! [`vmsa_from_initial_regs`] is used by the shared direct-boot loader for SNP
7//! backends, including KVM and MSHV. [`state_from_vmsa`] is currently used by
8//! KVM to apply the loader-built VMSA through KVM's register-based launch path;
9//! MSHV imports the VMSA page directly.
10
11use super::SegmentRegister;
12use super::TableRegister;
13use super::X86InitialRegs;
14use super::vp;
15use thiserror::Error;
16use x86defs::SegmentAttributes;
17use x86defs::snp::SevSelector;
18use x86defs::snp::SevVmsa;
19use zerocopy::FromZeros;
20
21const _: () = assert!(size_of::<SevVmsa>() <= hvdef::HV_PAGE_SIZE as usize);
22
23/// The subset of initial VP state represented by a direct-boot SNP VMSA.
24#[derive(Debug, Copy, Clone, PartialEq, Eq)]
25pub struct SnpVmsaState {
26    /// Architectural register state.
27    pub registers: vp::Registers,
28    /// Page attribute table state.
29    pub pat: u64,
30    /// Extended control register 0.
31    pub xcr0: u64,
32    /// Whether restricted interrupt injection is enabled.
33    pub restricted_injection: bool,
34}
35
36/// An error parsing a direct-boot SNP VMSA.
37#[derive(Debug, Error)]
38pub enum SnpVmsaError {
39    /// The VMSA contains state outside the supported direct-boot contract.
40    #[error("unsupported SNP VMSA state")]
41    UnsupportedState,
42}
43
44/// Configuration for a direct-boot SNP VMSA.
45#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
46pub struct SnpVmsaConfig {
47    /// Enables restricted interrupt injection.
48    pub restricted_injection: bool,
49}
50
51/// Builds the PSP ID block for `id_block` and the launch `policy`.
52pub fn snp_id_block(id_block: &crate::SnpIdBlock, policy: u64) -> x86defs::snp::SnpPspIdBlock {
53    x86defs::snp::SnpPspIdBlock {
54        ld: id_block.launch_digest,
55        family_id: id_block.family_id,
56        image_id: id_block.image_id,
57        version: id_block.version,
58        guest_svn: id_block.guest_svn,
59        policy,
60    }
61}
62
63/// Builds the PSP ID authentication page for `id_block`.
64pub fn snp_id_auth(id_block: &crate::SnpIdBlock) -> Box<x86defs::snp::SnpPspIdAuth> {
65    let mut auth = Box::new(x86defs::snp::SnpPspIdAuth::new_zeroed());
66    auth.id_key_algorithm = id_block.id_key_algorithm;
67    auth.author_key_algorithm = id_block.author_key_algorithm;
68    auth.id_block_signature.r = id_block.id_key_signature.r;
69    auth.id_block_signature.s = id_block.id_key_signature.s;
70    auth.id_key.curve = id_block.id_public_key.curve;
71    auth.id_key.qx = id_block.id_public_key.qx;
72    auth.id_key.qy = id_block.id_public_key.qy;
73    auth.id_key_signature.r = id_block.author_key_signature.r;
74    auth.id_key_signature.s = id_block.author_key_signature.s;
75    auth.author_key.curve = id_block.author_public_key.curve;
76    auth.author_key.qx = id_block.author_public_key.qx;
77    auth.author_key.qy = id_block.author_public_key.qy;
78    auth
79}
80
81/// Builds the direct-boot SNP VMSA corresponding to `initial`.
82pub fn vmsa_from_initial_regs(initial: &X86InitialRegs, config: SnpVmsaConfig) -> SevVmsa {
83    vmsa_from_state(&SnpVmsaState {
84        registers: initial.registers,
85        pat: initial.pat.value,
86        xcr0: x86defs::xsave::XFEATURE_X87,
87        restricted_injection: config.restricted_injection,
88    })
89}
90
91/// Parses and validates a direct-boot SNP VMSA.
92pub fn state_from_vmsa(vmsa: &SevVmsa) -> Result<SnpVmsaState, SnpVmsaError> {
93    let state = SnpVmsaState {
94        registers: vp::Registers {
95            rax: vmsa.rax,
96            rcx: vmsa.rcx,
97            rdx: vmsa.rdx,
98            rbx: vmsa.rbx,
99            rbp: vmsa.rbp,
100            rsp: vmsa.rsp,
101            rsi: vmsa.rsi,
102            rdi: vmsa.rdi,
103            r8: vmsa.r8,
104            r9: vmsa.r9,
105            r10: vmsa.r10,
106            r11: vmsa.r11,
107            r12: vmsa.r12,
108            r13: vmsa.r13,
109            r14: vmsa.r14,
110            r15: vmsa.r15,
111            rip: vmsa.rip,
112            rflags: vmsa.rflags,
113            cs: segment_from_vmsa(vmsa.cs),
114            ds: segment_from_vmsa(vmsa.ds),
115            es: segment_from_vmsa(vmsa.es),
116            fs: segment_from_vmsa(vmsa.fs),
117            gs: segment_from_vmsa(vmsa.gs),
118            ss: segment_from_vmsa(vmsa.ss),
119            tr: segment_from_vmsa(vmsa.tr),
120            ldtr: segment_from_vmsa(vmsa.ldtr),
121            gdtr: table_from_vmsa(vmsa.gdtr),
122            idtr: table_from_vmsa(vmsa.idtr),
123            cr0: vmsa.cr0,
124            cr2: vmsa.cr2,
125            cr3: vmsa.cr3,
126            cr4: vmsa.cr4,
127            cr8: 0,
128            efer: vmsa.efer & !x86defs::X64_EFER_SVME,
129        },
130        pat: vmsa.pat,
131        xcr0: vmsa.xcr0,
132        restricted_injection: vmsa.sev_features.restrict_injection(),
133    };
134
135    if &vmsa_from_state(&state) != vmsa {
136        return Err(SnpVmsaError::UnsupportedState);
137    }
138
139    Ok(state)
140}
141
142fn vmsa_from_state(state: &SnpVmsaState) -> SevVmsa {
143    let registers = &state.registers;
144    let mut vmsa = SevVmsa::new_zeroed();
145
146    vmsa.es = segment_to_vmsa(registers.es);
147    vmsa.cs = segment_to_vmsa(registers.cs);
148    vmsa.ss = segment_to_vmsa(registers.ss);
149    vmsa.ds = segment_to_vmsa(registers.ds);
150    vmsa.fs = segment_to_vmsa(registers.fs);
151    vmsa.gs = segment_to_vmsa(registers.gs);
152    vmsa.gdtr = table_to_vmsa(registers.gdtr);
153    vmsa.ldtr = segment_to_vmsa(registers.ldtr);
154    vmsa.idtr = table_to_vmsa(registers.idtr);
155    vmsa.tr = segment_to_vmsa(registers.tr);
156    vmsa.cpl = SegmentAttributes::from(registers.cs.attributes).descriptor_privilege_level();
157    vmsa.efer = registers.efer | x86defs::X64_EFER_SVME;
158    vmsa.cr4 = registers.cr4;
159    vmsa.cr3 = registers.cr3;
160    vmsa.cr0 = registers.cr0;
161    vmsa.rflags = registers.rflags;
162    vmsa.rip = registers.rip;
163    vmsa.rsp = registers.rsp;
164    vmsa.rax = registers.rax;
165    vmsa.cr2 = registers.cr2;
166    vmsa.pat = state.pat;
167    vmsa.rcx = registers.rcx;
168    vmsa.rdx = registers.rdx;
169    vmsa.rbx = registers.rbx;
170    vmsa.rbp = registers.rbp;
171    vmsa.rsi = registers.rsi;
172    vmsa.rdi = registers.rdi;
173    vmsa.r8 = registers.r8;
174    vmsa.r9 = registers.r9;
175    vmsa.r10 = registers.r10;
176    vmsa.r11 = registers.r11;
177    vmsa.r12 = registers.r12;
178    vmsa.r13 = registers.r13;
179    vmsa.r14 = registers.r14;
180    vmsa.r15 = registers.r15;
181    vmsa.sev_features.set_snp(true);
182    vmsa.sev_features
183        .set_restrict_injection(state.restricted_injection);
184    vmsa.xcr0 = state.xcr0;
185
186    vmsa
187}
188
189fn segment_to_vmsa(register: SegmentRegister) -> SevSelector {
190    SevSelector {
191        selector: register.selector,
192        attrib: (register.attributes & 0xff) | ((register.attributes >> 4) & 0xf00),
193        limit: register.limit,
194        base: register.base,
195    }
196}
197
198fn segment_from_vmsa(selector: SevSelector) -> SegmentRegister {
199    SegmentRegister {
200        selector: selector.selector,
201        attributes: (selector.attrib & 0xff) | ((selector.attrib & 0xf00) << 4),
202        limit: selector.limit,
203        base: selector.base,
204    }
205}
206
207fn table_to_vmsa(register: TableRegister) -> SevSelector {
208    SevSelector {
209        selector: 0,
210        attrib: 0,
211        limit: register.limit as u32,
212        base: register.base,
213    }
214}
215
216fn table_from_vmsa(selector: SevSelector) -> TableRegister {
217    TableRegister {
218        limit: selector.limit as u16,
219        base: selector.base,
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    fn test_id_block() -> crate::SnpIdBlock {
228        crate::SnpIdBlock {
229            author_key_enabled: 1,
230            launch_digest: [0x11; 48],
231            family_id: [0x22; 16],
232            image_id: [0x33; 16],
233            version: 1,
234            guest_svn: 7,
235            id_key_algorithm: 0x01020304,
236            author_key_algorithm: 0x05060708,
237            id_key_signature: x86defs::snp::SnpIdBlockSignature {
238                r: [0x44; 72],
239                s: [0x55; 72],
240            },
241            id_public_key: x86defs::snp::SnpIdBlockPublicKey {
242                curve: 2,
243                qx: [0x66; 72],
244                qy: [0x77; 72],
245            },
246            author_key_signature: x86defs::snp::SnpIdBlockSignature {
247                r: [0x88; 72],
248                s: [0x99; 72],
249            },
250            author_public_key: x86defs::snp::SnpIdBlockPublicKey {
251                curve: 3,
252                qx: [0xaa; 72],
253                qy: [0xbb; 72],
254            },
255        }
256    }
257
258    #[test]
259    fn snp_id_block_preserves_fields_and_policy() {
260        let source = test_id_block();
261        let id_block = snp_id_block(&source, 0x1234);
262
263        assert_eq!(id_block.ld, source.launch_digest);
264        assert_eq!(id_block.family_id, source.family_id);
265        assert_eq!(id_block.image_id, source.image_id);
266        assert_eq!(id_block.version, source.version);
267        assert_eq!(id_block.guest_svn, source.guest_svn);
268        assert_eq!(id_block.policy, 0x1234);
269    }
270
271    #[test]
272    fn snp_id_auth_serializes_algorithms() {
273        let id_block = test_id_block();
274        let id_auth = snp_id_auth(&id_block);
275
276        assert_eq!(id_auth.id_key_algorithm, id_block.id_key_algorithm);
277        assert_eq!(id_auth.author_key_algorithm, id_block.author_key_algorithm);
278    }
279
280    #[test]
281    fn snp_id_auth_serializes_signatures_and_keys() {
282        let id_block = test_id_block();
283        let id_auth = snp_id_auth(&id_block);
284
285        assert_eq!(id_auth.id_block_signature.r, id_block.id_key_signature.r);
286        assert_eq!(id_auth.id_block_signature.s, id_block.id_key_signature.s);
287        assert_eq!(id_auth.id_key.curve, id_block.id_public_key.curve);
288        assert_eq!(id_auth.id_key.qx, id_block.id_public_key.qx);
289        assert_eq!(id_auth.id_key.qy, id_block.id_public_key.qy);
290        assert_eq!(id_auth.id_key_signature.r, id_block.author_key_signature.r);
291        assert_eq!(id_auth.id_key_signature.s, id_block.author_key_signature.s);
292        assert_eq!(id_auth.author_key.curve, id_block.author_public_key.curve);
293        assert_eq!(id_auth.author_key.qx, id_block.author_public_key.qx);
294        assert_eq!(id_auth.author_key.qy, id_block.author_public_key.qy);
295    }
296
297    #[test]
298    fn snp_id_auth_zero_pads_reserved_bytes() {
299        let id_auth = snp_id_auth(&test_id_block());
300
301        assert!(id_auth.reserved0.iter().all(|&byte| byte == 0));
302        assert!(
303            id_auth
304                .id_block_signature
305                .reserved
306                .iter()
307                .all(|&byte| byte == 0)
308        );
309        assert!(id_auth.id_key.reserved.iter().all(|&byte| byte == 0));
310        assert!(id_auth.reserved1.iter().all(|&byte| byte == 0));
311        assert!(
312            id_auth
313                .id_key_signature
314                .reserved
315                .iter()
316                .all(|&byte| byte == 0)
317        );
318        assert!(id_auth.author_key.reserved.iter().all(|&byte| byte == 0));
319        assert!(id_auth.reserved2.iter().all(|&byte| byte == 0));
320    }
321
322    #[test]
323    fn direct_boot_vmsa_round_trips() {
324        let initial = X86InitialRegs {
325            registers: vp::Registers {
326                rax: 1,
327                rdx: 2,
328                rip: 0x100000,
329                rflags: 2,
330                cs: SegmentRegister {
331                    selector: 0x10,
332                    attributes: 0xa09b,
333                    limit: u32::MAX,
334                    base: 0,
335                },
336                efer: x86defs::X64_EFER_LME | x86defs::X64_EFER_LMA,
337                ..Default::default()
338            },
339            mtrrs: Default::default(),
340            pat: vp::Pat {
341                value: 0x7040600070406,
342            },
343        };
344
345        let vmsa = vmsa_from_initial_regs(&initial, SnpVmsaConfig::default());
346        let state = state_from_vmsa(&vmsa).unwrap();
347
348        assert_eq!(state.registers, initial.registers);
349        assert_eq!(state.pat, initial.pat.value);
350        assert_eq!(state.xcr0, x86defs::xsave::XFEATURE_X87);
351        assert!(vmsa.sev_features.snp());
352        assert_ne!(vmsa.efer & x86defs::X64_EFER_SVME, 0);
353    }
354
355    #[test]
356    fn rejects_unsupported_state() {
357        let initial = X86InitialRegs {
358            registers: Default::default(),
359            mtrrs: Default::default(),
360            pat: Default::default(),
361        };
362        let mut vmsa = vmsa_from_initial_regs(&initial, SnpVmsaConfig::default());
363        vmsa.virtual_tom = 0x1000;
364
365        assert!(matches!(
366            state_from_vmsa(&vmsa),
367            Err(SnpVmsaError::UnsupportedState)
368        ));
369    }
370
371    #[test]
372    fn enables_restricted_injection() {
373        let initial = X86InitialRegs {
374            registers: Default::default(),
375            mtrrs: Default::default(),
376            pat: Default::default(),
377        };
378
379        let vmsa = vmsa_from_initial_regs(
380            &initial,
381            SnpVmsaConfig {
382                restricted_injection: true,
383            },
384        );
385
386        assert!(vmsa.sev_features.restrict_injection());
387        assert!(state_from_vmsa(&vmsa).unwrap().restricted_injection);
388    }
389}