x86/tdx: Fix port I/O handling bugs
Subsystems (5)
Kiryl Shutsemau (Meta)
maintainerv4 cover x86/tdx: Fix port I/O handling bugs
Two bugs in the TDX guest port I/O #VE emulation, plus a small helper extracted from KVM to avoid open-coding partial-register-write logic in the second fix.
Patch 1 is an off-by-one in the mask used to clip the I/O value:GENMASK(BITS_PER_BYTE * size, 0) is one bit too wide. Unchanged from v3 1/2.
Patch 2 lifts KVM's instruction-emulator helper assign_register() out of arch/x86/kvm/emulate.c into <asm/insn-eval.h>, renamed to insn_assign_reg(). Dave suggested consolidating rather than adding a third copy of the same partial-register switch; the body is rewritten using plain arithmetic (suggested by David Laight) so the helper does not rely on -fno-strict-aliasing or little-endian byte order. KVM behaviour is unchanged.
Patch 3 fixes the architectural zero-extension of 32-bit IN: the old mask-based handle_in() preserves RAX[63:32] after inl, which is wrong. Now done by calling the helper.
Changes since v3:
- Patch 1/2 carried over unchanged as 1/3.
- Helper extracted from KVM (new patch 2/3) and used from handle_in() (Dave, David Laight).
- Reviewed-by tags from v3 2/2 dropped on patch 3/3 because the implementation changed substantially. v3 1/2 -> v4 1/3 Rb tags preserved (patch unchanged).
v3: https://lore.kernel.org/all/[email protected]/
Kiryl Shutsemau (Meta) (3):
x86/tdx: Fix off-by-one in port I/O handling
x86/insn-eval: Add insn_assign_reg() helper
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 | 25 +++++++++++++++++++++++++ arch/x86/kvm/emulate.c | 26 ++++---------------------- 3 files changed, 33 insertions(+), 28 deletions(-)
Signature · 1 line
2.54.0
Kiryl Shutsemau (Meta)
maintainerPATCH v4 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.
--- arch/x86/coco/tdx/tdx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
arch/x86/coco/tdx/tdx.c +2 -2
@@ -693,7 +693,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;/*
@@ -713,7 +713,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
Signature · 1 line
2.54.0
Re: [PATCH v4 1/3] x86/tdx: Fix off-by-one in port I/O handling
Quoted 10 lines · Kiryl Shutsemau (Meta)
handle_in()andhandle_out()inarch/x86/coco/tdx/tdx.cuse:u64 mask = GENMASK(BITS_PER_BYTE * size, 0);
GENMASK(h, l)includes bit h. For size=1 (INB), this producesGENMASK(8, 0)=0x1FF(9 bits) instead ofGENMASK(7, 0)=0xFF(8 bits). The mask is one bit too wide for all I/O sizes.Fix the mask calculation.
Quoted 7 lines
Fixes 03149948832a ("x86/tdx: Port I/O: Add runtime hypercalls")Reported-by Borys Tsyrulnikov <[email protected]>Link https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/ open hereSigned-off-by Kiryl Shutsemau (Meta) <[email protected]>Reviewed-by Kai Huang <[email protected]>Reviewed-by Kuppuswamy Sathyanarayanan <[email protected]>
Quoted 26 lines
--- arch/x86/coco/tdx/tdx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)arch/x86/coco/tdx/tdx.c +2
-2@@ -693,7 +693,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;/* @@ -713,7 +713,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
Re: [PATCH v4 1/3] x86/tdx: Fix off-by-one in port I/O handling
In reply to Kiryl Shutsemau (Meta)Quoted 10 lines · Kiryl Shutsemau (Meta)
handle_in()andhandle_out()inarch/x86/coco/tdx/tdx.cuse:u64 mask =
GENMASK(BITS_PER_BYTE * size, 0);
GENMASK(h, l)includes bit h. For size=1 (INB), this producesGENMASK(8, 0)=0x1FF(9 bits) instead ofGENMASK(7, 0)=0xFF(8 bits). The mask is one bit too wide for all I/O sizes.Fix the mask calculation.
Quoted 7 lines
Fixes 03149948832a ("x86/tdx: Port I/O: Add runtime hypercalls")Reported-by Borys Tsyrulnikov <[email protected]>Link https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/ open hereSigned-off-by Kiryl Shutsemau (Meta) <[email protected]>Reviewed-by Kai Huang <[email protected]>Reviewed-by Kuppuswamy Sathyanarayanan <[email protected]>
Kiryl Shutsemau (Meta)
maintainerPATCH v4 2/3 x86/insn-eval: Add insn_assign_reg() helper
In reply to Kiryl Shutsemau (Meta)KVM's instruction emulator has a small helper, assign_register(), that writes a value into a sub-register with x86 partial-register-write
semantics: 1- and 2-byte writes leave the upper bits of the destination untouched, 4-byte writes zero-extend to 64 bits, 8-byte writes overwrite the full register.
The TDX guest #VE handler needs the same logic for port I/O emulation to get 32-bit zero-extension right. Rather than copy-pasting the helper, lift it to <asm/insn-eval.h> as insn_assign_reg() so both can use it.
Rewrite the body using arithmetic instead of pointer punning so the helper does not depend on -fno-strict-aliasing or little-endian byte order, and 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.
--- arch/x86/include/asm/insn-eval.h | 25 +++++++++++++++++++++++++ arch/x86/kvm/emulate.c | 26 ++++---------------------- 2 files changed, 29 insertions(+), 22 deletions(-)
arch/x86/include/asm/insn-eval.h +25 -0
@@ -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,28 @@ enum insn_mmio_type insn_decode_mmio(struct insn *insn, int *bytes);bool insn_is_nop (struct insn *insn); +/* +* Write @val into *@reg with x86 partial-register-write semantics: a 1- +* or 2-byte write leaves the upper bits of the destination untouched; a +* 4-byte write zero-extends to 64 bits (matching IN[BWL], MOV[BWL] +* etc.); an 8-byte write overwrites the full register. +*/ +static inline void insn_assign_reg (unsigned long *reg,u64 val,int bytes) +{ +switch (bytes) { +case 1 : + *reg = (*reg & ~0xfful ) | (val &0xff ); +break ; +case 2 : + *reg = (*reg & ~0xfffful ) | (val &0xffff ); +break ; +case 4 : + *reg = (u32 )val; +break ; +case 8 : + *reg = val; +break ; + } +} +#endif /* _ASM_X86_INSN_EVAL_H */
arch/x86/kvm/emulate.c +4 -22
@@ -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)
@@ -1766,7 +1748,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)
@@ -2007,7 +1989,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;
Signature · 1 line
2.54.0
[email protected]
AI reviewRe: [PATCH v4 2/3] x86/insn-eval: Add insn_assign_reg() helper
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Unaligned `unsigned long` pointer dereference leading to Undefined Behavior and adjacent memory…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Unaligned unsigned long pointer dereference leading to Undefined Behavior and adjacent memory overwrite during 8-bit high-byte register emulation. --
commit a80417d075cbd125216356389da2d074bd0a780e
Author: Kiryl Shutsemau (Meta) <[email protected]>
x86/insn-eval: Add insn_assign_reg() helper
This commit lifts KVM's assign_register() into <asm/insn-eval.h> as insn_assign_reg() so it can be used by both KVM and the TDX guest #VE handler. It rewrites the helper to use arithmetic operations instead of pointer punning to avoid relying on specific aliasing or endianness.
Quoted 4 lines
arch/x86/include/asm/insn-eval.h +0
-0
[ ... ]
Quoted 6 lines
arch/x86/include/asm/insn-eval.h +2
-0@@ -46,4 +47,28 @@ enum insn_mmio_type insn_decode_mmio(struct insn *insn, int *bytes);bool insn_is_nop (struct insn *insn); +/* +* Write @val into *@reg with x86 partial-register-write semantics: a 1-
arch/x86/include/asm/insn-eval.h enum insn_mmio_type insn_decode_mmio(struct insn *insn, int *bytes); · line 61
Quoted 10 lines
arch/x86/include/asm/insn-eval.h +10
-0+* or 2-byte write leaves the upper bits of the destination untouched; a +* 4-byte write zero-extends to 64 bits (matching IN[BWL], MOV[BWL] +* etc.); an 8-byte write overwrites the full register. +*/ +static inline void insn_assign_reg (unsigned long *reg,u64 val,int bytes) +{ +switch (bytes) { +case 1 : + *reg = (*reg & ~0xfful ) | (val &0xff ); +break ;
[Severity: High]
Does this 64-bit read-modify-write cause an unaligned access and memory corruption when emulating high-byte registers?
In the KVM emulator, decode_register() generates a pointer offset by 1 byte for high-byte registers like AH, CH, DH, and BH:
arch/x86/kvm/emulate.c:decode_register() {
...
if (highbyte_regs && modrm_reg >= 4 && modrm_reg < 8)
p = (unsigned char *)reg_rmw(ctxt, modrm_reg & 3) + 1;
...
}When this unaligned pointer is passed to write_register_operand() and then to insn_assign_reg(), treating it as an unsigned long pointer and performing a 64-bit operation will result in an unaligned access.
Additionally, since the 8-byte operation starts at a +1 byte offset from the base register in the _regs array, won't this overwrite the lowest byte of the adjacent register in the array?
The previous implementation avoided this by casting to an 8-bit pointer before writing:
Quoted 4 lines
arch/x86/kvm/emulate.c +0
-0
[ ... ]
Quoted 14 lines
arch/x86/kvm/emulate.c +0
-10@@ -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 ;
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Re: [PATCH v4 2/3] x86/insn-eval: Add insn_assign_reg() helper
In reply to Kiryl Shutsemau (Meta)Quoted 10 lines · Kiryl Shutsemau (Meta)
KVM's instruction emulator has a small helper,
assign_register(), that writes a value into a sub-register with x86 partial-register-write
semantics: 1- and 2-byte writes leave the upper bits of the destination untouched, 4-byte writes zero-extend to 64 bits, 8-byte writes overwrite the full register.The TDX guest #VE handler needs the same logic for port I/O emulation to get 32-bit zero-extension right. Rather than copy-pasting the helper, lift it to <
asm/insn-eval.h> asinsn_assign_reg()so both can use it.
Quoted 8 lines
Rewrite the body using arithmetic instead of pointer punning so the helper does not depend on -fno-strict-aliasing or little-endian byte order, and 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 <[email protected]>
We should probably ping Sean/Paolo to ack this if we want Dave to take it. Is it the plan?
Borislav Petkov
maintainerRe: [PATCH v4 2/3] x86/insn-eval: Add insn_assign_reg() helper
Quoted 2 lines · Rick P
We should probably ping Sean/Paolo to ack this if we want Dave to take it. Is it the plan?
Not before Sashiko comments have been addressed:
https://sashiko.dev/#/patchset/cover.1780584300.git.kas%40kernel.org
Signature · 4 lines
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquetteSean Christopherson
maintainerRe: [PATCH v4 2/3] x86/insn-eval: Add insn_assign_reg() helper
In reply to Kiryl Shutsemau (Meta)In the shortlog, please capture that code is being moved out of KVM. This isn't
"adding" a helper, it's moving and renaming an existing helper. The misleading
shortlog is quite literally why I didn't look at this for a ~month, I thought it was entirely outside of my normal scope.
Quoted 6 lines · Kiryl Shutsemau (Meta)
KVM's instruction emulator has a small helper,
assign_register(), that writes a value into a sub-register with x86 partial-register-write
semantics: 1- and 2-byte writes leave the upper bits of the destination untouched, 4-byte writes zero-extend to 64 bits, 8-byte writes overwrite the full register.
Quoted 6 lines
The TDX guest #VE handler needs the same logic for port I/O emulation to get 32-bit zero-extension right. Rather than copy-pasting the helper, lift it to <
asm/insn-eval.h> asinsn_assign_reg()so both can use it.Rewrite the body using arithmetic instead of pointer punning so the helper does not depend on -fno-strict-aliasing
Meh, building with -fno-strict-aliasing is 100% mandatory for the kernel. IMO, even so much as suggesting that strict aliasing is something worth shooting for is a misleading and wrong.
https://lore.kernel.org/all/CAHk-=wh921g_+TJ33JRxRGFM2uruMdqG-SONfFOD9UOsLQJ_uw@mail.gmail.com
Quoted 1 line
or little-endian byte order,
Oh come on, this is low level x86 code used to write registers.
Quoted 24 lines
and 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 <[email protected]>--- arch/x86/include/asm/insn-eval.h | 25 +++++++++++++++++++++++++ arch/x86/kvm/emulate.c | 26 ++++---------------------- 2 files changed, 29 insertions(+), 22 deletions(-)arch/x86/include/asm/insn-eval.h +1
-0@@ -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)
arch/x86/include/asm/insn-eval.h enum insn_mmio_type insn_decode_mmio(struct insn *insn, int *bytes); · line 52
Quoted 6 lines
arch/x86/include/asm/insn-eval.h +3
-0@@ -46,4 +47,28 @@ enum insn_mmio_type insn_decode_mmio(struct insn *insn, int *bytes);bool insn_is_nop (struct insn *insn); +/* +* Write @val into *@reg with x86 partial-register-write semantics: a 1- +* or 2-byte write leaves the upper bits of the destination untouched; a
Careful with the "upper bits" wording. As Sashiko pointed out, this is used for {A,B,C,D}H sub-registers as well, in which case the *lower* bits are also left untouched.
arch/x86/include/asm/insn-eval.h enum insn_mmio_type insn_decode_mmio(struct insn *insn, int *bytes); · line 60
Quoted 8 lines
arch/x86/include/asm/insn-eval.h +8
-0+* 4-byte write zero-extends to 64 bits (matching IN[BWL], MOV[BWL] +* etc.); an 8-byte write overwrites the full register. +*/ +static inline void insn_assign_reg (unsigned long *reg,u64 val,int bytes) +{ +switch (bytes) { +case 1 : + *reg = (*reg & ~0xfful ) | (val &0xff );
Sashiko's feedback aside, I strongly prefer KVM's approach as I find it much more intuitive. And its far, far more consistent with respect to the 4-byte and 8-byte cases.
Quoted 12 lines
arch/x86/include/asm/insn-eval.h +12
-0+break ; +case 2 : + *reg = (*reg & ~0xfffful ) | (val &0xffff ); +break ; +case 4 : + *reg = (u32 )val; +break ; +case 8 : + *reg = val; +break ; + } +}
Kiryl Shutsemau (Meta)
maintainerPATCH v4 3/3 x86/tdx: Fix zero-extension for 32-bit port I/O
In reply to Kiryl Shutsemau (Meta)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.
--- arch/x86/coco/tdx/tdx.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-)
arch/x86/coco/tdx/tdx.c +3 -5
@@ -693,8 +693,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
@@ -702,11 +702,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; }
Signature · 1 line
2.54.0
[email protected]
AI reviewRe: [PATCH v4 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] handle_cpuid() fails to clear the upper 32 bits of RAX, RBX, RCX, RDX, allowing an untrusted VMM to inject garbage into guest registers. --
commit 1a8c131a02100639d5d76e680c87ef32990b4663
Author: Kiryl Shutsemau (Meta) <[email protected]>
x86/tdx: Fix zero-extension for 32-bit port I/O
This commit fixes the zero-extension behavior for emulated 32-bit port I/O in handle_in(). It uses insn_assign_reg() to correctly write the result into RAX with proper partial-register-write semantics, ensuring the upper 32 bits are zeroed for 4-byte reads.
Quoted 15 lines
arch/x86/coco/tdx/tdx.c +1
-1@@ -693,8 +693,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
arch/x86/coco/tdx/tdx.c static bool handle_in(struct pt_regs *regs, int size, int port) · line 707
Quoted 10 lines
arch/x86/coco/tdx/tdx.c +2
-4@@ -702,11 +702,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);
[Severity: High]
This isn't a bug introduced by this patch, but while you are fixing the zero-extension semantics for handle_in(), could the same issue be present in handle_cpuid() when emulating CPUID?
In arch/x86/coco/tdx/tdx.c:handle_cpuid():
/* * As per TDX GHCI CPUID ABI, r12-r15 registers contain contents of * EAX, EBX, ECX, EDX registers after the CPUID instruction execution. * So copy the register contents back to pt_regs. */ regs->ax = args.r12; regs->bx = args.r13; regs->cx = args.r14; regs->dx = args.r15;
Does this code fail to zero out the upper 32 bits of RAX, RBX, RCX, and RDX in 64-bit mode?
Since CPUID should clear the upper 32 bits of these registers, does directly assigning the 64-bit values from the untrusted hypervisor allow a malicious VMM to inject non-zero upper bits into the guest registers?
If guest software relies on the upper 32 bits being zeroed after CPUID, could this lead to unexpected behavior or bounds-check bypasses in 64-bit contexts?
Quoted 3 lines
arch/x86/coco/tdx/tdx.c +0
-0return success; }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Re: [PATCH v4 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O
In reply to Kiryl Shutsemau (Meta)Quoted 9 lines · Kiryl Shutsemau (Meta)
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.
Quoted 5 lines
Fixes 03149948832a ("x86/tdx: Port I/O: Add runtime hypercalls")Reported-by Borys Tsyrulnikov <[email protected]>Link https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/ open hereSigned-off-by Kiryl Shutsemau <[email protected]>
I think the concern sashiko commented in patch 2 is valid.
But for this patch itself,
Quoted 32 lines
--- arch/x86/coco/tdx/tdx.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-)arch/x86/coco/tdx/tdx.c +3
-5@@ -693,8 +693,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 @@ -702,11 +702,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; }
Kiryl Shutsemau
maintainerRe: [PATCH v4 3/3] x86/tdx: Fix zero-extension for 32-bit port I/O
Quoted 12 lines · Binbin Wu
Quoted 9 lines · Kiryl Shutsemau (Meta)
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.
Quoted 7 lines
Quoted 5 lines
Fixes 03149948832a ("x86/tdx: Port I/O: Add runtime hypercalls")Reported-by Borys Tsyrulnikov <[email protected]>Link https://lore.kernel.org/all/CAKw_Dz96rfSQc6Rn+9QBcUFHhmkK+9zu+P=bxowfZwxrATCBRg@mail.gmail.com/ open hereSigned-off-by Kiryl Shutsemau <[email protected]>I think the concern sashiko commented in patch 2 is valid.
Yeah. I guess I'll just use the KVM implementation verbatim.
Dave, any objections?
Signature · 1 line
Kiryl Shutsemau / Kirill A. Shutemov