Skip to content

TDX: Configure MSR intercepts for VTL 1, refactor MSR intercepts in general #1251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions openhcl/hcl/src/ioctl/tdx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,31 @@ impl<'a> ProcessorRunner<'a, Tdx<'a>> {
self.read_vmcs(vtl, field) as u16
}

/// Sets the MSR bitmap intercept bit for the given MSR index.
///
/// Panics if there is an error in the TDX module when writing the bit.
pub fn set_msr_bit(&self, vtl: GuestVtl, msr_index: u32, write: bool, intercept: bool) {
let mut word_index = (msr_index & 0xFFFF) / 64;

if msr_index & 0x80000000 == 0x80000000 {
assert!((0xC0000000..=0xC0001FFF).contains(&msr_index));
word_index += 0x80;
} else {
assert!(msr_index <= 0x00001FFF);
}

if write {
word_index += 0x100;
}

self.write_msr_bitmap(
vtl,
word_index,
1 << (msr_index as u64 & 0x3F),
if intercept { !0 } else { 0 },
);
}

/// Writes 64-bit word with index `i` of the MSR bitmap.
///
/// Only updates the bits that are set in `mask`. Returns the old value of
Expand Down
1 change: 1 addition & 0 deletions openhcl/virt_mshv_vtl/src/processor/hardware_cvm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ impl<T, B: HardwareIsolatedBacking> UhHypercallHandler<'_, '_, T, B> {
&mut self,
intercept_control: hvdef::HvRegisterCrInterceptControl,
) -> HvResult<()> {
// We support intercepting all writes except msr_sgx_launch_control_write, but no reads.
let supported_controls = hvdef::HvRegisterCrInterceptControl::new()
.with_cr0_write(true)
.with_cr4_write(true)
Expand Down
173 changes: 92 additions & 81 deletions openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,36 @@ use x86defs::vmx::VmxExitBasic;
use x86emu::Gp;
use x86emu::Segment;

/// MSRs that are allowed to be read by the guest without interception.
const MSR_ALLOWED_READ: &[u32] = &[
x86defs::X86X_MSR_TSC,
X86X_MSR_EFER,
x86defs::X86X_MSR_STAR,
x86defs::X86X_MSR_LSTAR,
x86defs::X86X_MSR_SFMASK,
x86defs::X86X_MSR_SYSENTER_CS,
x86defs::X86X_MSR_SYSENTER_ESP,
x86defs::X86X_MSR_SYSENTER_EIP,
];

/// MSRs that are allowed to be read and written by the guest without interception.
const MSR_ALLOWED_READ_WRITE: &[u32] = &[
x86defs::X64_MSR_FS_BASE,
x86defs::X64_MSR_GS_BASE,
x86defs::X64_MSR_KERNEL_GS_BASE,
x86defs::X86X_MSR_SPEC_CTRL,
x86defs::X86X_MSR_U_CET,
x86defs::X86X_MSR_S_CET,
x86defs::X86X_MSR_PL0_SSP,
x86defs::X86X_MSR_PL1_SSP,
x86defs::X86X_MSR_PL2_SSP,
x86defs::X86X_MSR_PL3_SSP,
x86defs::X86X_MSR_TSC_AUX,
x86defs::X86X_MSR_INTERRUPT_SSP_TABLE_ADDR,
x86defs::X86X_IA32_MSR_XFD,
x86defs::X86X_IA32_MSR_XFD_ERR,
];

#[derive(Debug)]
struct TdxExit<'a>(&'a tdx_tdg_vp_enter_exit_info);

Expand Down Expand Up @@ -318,77 +348,6 @@ impl VirtualRegister {
}
}

const BITMAP_SIZE: usize = HV_PAGE_SIZE as usize / 8;
/// Bitmap used to control MSR intercepts.
struct MsrBitmap {
bitmap: [u64; BITMAP_SIZE],
}

impl MsrBitmap {
fn new() -> Self {
// Initialize the bitmap with the default behavior of all 1s, which
// means intercept.
let mut bitmap = [u64::MAX; BITMAP_SIZE];

let mut clear_msr_bit = |msr_index: u32, write: bool| {
let mut word_index = ((msr_index & 0xFFFF) / 64) as usize;

if msr_index & 0x80000000 == 0x80000000 {
assert!((0xC0000000..=0xC0001FFF).contains(&msr_index));
word_index += 0x80;
} else {
assert!(msr_index <= 0x00001FFF);
}

if write {
word_index += 0x100;
}

// Clear the specified bit
bitmap[word_index] &= !(1 << (msr_index as u64 & 0x3F));
};

const ALLOWED_READ: &[u32] = &[
x86defs::X86X_MSR_TSC,
X86X_MSR_EFER,
x86defs::X86X_MSR_STAR,
x86defs::X86X_MSR_LSTAR,
x86defs::X86X_MSR_SFMASK,
x86defs::X86X_MSR_SYSENTER_CS,
x86defs::X86X_MSR_SYSENTER_ESP,
x86defs::X86X_MSR_SYSENTER_EIP,
];

const ALLOWED_READ_WRITE: &[u32] = &[
x86defs::X64_MSR_FS_BASE,
x86defs::X64_MSR_GS_BASE,
x86defs::X64_MSR_KERNEL_GS_BASE,
x86defs::X86X_MSR_SPEC_CTRL,
x86defs::X86X_MSR_U_CET,
x86defs::X86X_MSR_S_CET,
x86defs::X86X_MSR_PL0_SSP,
x86defs::X86X_MSR_PL1_SSP,
x86defs::X86X_MSR_PL2_SSP,
x86defs::X86X_MSR_PL3_SSP,
x86defs::X86X_MSR_TSC_AUX,
x86defs::X86X_MSR_INTERRUPT_SSP_TABLE_ADDR,
x86defs::X86X_IA32_MSR_XFD,
x86defs::X86X_IA32_MSR_XFD_ERR,
];

for &msr in ALLOWED_READ {
clear_msr_bit(msr, false);
}

for &msr in ALLOWED_READ_WRITE {
clear_msr_bit(msr, false);
clear_msr_bit(msr, true);
}

Self { bitmap }
}
}

/// Backing for TDX partitions.
#[derive(InspectMut)]
pub struct TdxBacked {
Expand Down Expand Up @@ -643,7 +602,51 @@ impl HardwareIsolatedBacking for TdxBacked {
.into_bits(),
);

// TODO Update MSR bitmaps
// Update MSR intercepts. We only need to update those that are allowed
// to be passed through, as the default otherwise is to always intercept.
// See [`MSR_ALLOWED_READ_WRITE`].
this.runner.set_msr_bit(
vtl,
x86defs::X86X_MSR_TSC_AUX,
true,
intercept_control.msr_tsc_aux_write(),
);
this.runner.set_msr_bit(
vtl,
x86defs::X86X_MSR_S_CET,
true,
intercept_control.msr_scet_write(),
);
this.runner.set_msr_bit(
vtl,
x86defs::X86X_MSR_PL0_SSP,
true,
intercept_control.msr_pls_ssp_write(),
);
this.runner.set_msr_bit(
vtl,
x86defs::X86X_MSR_PL1_SSP,
true,
intercept_control.msr_pls_ssp_write(),
);
this.runner.set_msr_bit(
vtl,
x86defs::X86X_MSR_PL2_SSP,
true,
intercept_control.msr_pls_ssp_write(),
);
this.runner.set_msr_bit(
vtl,
x86defs::X86X_MSR_PL3_SSP,
true,
intercept_control.msr_pls_ssp_write(),
);
this.runner.set_msr_bit(
vtl,
x86defs::X86X_MSR_INTERRUPT_SSP_TABLE_ADDR,
true,
intercept_control.msr_pls_ssp_write(),
);
}
}

Expand Down Expand Up @@ -793,14 +796,15 @@ impl BackingPrivate for TdxBacked {
shared.cr_guest_host_mask(ShadowedRegister::Cr4),
);

// Configure the MSR bitmap for this VP. Since the default MSR bitmap
// is all ones, only those values that are not all ones need to be set in
// the TDX module.
let bitmap = MsrBitmap::new();
for (i, &word) in bitmap.bitmap.iter().enumerate() {
if word != u64::MAX {
params.runner.write_msr_bitmap(vtl, i as u32, !0, word);
}
// Configure the MSR bitmap for this VP. Since the default MSR bitmap
// is set to intercept everything only the MSRs that we want to allow
// to passthrough need to be set.
for msr in MSR_ALLOWED_READ {
params.runner.set_msr_bit(vtl, *msr, false, false);
}
for msr in MSR_ALLOWED_READ_WRITE {
params.runner.set_msr_bit(vtl, *msr, false, false);
params.runner.set_msr_bit(vtl, *msr, true, false);
}

// Set the exception bitmap.
Expand Down Expand Up @@ -1797,7 +1801,14 @@ impl UhProcessor<'_, TdxBacked> {
.msr_write(msr, value)
.or_else_if_unknown(|| self.write_msr_cvm(msr, value, intercepted_vtl))
.or_else_if_unknown(|| self.write_msr(msr, value, intercepted_vtl))
.or_else_if_unknown(|| self.write_msr_tdx(msr, value, intercepted_vtl));
.or_else_if_unknown(|| self.write_msr_tdx(msr, value, intercepted_vtl))
.or_else_if_unknown(|| {
// Sanity check
if MSR_ALLOWED_READ_WRITE.contains(&msr) {
unreachable!("intercepted a write to MSR {msr}, configured for passthrough by default, that wasn't registered for intercepts by a higher VTL");
}
Err(MsrError::Unknown)
});

let inject_gp = match result {
Ok(()) => false,
Expand Down