Re: [PATCH v15 18/41] KVM: x86: Don't emulate instructions affected by CET features

Chao Gao
2025-09-17T08:16:58Z

On Fri, Sep 12, 2025 at 04:22:56PM -0700, Sean Christopherson wrote:
>From: Yang Weijiang <weijiang.yang@intel.com>
>
>Don't emulate branch instructions, e.g. CALL/RET/JMP etc., that are
>affected by Shadow Stacks and/or Indirect Branch Tracking when said
>features are enabled in the guest, as fully emulating CET would require
>significant complexity for no practical benefit (KVM shouldn't need to
>emulate branch instructions on modern hosts). Simply doing nothing isn't
>an option as that would allow a malicious entity to subvert CET
>protections via the emulator.
>
>Note! On far transfers, do NOT consult the current privilege level and
>instead treat SHSTK/IBT as being enabled if they're enabled for User *or*
>Supervisor mode. On inter-privilege level far transfers, SHSTK and IBT
>can be in play for the target privilege level, i.e. checking the current
>privilege could get a false negative, and KVM doesn't know the target
>privilege level until emulation gets under way.

I modified KUT's cet.c to verify that near jumps, near returns, and far
transfers (e.g., IRET) trigger the emulation failure logic added by this
patch when guests enable Shadow Stack or IBT.

I found only one minor issue: near return instructions were not tagged with
ShadowStack. The following diff fixes this issue:

diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c
index e4be54a677b0..b1c9816bd5c6 100644
--- a/arch/x86/kvm/emulate.c
+++ b/arch/x86/kvm/emulate.c
@@ -4326,8 +4326,8 @@ static const struct opcode opcode_table[256] = {
X8(I(DstReg | SrcImm64 | Mov, em_mov)),
/* 0xC0 - 0xC7 */
G(ByteOp | Src2ImmByte, group2), G(Src2ImmByte, group2),
- I(ImplicitOps | NearBranch | SrcImmU16 | IsBranch, em_ret_near_imm),
- I(ImplicitOps | NearBranch | IsBranch, em_ret),
+ I(ImplicitOps | NearBranch | SrcImmU16 | IsBranch | ShadowStack, em_ret_near_imm),
+ I(ImplicitOps | NearBranch | IsBranch | ShadowStack, em_ret),
I(DstReg | SrcMemFAddr | ModRM | No64 | Src2ES, em_lseg),
I(DstReg | SrcMemFAddr | ModRM | No64 | Src2DS, em_lseg),
G(ByteOp, group11), G(0, group11),

And for reference, below are the changes I made to KUT's cet.c

diff --git a/x86/cet.c b/x86/cet.c
index 42d2b1fc..ff6b17f6 100644
--- a/x86/cet.c
+++ b/x86/cet.c
@@ -30,6 +30,8 @@ static u64 cet_shstk_func(void)
*/
printf("Try to temper the return-address, this causes #CP on returning...\n");
*(ret_addr + 1) = 0xdeaddead;
+ /* Verify that near return causes emulation failure */
+ asm volatile (KVM_FEP "ret\n");

return 0;
}
@@ -45,7 +47,8 @@ static u64 cet_ibt_func(void)
asm volatile ("movq $2, %rcx\n"
"dec %rcx\n"
"leaq 2f(%rip), %rax\n"
- "jmp *%rax \n"
+ /* Verify that near jmp causes emulation failure */
+ KVM_FEP "jmp *%rax \n"
"2:\n"
"dec %rcx\n");
return 0;
@@ -111,6 +114,12 @@ int main(int ac, char **av)
/* Enable CET master control bit in CR4. */
write_cr4(read_cr4() | X86_CR4_CET);

+ /*
+ * Verify "Far transfers" causes emulation failure even if shadow
+ * stack isn't enabled for the current privilege level
+ */
+ asm volatile (KVM_FEP "iret\n");
+
printf("Unit test for CET user mode...\n");
run_in_user((usermode_func)cet_shstk_func, GP_VECTOR, 0, 0, 0, 0, &rvc);
report(cp_count == 1, "Completed shadow-stack protection test successfully.");