* [PATCH v6 0/3] x86/tdx: Fix port I/O handling bugs
@ 2026-07-13 13:37 Kiryl Shutsemau
2026-07-13 13:37 ` [PATCH v6 1/3] x86/tdx: Fix off-by-one in port I/O handling Kiryl Shutsemau
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Kiryl Shutsemau @ 2026-07-13 13:37 UTC (permalink / raw)
To: Dave Hansen, Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86
Cc: Sean Christopherson, Paolo Bonzini, Kuppuswamy Sathyanarayanan,
Kai Huang, Xiaoyao Li, Rick Edgecombe, Binbin Wu, David Laight,
Andi Kleen, Dan Williams, Borys Tsyrulnikov, kvm, linux-coco,
linux-kernel, stable, Kiryl Shutsemau (Meta)
From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
Three fixes for emulated port I/O in the TDX guest #VE handler.
Patch 1 fixes an off-by-one in the GENMASK() used by handle_in() and
handle_out(): the mask was one bit too wide for every I/O size.
Patch 3 fixes 32-bit port IN to zero-extend into RAX, per x86
semantics, instead of preserving the upper 32 bits. To avoid
open-coding the partial-register-write rules, patch 2 first moves KVM's
assign_register() into <asm/insn-eval.h> as insn_assign_reg() so both
KVM and the #VE handler can share it.
Changes since v5:
- Patch 2: reword the shortlog and comment; no functional change
(Sean, David Laight). Collect Acked-by from Sean.
- Patches 1 and 3 unchanged.
v5: https://lore.kernel.org/all/20260701110547.764083-1-kirill@shutemov.name/
v4: https://lore.kernel.org/all/cover.1780584300.git.kas@kernel.org/
Kiryl Shutsemau (Meta) (3):
x86/tdx: Fix off-by-one in port I/O handling
x86/insn-eval: Move assign_register() out of KVM as insn_assign_reg()
x86/tdx: Fix zero-extension for 32-bit port I/O
arch/x86/coco/tdx/tdx.c | 10 ++++-----
arch/x86/include/asm/insn-eval.h | 36 ++++++++++++++++++++++++++++++++
arch/x86/kvm/emulate.c | 26 ++++-------------------
3 files changed, 44 insertions(+), 28 deletions(-)
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v6 1/3] x86/tdx: Fix off-by-one in port I/O handling
2026-07-13 13:37 [PATCH v6 0/3] x86/tdx: Fix port I/O handling bugs Kiryl Shutsemau
@ 2026-07-13 13:37 ` Kiryl Shutsemau
2026-07-13 13:50 ` sashiko-bot
2026-07-13 21:46 ` [tip: x86/tdx] " tip-bot2 for Kiryl Shutsemau (Meta)
2026-07-13 13:37 ` [PATCH v6 2/3] x86/insn-eval: Move assign_register() out of KVM as insn_assign_reg() Kiryl Shutsemau
2026-07-13 13:37 ` [PATCH v6 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O Kiryl Shutsemau
2 siblings, 2 replies; 9+ messages in thread
From: Kiryl Shutsemau @ 2026-07-13 13:37 UTC (permalink / raw)
To: Dave Hansen, Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86
Cc: Sean Christopherson, Paolo Bonzini, Kuppuswamy Sathyanarayanan,
Kai Huang, Xiaoyao Li, Rick Edgecombe, Binbin Wu, David Laight,
Andi Kleen, Dan Williams, Borys Tsyrulnikov, kvm, linux-coco,
linux-kernel, stable, Kiryl Shutsemau (Meta)
From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
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 <tsyrulnikov.borys@gmail.com>
Link: https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Kai Huang <kai.huang@intel.com>
Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
Reviewed-by: Binbin Wu <binbin.wu@linux.intel.com>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: stable@vger.kernel.org
---
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
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v6 2/3] x86/insn-eval: Move assign_register() out of KVM as insn_assign_reg()
2026-07-13 13:37 [PATCH v6 0/3] x86/tdx: Fix port I/O handling bugs Kiryl Shutsemau
2026-07-13 13:37 ` [PATCH v6 1/3] x86/tdx: Fix off-by-one in port I/O handling Kiryl Shutsemau
@ 2026-07-13 13:37 ` Kiryl Shutsemau
2026-07-13 21:46 ` [tip: x86/tdx] " tip-bot2 for Kiryl Shutsemau (Meta)
2026-07-13 13:37 ` [PATCH v6 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O Kiryl Shutsemau
2 siblings, 1 reply; 9+ messages in thread
From: Kiryl Shutsemau @ 2026-07-13 13:37 UTC (permalink / raw)
To: Dave Hansen, Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86
Cc: Sean Christopherson, Paolo Bonzini, Kuppuswamy Sathyanarayanan,
Kai Huang, Xiaoyao Li, Rick Edgecombe, Binbin Wu, David Laight,
Andi Kleen, Dan Williams, Borys Tsyrulnikov, kvm, linux-coco,
linux-kernel, stable, Kiryl Shutsemau (Meta)
From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
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 <asm/insn-eval.h>, rename
it to insn_assign_reg(), and route KVM's callers through it.
Add <asm/insn.h> 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) <kas@kernel.org>
Acked-by: Sean Christopherson <seanjc@google.com>
Cc: stable@vger.kernel.org # prerequisite for the following 32-bit port I/O zero-extension fix
---
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 <linux/compiler.h>
#include <linux/bug.h>
#include <linux/err.h>
+#include <asm/insn.h>
#include <asm/ptrace.h>
#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 <linux/stringify.h>
#include <asm/debugreg.h>
+#include <asm/insn-eval.h>
#include <asm/nospec-branch.h>
#include <asm/ibt.h>
#include <asm/text-patching.h>
@@ -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;
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v6 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O
2026-07-13 13:37 [PATCH v6 0/3] x86/tdx: Fix port I/O handling bugs Kiryl Shutsemau
2026-07-13 13:37 ` [PATCH v6 1/3] x86/tdx: Fix off-by-one in port I/O handling Kiryl Shutsemau
2026-07-13 13:37 ` [PATCH v6 2/3] x86/insn-eval: Move assign_register() out of KVM as insn_assign_reg() Kiryl Shutsemau
@ 2026-07-13 13:37 ` Kiryl Shutsemau
2026-07-13 13:51 ` sashiko-bot
2026-07-13 21:46 ` [tip: x86/tdx] " tip-bot2 for Kiryl Shutsemau (Meta)
2 siblings, 2 replies; 9+ messages in thread
From: Kiryl Shutsemau @ 2026-07-13 13:37 UTC (permalink / raw)
To: Dave Hansen, Thomas Gleixner, Ingo Molnar, Borislav Petkov, x86
Cc: Sean Christopherson, Paolo Bonzini, Kuppuswamy Sathyanarayanan,
Kai Huang, Xiaoyao Li, Rick Edgecombe, Binbin Wu, David Laight,
Andi Kleen, Dan Williams, Borys Tsyrulnikov, kvm, linux-coco,
linux-kernel, stable, Kiryl Shutsemau (Meta)
From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
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 <tsyrulnikov.borys@gmail.com>
Link: https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Binbin Wu <binbin.wu@linux.intel.com>
Cc: stable@vger.kernel.org
---
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<Instruction.IO>".
*/
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;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v6 1/3] x86/tdx: Fix off-by-one in port I/O handling
2026-07-13 13:37 ` [PATCH v6 1/3] x86/tdx: Fix off-by-one in port I/O handling Kiryl Shutsemau
@ 2026-07-13 13:50 ` sashiko-bot
2026-07-13 21:46 ` [tip: x86/tdx] " tip-bot2 for Kiryl Shutsemau (Meta)
1 sibling, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-07-13 13:50 UTC (permalink / raw)
To: Kiryl Shutsemau; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Emulated 32-bit I/O reads (inl) fail to zero-extend the result into the 64-bit RAX register, violating x86-64 semantics.
- [High] Guest register state is corrupted on the error/fault path of an I/O read.
--
--- Patch [1]: [PATCH v6 1/3] x86/tdx: Fix off-by-one in port I/O handling ---
commit 6a0c705281bac273b6793004a9d727acc5a4f92d
Author: Kiryl Shutsemau (Meta) <kas@kernel.org>
x86/tdx: Fix off-by-one in port I/O handling
handle_in() and handle_out() in arch/x86/coco/tdx/tdx.c use a mask that
is one bit too wide for all I/O sizes. For size=1 (INB), this produces
GENMASK(8, 0) = 0x1FF (9 bits) instead of GENMASK(7, 0) = 0xFF (8 bits).
This commit fixes the mask calculation.
Link: https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/
> diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c
> index 29b6f1ed59ec5..b8bbd715fb624 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);
[Severity: High]
This isn't a bug introduced by this patch, but does the bitwise operation in
arch/x86/coco/tdx/tdx.c:handle_in() properly zero-extend a 32-bit read?
When a guest executes a 32-bit in instruction (inl) triggering handle_in()
with size == 4, mask is evaluated to 32 bits (0xFFFFFFFF).
arch/x86/coco/tdx/tdx.c:handle_in() {
...
success = !__tdx_hypercall(&args);
/* Update part of the register affected by the emulated instruction */
regs->ax &= ~mask;
if (success)
regs->ax |= args.r11 & mask;
...
}
The bitwise operation regs->ax &= ~mask will clear the lower 32 bits, but it
incorrectly preserves the upper 32 bits of the 64-bit register. x86-64
architectural semantics require any 32-bit write to a general-purpose
register to zero-extend to 64 bits.
Could this cause data corruption for guest software expecting RAX to be
zero-extended?
[Severity: High]
This is another pre-existing issue, but is it safe to unconditionally clear
the target bits in regs->ax on the error path?
If the VMM rejects the hypercall (__tdx_hypercall() returns a non-zero
error), handle_in() still performs regs->ax &= ~mask; and returns false.
The kernel then injects a #GP fault into the guest.
The guest exception handler will observe a partially modified RAX register
instead of the unmodified state from before the faulting instruction.
Can this violate architectural guarantees for faults by exposing corrupted
register state?
> bool success;
>
> /*
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260713133753.223947-1-kirill@shutemov.name?part=1
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v6 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O
2026-07-13 13:37 ` [PATCH v6 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O Kiryl Shutsemau
@ 2026-07-13 13:51 ` sashiko-bot
2026-07-13 21:46 ` [tip: x86/tdx] " tip-bot2 for Kiryl Shutsemau (Meta)
1 sibling, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-07-13 13:51 UTC (permalink / raw)
To: Kiryl Shutsemau; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] The TDX #VE handler for the CPUID instruction fails to zero the upper 32 bits of the destination registers, exposing the guest to potentially malicious or garbage data from the VMM.
- [High] The TDX #VE handler for MMIO reads fails to zero the upper 32 bits of the destination register for 32-bit MOVZX and MOVSX instructions.
- [High] The TDX #VE handler for MSR writes corrupts the MSR data sent to the VMM if the guest's RAX register contains non-zero upper 32 bits.
--
--- Patch [3]: [PATCH v6 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O ---
commit 54fa9007fcfc0403e17720722b833050a2d55438
Author: Kiryl Shutsemau (Meta) <kas@kernel.org>
x86/tdx: Fix zero-extension for 32-bit port I/O
This commit updates handle_in() to use insn_assign_reg() for 32-bit port IN
instructions. This ensures proper partial-register-write semantics where
4-byte forms correctly zero-extend to the full 64-bit register.
Link: https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/
> diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c
> index b8bbd715fb624..f904a636d449b 100644
> --- a/arch/x86/coco/tdx/tdx.c
> +++ b/arch/x86/coco/tdx/tdx.c
> @@ -703,11 +703,9 @@ static bool handle_in(struct pt_regs *regs, int size, int port)
> * "TDG.VP.VMCALL<Instruction.IO>".
> */
> 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;
> }
[Severity: High]
This is a pre-existing issue, but since you are fixing zero-extension issues
in this file, I noticed another zero-extension problem in handle_mmio() for
32-bit destination registers.
If a guest executes an instruction like MOVZX EAX, BYTE PTR [mem], the #VE
handler calculates extend_size:
arch/x86/coco/tdx/tdx.c:handle_mmio() {
...
case INSN_MMIO_READ_ZERO_EXTEND:
/* Zero extend based on operand size */
extend_size = insn.opnd_bytes;
break;
...
}
If insn.opnd_bytes is 4, doesn't this cause the subsequent memset to only
clear the lower 4 bytes of regs->ax, leaving the upper 32 bits containing
stale data and violating x86-64 architecture rules?
[Severity: High]
This isn't a bug introduced by this patch, but does handle_cpuid() have a
similar problem with zero-extending registers?
When a guest executes CPUID, it traps to the #VE handler. handle_cpuid()
copies the VMM hypercall results directly into the registers:
arch/x86/coco/tdx/tdx.c:handle_cpuid() {
...
regs->ax = args.r12;
regs->bx = args.r13;
regs->cx = args.r14;
regs->dx = args.r15;
...
}
Could an untrusted VMM inject non-zero data into the upper 32 bits of these
registers and violate x86-64 CPUID semantics since they are not being
zero-extended?
[Severity: High]
This is also a pre-existing issue, but could write_msr() corrupt the MSR
data sent to the VMM if the guest's RAX register contains non-zero upper
32 bits?
The payload for WRMSR is constructed like this:
arch/x86/coco/tdx/tdx.c:write_msr() {
...
struct tdx_module_args args = {
...
.r13 = (u64)regs->dx << 32 | regs->ax,
};
...
}
Since WRMSR ignores the upper 32 bits of RAX, the architecture permits
garbage in them. Because regs->ax is not cast to a 32-bit integer before
the bitwise-OR, won't any set bits in its upper 32 bits bleed into the
EDX portion of the payload?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260713133753.223947-1-kirill@shutemov.name?part=3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [tip: x86/tdx] x86/tdx: Fix zero-extension for 32-bit port I/O
2026-07-13 13:37 ` [PATCH v6 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O Kiryl Shutsemau
2026-07-13 13:51 ` sashiko-bot
@ 2026-07-13 21:46 ` tip-bot2 for Kiryl Shutsemau (Meta)
1 sibling, 0 replies; 9+ messages in thread
From: tip-bot2 for Kiryl Shutsemau (Meta) @ 2026-07-13 21:46 UTC (permalink / raw)
To: linux-tip-commits
Cc: Borys Tsyrulnikov, Kiryl Shutsemau (Meta), Dave Hansen, Binbin Wu,
stable, x86, linux-kernel
The following commit has been merged into the x86/tdx branch of tip:
Commit-ID: 941370fc93cc3474e26811f4d3b062903eefe2cf
Gitweb: https://git.kernel.org/tip/941370fc93cc3474e26811f4d3b062903eefe2cf
Author: Kiryl Shutsemau (Meta) <kas@kernel.org>
AuthorDate: Mon, 13 Jul 2026 14:37:53 +01:00
Committer: Dave Hansen <dave.hansen@linux.intel.com>
CommitterDate: Mon, 13 Jul 2026 14:45:07 -07:00
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 <tsyrulnikov.borys@gmail.com>
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Reviewed-by: Binbin Wu <binbin.wu@linux.intel.com>
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 b8bbd71..f904a63 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<Instruction.IO>".
*/
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;
}
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [tip: x86/tdx] x86/insn-eval: Move assign_register() out of KVM as insn_assign_reg()
2026-07-13 13:37 ` [PATCH v6 2/3] x86/insn-eval: Move assign_register() out of KVM as insn_assign_reg() Kiryl Shutsemau
@ 2026-07-13 21:46 ` tip-bot2 for Kiryl Shutsemau (Meta)
0 siblings, 0 replies; 9+ messages in thread
From: tip-bot2 for Kiryl Shutsemau (Meta) @ 2026-07-13 21:46 UTC (permalink / raw)
To: linux-tip-commits
Cc: Kiryl Shutsemau (Meta), Dave Hansen, Sean Christopherson, stable,
x86, linux-kernel
The following commit has been merged into the x86/tdx branch of tip:
Commit-ID: 1fe104b048d77d6cb25bd938e6a67450fb50e61d
Gitweb: https://git.kernel.org/tip/1fe104b048d77d6cb25bd938e6a67450fb50e61d
Author: Kiryl Shutsemau (Meta) <kas@kernel.org>
AuthorDate: Mon, 13 Jul 2026 14:37:52 +01:00
Committer: Dave Hansen <dave.hansen@linux.intel.com>
CommitterDate: Mon, 13 Jul 2026 14:45:07 -07:00
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 <asm/insn-eval.h>, rename
it to insn_assign_reg(), and route KVM's callers through it.
Add <asm/insn.h> 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) <kas@kernel.org>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Acked-by: Sean Christopherson <seanjc@google.com>
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 4733e90..ae05647 100644
--- a/arch/x86/include/asm/insn-eval.h
+++ b/arch/x86/include/asm/insn-eval.h
@@ -9,6 +9,7 @@
#include <linux/compiler.h>
#include <linux/bug.h>
#include <linux/err.h>
+#include <asm/insn.h>
#include <asm/ptrace.h>
#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 b566ab5..c6dcb5a 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -24,6 +24,7 @@
#include "kvm_emulate.h"
#include <linux/stringify.h>
#include <asm/debugreg.h>
+#include <asm/insn-eval.h>
#include <asm/nospec-branch.h>
#include <asm/ibt.h>
#include <asm/text-patching.h>
@@ -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;
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [tip: x86/tdx] x86/tdx: Fix off-by-one in port I/O handling
2026-07-13 13:37 ` [PATCH v6 1/3] x86/tdx: Fix off-by-one in port I/O handling Kiryl Shutsemau
2026-07-13 13:50 ` sashiko-bot
@ 2026-07-13 21:46 ` tip-bot2 for Kiryl Shutsemau (Meta)
1 sibling, 0 replies; 9+ messages in thread
From: tip-bot2 for Kiryl Shutsemau (Meta) @ 2026-07-13 21:46 UTC (permalink / raw)
To: linux-tip-commits
Cc: Borys Tsyrulnikov, Kiryl Shutsemau (Meta), Dave Hansen, Kai Huang,
Kuppuswamy Sathyanarayanan, Binbin Wu, Rick Edgecombe, stable,
x86, linux-kernel
The following commit has been merged into the x86/tdx branch of tip:
Commit-ID: 0f63e656b1c679d32ac595de29d10c03efca6a25
Gitweb: https://git.kernel.org/tip/0f63e656b1c679d32ac595de29d10c03efca6a25
Author: Kiryl Shutsemau (Meta) <kas@kernel.org>
AuthorDate: Mon, 13 Jul 2026 14:37:51 +01:00
Committer: Dave Hansen <dave.hansen@linux.intel.com>
CommitterDate: Mon, 13 Jul 2026 14:45:07 -07:00
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 <tsyrulnikov.borys@gmail.com>
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Reviewed-by: Kai Huang <kai.huang@intel.com>
Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
Reviewed-by: Binbin Wu <binbin.wu@linux.intel.com>
Reviewed-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
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 29b6f1e..b8bbd71 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
^ permalink raw reply related [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-07-13 21:46 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-13 13:37 [PATCH v6 0/3] x86/tdx: Fix port I/O handling bugs Kiryl Shutsemau
2026-07-13 13:37 ` [PATCH v6 1/3] x86/tdx: Fix off-by-one in port I/O handling Kiryl Shutsemau
2026-07-13 13:50 ` sashiko-bot
2026-07-13 21:46 ` [tip: x86/tdx] " tip-bot2 for Kiryl Shutsemau (Meta)
2026-07-13 13:37 ` [PATCH v6 2/3] x86/insn-eval: Move assign_register() out of KVM as insn_assign_reg() Kiryl Shutsemau
2026-07-13 21:46 ` [tip: x86/tdx] " tip-bot2 for Kiryl Shutsemau (Meta)
2026-07-13 13:37 ` [PATCH v6 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O Kiryl Shutsemau
2026-07-13 13:51 ` sashiko-bot
2026-07-13 21:46 ` [tip: x86/tdx] " tip-bot2 for Kiryl Shutsemau (Meta)
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.