From: David Woodhouse <[email protected]>

When a higher VTL arms the ldtr_write or tr_write bits of
HvRegisterCrInterceptControl, hv_divert_dt_write() covers GDTR and
IDTR writes, but an LTR in the lower VTL retires natively, and the
LDTR divert sat in emulator_set_segment(), which task-switch emulation
also reaches via __load_segment_descriptor(). A protected-mode task
switch with the intercept armed — reachable by a malicious lower VTL,
even though 64-bit Windows takes no task switches — had its LDTR load
silently dropped, and the higher VTL received a bogus mid-task-switch
intercept carrying the wrong RIP and instruction length. The TLFS
intercepts cover the LLDT and LTR instructions only.

Add a divert_seg_write(ctxt, selector, seg) emulator op and call it
from em_lldt() and em_ltr() before load_segment_descriptor().
Diverting from the instruction handlers, before any architectural
effect, keeps hardware task switches out of the intercept; for LTR it
also matters that __load_segment_descriptor() marks the TSS descriptor
busy in the GDT first, and a busy descriptor would #GP the LTR when it
re-executes after a cooperative VTL return. emulator_set_segment() no
longer diverts anything.

No vmx change is needed: SECONDARY_EXEC_DESC already exits on LTR
(handle_desc covers EXIT_REASON_LDTR_TR) whenever any
reg_intercept_control bit is set.

Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: LLM
---
 arch/x86/kvm/emulate.c     | 21 +++++++++++++++++++++
 arch/x86/kvm/kvm_emulate.h | 10 ++++++++++
 arch/x86/kvm/x86.c         | 33 +++++++++++++++++++++------------
 3 files changed, 52 insertions(+), 12 deletions(-)

diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index 8013dccb3110..86d165b8185c 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -3387,6 +3387,16 @@ static int em_lldt(struct x86_emulate_ctxt *ctxt)
 
        /* Disable writeback. */
        ctxt->dst.type = OP_NONE;
+
+       /*
+        * A higher VTL's LDTR-write intercept suspends the LLDT before any
+        * architectural effect.  Diverting here rather than in
+        * emulator_set_segment() keeps task switches, which load LDTR
+        * through set_segment as well, out of the intercept.
+        */
+       if (ctxt->ops->divert_seg_write(ctxt, sel, VCPU_SREG_LDTR))
+               return X86EMUL_CONTINUE;
+
        return load_segment_descriptor(ctxt, sel, VCPU_SREG_LDTR);
 }
 
@@ -3401,6 +3411,17 @@ static int em_ltr(struct x86_emulate_ctxt *ctxt)
 
        /* Disable writeback. */
        ctxt->dst.type = OP_NONE;
+
+       /*
+        * A higher VTL's TR-write intercept suspends the LTR before any
+        * architectural effect.  The divert must precede
+        * load_segment_descriptor(): that path marks the TSS descriptor
+        * busy in the GDT, and a busy descriptor would #GP the re-executed
+        * LTR after a cooperative VTL return.
+        */
+       if (ctxt->ops->divert_seg_write(ctxt, sel, VCPU_SREG_TR))
+               return X86EMUL_CONTINUE;
+
        return load_segment_descriptor(ctxt, sel, VCPU_SREG_TR);
 }
 
diff --git a/arch/x86/kvm/kvm_emulate.h b/arch/x86/kvm/kvm_emulate.h
index 0abff36d0994..8a3b9a40934b 100644
--- a/arch/x86/kvm/kvm_emulate.h
+++ b/arch/x86/kvm/kvm_emulate.h
@@ -202,6 +202,16 @@ struct x86_emulate_ops {
                            struct desc_struct *desc, u32 *base3, int seg);
        void (*set_segment)(struct x86_emulate_ctxt *ctxt, u16 selector,
                            struct desc_struct *desc, u32 base3, int seg);
+       /*
+        * Divert an LTR or LLDT into a Hyper-V VSM register-intercept exit
+        * when the higher VTL armed one.  Returns true when the write was
+        * diverted: the instruction must then have no architectural effect,
+        * so this is checked before descriptor validation (and, for LTR,
+        * the busy-bit write).  Only the instructions divert; task-switch
+        * loads of LDTR/TR are not covered by the TLFS intercepts.
+        */
+       bool (*divert_seg_write)(struct x86_emulate_ctxt *ctxt, u16 selector,
+                                int seg);
        unsigned long (*get_cached_segment_base)(struct x86_emulate_ctxt *ctxt,
                                                 int seg);
        void (*get_gdt)(struct x86_emulate_ctxt *ctxt, struct desc_ptr *dt);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 052821799120..c5122e6f42bd 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8859,9 +8859,11 @@ static bool hv_divert_dt_write(struct x86_emulate_ctxt 
*ctxt, u32 hv_reg,
 #define HV_CR_INTERCEPT_GDTR_WRITE     BIT_ULL(15)
 #define HV_CR_INTERCEPT_IDTR_WRITE     BIT_ULL(16)
 #define HV_CR_INTERCEPT_LDTR_WRITE     BIT_ULL(17)
+#define HV_CR_INTERCEPT_TR_WRITE       BIT_ULL(18)
 #define HV_X64_REGISTER_IDTR_ID                0x00070000
 #define HV_X64_REGISTER_GDTR_ID                0x00070001
 #define HV_X64_REGISTER_LDTR_ID                0x00060006
+#define HV_X64_REGISTER_TR_ID          0x00060007
 
 static void emulator_set_gdt(struct x86_emulate_ctxt *ctxt, struct desc_ptr 
*dt)
 {
@@ -8949,22 +8951,28 @@ static void emulator_set_segment(struct 
x86_emulate_ctxt *ctxt, u16 selector,
        var.unusable = !var.present;
        var.padding = 0;
 
-       if (seg == VCPU_SREG_LDTR) {
-               struct desc_ptr sel_as_dt = {
-                       .address = selector,
-                       .size = 0,
-               };
-
-               /* The intercept message carries the written selector. */
-               if (hv_divert_dt_write(ctxt, HV_X64_REGISTER_LDTR_ID,
-                                      &sel_as_dt, HV_CR_INTERCEPT_LDTR_WRITE))
-                       return;
-       }
-
        kvm_set_segment(vcpu, &var, seg);
        return;
 }
 
+static bool emulator_divert_seg_write(struct x86_emulate_ctxt *ctxt,
+                                     u16 selector, int seg)
+{
+       struct desc_ptr sel_as_dt = {
+               .address = selector,
+               .size = 0,
+       };
+
+       /* The intercept message carries the written selector. */
+       if (seg == VCPU_SREG_LDTR)
+               return hv_divert_dt_write(ctxt, HV_X64_REGISTER_LDTR_ID,
+                                         &sel_as_dt,
+                                         HV_CR_INTERCEPT_LDTR_WRITE);
+
+       return hv_divert_dt_write(ctxt, HV_X64_REGISTER_TR_ID, &sel_as_dt,
+                                 HV_CR_INTERCEPT_TR_WRITE);
+}
+
 static int emulator_get_msr_with_filter(struct x86_emulate_ctxt *ctxt,
                                        u32 msr_index, u64 *pdata)
 {
@@ -9167,6 +9175,7 @@ static const struct x86_emulate_ops emulate_ops = {
        .pio_out_emulated    = emulator_pio_out_emulated,
        .get_segment         = emulator_get_segment,
        .set_segment         = emulator_set_segment,
+       .divert_seg_write    = emulator_divert_seg_write,
        .get_cached_segment_base = emulator_get_cached_segment_base,
        .get_gdt             = emulator_get_gdt,
        .get_idt             = emulator_get_idt,
-- 
2.50.1


Reply via email to