From 0f63e656b1c679d32ac595de29d10c03efca6a25 Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Mon, 13 Jul 2026 14:37:51 +0100 Subject: [PATCH 1/3] x86/tdx: Fix off-by-one in port I/O handling handle_in() and handle_out() in arch/x86/coco/tdx/tdx.c use: u64 mask = GENMASK(BITS_PER_BYTE * size, 0); GENMASK(h, l) includes bit h. For size=1 (INB), this produces GENMASK(8, 0) = 0x1FF (9 bits) instead of GENMASK(7, 0) = 0xFF (8 bits). The mask is one bit too wide for all I/O sizes. Fix the mask calculation. Fixes: 03149948832a ("x86/tdx: Port I/O: Add runtime hypercalls") Reported-by: Borys Tsyrulnikov Signed-off-by: Kiryl Shutsemau (Meta) Signed-off-by: Dave Hansen Reviewed-by: Kai Huang Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Binbin Wu Reviewed-by: Rick Edgecombe Link: https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/ Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260713133753.223947-2-kirill@shutemov.name --- arch/x86/coco/tdx/tdx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c index 29b6f1ed59ec..b8bbd715fb62 100644 --- a/arch/x86/coco/tdx/tdx.c +++ b/arch/x86/coco/tdx/tdx.c @@ -694,7 +694,7 @@ static bool handle_in(struct pt_regs *regs, int size, int port) .r13 = PORT_READ, .r14 = port, }; - u64 mask = GENMASK(BITS_PER_BYTE * size, 0); + u64 mask = GENMASK(BITS_PER_BYTE * size - 1, 0); bool success; /* @@ -714,7 +714,7 @@ static bool handle_in(struct pt_regs *regs, int size, int port) static bool handle_out(struct pt_regs *regs, int size, int port) { - u64 mask = GENMASK(BITS_PER_BYTE * size, 0); + u64 mask = GENMASK(BITS_PER_BYTE * size - 1, 0); /* * Emulate the I/O write via hypercall. More info about ABI can be found From 1fe104b048d77d6cb25bd938e6a67450fb50e61d Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Mon, 13 Jul 2026 14:37:52 +0100 Subject: [PATCH 2/3] x86/insn-eval: Move assign_register() out of KVM as insn_assign_reg() KVM's instruction emulator has a small helper, assign_register(), that writes a value into a register following the x86 rules for writes to general-purpose registers: an 8- or 16-bit write leaves the rest of the register untouched, a 32-bit write zero-extends the result to 64 bits, and a 64-bit write replaces the whole register. The TDX guest #VE handler needs the same logic for port I/O emulation to get 32-bit zero-extension right. Rather than add a third copy of the same switch, move the helper verbatim to , rename it to insn_assign_reg(), and route KVM's callers through it. Add to the header's includes so it builds standalone in callers that have not pulled it in transitively. No functional change. Signed-off-by: Kiryl Shutsemau (Meta) Signed-off-by: Dave Hansen Acked-by: Sean Christopherson Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260713133753.223947-3-kirill@shutemov.name --- arch/x86/include/asm/insn-eval.h | 36 ++++++++++++++++++++++++++++++++ arch/x86/kvm/emulate.c | 26 ++++------------------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/arch/x86/include/asm/insn-eval.h b/arch/x86/include/asm/insn-eval.h index 4733e9064ee5..ae05647a0afb 100644 --- a/arch/x86/include/asm/insn-eval.h +++ b/arch/x86/include/asm/insn-eval.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #define INSN_CODE_SEG_ADDR_SZ(params) ((params >> 4) & 0xf) @@ -46,4 +47,39 @@ enum insn_mmio_type insn_decode_mmio(struct insn *insn, int *bytes); bool insn_is_nop(struct insn *insn); +/* + * Write @val into *@reg following the x86 rules for writes to + * general-purpose registers (Intel SDM Vol. 1, "General-Purpose + * Registers in 64-Bit Mode"): an 8- or 16-bit write leaves the rest of + * the register untouched, a 32-bit write zero-extends the result into + * the upper 32 bits, and a 64-bit write replaces the whole register. + * + * @bytes is the width of the write, not a property of the instruction: + * an instruction that, say, sign-extends a 32-bit immediate into a + * 64-bit register does a 64-bit write here. + * + * @reg need not be 8-byte aligned: KVM's instruction emulator offsets + * the pointer by one byte to address the high-byte registers (AH, CH, + * DH, BH). Use narrow stores for the sub-word cases so the access + * width matches @bytes and the adjacent bytes are left alone. + */ +static inline void insn_assign_reg(unsigned long *reg, u64 val, int bytes) +{ + switch (bytes) { + case 1: + *(u8 *)reg = (u8)val; + break; + case 2: + *(u16 *)reg = (u16)val; + break; + case 4: + /* A 32-bit write zero-extends into the upper 32 bits. */ + *reg = (u32)val; + break; + case 8: + *reg = val; + break; + } +} + #endif /* _ASM_X86_INSN_EVAL_H */ diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c index b566ab5c7515..c6dcb5ac48af 100644 --- a/arch/x86/kvm/emulate.c +++ b/arch/x86/kvm/emulate.c @@ -24,6 +24,7 @@ #include "kvm_emulate.h" #include #include +#include #include #include #include @@ -439,25 +440,6 @@ static void assign_masked(ulong *dest, ulong src, ulong mask) *dest = (*dest & ~mask) | (src & mask); } -static void assign_register(unsigned long *reg, u64 val, int bytes) -{ - /* The 4-byte case *is* correct: in 64-bit mode we zero-extend. */ - switch (bytes) { - case 1: - *(u8 *)reg = (u8)val; - break; - case 2: - *(u16 *)reg = (u16)val; - break; - case 4: - *reg = (u32)val; - break; /* 64b: zero-extend */ - case 8: - *reg = val; - break; - } -} - static inline unsigned long ad_mask(struct x86_emulate_ctxt *ctxt) { return (1UL << (ctxt->ad_bytes << 3)) - 1; @@ -505,7 +487,7 @@ register_address_increment(struct x86_emulate_ctxt *ctxt, int reg, int inc) { ulong *preg = reg_rmw(ctxt, reg); - assign_register(preg, *preg + inc, ctxt->ad_bytes); + insn_assign_reg(preg, *preg + inc, ctxt->ad_bytes); } static void rsp_increment(struct x86_emulate_ctxt *ctxt, int inc) @@ -1767,7 +1749,7 @@ static int load_segment_descriptor(struct x86_emulate_ctxt *ctxt, static void write_register_operand(struct operand *op) { - return assign_register(op->addr.reg, op->val, op->bytes); + return insn_assign_reg(op->addr.reg, op->val, op->bytes); } static int writeback(struct x86_emulate_ctxt *ctxt, struct operand *op) @@ -2008,7 +1990,7 @@ static int em_popa(struct x86_emulate_ctxt *ctxt) rc = emulate_pop(ctxt, &val, ctxt->op_bytes); if (rc != X86EMUL_CONTINUE) break; - assign_register(reg_rmw(ctxt, reg), val, ctxt->op_bytes); + insn_assign_reg(reg_rmw(ctxt, reg), val, ctxt->op_bytes); --reg; } return rc; From 941370fc93cc3474e26811f4d3b062903eefe2cf Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Mon, 13 Jul 2026 14:37:53 +0100 Subject: [PATCH 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O According to x86 architecture rules, 32-bit operations zero-extend the result to 64 bits. The current implementation of handle_in() only masks the lower 32 bits, which preserves the upper 32 bits of RAX when a 32-bit port IN instruction is emulated. Use insn_assign_reg() to write the result back into RAX with proper partial-register-write semantics: 1- and 2-byte forms leave the upper bits untouched, the 4-byte form zero-extends to the full register. Fixes: 03149948832a ("x86/tdx: Port I/O: Add runtime hypercalls") Reported-by: Borys Tsyrulnikov Signed-off-by: Kiryl Shutsemau (Meta) Signed-off-by: Dave Hansen Reviewed-by: Binbin Wu Link: https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/ Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260713133753.223947-4-kirill@shutemov.name --- arch/x86/coco/tdx/tdx.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c index b8bbd715fb62..f904a636d449 100644 --- a/arch/x86/coco/tdx/tdx.c +++ b/arch/x86/coco/tdx/tdx.c @@ -694,8 +694,8 @@ static bool handle_in(struct pt_regs *regs, int size, int port) .r13 = PORT_READ, .r14 = port, }; - u64 mask = GENMASK(BITS_PER_BYTE * size - 1, 0); bool success; + u64 val; /* * Emulate the I/O read via hypercall. More info about ABI can be found @@ -703,11 +703,9 @@ static bool handle_in(struct pt_regs *regs, int size, int port) * "TDG.VP.VMCALL". */ success = !__tdx_hypercall(&args); + val = success ? args.r11 : 0; - /* Update part of the register affected by the emulated instruction */ - regs->ax &= ~mask; - if (success) - regs->ax |= args.r11 & mask; + insn_assign_reg(®s->ax, val, size); return success; }