use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct MsrEntry {
pub index: u32,
pub value: u64,
}
#[derive(Debug, Clone)]
pub(crate) struct MsrResetState {
baseline: Vec<MsrEntry>,
guest_msrs: Vec<u32>,
}
const CORE_PERSIST_MSRS: &[u32] = &[MSR_KERNEL_GS_BASE, MSR_TSC, MSR_TSC_ADJUST, MSR_IA32_SSP];
impl MsrResetState {
pub fn capture(
mut indices: Vec<u32>,
guest_msrs: &[u32],
read: impl FnOnce(
&[u32],
)
-> Result<Vec<MsrEntry>, crate::hypervisor::virtual_machine::RegisterError>,
) -> Result<Self, crate::hypervisor::virtual_machine::RegisterError> {
indices.sort_unstable();
indices.dedup();
let baseline = read(&indices)?;
let mut guest_msrs = guest_msrs.to_vec();
guest_msrs.sort_unstable();
guest_msrs.dedup();
Ok(Self {
baseline,
guest_msrs,
})
}
#[cfg(test)]
pub fn reset_indices(&self) -> Vec<u32> {
self.baseline.iter().map(|entry| entry.index).collect()
}
pub fn persist_indices(&self) -> Vec<u32> {
self.baseline
.iter()
.map(|entry| entry.index)
.filter(|index| CORE_PERSIST_MSRS.contains(index) || self.guest_msrs.contains(index))
.collect()
}
pub fn validate_snapshot(
&self,
snapshot: &[MsrEntry],
) -> Result<Vec<MsrEntry>, crate::hypervisor::virtual_machine::RegisterError> {
use crate::hypervisor::virtual_machine::RegisterError;
let restorable = self.persist_indices();
for entry in snapshot {
if !restorable.contains(&entry.index) {
return Err(RegisterError::InvalidSnapshotMsrIndex { index: entry.index });
}
}
Ok(self
.baseline
.iter()
.map(|base| MsrEntry {
index: base.index,
value: snapshot
.iter()
.find(|entry| entry.index == base.index)
.map_or(base.value, |entry| entry.value),
})
.collect())
}
}
pub(crate) const MSR_TSC: u32 = 0x10;
pub(crate) const MSR_TSC_ADJUST: u32 = 0x3B;
pub(crate) const MSR_SPEC_CTRL: u32 = 0x48;
pub(crate) const MSR_UMWAIT_CONTROL: u32 = 0xE1;
pub(crate) const MSR_MPERF: u32 = 0xE7;
pub(crate) const MSR_APERF: u32 = 0xE8;
pub(crate) const MSR_MTRR_CAP: u32 = 0xFE;
pub(crate) const MSR_TSX_CTRL: u32 = 0x122;
pub(crate) const MSR_SYSENTER_CS: u32 = 0x174;
pub(crate) const MSR_SYSENTER_ESP: u32 = 0x175;
pub(crate) const MSR_SYSENTER_EIP: u32 = 0x176;
pub(crate) const MSR_XFD: u32 = 0x1C4;
pub(crate) const MSR_XFD_ERR: u32 = 0x1C5;
pub(crate) const MSR_DEBUGCTL: u32 = 0x1D9;
pub(crate) const MSR_MTRR_FIX64K_00000: u32 = 0x250;
pub(crate) const MSR_PAT: u32 = 0x277;
pub(crate) const MSR_MTRR_DEF_TYPE: u32 = 0x2FF;
pub(crate) const MSR_U_CET: u32 = 0x6A0;
pub(crate) const MSR_S_CET: u32 = 0x6A2;
pub(crate) const MSR_PL0_SSP: u32 = 0x6A4;
pub(crate) const MSR_PL1_SSP: u32 = 0x6A5;
pub(crate) const MSR_PL2_SSP: u32 = 0x6A6;
pub(crate) const MSR_PL3_SSP: u32 = 0x6A7;
pub(crate) const MSR_INTERRUPT_SSP_TABLE_ADDR: u32 = 0x6A8;
pub(crate) const MSR_IA32_SSP: u32 = 0x7A0;
pub(crate) const MSR_TSC_DEADLINE: u32 = 0x6E0;
pub(crate) const MSR_BNDCFGS: u32 = 0xD90;
pub(crate) const MSR_XSS: u32 = 0xDA0;
pub(crate) const MSR_STAR: u32 = 0xC000_0081;
pub(crate) const MSR_LSTAR: u32 = 0xC000_0082;
pub(crate) const MSR_CSTAR: u32 = 0xC000_0083;
pub(crate) const MSR_SFMASK: u32 = 0xC000_0084;
pub(crate) const MSR_KERNEL_GS_BASE: u32 = 0xC000_0102;
pub(crate) const MSR_TSC_AUX: u32 = 0xC000_0103;
pub(crate) const MSR_VIRT_SPEC_CTRL: u32 = 0xC001_011F;
const HYPERV_VARIABLE_MTRR_COUNT: u8 = 16;
const NON_MTRR_RESETTABLE_MSRS: &[u32] = &[
MSR_SYSENTER_CS,
MSR_SYSENTER_ESP,
MSR_SYSENTER_EIP,
MSR_DEBUGCTL,
MSR_PAT,
MSR_STAR,
MSR_LSTAR,
MSR_CSTAR,
MSR_SFMASK,
MSR_KERNEL_GS_BASE,
MSR_SPEC_CTRL,
MSR_VIRT_SPEC_CTRL,
MSR_U_CET,
MSR_S_CET,
MSR_PL0_SSP,
MSR_PL1_SSP,
MSR_PL2_SSP,
MSR_PL3_SSP,
MSR_INTERRUPT_SSP_TABLE_ADDR,
MSR_XSS,
MSR_TSC,
MSR_TSC_ADJUST,
MSR_TSC_AUX,
MSR_MPERF,
MSR_APERF,
MSR_TSX_CTRL,
MSR_XFD,
MSR_XFD_ERR,
MSR_UMWAIT_CONTROL,
MSR_TSC_DEADLINE,
MSR_BNDCFGS,
];
const HYPERV_ONLY_RESETTABLE_MSRS: &[u32] = &[MSR_IA32_SSP];
const MTRR_RESET_INDICES: &[u32] = &[
MSR_MTRR_DEF_TYPE,
0x200, 0x201, 0x202, 0x203, 0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20A, 0x20B, 0x20C, 0x20D, 0x20E, 0x20F, 0x210, 0x211, 0x212, 0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21A, 0x21B, 0x21C, 0x21D, 0x21E, 0x21F, MSR_MTRR_FIX64K_00000, 0x258, 0x259, 0x268, 0x269, 0x26A, 0x26B, 0x26C, 0x26D, 0x26E, 0x26F, ];
pub(crate) fn is_resettable_msr(index: u32) -> bool {
NON_MTRR_RESETTABLE_MSRS.contains(&index) || is_mtrr_reset_index(index)
}
pub(crate) fn filterless_core_reset_candidates() -> impl Iterator<Item = u32> {
NON_MTRR_RESETTABLE_MSRS
.iter()
.chain(HYPERV_ONLY_RESETTABLE_MSRS)
.copied()
}
#[cfg(test)]
pub(crate) fn resettable_msr_indices() -> impl Iterator<Item = u32> {
NON_MTRR_RESETTABLE_MSRS
.iter()
.chain(HYPERV_ONLY_RESETTABLE_MSRS)
.chain(MTRR_RESET_INDICES)
.copied()
}
pub(crate) fn hyperv_mtrr_reset_indices(
mtrr_cap: u64,
) -> Result<Vec<u32>, crate::hypervisor::virtual_machine::CreateVmError> {
use crate::hypervisor::virtual_machine::CreateVmError;
let advertised = (mtrr_cap & 0xff) as u8;
if advertised > HYPERV_VARIABLE_MTRR_COUNT {
return Err(CreateVmError::UnexpectedVariableMtrrCount {
advertised,
maximum: HYPERV_VARIABLE_MTRR_COUNT,
});
}
let mut indices = Vec::with_capacity(1 + usize::from(advertised) * 2 + 11);
indices.push(MSR_MTRR_DEF_TYPE);
indices.extend((0..u32::from(advertised) * 2).map(|offset| 0x200 + offset));
indices.extend([
MSR_MTRR_FIX64K_00000,
0x258,
0x259,
0x268,
0x269,
0x26A,
0x26B,
0x26C,
0x26D,
0x26E,
0x26F,
]);
Ok(indices)
}
pub(crate) fn is_mtrr_reset_index(index: u32) -> bool {
MTRR_RESET_INDICES.contains(&index)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hypervisor::virtual_machine::{CreateVmError, RegisterError};
fn state() -> MsrResetState {
MsrResetState {
baseline: vec![
MsrEntry {
index: MSR_SYSENTER_CS,
value: 0x1C,
},
MsrEntry {
index: MSR_SYSENTER_ESP,
value: 0x2C,
},
MsrEntry {
index: MSR_KERNEL_GS_BASE,
value: 0x3C,
},
],
guest_msrs: vec![MSR_SYSENTER_CS, MSR_SYSENTER_ESP],
}
}
#[test]
fn hyperv_mtrr_indices_follow_guest_capability() {
assert_eq!(
hyperv_mtrr_reset_indices(2).unwrap(),
[
MSR_MTRR_DEF_TYPE,
0x200,
0x201,
0x202,
0x203,
MSR_MTRR_FIX64K_00000,
0x258,
0x259,
0x268,
0x269,
0x26A,
0x26B,
0x26C,
0x26D,
0x26E,
0x26F,
]
);
let fixed_bit_does_not_change_reset_set = hyperv_mtrr_reset_indices(2 | (1 << 8)).unwrap();
assert_eq!(
fixed_bit_does_not_change_reset_set,
hyperv_mtrr_reset_indices(2).unwrap()
);
}
#[test]
fn hyperv_mtrr_count_rejects_more_than_sixteen_pairs() {
let indices = hyperv_mtrr_reset_indices(16).unwrap();
assert!(indices.contains(&0x21F));
assert_eq!(indices.last(), Some(&0x26F));
assert!(indices.iter().all(|&index| is_resettable_msr(index)));
assert!(matches!(
hyperv_mtrr_reset_indices(17),
Err(CreateVmError::UnexpectedVariableMtrrCount {
advertised: 17,
maximum: 16
})
));
}
#[test]
fn snapshot_msr_validation_accepts_exact_canonical_set() {
let supplied = vec![
MsrEntry {
index: MSR_SYSENTER_CS,
value: 1,
},
MsrEntry {
index: MSR_SYSENTER_ESP,
value: 2,
},
MsrEntry {
index: MSR_KERNEL_GS_BASE,
value: 3,
},
];
assert_eq!(state().validate_snapshot(&supplied).unwrap(), supplied);
}
#[test]
fn snapshot_msr_validation_baselines_omitted_indices() {
let supplied = vec![
MsrEntry {
index: MSR_SYSENTER_CS,
value: 1,
},
MsrEntry {
index: MSR_KERNEL_GS_BASE,
value: 3,
},
];
assert_eq!(
state().validate_snapshot(&supplied).unwrap(),
vec![
MsrEntry {
index: MSR_SYSENTER_CS,
value: 1
},
MsrEntry {
index: MSR_SYSENTER_ESP,
value: 0x2C
},
MsrEntry {
index: MSR_KERNEL_GS_BASE,
value: 3
},
]
);
}
#[test]
fn snapshot_msr_validation_rejects_index_outside_reset_set() {
let supplied = vec![
MsrEntry {
index: MSR_SYSENTER_CS,
value: 1,
},
MsrEntry {
index: MSR_SYSENTER_ESP,
value: 2,
},
MsrEntry {
index: MSR_PAT,
value: 3,
},
];
assert!(matches!(
state().validate_snapshot(&supplied),
Err(RegisterError::InvalidSnapshotMsrIndex { index }) if index == MSR_PAT
));
}
#[test]
fn snapshot_msr_validation_rejects_reset_index_that_is_not_declared() {
let state = MsrResetState {
baseline: vec![
MsrEntry {
index: MSR_PAT,
value: 1,
},
MsrEntry {
index: MSR_KERNEL_GS_BASE,
value: 2,
},
MsrEntry {
index: MSR_SYSENTER_CS,
value: 3,
},
],
guest_msrs: vec![MSR_SYSENTER_CS],
};
assert!(matches!(
state.validate_snapshot(&[MsrEntry {
index: MSR_PAT,
value: 9,
}]),
Err(RegisterError::InvalidSnapshotMsrIndex { index }) if index == MSR_PAT
));
assert!(
state
.validate_snapshot(&[
MsrEntry {
index: MSR_SYSENTER_CS,
value: 9,
},
MsrEntry {
index: MSR_KERNEL_GS_BASE,
value: 8,
},
])
.is_ok()
);
}
#[test]
fn snapshot_msr_validation_accepts_empty_canonical_set() {
let state = MsrResetState {
baseline: Vec::new(),
guest_msrs: Vec::new(),
};
assert!(state.validate_snapshot(&[]).unwrap().is_empty());
}
#[test]
fn persist_indices_covers_guest_msrs_and_core_only() {
let state = MsrResetState {
baseline: vec![
MsrEntry {
index: MSR_PAT,
value: 1,
},
MsrEntry {
index: MSR_KERNEL_GS_BASE,
value: 2,
},
MsrEntry {
index: MSR_SYSENTER_CS,
value: 3,
},
],
guest_msrs: vec![MSR_SYSENTER_CS],
};
assert_eq!(
state.persist_indices(),
vec![MSR_KERNEL_GS_BASE, MSR_SYSENTER_CS]
);
}
}