Skip to content

Commit 4c850ad

Browse files
authored
TDX: LDTR and TR access emulation (#1237)
This fills in the todo for emulating these 4 instructions, as well as including some small refactors to the previous 4, and adding a missing security check for load instructions. I don't have a way to test this end-to-end currently, but the refactors at least don't break the code that I can test.
1 parent b08317a commit 4c850ad

File tree

2 files changed

+187
-45
lines changed
  • openhcl/virt_mshv_vtl/src/processor/tdx
  • vm/x86/x86defs/src

2 files changed

+187
-45
lines changed

openhcl/virt_mshv_vtl/src/processor/tdx/mod.rs

+184-43
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ impl UhProcessor<'_, TdxBacked> {
11381138
}
11391139

11401140
if self.backing.vtls[vtl].processor_controls != new_processor_controls {
1141-
tracing::debug!(?new_processor_controls, ?vtl, "requesting window change");
1141+
tracing::trace!(?new_processor_controls, ?vtl, "requesting window change");
11421142
self.runner.write_vmcs32(
11431143
vtl,
11441144
VmcsField::VMX_VMCS_PROCESSOR_CONTROLS,
@@ -2071,10 +2071,10 @@ impl UhProcessor<'_, TdxBacked> {
20712071
HvX64RegisterName::Gdtr
20722072
}
20732073
};
2074-
// We only support fowarding intercepts for descriptor table writes today.
2075-
if (info.instruction().is_write()
2074+
// We only support fowarding intercepts for descriptor table loads today.
2075+
if (info.instruction().is_load()
20762076
&& !self.cvm_try_protect_secure_register_write(intercepted_vtl, reg, 0))
2077-
|| !info.instruction().is_write()
2077+
|| !info.instruction().is_load()
20782078
{
20792079
self.emulate_gdtr_or_idtr(intercepted_vtl, dev).await?;
20802080
}
@@ -2091,10 +2091,10 @@ impl UhProcessor<'_, TdxBacked> {
20912091
}
20922092
LdtrOrTrInstruction::Str | LdtrOrTrInstruction::Ltr => HvX64RegisterName::Tr,
20932093
};
2094-
// We only support fowarding intercepts for descriptor table writes today.
2095-
if (info.instruction().is_write()
2094+
// We only support fowarding intercepts for descriptor table loads today.
2095+
if (info.instruction().is_load()
20962096
&& !self.cvm_try_protect_secure_register_write(intercepted_vtl, reg, 0))
2097-
|| !info.instruction().is_write()
2097+
|| !info.instruction().is_load()
20982098
{
20992099
self.emulate_ldtr_or_tr(intercepted_vtl, dev).await?;
21002100
}
@@ -2752,7 +2752,9 @@ impl<T: CpuIo> X86EmulatorSupport for UhEmulationState<'_, '_, T, TdxBacked> {
27522752
let exit_info = TdxExit(self.vp.runner.tdx_vp_enter_exit_info());
27532753
let ept_info = VmxEptExitQualification::from(exit_info.qualification());
27542754

2755-
if ept_info.gva_valid() {
2755+
if exit_info.code().vmx_exit().basic_reason() == VmxExitBasic::EPT_VIOLATION
2756+
&& ept_info.gva_valid()
2757+
{
27562758
Some(virt_support_x86emu::emulate::InitialTranslation {
27572759
gva: exit_info.gla(),
27582760
gpa: exit_info.gpa(),
@@ -3061,44 +3063,26 @@ impl UhProcessor<'_, TdxBacked> {
30613063
VmxExitBasic::GDTR_OR_IDTR
30623064
);
30633065
let instr_info = GdtrOrIdtrInstructionInfo::from(exit_info.instr_info().info());
3064-
let gps = self.runner.tdx_enter_guest_gps();
30653066

3066-
// Check if read instructions are blocked by UMIP.
3067-
if !instr_info.instruction().is_write()
3068-
&& exit_info.cpl() > 0
3069-
&& self.read_cr4(vtl) & X64_CR4_UMIP != 0
3067+
// Check if load instructions are executed outside of kernel mode.
3068+
// Check if store instructions are blocked by UMIP.
3069+
if (instr_info.instruction().is_load() && exit_info.cpl() != 0)
3070+
|| (!instr_info.instruction().is_load()
3071+
&& exit_info.cpl() > 0
3072+
&& self.read_cr4(vtl) & X64_CR4_UMIP != 0)
30703073
{
30713074
self.inject_gpf(vtl);
30723075
return Ok(());
30733076
}
30743077

3075-
// Displacement is stored in the qualification field for these instructions.
3076-
let mut gva = exit_info.qualification();
3077-
if !instr_info.base_register_invalid() {
3078-
gva += gps[instr_info.base_register() as usize];
3079-
}
3080-
if !instr_info.index_register_invalid() {
3081-
gva += gps[instr_info.index_register() as usize] << instr_info.scaling();
3082-
}
3083-
match instr_info.address_size() {
3084-
// 16-bit address size
3085-
0 => gva &= 0xFFFF,
3086-
// 32-bit address size
3087-
1 => gva &= 0xFFFFFFFF,
3088-
// 64-bit address size
3089-
2 => {}
3090-
_ => unreachable!(),
3091-
}
3092-
3093-
let segment = match instr_info.segment_register() {
3094-
0 => Segment::ES,
3095-
1 => Segment::CS,
3096-
2 => Segment::SS,
3097-
3 => Segment::DS,
3098-
4 => Segment::FS,
3099-
5 => Segment::GS,
3100-
_ => unreachable!(),
3101-
};
3078+
let (gva, segment) = self.compute_gva_for_table_access_emulation(
3079+
exit_info.qualification(),
3080+
(!instr_info.base_register_invalid()).then_some(instr_info.base_register()),
3081+
(!instr_info.index_register_invalid()).then_some(instr_info.index_register()),
3082+
instr_info.scaling(),
3083+
instr_info.address_size(),
3084+
instr_info.segment_register(),
3085+
);
31023086

31033087
let gm = &self.partition.gm[vtl];
31043088
let interruption_pending = self.backing.vtls[vtl].interruption_information.valid();
@@ -3176,10 +3160,167 @@ impl UhProcessor<'_, TdxBacked> {
31763160

31773161
async fn emulate_ldtr_or_tr(
31783162
&mut self,
3179-
_vtl: GuestVtl,
3180-
_dev: &impl CpuIo,
3163+
vtl: GuestVtl,
3164+
dev: &impl CpuIo,
31813165
) -> Result<(), VpHaltReason<UhRunVpError>> {
3182-
todo!()
3166+
let exit_info = TdxExit(self.runner.tdx_vp_enter_exit_info());
3167+
assert_eq!(
3168+
exit_info.code().vmx_exit().basic_reason(),
3169+
VmxExitBasic::LDTR_OR_TR
3170+
);
3171+
let instr_info = LdtrOrTrInstructionInfo::from(exit_info.instr_info().info());
3172+
3173+
// Check if load instructions are executed outside of kernel mode.
3174+
// Check if store instructions are blocked by UMIP.
3175+
if (instr_info.instruction().is_load() && exit_info.cpl() != 0)
3176+
|| (!instr_info.instruction().is_load()
3177+
&& exit_info.cpl() > 0
3178+
&& self.read_cr4(vtl) & X64_CR4_UMIP != 0)
3179+
{
3180+
self.inject_gpf(vtl);
3181+
return Ok(());
3182+
}
3183+
3184+
let gm = &self.partition.gm[vtl];
3185+
let interruption_pending = self.backing.vtls[vtl].interruption_information.valid();
3186+
3187+
match instr_info.instruction() {
3188+
LdtrOrTrInstruction::Sldt | LdtrOrTrInstruction::Str => {
3189+
let value = self.runner.read_vmcs16(
3190+
vtl,
3191+
if matches!(instr_info.instruction(), LdtrOrTrInstruction::Sldt) {
3192+
TdxSegmentReg::Ldtr
3193+
} else {
3194+
TdxSegmentReg::Tr
3195+
}
3196+
.selector(),
3197+
);
3198+
3199+
if instr_info.memory_or_register() {
3200+
let gps = self.runner.tdx_enter_guest_gps_mut();
3201+
gps[instr_info.register_1() as usize] = value.into();
3202+
} else {
3203+
let (gva, segment) = self.compute_gva_for_table_access_emulation(
3204+
exit_info.qualification(),
3205+
(!instr_info.base_register_invalid()).then_some(instr_info.base_register()),
3206+
(!instr_info.index_register_invalid())
3207+
.then_some(instr_info.index_register()),
3208+
instr_info.scaling(),
3209+
instr_info.address_size(),
3210+
instr_info.segment_register(),
3211+
);
3212+
let mut emulation_state = UhEmulationState {
3213+
vp: &mut *self,
3214+
interruption_pending,
3215+
devices: dev,
3216+
vtl,
3217+
cache: TdxEmulationCache::default(),
3218+
};
3219+
emulate_insn_memory_op(
3220+
&mut emulation_state,
3221+
gm,
3222+
dev,
3223+
gva,
3224+
segment,
3225+
x86emu::AlignmentMode::Standard,
3226+
EmulatedMemoryOperation::Write(&value.to_le_bytes()),
3227+
)
3228+
.await?;
3229+
}
3230+
}
3231+
3232+
LdtrOrTrInstruction::Lldt | LdtrOrTrInstruction::Ltr => {
3233+
let value = if instr_info.memory_or_register() {
3234+
let gps = self.runner.tdx_enter_guest_gps();
3235+
gps[instr_info.register_1() as usize] as u16
3236+
} else {
3237+
let (gva, segment) = self.compute_gva_for_table_access_emulation(
3238+
exit_info.qualification(),
3239+
(!instr_info.base_register_invalid()).then_some(instr_info.base_register()),
3240+
(!instr_info.index_register_invalid())
3241+
.then_some(instr_info.index_register()),
3242+
instr_info.scaling(),
3243+
instr_info.address_size(),
3244+
instr_info.segment_register(),
3245+
);
3246+
let mut emulation_state = UhEmulationState {
3247+
vp: &mut *self,
3248+
interruption_pending,
3249+
devices: dev,
3250+
vtl,
3251+
cache: TdxEmulationCache::default(),
3252+
};
3253+
let mut buf = [0u8; 2];
3254+
emulate_insn_memory_op(
3255+
&mut emulation_state,
3256+
gm,
3257+
dev,
3258+
gva,
3259+
segment,
3260+
x86emu::AlignmentMode::Standard,
3261+
EmulatedMemoryOperation::Read(&mut buf),
3262+
)
3263+
.await?;
3264+
u16::from_le_bytes(buf)
3265+
};
3266+
self.runner.write_vmcs16(
3267+
vtl,
3268+
if matches!(instr_info.instruction(), LdtrOrTrInstruction::Lldt) {
3269+
TdxSegmentReg::Ldtr
3270+
} else {
3271+
TdxSegmentReg::Tr
3272+
}
3273+
.selector(),
3274+
!0,
3275+
value,
3276+
);
3277+
}
3278+
}
3279+
3280+
self.advance_to_next_instruction(vtl);
3281+
Ok(())
3282+
}
3283+
3284+
fn compute_gva_for_table_access_emulation(
3285+
&self,
3286+
qualification: u64,
3287+
base_reg: Option<u8>,
3288+
index_reg: Option<u8>,
3289+
scaling: u8,
3290+
address_size: u8,
3291+
segment_register: u8,
3292+
) -> (u64, Segment) {
3293+
let gps = self.runner.tdx_enter_guest_gps();
3294+
3295+
// Displacement is stored in the qualification field for these instructions.
3296+
let mut gva = qualification;
3297+
if let Some(base_register) = base_reg {
3298+
gva += gps[base_register as usize];
3299+
}
3300+
if let Some(index_register) = index_reg {
3301+
gva += gps[index_register as usize] << scaling;
3302+
}
3303+
match address_size {
3304+
// 16-bit address size
3305+
0 => gva &= 0xFFFF,
3306+
// 32-bit address size
3307+
1 => gva &= 0xFFFFFFFF,
3308+
// 64-bit address size
3309+
2 => {}
3310+
_ => unreachable!(),
3311+
}
3312+
3313+
let segment = match segment_register {
3314+
0 => Segment::ES,
3315+
1 => Segment::CS,
3316+
2 => Segment::SS,
3317+
3 => Segment::DS,
3318+
4 => Segment::FS,
3319+
5 => Segment::GS,
3320+
_ => unreachable!(),
3321+
};
3322+
3323+
(gva, segment)
31833324
}
31843325
}
31853326

vm/x86/x86defs/src/vmx.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ impl GdtrOrIdtrInstruction {
341341
self as u8
342342
}
343343

344-
pub const fn is_write(self) -> bool {
344+
pub const fn is_load(self) -> bool {
345345
match self {
346346
GdtrOrIdtrInstruction::Lgdt | GdtrOrIdtrInstruction::Lidt => true,
347347
GdtrOrIdtrInstruction::Sgdt | GdtrOrIdtrInstruction::Sidt => false,
@@ -358,6 +358,7 @@ pub struct LdtrOrTrInstructionInfo {
358358
pub register_1: u8,
359359
#[bits(3)]
360360
pub address_size: u8,
361+
/// 0 - Memory, 1 - Register
361362
pub memory_or_register: bool,
362363
#[bits(4)]
363364
_reserved2: u8,
@@ -398,7 +399,7 @@ impl LdtrOrTrInstruction {
398399
self as u8
399400
}
400401

401-
pub const fn is_write(self) -> bool {
402+
pub const fn is_load(self) -> bool {
402403
match self {
403404
LdtrOrTrInstruction::Lldt | LdtrOrTrInstruction::Ltr => true,
404405
LdtrOrTrInstruction::Sldt | LdtrOrTrInstruction::Str => false,

0 commit comments

Comments
 (0)