Skip to content

Commit 57c3db7

Browse files
committed
KVM: x86: Don't emulate instructions affected by CET features
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. To detect instructions that are subject to IBT or affect IBT state, use the existing IsBranch flag along with the source operand type to detect indirect branches, and the existing NearBranch flag to detect far JMPs and CALLs, all of which are effectively indirect. Explicitly check for emulation of IRET, FAR RET (IMM), and SYSEXIT (the ret-like far branches) instead of adding another flag, e.g. IsRet, as it's unlikely the emulator will ever need to check for return-like instructions outside of this one specific flow. Use an allow-list instead of a deny-list because (a) it's a shorter list and (b) so that a missed entry gets a false positive, not a false negative (i.e. reject emulation instead of clobbering CET state). For Shadow Stacks, explicitly track instructions that directly affect the current SSP, as KVM's emulator doesn't have existing flags that can be used to precisely detect such instructions. Alternatively, the em_xxx() helpers could directly check for ShadowStack interactions, but using a dedicated flag is arguably easier to audit, and allows for handling both IBT and SHSTK in one fell swoop. 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. Note #2, FAR JMP from 64-bit mode to compatibility mode interacts with the current SSP, but only to ensure SSP[63:32] == 0. Don't tag FAR JMP as SHSTK, which would be rather confusing and would result in FAR JMP being rejected unnecessarily the vast majority of the time (ignoring that it's unlikely to ever be emulated). A future commit will add the #GP(0) check for the specific FAR JMP scenario. Note #3, task switches also modify SSP and so need to be rejected. That too will be addressed in a future commit. Suggested-by: Chao Gao <[email protected]> Originally-by: Yang Weijiang <[email protected]> Cc: Mathias Krause <[email protected]> Cc: John Allen <[email protected]> Cc: Rick Edgecombe <[email protected]> Reviewed-by: Chao Gao <[email protected]> Reviewed-by: Binbin Wu <[email protected]> Reviewed-by: Xiaoyao Li <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Sean Christopherson <[email protected]>
1 parent 584ba3f commit 57c3db7

File tree

1 file changed

+102
-13
lines changed

1 file changed

+102
-13
lines changed

arch/x86/kvm/emulate.c

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@
178178
#define IncSP ((u64)1 << 54) /* SP is incremented before ModRM calc */
179179
#define TwoMemOp ((u64)1 << 55) /* Instruction has two memory operand */
180180
#define IsBranch ((u64)1 << 56) /* Instruction is considered a branch. */
181+
#define ShadowStack ((u64)1 << 57) /* Instruction affects Shadow Stacks. */
181182

182183
#define DstXacc (DstAccLo | SrcAccHi | SrcWrite)
183184

@@ -4068,8 +4069,8 @@ static const struct opcode group4[] = {
40684069
static const struct opcode group5[] = {
40694070
F(DstMem | SrcNone | Lock, em_inc),
40704071
F(DstMem | SrcNone | Lock, em_dec),
4071-
I(SrcMem | NearBranch | IsBranch, em_call_near_abs),
4072-
I(SrcMemFAddr | ImplicitOps | IsBranch, em_call_far),
4072+
I(SrcMem | NearBranch | IsBranch | ShadowStack, em_call_near_abs),
4073+
I(SrcMemFAddr | ImplicitOps | IsBranch | ShadowStack, em_call_far),
40734074
I(SrcMem | NearBranch | IsBranch, em_jmp_abs),
40744075
I(SrcMemFAddr | ImplicitOps | IsBranch, em_jmp_far),
40754076
I(SrcMem | Stack | TwoMemOp, em_push), D(Undefined),
@@ -4304,7 +4305,7 @@ static const struct opcode opcode_table[256] = {
43044305
DI(SrcAcc | DstReg, pause), X7(D(SrcAcc | DstReg)),
43054306
/* 0x98 - 0x9F */
43064307
D(DstAcc | SrcNone), I(ImplicitOps | SrcAcc, em_cwd),
4307-
I(SrcImmFAddr | No64 | IsBranch, em_call_far), N,
4308+
I(SrcImmFAddr | No64 | IsBranch | ShadowStack, em_call_far), N,
43084309
II(ImplicitOps | Stack, em_pushf, pushf),
43094310
II(ImplicitOps | Stack, em_popf, popf),
43104311
I(ImplicitOps, em_sahf), I(ImplicitOps, em_lahf),
@@ -4324,19 +4325,19 @@ static const struct opcode opcode_table[256] = {
43244325
X8(I(DstReg | SrcImm64 | Mov, em_mov)),
43254326
/* 0xC0 - 0xC7 */
43264327
G(ByteOp | Src2ImmByte, group2), G(Src2ImmByte, group2),
4327-
I(ImplicitOps | NearBranch | SrcImmU16 | IsBranch, em_ret_near_imm),
4328-
I(ImplicitOps | NearBranch | IsBranch, em_ret),
4328+
I(ImplicitOps | NearBranch | SrcImmU16 | IsBranch | ShadowStack, em_ret_near_imm),
4329+
I(ImplicitOps | NearBranch | IsBranch | ShadowStack, em_ret),
43294330
I(DstReg | SrcMemFAddr | ModRM | No64 | Src2ES, em_lseg),
43304331
I(DstReg | SrcMemFAddr | ModRM | No64 | Src2DS, em_lseg),
43314332
G(ByteOp, group11), G(0, group11),
43324333
/* 0xC8 - 0xCF */
43334334
I(Stack | SrcImmU16 | Src2ImmByte, em_enter),
43344335
I(Stack, em_leave),
4335-
I(ImplicitOps | SrcImmU16 | IsBranch, em_ret_far_imm),
4336-
I(ImplicitOps | IsBranch, em_ret_far),
4337-
D(ImplicitOps | IsBranch), DI(SrcImmByte | IsBranch, intn),
4336+
I(ImplicitOps | SrcImmU16 | IsBranch | ShadowStack, em_ret_far_imm),
4337+
I(ImplicitOps | IsBranch | ShadowStack, em_ret_far),
4338+
D(ImplicitOps | IsBranch), DI(SrcImmByte | IsBranch | ShadowStack, intn),
43384339
D(ImplicitOps | No64 | IsBranch),
4339-
II(ImplicitOps | IsBranch, em_iret, iret),
4340+
II(ImplicitOps | IsBranch | ShadowStack, em_iret, iret),
43404341
/* 0xD0 - 0xD7 */
43414342
G(Src2One | ByteOp, group2), G(Src2One, group2),
43424343
G(Src2CL | ByteOp, group2), G(Src2CL, group2),
@@ -4352,7 +4353,7 @@ static const struct opcode opcode_table[256] = {
43524353
I2bvIP(SrcImmUByte | DstAcc, em_in, in, check_perm_in),
43534354
I2bvIP(SrcAcc | DstImmUByte, em_out, out, check_perm_out),
43544355
/* 0xE8 - 0xEF */
4355-
I(SrcImm | NearBranch | IsBranch, em_call),
4356+
I(SrcImm | NearBranch | IsBranch | ShadowStack, em_call),
43564357
D(SrcImm | ImplicitOps | NearBranch | IsBranch),
43574358
I(SrcImmFAddr | No64 | IsBranch, em_jmp_far),
43584359
D(SrcImmByte | ImplicitOps | NearBranch | IsBranch),
@@ -4371,7 +4372,7 @@ static const struct opcode opcode_table[256] = {
43714372
static const struct opcode twobyte_table[256] = {
43724373
/* 0x00 - 0x0F */
43734374
G(0, group6), GD(0, &group7), N, N,
4374-
N, I(ImplicitOps | EmulateOnUD | IsBranch, em_syscall),
4375+
N, I(ImplicitOps | EmulateOnUD | IsBranch | ShadowStack, em_syscall),
43754376
II(ImplicitOps | Priv, em_clts, clts), N,
43764377
DI(ImplicitOps | Priv, invd), DI(ImplicitOps | Priv, wbinvd), N, N,
43774378
N, D(ImplicitOps | ModRM | SrcMem | NoAccess), N, N,
@@ -4402,8 +4403,8 @@ static const struct opcode twobyte_table[256] = {
44024403
IIP(ImplicitOps, em_rdtsc, rdtsc, check_rdtsc),
44034404
II(ImplicitOps | Priv, em_rdmsr, rdmsr),
44044405
IIP(ImplicitOps, em_rdpmc, rdpmc, check_rdpmc),
4405-
I(ImplicitOps | EmulateOnUD | IsBranch, em_sysenter),
4406-
I(ImplicitOps | Priv | EmulateOnUD | IsBranch, em_sysexit),
4406+
I(ImplicitOps | EmulateOnUD | IsBranch | ShadowStack, em_sysenter),
4407+
I(ImplicitOps | Priv | EmulateOnUD | IsBranch | ShadowStack, em_sysexit),
44074408
N, N,
44084409
N, N, N, N, N, N, N, N,
44094410
/* 0x40 - 0x4F */
@@ -4514,6 +4515,60 @@ static const struct opcode opcode_map_0f_38[256] = {
45144515
#undef I2bvIP
45154516
#undef I6ALU
45164517

4518+
static bool is_shstk_instruction(struct x86_emulate_ctxt *ctxt)
4519+
{
4520+
return ctxt->d & ShadowStack;
4521+
}
4522+
4523+
static bool is_ibt_instruction(struct x86_emulate_ctxt *ctxt)
4524+
{
4525+
u64 flags = ctxt->d;
4526+
4527+
if (!(flags & IsBranch))
4528+
return false;
4529+
4530+
/*
4531+
* All far JMPs and CALLs (including SYSCALL, SYSENTER, and INTn) are
4532+
* indirect and thus affect IBT state. All far RETs (including SYSEXIT
4533+
* and IRET) are protected via Shadow Stacks and thus don't affect IBT
4534+
* state. IRET #GPs when returning to virtual-8086 and IBT or SHSTK is
4535+
* enabled, but that should be handled by IRET emulation (in the very
4536+
* unlikely scenario that KVM adds support for fully emulating IRET).
4537+
*/
4538+
if (!(flags & NearBranch))
4539+
return ctxt->execute != em_iret &&
4540+
ctxt->execute != em_ret_far &&
4541+
ctxt->execute != em_ret_far_imm &&
4542+
ctxt->execute != em_sysexit;
4543+
4544+
switch (flags & SrcMask) {
4545+
case SrcReg:
4546+
case SrcMem:
4547+
case SrcMem16:
4548+
case SrcMem32:
4549+
return true;
4550+
case SrcMemFAddr:
4551+
case SrcImmFAddr:
4552+
/* Far branches should be handled above. */
4553+
WARN_ON_ONCE(1);
4554+
return true;
4555+
case SrcNone:
4556+
case SrcImm:
4557+
case SrcImmByte:
4558+
/*
4559+
* Note, ImmU16 is used only for the stack adjustment operand on ENTER
4560+
* and RET instructions. ENTER isn't a branch and RET FAR is handled
4561+
* by the NearBranch check above. RET itself isn't an indirect branch.
4562+
*/
4563+
case SrcImmU16:
4564+
return false;
4565+
default:
4566+
WARN_ONCE(1, "Unexpected Src operand '%llx' on branch",
4567+
flags & SrcMask);
4568+
return false;
4569+
}
4570+
}
4571+
45174572
static unsigned imm_size(struct x86_emulate_ctxt *ctxt)
45184573
{
45194574
unsigned size;
@@ -4943,6 +4998,40 @@ int x86_decode_insn(struct x86_emulate_ctxt *ctxt, void *insn, int insn_len, int
49434998

49444999
ctxt->execute = opcode.u.execute;
49455000

5001+
/*
5002+
* Reject emulation if KVM might need to emulate shadow stack updates
5003+
* and/or indirect branch tracking enforcement, which the emulator
5004+
* doesn't support.
5005+
*/
5006+
if ((is_ibt_instruction(ctxt) || is_shstk_instruction(ctxt)) &&
5007+
ctxt->ops->get_cr(ctxt, 4) & X86_CR4_CET) {
5008+
u64 u_cet = 0, s_cet = 0;
5009+
5010+
/*
5011+
* Check both User and Supervisor on far transfers as inter-
5012+
* privilege level transfers are impacted by CET at the target
5013+
* privilege level, and that is not known at this time. The
5014+
* expectation is that the guest will not require emulation of
5015+
* any CET-affected instructions at any privilege level.
5016+
*/
5017+
if (!(ctxt->d & NearBranch))
5018+
u_cet = s_cet = CET_SHSTK_EN | CET_ENDBR_EN;
5019+
else if (ctxt->ops->cpl(ctxt) == 3)
5020+
u_cet = CET_SHSTK_EN | CET_ENDBR_EN;
5021+
else
5022+
s_cet = CET_SHSTK_EN | CET_ENDBR_EN;
5023+
5024+
if ((u_cet && ctxt->ops->get_msr(ctxt, MSR_IA32_U_CET, &u_cet)) ||
5025+
(s_cet && ctxt->ops->get_msr(ctxt, MSR_IA32_S_CET, &s_cet)))
5026+
return EMULATION_FAILED;
5027+
5028+
if ((u_cet | s_cet) & CET_SHSTK_EN && is_shstk_instruction(ctxt))
5029+
return EMULATION_FAILED;
5030+
5031+
if ((u_cet | s_cet) & CET_ENDBR_EN && is_ibt_instruction(ctxt))
5032+
return EMULATION_FAILED;
5033+
}
5034+
49465035
if (unlikely(emulation_type & EMULTYPE_TRAP_UD) &&
49475036
likely(!(ctxt->d & EmulateOnUD)))
49485037
return EMULATION_FAILED;

0 commit comments

Comments
 (0)