vmm/cpu_config/x86_64/
custom_cpu_template.rs

1// Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4/// Guest config sub-module specifically useful for
5/// config templates.
6use std::borrow::Cow;
7
8use serde::de::Error as SerdeError;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11use crate::arch::x86_64::cpu_model::{CpuModel, SKYLAKE_FMS};
12use crate::cpu_config::templates::{
13    CpuTemplateType, GetCpuTemplate, GetCpuTemplateError, KvmCapability, RegisterValueFilter,
14};
15use crate::cpu_config::templates_serde::*;
16use crate::cpu_config::x86_64::cpuid::KvmCpuidFlags;
17use crate::cpu_config::x86_64::cpuid::common::get_vendor_id_from_host;
18use crate::cpu_config::x86_64::static_cpu_templates::{StaticCpuTemplate, c3, t2, t2a, t2cl, t2s};
19use crate::logger::warn;
20
21impl GetCpuTemplate for Option<CpuTemplateType> {
22    fn get_cpu_template(&self) -> Result<Cow<'_, CustomCpuTemplate>, GetCpuTemplateError> {
23        use GetCpuTemplateError::*;
24
25        match self {
26            Some(template_type) => match template_type {
27                CpuTemplateType::Custom(template) => Ok(Cow::Borrowed(template)),
28                CpuTemplateType::Static(template) => {
29                    // Return early for `None` due to no valid vendor and CPU models.
30                    if template == &StaticCpuTemplate::None {
31                        return Err(InvalidStaticCpuTemplate(StaticCpuTemplate::None));
32                    }
33
34                    if &get_vendor_id_from_host().map_err(GetCpuVendor)?
35                        != template.get_supported_vendor()
36                    {
37                        return Err(CpuVendorMismatched);
38                    }
39
40                    let cpu_model = CpuModel::get_cpu_model();
41                    if !template.get_supported_cpu_models().contains(&cpu_model) {
42                        return Err(InvalidCpuModel);
43                    }
44
45                    match template {
46                        StaticCpuTemplate::C3 => {
47                            if cpu_model == SKYLAKE_FMS {
48                                warn!(
49                                    "On processors that do not enumerate FBSDP_NO, PSDP_NO and \
50                                     SBDR_SSDP_NO on IA32_ARCH_CAPABILITIES MSR, the guest kernel \
51                                     does not apply the mitigation against MMIO stale data \
52                                     vulnerability."
53                                );
54                            }
55                            Ok(Cow::Owned(c3::c3()))
56                        }
57                        StaticCpuTemplate::T2 => Ok(Cow::Owned(t2::t2())),
58                        StaticCpuTemplate::T2S => Ok(Cow::Owned(t2s::t2s())),
59                        StaticCpuTemplate::T2CL => Ok(Cow::Owned(t2cl::t2cl())),
60                        StaticCpuTemplate::T2A => Ok(Cow::Owned(t2a::t2a())),
61                        StaticCpuTemplate::None => unreachable!(), // Handled earlier
62                    }
63                }
64            },
65            None => Ok(Cow::Owned(CustomCpuTemplate::default())),
66        }
67    }
68}
69
70/// CPUID register enumeration
71#[allow(missing_docs)]
72#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash, Ord, PartialOrd)]
73pub enum CpuidRegister {
74    Eax,
75    Ebx,
76    Ecx,
77    Edx,
78}
79
80/// Target register to be modified by a bitmap.
81#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
82pub struct CpuidRegisterModifier {
83    /// CPUID register to be modified by the bitmap.
84    #[serde(
85        deserialize_with = "deserialize_cpuid_register",
86        serialize_with = "serialize_cpuid_register"
87    )]
88    pub register: CpuidRegister,
89    /// Bit mapping to be applied as a modifier to the
90    /// register's value at the address provided.
91    pub bitmap: RegisterValueFilter<u32>,
92}
93
94/// Composite type that holistically provides
95/// the location of a specific register being used
96/// in the context of a CPUID tree.
97#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
98pub struct CpuidLeafModifier {
99    /// Leaf value.
100    #[serde(
101        deserialize_with = "deserialize_from_str_u32",
102        serialize_with = "serialize_to_hex_str"
103    )]
104    pub leaf: u32,
105    /// Sub-Leaf value.
106    #[serde(
107        deserialize_with = "deserialize_from_str_u32",
108        serialize_with = "serialize_to_hex_str"
109    )]
110    pub subleaf: u32,
111    /// KVM feature flags for this leaf-subleaf.
112    #[serde(deserialize_with = "deserialize_kvm_cpuid_flags")]
113    pub flags: KvmCpuidFlags,
114    /// All registers to be modified under the sub-leaf.
115    pub modifiers: Vec<CpuidRegisterModifier>,
116}
117
118/// Wrapper type to containing x86_64 CPU config modifiers.
119#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
120#[serde(deny_unknown_fields)]
121pub struct CustomCpuTemplate {
122    /// Additional kvm capabilities to check before
123    /// configuring vcpus.
124    #[serde(default)]
125    pub kvm_capabilities: Vec<KvmCapability>,
126    /// Modifiers for CPUID configuration.
127    #[serde(default)]
128    pub cpuid_modifiers: Vec<CpuidLeafModifier>,
129    /// Modifiers for model specific registers.
130    #[serde(default)]
131    pub msr_modifiers: Vec<RegisterModifier>,
132}
133
134impl CustomCpuTemplate {
135    /// Get an iterator of MSR indices that are modified by the CPU template.
136    pub fn msr_index_iter(&self) -> impl ExactSizeIterator<Item = u32> + '_ {
137        self.msr_modifiers.iter().map(|modifier| modifier.addr)
138    }
139
140    /// Validate the correctness of the template.
141    pub fn validate(&self) -> Result<(), serde_json::Error> {
142        Ok(())
143    }
144}
145
146/// Wrapper of a mask defined as a bitmap to apply
147/// changes to a given register's value.
148#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)]
149pub struct RegisterModifier {
150    /// Pointer of the location to be bit mapped.
151    #[serde(
152        deserialize_with = "deserialize_from_str_u32",
153        serialize_with = "serialize_to_hex_str"
154    )]
155    pub addr: u32,
156    /// Bit mapping to be applied as a modifier to the
157    /// register's value at the address provided.
158    pub bitmap: RegisterValueFilter<u64>,
159}
160
161fn deserialize_kvm_cpuid_flags<'de, D>(deserializer: D) -> Result<KvmCpuidFlags, D::Error>
162where
163    D: Deserializer<'de>,
164{
165    let flag = u32::deserialize(deserializer)?;
166    Ok(KvmCpuidFlags(flag))
167}
168
169fn deserialize_cpuid_register<'de, D>(deserializer: D) -> Result<CpuidRegister, D::Error>
170where
171    D: Deserializer<'de>,
172{
173    let cpuid_register_str = String::deserialize(deserializer)?;
174
175    Ok(match cpuid_register_str.as_str() {
176        "eax" => CpuidRegister::Eax,
177        "ebx" => CpuidRegister::Ebx,
178        "ecx" => CpuidRegister::Ecx,
179        "edx" => CpuidRegister::Edx,
180        _ => {
181            return Err(D::Error::custom(
182                "Invalid CPUID register. Must be one of [eax, ebx, ecx, edx]",
183            ));
184        }
185    })
186}
187
188fn serialize_cpuid_register<S>(cpuid_reg: &CpuidRegister, serializer: S) -> Result<S::Ok, S::Error>
189where
190    S: Serializer,
191{
192    match cpuid_reg {
193        CpuidRegister::Eax => serializer.serialize_str("eax"),
194        CpuidRegister::Ebx => serializer.serialize_str("ebx"),
195        CpuidRegister::Ecx => serializer.serialize_str("ecx"),
196        CpuidRegister::Edx => serializer.serialize_str("edx"),
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use serde_json::Value;
203
204    use super::*;
205    use crate::cpu_config::x86_64::test_utils::{TEST_TEMPLATE_JSON, build_test_template};
206
207    #[test]
208    fn test_get_cpu_template_with_no_template() {
209        // Test `get_cpu_template()` when no template is provided. The empty owned
210        // `CustomCpuTemplate` should be returned.
211        let cpu_template = None;
212        assert_eq!(
213            cpu_template.get_cpu_template().unwrap(),
214            Cow::Owned(CustomCpuTemplate::default()),
215        );
216    }
217
218    #[test]
219    fn test_get_cpu_template_with_c3_static_template() {
220        // Test `get_cpu_template()` when C3 static CPU template is specified. The owned
221        // `CustomCpuTemplate` should be returned if CPU vendor is Intel and the CPU model is
222        // supported. Otherwise, it should fail.
223        let c3 = StaticCpuTemplate::C3;
224        let cpu_template = Some(CpuTemplateType::Static(c3));
225        if &get_vendor_id_from_host().unwrap() == c3.get_supported_vendor() {
226            if c3
227                .get_supported_cpu_models()
228                .contains(&CpuModel::get_cpu_model())
229            {
230                assert_eq!(
231                    cpu_template.get_cpu_template().unwrap(),
232                    Cow::Owned(c3::c3())
233                );
234            } else {
235                assert_eq!(
236                    cpu_template.get_cpu_template().unwrap_err(),
237                    GetCpuTemplateError::InvalidCpuModel,
238                );
239            }
240        } else {
241            assert_eq!(
242                cpu_template.get_cpu_template().unwrap_err(),
243                GetCpuTemplateError::CpuVendorMismatched,
244            );
245        }
246    }
247
248    #[test]
249    fn test_get_cpu_template_with_t2_static_template() {
250        // Test `get_cpu_template()` when T2 static CPU template is specified. The owned
251        // `CustomCpuTemplate` should be returned if CPU vendor is Intel and the CPU model is
252        // supported. Otherwise, it should fail.
253        let t2 = StaticCpuTemplate::T2;
254        let cpu_template = Some(CpuTemplateType::Static(t2));
255        if &get_vendor_id_from_host().unwrap() == t2.get_supported_vendor() {
256            if t2
257                .get_supported_cpu_models()
258                .contains(&CpuModel::get_cpu_model())
259            {
260                assert_eq!(
261                    cpu_template.get_cpu_template().unwrap(),
262                    Cow::Owned(t2::t2())
263                );
264            } else {
265                assert_eq!(
266                    cpu_template.get_cpu_template().unwrap_err(),
267                    GetCpuTemplateError::InvalidCpuModel,
268                );
269            }
270        } else {
271            assert_eq!(
272                cpu_template.get_cpu_template().unwrap_err(),
273                GetCpuTemplateError::CpuVendorMismatched,
274            );
275        }
276    }
277
278    #[test]
279    fn test_get_cpu_template_with_t2s_static_template() {
280        // Test `get_cpu_template()` when T2S static CPU template is specified. The owned
281        // `CustomCpuTemplate` should be returned if CPU vendor is Intel and the CPU model is
282        // supported. Otherwise, it should fail.
283        let t2s = StaticCpuTemplate::T2S;
284        let cpu_template = Some(CpuTemplateType::Static(t2s));
285        if &get_vendor_id_from_host().unwrap() == t2s.get_supported_vendor() {
286            if t2s
287                .get_supported_cpu_models()
288                .contains(&CpuModel::get_cpu_model())
289            {
290                assert_eq!(
291                    cpu_template.get_cpu_template().unwrap(),
292                    Cow::Owned(t2s::t2s())
293                );
294            } else {
295                assert_eq!(
296                    cpu_template.get_cpu_template().unwrap_err(),
297                    GetCpuTemplateError::InvalidCpuModel,
298                );
299            }
300        } else {
301            assert_eq!(
302                cpu_template.get_cpu_template().unwrap_err(),
303                GetCpuTemplateError::CpuVendorMismatched,
304            );
305        }
306    }
307
308    #[test]
309    fn test_t2cl_template_equality() {
310        // For coverage purposes, this test forces usage of T2CL and bypasses
311        // validation that is generally applied which usually enforces that T2CL
312        // can only be used on Cascade Lake (or newer) CPUs.
313        let t2cl_custom_template = CpuTemplateType::Custom(t2cl::t2cl());
314        // This test also demonstrates the difference in concept between custom and static
315        // templates, while practically T2CL is consistent for the user, in code
316        // the static template of T2CL, and the custom template of T2CL are not equivalent.
317        assert_ne!(
318            t2cl_custom_template,
319            CpuTemplateType::Static(StaticCpuTemplate::T2CL)
320        );
321    }
322
323    #[test]
324    fn test_get_cpu_template_with_t2cl_static_template() {
325        // Test `get_cpu_template()` when T2CL static CPU template is specified. The owned
326        // `CustomCpuTemplate` should be returned if CPU vendor is Intel and the CPU model is
327        // supported. Otherwise, it should fail.
328        let t2cl = StaticCpuTemplate::T2CL;
329        let cpu_template = Some(CpuTemplateType::Static(t2cl));
330        if &get_vendor_id_from_host().unwrap() == t2cl.get_supported_vendor() {
331            if t2cl
332                .get_supported_cpu_models()
333                .contains(&CpuModel::get_cpu_model())
334            {
335                assert_eq!(
336                    cpu_template.get_cpu_template().unwrap(),
337                    Cow::Owned(t2cl::t2cl())
338                );
339            } else {
340                assert_eq!(
341                    cpu_template.get_cpu_template().unwrap_err(),
342                    GetCpuTemplateError::InvalidCpuModel,
343                );
344            }
345        } else {
346            assert_eq!(
347                cpu_template.get_cpu_template().unwrap_err(),
348                GetCpuTemplateError::CpuVendorMismatched,
349            );
350        }
351    }
352
353    #[test]
354    fn test_get_cpu_template_with_t2a_static_template() {
355        // Test `get_cpu_template()` when T2A static CPU template is specified. The owned
356        // `CustomCpuTemplate` should be returned if CPU vendor is AMD. Otherwise it should fail.
357        let t2a = StaticCpuTemplate::T2A;
358        let cpu_template = Some(CpuTemplateType::Static(t2a));
359        if &get_vendor_id_from_host().unwrap() == t2a.get_supported_vendor() {
360            if t2a
361                .get_supported_cpu_models()
362                .contains(&CpuModel::get_cpu_model())
363            {
364                assert_eq!(
365                    cpu_template.get_cpu_template().unwrap(),
366                    Cow::Owned(t2a::t2a())
367                );
368            } else {
369                assert_eq!(
370                    cpu_template.get_cpu_template().unwrap_err(),
371                    GetCpuTemplateError::InvalidCpuModel,
372                );
373            }
374        } else {
375            assert_eq!(
376                cpu_template.get_cpu_template().unwrap_err(),
377                GetCpuTemplateError::CpuVendorMismatched,
378            );
379        }
380    }
381
382    #[test]
383    fn test_get_cpu_template_with_none_static_template() {
384        // Test `get_cpu_template()` when no static CPU template is provided.
385        // `InvalidStaticCpuTemplate` error should be returned because it is no longer valid and
386        // was replaced with `None` of `Option<CpuTemplateType>`.
387        let cpu_template = Some(CpuTemplateType::Static(StaticCpuTemplate::None));
388        assert_eq!(
389            cpu_template.get_cpu_template().unwrap_err(),
390            GetCpuTemplateError::InvalidStaticCpuTemplate(StaticCpuTemplate::None)
391        );
392
393        // Test the Display for StaticCpuTemplate
394        assert_eq!(format!("{}", StaticCpuTemplate::None), "None");
395    }
396
397    #[test]
398    fn test_get_cpu_template_with_custom_template() {
399        // Test `get_cpu_template()` when a custom CPU template is provided. The borrowed
400        // `CustomCpuTemplate` should be returned.
401        let inner_cpu_template = CustomCpuTemplate::default();
402        let cpu_template = Some(CpuTemplateType::Custom(inner_cpu_template.clone()));
403        assert_eq!(
404            cpu_template.get_cpu_template().unwrap(),
405            Cow::Borrowed(&inner_cpu_template)
406        );
407    }
408
409    #[test]
410    fn test_malformed_json() {
411        // Misspelled field name, register
412        let cpu_template_result = serde_json::from_str::<CustomCpuTemplate>(
413            r#"{
414                    "cpuid_modifiers": [
415                        {
416                            "leaf": "0x80000001",
417                            "subleaf": "0b000111",
418                            "flags": 0,
419                            "modifiers": [
420                                {
421                                    "register": "ekx",
422                                    "bitmap": "0bx00100xxx1xxxxxxxxxxxxxxxxxxxxx1"
423                                }
424                            ]
425                        },
426                    ],
427                }"#,
428        );
429        assert!(
430            cpu_template_result
431                .unwrap_err()
432                .to_string()
433                .contains("Invalid CPUID register. Must be one of [eax, ebx, ecx, edx]")
434        );
435
436        // Malformed MSR register address
437        let cpu_template_result = serde_json::from_str::<CustomCpuTemplate>(
438            r#"{
439                    "msr_modifiers":  [
440                        {
441                            "addr": "0jj0",
442                            "bitmap": "0bx00100xxx1xxxx00xxx1xxxxxxxxxxx1"
443                        },
444                    ]
445                }"#,
446        );
447        let error_msg: String = cpu_template_result.unwrap_err().to_string();
448        // Formatted error expected clarifying the number system prefix is missing
449        assert!(
450            error_msg.contains("No supported number system prefix found in value"),
451            "{}",
452            error_msg
453        );
454
455        // Malformed CPUID leaf address
456        let cpu_template_result = serde_json::from_str::<CustomCpuTemplate>(
457            r#"{
458                    "cpuid_modifiers": [
459                        {
460                            "leaf": "k",
461                            "subleaf": "0b000111",
462                            "flags": 0,
463                            "modifiers": [
464                                {
465                                    "register": "eax",
466                                    "bitmap": "0bx00100xxx1xxxxxxxxxxxxxxxxxxxxx1"
467                                }
468                            ]
469                        },
470                    ],
471                }"#,
472        );
473        let error_msg: String = cpu_template_result.unwrap_err().to_string();
474        // Formatted error expected clarifying the number system prefix is missing
475        assert!(
476            error_msg.contains("No supported number system prefix found in value"),
477            "{}",
478            error_msg
479        );
480        // Malformed 64-bit bitmap - filter failed
481        let cpu_template_result = serde_json::from_str::<CustomCpuTemplate>(
482            r#"{
483                    "msr_modifiers":  [
484                        {
485                            "addr": "0x200",
486                            "bitmap": "0bx0?1_0_0x_?x1xxxx00xxx1xxxxxxxxxxx1"
487                        },
488                    ]
489                }"#,
490        );
491        assert!(cpu_template_result.unwrap_err().to_string().contains(
492            "Failed to parse string [0bx0?1_0_0x_?x1xxxx00xxx1xxxxxxxxxxx1] as a bitmap"
493        ));
494        // Malformed 64-bit bitmap - value failed
495        let cpu_template_result = serde_json::from_str::<CustomCpuTemplate>(
496            r#"{
497                    "msr_modifiers":  [
498                        {
499                            "addr": "0x200",
500                            "bitmap": "0bx00100x0x1xxxx05xxx1xxxxxxxxxxx1"
501                        },
502                    ]
503                }"#,
504        );
505        assert!(
506            cpu_template_result.unwrap_err().to_string().contains(
507                "Failed to parse string [0bx00100x0x1xxxx05xxx1xxxxxxxxxxx1] as a bitmap"
508            )
509        );
510    }
511
512    #[test]
513    fn test_deserialization_lifecycle() {
514        let cpu_template = serde_json::from_str::<CustomCpuTemplate>(TEST_TEMPLATE_JSON)
515            .expect("Failed to deserialize custom CPU template.");
516        assert_eq!(5, cpu_template.cpuid_modifiers.len());
517        assert_eq!(4, cpu_template.msr_modifiers.len());
518    }
519
520    #[test]
521    fn test_serialization_lifecycle() {
522        let template = build_test_template();
523        let template_json_str_result = serde_json::to_string_pretty(&template);
524        let template_json = template_json_str_result.unwrap();
525
526        let deserialization_result = serde_json::from_str::<CustomCpuTemplate>(&template_json);
527        assert_eq!(template, deserialization_result.unwrap());
528    }
529
530    /// Test to confirm that templates for different CPU architectures have
531    /// a size bitmask that is supported by the architecture when serialized to JSON.
532    #[test]
533    fn test_bitmap_width() {
534        let mut cpuid_checked = false;
535        let mut msr_checked = false;
536
537        let template = build_test_template();
538
539        let x86_template_str =
540            serde_json::to_string(&template).expect("Error serializing x86 template");
541        let json_tree: Value = serde_json::from_str(&x86_template_str)
542            .expect("Error deserializing x86 template JSON string");
543
544        // Check that bitmaps for CPUID values are 32-bits in width
545        if let Some(cpuid_modifiers_root) = json_tree.get("cpuid_modifiers") {
546            let cpuid_mod_node = &cpuid_modifiers_root.as_array().unwrap()[0];
547            if let Some(modifiers_node) = cpuid_mod_node.get("modifiers") {
548                let mod_node = &modifiers_node.as_array().unwrap()[0];
549                if let Some(bit_map_str) = mod_node.get("bitmap") {
550                    // 32-bit width with a "0b" prefix for binary-formatted numbers
551                    assert_eq!(bit_map_str.as_str().unwrap().len(), 34);
552                    cpuid_checked = true;
553                }
554            }
555        }
556
557        // Check that bitmaps for MSRs are 64-bits in width
558        if let Some(msr_modifiers_root) = json_tree.get("msr_modifiers") {
559            let msr_mod_node = &msr_modifiers_root.as_array().unwrap()[0];
560            if let Some(bit_map_str) = msr_mod_node.get("bitmap") {
561                // 64-bit width with a "0b" prefix for binary-formatted numbers
562                assert_eq!(bit_map_str.as_str().unwrap().len(), 66);
563                assert!(bit_map_str.as_str().unwrap().starts_with("0b"));
564                msr_checked = true;
565            }
566        }
567
568        assert!(
569            cpuid_checked,
570            "CPUID bitmap width in a x86_64 template was not tested."
571        );
572        assert!(
573            msr_checked,
574            "MSR bitmap width in a x86_64 template was not tested."
575        );
576    }
577}