public inbox for kvm@vger.kernel.org
 help / color / mirror / Atom feed
From: Sean Christopherson <seanjc@google.com>
To: Mathias Krause <minipli@grsecurity.net>
Cc: kvm@vger.kernel.org, Chao Gao <chao.gao@intel.com>,
	 Paolo Bonzini <pbonzini@redhat.com>
Subject: Re: [kvm-unit-tests PATCH v3 17/17] x86/cet: Add testcases to verify KVM rejects emulation of CET instructions
Date: Fri, 14 Nov 2025 12:42:58 -0800	[thread overview]
Message-ID: <aReUUkuMjnlYKzTR@google.com> (raw)
In-Reply-To: <fc886a22-49f3-4627-8ba6-933099e7640d@grsecurity.net>

On Fri, Nov 14, 2025, Mathias Krause wrote:
> On 14.11.25 01:12, Sean Christopherson wrote:
> > +#define SHSTK_TEST_UNSUPPORTED_INSTRUCTION(insn)			\
> > +do {									\
> > +	uint8_t vector = __CET_TEST_UNSUPPORTED_INSTRUCTION(insn);	\
> > +									\
> > +	report(vector == UD_VECTOR, "Wanted #UD on %s, got %s",		\
> > +	       insn, exception_mnemonic(vector));			\
> > +} while (0)
> > +
> > +/*
> > + * Treat IRET as unsupported with IBT even though the minimal interactions with
> > + * IBT _could_ be easily emulated by KVM, as KVM doesn't support emulating IRET
> > + * outside of Real Mode.
> > + */
> > +#define CET_TEST_UNSUPPORTED_INSTRUCTIONS(CET)				\
> > +do {									\
> > +	CET##_TEST_UNSUPPORTED_INSTRUCTION("callq *%%rax");		\
> > +	CET##_TEST_UNSUPPORTED_INSTRUCTION("lcall *%0");		\
> 
> Maybe spell out that this is a 32bit far call ("lcalll *%0"), as only
> these are supported on AMD *and* Intel? (Intel can do 64bit ones too.)
> 
> > +	CET##_TEST_UNSUPPORTED_INSTRUCTION("syscall");			\
> > +	CET##_TEST_UNSUPPORTED_INSTRUCTION("sysenter");			\
> > +	CET##_TEST_UNSUPPORTED_INSTRUCTION("iretq");			\
> 
> These, if emulated, would be rather disastrous to the test. :D
> - SYSCALL messes badly with the register state.
> - SYSENTER should #UD in long mode on AMD anyway but Intel allows it.
>   However, it'll likely #GP early, prior to messing with register state,
>   as IA32_SYSENTER_CS doesn't get initialized as the CPU expects it to
>   be.
> - The stack frame for IRETQ isn't properly set up (lacks CS, RFLAGS, SS,
>   RSP) but even if it'll #GP early for the non-canonical address, it'll
>   have modified RSP, making the recovery "pop %rax" pop an unrelated
>   stack word, likely making the code crash soon after.
> 
> So these instructions really *rely* on KVM to #UD on these early.
> Dunno, but it feels like the tests should be made more robust wrt.
> possible future emulation by KVM?

I don't necessarily disagree, but I'm not convinced it'd be worth the effort to
properly set everything up _and_ keep it functional.  Unless KVM is buggy, the
fallback will be completely untested.  I hate user-hostile tests as much as the
next person (probably more), but for this one my vote is to punt and cross our
fingers that KVM never screws up.

> > +} while (0)
> > +
> > +static uint64_t cet_shstk_emulation(void)
> > +{
> > +	CET_TEST_UNSUPPORTED_INSTRUCTIONS(SHSTK);
> > +
> > +	SHSTK_TEST_UNSUPPORTED_INSTRUCTION("call 1f");
> > +	SHSTK_TEST_UNSUPPORTED_INSTRUCTION("retq");
> > +	SHSTK_TEST_UNSUPPORTED_INSTRUCTION("retq $10");
> > +	SHSTK_TEST_UNSUPPORTED_INSTRUCTION("lretq");
> > +	SHSTK_TEST_UNSUPPORTED_INSTRUCTION("lretq $10");
> > +
> > +	/* Do a handful of JMPs to verify they aren't impacted by SHSTK. */
> > +	asm volatile(KVM_FEP "jmp 1f\n\t"
> > +		     "1:\n\t"
> > +		     KVM_FEP "lea 2f(%%rip), %%rax\n\t"
> > +		     KVM_FEP "jmp *%%rax\n\t"
>                               ^^^^^^^^^^
> Fortunately, this indirect branch runs only with shadow stack enabled,
> no IBT. If it would, than below misses an ENDBR64.

50/50 as to whether I noticed that or just got lucky :-)

>                      vvv
> > +		     "2:\n\t"> +		     KVM_FEP "push $" xstr(USER_CS) "\n\t"
> > +		     KVM_FEP "lea 3f(%%rip), %%rax\n\t"
> > +		     KVM_FEP "push %%rax\n\t"
> > +		     /* Manually encode ljmpq, which gas doesn't recognize :-( */
> 
> LJMPQ is actually only supported on Intel systems. AMD doesn't support
> it and one has to emulate it via "push $cs; push $rip; lretq".

Well that explains why gas wasn't being very nice to me.  I'll tweak the comment.

> Dunno if it's a feature or bug of KVM to emulate LJMPQ fine on AMD -- if it
> does, that is!

Bug?  Sort of?  KVM won't emulate it on #UD, so it's only very limited emulation.
E.g. the guest would have to force emulation, put the jump source in emulated MMIO,
or play TLB games to get KVM to emulate

> > +		     KVM_FEP ".byte 0x48\n\t"
> > +		     "ljmpl *(%%rsp)\n\t"
> > +		     "3:\n\t"
> > +		     KVM_FEP "pop %%rax\n\t"
> > +		     KVM_FEP "pop %%rax\n\t"
> > +		     ::: "eax");
> > +
> > +	return 0;
> > +}
> > +
> 
> > +/*
> > + * Don't invoke printf() or report() in the IBT testcase, as it will likely
> > + * generate an indirect branch without an endbr64 annotation and thus #CP.
> 
> Uhm, x86/Makefile.x86_64 has this:
> 
> fcf_protection_full := $(call cc-option, -fcf-protection=full,)
> COMMON_CFLAGS += -mno-red-zone -mno-sse -mno-sse2 $(fcf_protection_full)
> [...]
> ifneq ($(fcf_protection_full),)
> tests += $(TEST_DIR)/cet.$(exe)
> endif
> 
> So all code that needs it should have a leading ENDBR64 or the cet test
> wouldn't be part of the test suite.
> 
> However, I also notice it isn't working...
> 
> [digging in, hours later...]
> 
> After tweaking the exception handling to be able to tweak the IBT
> tracker state (which would be stuck at WAIT_FOR_ENDBRANCH for a missing
> ENDBR64), it still wouldn't work for me. Bummer! Further instrumentation
> showed, the code triggered within exception_mnemonic() which caused only
> more question marks -- it's just a simple switch, right? Though, looking
> at the disassembly made it crystal clear:
> 
> 000000000040707c <exception_mnemonic>:
>   40707c:       endbr64
>   407080:       cmp    $0x1e,%edi
>   407083:       ja     407117 <exception_mnemonic+0x9b>
>   407089:       mov    %edi,%edi
>   40708b:       notrack jmp *0x4107e0(,%rdi,8)
>     ::
>   4070b1:       mov    $0x411c7c,%eax	# <-- #CP(3) here
> 
> The switch block caused gcc to emit a jump table and an indirect jump
> for it (to 40708b). The jump is prefixed with 'notrack' to keep the size
> of the jump target stubs small, allowing for omitting the ENDBR64
> instruction. However, the IBT test code doesn't enable that in the MSR,
> leading the CPU to raising a #CP(3) for this indirect jump. D'oh!
> 
> So we can either disable the generation of jump tables in gcc
> (-fno-jump-tables) or just allow the 'notrack' handling. I guess the
> latter is easier.

And much more helpful for people like me, who know just enough about IBT to be
dangerous, but don't fully understand how everything works the end-to-end.
 
> Progress, but still something to fix, namely the "ljmp *%0" which is,
> apparently, successfully branching to address 0 and then triggering a
> #GP(0). But maybe that's just because I'm on an old version of the KVM
> CET series, haven't build v6.18-rcX yet.

I suspect it's your old version, it passes on my end.

> Anyhow, one less puzzle, so should be good?

Ya, I'll slot this in for the next version (instead of having it be a fixup).

      reply	other threads:[~2025-11-14 20:43 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-14  0:12 [kvm-unit-tests PATCH v3 00/17] x86: Improve CET tests Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 01/17] x86/run_in_user: Add an "end branch" marker on the user_mode destination Sean Christopherson
2025-11-14 15:51   ` Mathias Krause
2025-11-14 20:46     ` Sean Christopherson
2025-11-15  5:09       ` Mathias Krause
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 02/17] x86: cet: Pass virtual addresses to invlpg Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 03/17] x86: cet: Remove unnecessary memory zeroing for shadow stack Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 04/17] x86: cet: Directly check for #CP exception in run_in_user() Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 05/17] x86: cet: Validate #CP error code Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 06/17] x86: cet: Use report_skip() Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 07/17] x86: cet: Drop unnecessary casting Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 08/17] x86: cet: Validate writing unaligned values to SSP MSR causes #GP Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 09/17] x86: cet: Validate CET states during VMX transitions Sean Christopherson
2025-11-14  8:10   ` Mathias Krause
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 10/17] x86: cet: Make shadow stack less fragile Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 11/17] x86: cet: Simplify IBT test Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 12/17] x86: cet: Use symbolic values for the #CP error codes Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 13/17] x86: cet: Test far returns too Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 14/17] x86: Avoid top-most page for vmalloc on x86-64 Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 15/17] x86/cet: Run SHSTK and IBT tests as appropriate if either feature is supported Sean Christopherson
2025-11-14  8:59   ` Mathias Krause
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 16/17] x86/cet: Drop the "intel_" prefix from the CET testcase Sean Christopherson
2025-11-14  0:12 ` [kvm-unit-tests PATCH v3 17/17] x86/cet: Add testcases to verify KVM rejects emulation of CET instructions Sean Christopherson
2025-11-14 15:19   ` Mathias Krause
2025-11-14 20:42     ` Sean Christopherson [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=aReUUkuMjnlYKzTR@google.com \
    --to=seanjc@google.com \
    --cc=chao.gao@intel.com \
    --cc=kvm@vger.kernel.org \
    --cc=minipli@grsecurity.net \
    --cc=pbonzini@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox