mirror of
https://github.com/xen-project/xen.git
synced 2026-10-28 18:21:39 +01:00
A TSX Abort is one option mitigate Native-BHI, but a guest kernel doesn't get
to see this if Xen has turned RTM off using MSR_TSX_{CTRL,FORCE_ABORT}.
Therefore, the meaning of RTM_ALWAYS_ABORT has been adjusted to "XBEGIN won't
fault", and it should be exposed to guests so they can make a better decision.
Expose it in the max policy for any RTM-capable system. Offer it by default
only if RTM has been disabled.
Update test-tsx to account for this new meaning. While adjusting the logic in
test_guest_policies(), take the opportunity to use feature names (now they're
available) to make the logic easier to follow.
This is part of XSA-456 / CVE-2024-2201.
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
556 lines
16 KiB
C
556 lines
16 KiB
C
/*
|
|
* TSX settings and consistency tests
|
|
*
|
|
* This tests various behaviours and invariants with regards to TSX. It
|
|
* ideally wants running for several microcode versions, and all applicable
|
|
* tsx= commandline settings, on a single CPU, including after an S3
|
|
* suspend/resume event.
|
|
*
|
|
* It tests specifically:
|
|
* - The consistency of MSR_TSX_CTRL/MSR_TSX_FORCE_ABORT values across the
|
|
* system, and their accessibility WRT data in the host CPU policy.
|
|
* - The actual behaviour of RTM on the system.
|
|
* - Cross-check the default/max policies based on the actual RTM behaviour.
|
|
* - Create some guests, check their defaults, and check that the defaults
|
|
* can be changed.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/ucontext.h>
|
|
|
|
#include <xenctrl.h>
|
|
#include <xenguest.h>
|
|
#include <xen-tools/common-macros.h>
|
|
|
|
#include "xg_private.h"
|
|
|
|
enum {
|
|
#define XEN_CPUFEATURE(name, value) X86_FEATURE_##name = value,
|
|
#include <xen/arch-x86/cpufeatureset.h>
|
|
};
|
|
#define bitmaskof(idx) (1u << ((idx) & 31))
|
|
|
|
#define MSR_ARCH_CAPABILITIES 0x0000010a
|
|
#define ARCH_CAPS_TSX_CTRL (1 << 7)
|
|
#define MSR_TSX_FORCE_ABORT 0x0000010f
|
|
#define MSR_TSX_CTRL 0x00000122
|
|
#define MSR_MCU_OPT_CTRL 0x00000123
|
|
|
|
static unsigned int nr_failures;
|
|
#define fail(fmt, ...) \
|
|
({ \
|
|
nr_failures++; \
|
|
(void)printf(fmt, ##__VA_ARGS__); \
|
|
})
|
|
|
|
static xc_interface *xch;
|
|
|
|
/*
|
|
* Policies, arranged as an array for easy collection of all of them. We
|
|
* don't care about the raw policy (index 0) so reuse that for the guest
|
|
* policy.
|
|
*/
|
|
static struct xc_cpu_policy policies[6];
|
|
#define guest_policy policies[0]
|
|
#define host policies[XEN_SYSCTL_cpu_policy_host]
|
|
#define pv_max policies[XEN_SYSCTL_cpu_policy_pv_max]
|
|
#define hvm_max policies[XEN_SYSCTL_cpu_policy_hvm_max]
|
|
#define pv_default policies[XEN_SYSCTL_cpu_policy_pv_default]
|
|
#define hvm_default policies[XEN_SYSCTL_cpu_policy_hvm_default]
|
|
|
|
static bool xen_has_pv = true, xen_has_hvm = true;
|
|
|
|
static xc_physinfo_t physinfo;
|
|
|
|
static enum rtm_behaviour {
|
|
RTM_UD,
|
|
RTM_OK,
|
|
RTM_ABORT,
|
|
} rtm_behaviour;
|
|
|
|
/*
|
|
* Test a specific TSX MSR for consistency across the system, taking into
|
|
* account whether it ought to be accessible or not.
|
|
*
|
|
* We can't query offline CPUs, so skip those if encountered. We don't care
|
|
* particularly for the exact MSR value, but we do care that it is the same
|
|
* everywhere.
|
|
*/
|
|
static void test_tsx_msr_consistency(unsigned int msr, bool accessible)
|
|
{
|
|
uint64_t cpu0_val = ~0;
|
|
|
|
for ( unsigned int cpu = 0; cpu <= physinfo.max_cpu_id; ++cpu )
|
|
{
|
|
xc_resource_entry_t ent = {
|
|
.u.cmd = XEN_RESOURCE_OP_MSR_READ,
|
|
.idx = msr,
|
|
};
|
|
xc_resource_op_t op = {
|
|
.cpu = cpu,
|
|
.entries = &ent,
|
|
.nr_entries = 1,
|
|
};
|
|
int rc = xc_resource_op(xch, 1, &op);
|
|
|
|
if ( rc < 0 )
|
|
{
|
|
/* Don't emit a message for offline CPUs */
|
|
if ( errno != ENODEV )
|
|
fail(" xc_resource_op() for CPU%u failed: rc %d, errno %d - %s\n",
|
|
cpu, rc, errno, strerror(errno));
|
|
continue;
|
|
}
|
|
|
|
if ( accessible )
|
|
{
|
|
if ( rc != 1 )
|
|
{
|
|
fail(" Expected 1 result, got %d\n", rc);
|
|
continue;
|
|
}
|
|
if ( ent.u.ret != 0 )
|
|
{
|
|
fail(" Expected ok, got %d\n", ent.u.ret);
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( rc != 0 )
|
|
fail(" Expected 0 results, got %u\n", rc);
|
|
else if ( ent.u.ret != -EPERM )
|
|
fail(" Expected -EPERM, got %d\n", ent.u.ret);
|
|
continue;
|
|
}
|
|
|
|
if ( cpu == 0 )
|
|
{
|
|
cpu0_val = ent.val;
|
|
printf(" CPU0 val %#"PRIx64"\n", cpu0_val);
|
|
}
|
|
else if ( ent.val != cpu0_val )
|
|
fail(" CPU%u val %#"PRIx64" differs from CPU0 %#"PRIx64"\n",
|
|
cpu, ent.val, cpu0_val);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check all TSX MSRs, and in particular that their accessibility matches what
|
|
* is expressed in the host CPU policy.
|
|
*/
|
|
static void test_tsx_msrs(void)
|
|
{
|
|
printf("Testing MSR_TSX_FORCE_ABORT consistency\n");
|
|
test_tsx_msr_consistency(
|
|
MSR_TSX_FORCE_ABORT, host.policy.feat.tsx_force_abort);
|
|
|
|
printf("Testing MSR_TSX_CTRL consistency\n");
|
|
test_tsx_msr_consistency(
|
|
MSR_TSX_CTRL, host.policy.arch_caps.tsx_ctrl);
|
|
|
|
printf("Testing MSR_MCU_OPT_CTRL consistency\n");
|
|
test_tsx_msr_consistency(
|
|
MSR_MCU_OPT_CTRL, host.policy.feat.srbds_ctrl);
|
|
}
|
|
|
|
/*
|
|
* Probe for how RTM behaves, deliberately not inspecting CPUID.
|
|
* Distinguishes between "no support at all" (i.e. XBEGIN suffers #UD),
|
|
* working ok, and appearing to always abort.
|
|
*/
|
|
static enum rtm_behaviour probe_rtm_behaviour(void)
|
|
{
|
|
for ( unsigned int i = 0; i < 1000; ++i )
|
|
{
|
|
/*
|
|
* Opencoding the RTM infrastructure from immintrin.h, because we
|
|
* still support older versions of GCC. Also so we can include #UD
|
|
* detection logic.
|
|
*/
|
|
#define XBEGIN_STARTED -1
|
|
#define XBEGIN_UD -2
|
|
unsigned int status = XBEGIN_STARTED;
|
|
|
|
asm volatile ( ".Lxbegin: .byte 0xc7,0xf8,0,0,0,0" /* XBEGIN 1f; 1: */
|
|
: "+a" (status) :: "memory" );
|
|
if ( status == XBEGIN_STARTED )
|
|
{
|
|
asm volatile ( ".byte 0x0f,0x01,0xd5" ::: "memory" ); /* XEND */
|
|
return RTM_OK;
|
|
}
|
|
else if ( status == XBEGIN_UD )
|
|
return RTM_UD;
|
|
}
|
|
|
|
return RTM_ABORT;
|
|
}
|
|
|
|
static struct sigaction old_sigill;
|
|
|
|
static void sigill_handler(int signo, siginfo_t *info, void *extra)
|
|
{
|
|
extern const char xbegin_label[] asm(".Lxbegin");
|
|
|
|
if ( info->si_addr == xbegin_label &&
|
|
memcmp(info->si_addr, "\xc7\xf8\x00\x00\x00\x00", 6) == 0 )
|
|
{
|
|
ucontext_t *context = extra;
|
|
|
|
/*
|
|
* Found the XBEGIN instruction. Step over it, and update `status` to
|
|
* signal #UD.
|
|
*/
|
|
#if defined(__linux__)
|
|
# ifdef __x86_64__
|
|
context->uc_mcontext.gregs[REG_RIP] += 6;
|
|
context->uc_mcontext.gregs[REG_RAX] = XBEGIN_UD;
|
|
# else
|
|
context->uc_mcontext.gregs[REG_EIP] += 6;
|
|
context->uc_mcontext.gregs[REG_EAX] = XBEGIN_UD;
|
|
# endif
|
|
|
|
#elif defined(__FreeBSD__)
|
|
# ifdef __x86_64__
|
|
context->uc_mcontext.mc_rip += 6;
|
|
context->uc_mcontext.mc_rax = XBEGIN_UD;
|
|
# else
|
|
context->uc_mcontext.mc_eip += 6;
|
|
context->uc_mcontext.mc_eax = XBEGIN_UD;
|
|
# endif
|
|
|
|
#elif defined(__NetBSD__)
|
|
# ifdef __x86_64__
|
|
context->uc_mcontext.__gregs[_REG_RIP] += 6;
|
|
context->uc_mcontext.__gregs[_REG_RAX] = XBEGIN_UD;
|
|
# else
|
|
context->uc_mcontext.__gregs[_REG_EIP] += 6;
|
|
context->uc_mcontext.__gregs[_REG_EAX] = XBEGIN_UD;
|
|
# endif
|
|
|
|
#else
|
|
# error Unknown environment - please adjust
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Not the SIGILL we're looking for... Restore the old handler and
|
|
* try again. Will likely coredump as a result.
|
|
*/
|
|
sigaction(SIGILL, &old_sigill, NULL);
|
|
}
|
|
}
|
|
|
|
static void test_rtm_behaviour(void)
|
|
{
|
|
struct sigaction new_sigill = {
|
|
.sa_flags = SA_SIGINFO,
|
|
.sa_sigaction = sigill_handler,
|
|
};
|
|
const char *str;
|
|
|
|
printf("Testing RTM behaviour\n");
|
|
|
|
/*
|
|
* Install a custom SIGILL handler while probing for RTM behaviour, as the
|
|
* XBEGIN instruction might suffer #UD.
|
|
*/
|
|
sigaction(SIGILL, &new_sigill, &old_sigill);
|
|
rtm_behaviour = probe_rtm_behaviour();
|
|
sigaction(SIGILL, &old_sigill, NULL);
|
|
|
|
switch ( rtm_behaviour )
|
|
{
|
|
case RTM_UD: str = "#UD"; break;
|
|
case RTM_OK: str = "OK"; break;
|
|
case RTM_ABORT: str = "Abort"; break;
|
|
default: str = NULL; break;
|
|
}
|
|
|
|
if ( str )
|
|
printf(" Got %s\n", str);
|
|
else
|
|
return fail(" Got unexpected behaviour %d\n", rtm_behaviour);
|
|
|
|
if ( host.policy.feat.rtm )
|
|
{
|
|
if ( rtm_behaviour == RTM_UD )
|
|
fail(" Host reports RTM, but appears unavailable\n");
|
|
}
|
|
else
|
|
{
|
|
if ( rtm_behaviour != RTM_UD )
|
|
fail(" Host reports no RTM, but appears available\n");
|
|
}
|
|
}
|
|
|
|
static void dump_tsx_details(const struct cpu_policy *p, const char *pref)
|
|
{
|
|
printf(" %s RTM %u, HLE %u, TSX_FORCE_ABORT %u, RTM_ALWAYS_ABORT %u, TSX_CTRL %u\n",
|
|
pref,
|
|
p->feat.rtm,
|
|
p->feat.hle,
|
|
p->feat.tsx_force_abort,
|
|
p->feat.rtm_always_abort,
|
|
p->arch_caps.tsx_ctrl);
|
|
}
|
|
|
|
/* Sanity test various invariants we expect in the default/max policies. */
|
|
static void test_guest_policies(const struct cpu_policy *max,
|
|
const struct cpu_policy *def)
|
|
{
|
|
dump_tsx_details(max, "Max:");
|
|
dump_tsx_details(def, "Def:");
|
|
|
|
if ( max->feat.tsx_force_abort || def->feat.tsx_force_abort ||
|
|
max->feat.srbds_ctrl || def->feat.srbds_ctrl ||
|
|
max->arch_caps.tsx_ctrl || def->arch_caps.tsx_ctrl )
|
|
fail(" Xen-only TSX controls offered to guest\n");
|
|
|
|
switch ( rtm_behaviour )
|
|
{
|
|
case RTM_UD:
|
|
if ( max->feat.hle || def->feat.hle ||
|
|
max->feat.rtm || def->feat.rtm ||
|
|
max->feat.rtm_always_abort || def->feat.rtm_always_abort )
|
|
fail(" HLE/RTM/RTM_AA offered to guests despite not being available\n");
|
|
break;
|
|
|
|
case RTM_ABORT:
|
|
if ( def->feat.hle || def->feat.rtm )
|
|
fail(" HLE/RTM offered to guests by default despite not being usable\n");
|
|
if ( !def->feat.rtm_always_abort )
|
|
fail(" RTM_AA not offered to guests by default despite being available\n");
|
|
break;
|
|
|
|
case RTM_OK:
|
|
if ( !max->feat.rtm || !def->feat.rtm )
|
|
fail(" RTM not offered to guests despite being available\n");
|
|
break;
|
|
}
|
|
|
|
if ( def->feat.hle )
|
|
fail(" Fail: HLE offered in default policy\n");
|
|
|
|
if ( def->feat.rtm && def->feat.rtm_always_abort )
|
|
fail(" Fail: Both RTM and RTM_AA offered in default policy\n");
|
|
}
|
|
|
|
static void test_def_max_policies(void)
|
|
{
|
|
if ( xen_has_pv )
|
|
{
|
|
printf("Testing PV default/max policies\n");
|
|
test_guest_policies(&pv_max.policy, &pv_default.policy);
|
|
}
|
|
|
|
if ( xen_has_hvm )
|
|
{
|
|
printf("Testing HVM default/max policies\n");
|
|
test_guest_policies(&hvm_max.policy, &hvm_default.policy);
|
|
}
|
|
}
|
|
|
|
static void test_guest(struct xen_domctl_createdomain *c)
|
|
{
|
|
uint32_t domid = 0;
|
|
int rc;
|
|
|
|
rc = xc_domain_create(xch, &domid, c);
|
|
if ( rc )
|
|
return fail(" Domain create failure: %d - %s\n",
|
|
errno, strerror(errno));
|
|
|
|
printf(" Created d%u\n", domid);
|
|
|
|
rc = xc_cpu_policy_get_domain(xch, domid, &guest_policy);
|
|
if ( rc )
|
|
{
|
|
fail(" Failed to obtain domain policy: %d - %s\n",
|
|
errno, strerror(errno));
|
|
goto out;
|
|
}
|
|
|
|
dump_tsx_details(&guest_policy.policy, "Cur:");
|
|
|
|
/*
|
|
* Check defaults given to the guest.
|
|
*/
|
|
if ( guest_policy.policy.feat.rtm != (rtm_behaviour == RTM_OK) )
|
|
fail(" RTM %u in guest, despite rtm behaviour\n",
|
|
guest_policy.policy.feat.rtm);
|
|
|
|
if ( guest_policy.policy.feat.hle ||
|
|
guest_policy.policy.feat.tsx_force_abort ||
|
|
guest_policy.policy.feat.srbds_ctrl ||
|
|
guest_policy.policy.arch_caps.tsx_ctrl )
|
|
fail(" Unexpected features advertised\n");
|
|
|
|
if ( host.policy.feat.rtm )
|
|
{
|
|
unsigned int _7b0, _7d0;
|
|
|
|
/*
|
|
* If host RTM is available, all combinations of guest flags should be
|
|
* possible. Flip both HLE/RTM to check non-default settings.
|
|
*/
|
|
_7b0 = (guest_policy.policy.feat.raw[0].b ^=
|
|
(bitmaskof(X86_FEATURE_HLE) | bitmaskof(X86_FEATURE_RTM)));
|
|
_7d0 = (guest_policy.policy.feat.raw[0].d ^=
|
|
bitmaskof(X86_FEATURE_RTM_ALWAYS_ABORT));
|
|
|
|
/* Set the new policy. */
|
|
rc = xc_cpu_policy_set_domain(xch, domid, &guest_policy);
|
|
if ( rc )
|
|
{
|
|
fail(" Failed to set domain policy: %d - %s\n",
|
|
errno, strerror(errno));
|
|
goto out;
|
|
}
|
|
|
|
/* Re-get the new policy. */
|
|
rc = xc_cpu_policy_get_domain(xch, domid, &guest_policy);
|
|
if ( rc )
|
|
{
|
|
fail(" Failed to obtain domain policy: %d - %s\n",
|
|
errno, strerror(errno));
|
|
goto out;
|
|
}
|
|
|
|
dump_tsx_details(&guest_policy.policy, "Cur:");
|
|
|
|
if ( guest_policy.policy.feat.raw[0].b != _7b0 )
|
|
{
|
|
fail(" Expected CPUID.7[0].b 0x%08x differs from actual 0x%08x\n",
|
|
_7b0, guest_policy.policy.feat.raw[0].b);
|
|
goto out;
|
|
}
|
|
|
|
if ( guest_policy.policy.feat.raw[0].d != _7d0 )
|
|
{
|
|
fail(" Expected CPUID.7[0].d 0x%08x differs from actual 0x%08x\n",
|
|
_7d0, guest_policy.policy.feat.raw[0].d);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
rc = xc_domain_destroy(xch, domid);
|
|
if ( rc )
|
|
fail(" Failed to destroy domain: %d - %s\n",
|
|
errno, strerror(errno));
|
|
}
|
|
|
|
static void test_guests(void)
|
|
{
|
|
if ( xen_has_pv )
|
|
{
|
|
struct xen_domctl_createdomain c = {
|
|
.max_vcpus = 1,
|
|
.max_grant_frames = 1,
|
|
.grant_opts = XEN_DOMCTL_GRANT_version(1),
|
|
};
|
|
|
|
printf("Testing PV guest\n");
|
|
test_guest(&c);
|
|
}
|
|
|
|
if ( xen_has_hvm )
|
|
{
|
|
struct xen_domctl_createdomain c = {
|
|
.flags = XEN_DOMCTL_CDF_hvm,
|
|
.max_vcpus = 1,
|
|
.max_grant_frames = 1,
|
|
.grant_opts = XEN_DOMCTL_GRANT_version(1),
|
|
.arch = {
|
|
.emulation_flags = XEN_X86_EMU_LAPIC,
|
|
},
|
|
};
|
|
|
|
if ( physinfo.capabilities & XEN_SYSCTL_PHYSCAP_hap )
|
|
c.flags |= XEN_DOMCTL_CDF_hap;
|
|
else if ( !(physinfo.capabilities & XEN_SYSCTL_PHYSCAP_shadow) )
|
|
return fail(" HVM available, but neither HAP nor Shadow\n");
|
|
|
|
printf("Testing HVM guest\n");
|
|
test_guest(&c);
|
|
}
|
|
}
|
|
|
|
/* Obtain some general data, then run the tests. */
|
|
static void test_tsx(void)
|
|
{
|
|
int rc;
|
|
|
|
/* Read all policies except raw. */
|
|
for ( unsigned int i = XEN_SYSCTL_cpu_policy_host;
|
|
i <= XEN_SYSCTL_cpu_policy_hvm_default; ++i )
|
|
{
|
|
rc = xc_cpu_policy_get_system(xch, i, &policies[i]);
|
|
|
|
if ( rc == -1 && errno == EOPNOTSUPP )
|
|
{
|
|
/*
|
|
* Use EOPNOTSUPP to spot Xen missing CONFIG_{PV,HVM}, and adjust
|
|
* later testing accordingly.
|
|
*/
|
|
switch ( i )
|
|
{
|
|
case XEN_SYSCTL_cpu_policy_pv_max:
|
|
case XEN_SYSCTL_cpu_policy_pv_default:
|
|
if ( xen_has_pv )
|
|
printf(" Xen doesn't support PV\n");
|
|
xen_has_pv = false;
|
|
continue;
|
|
|
|
case XEN_SYSCTL_cpu_policy_hvm_max:
|
|
case XEN_SYSCTL_cpu_policy_hvm_default:
|
|
if ( xen_has_hvm )
|
|
printf(" Xen doesn't support HVM\n");
|
|
xen_has_hvm = false;
|
|
continue;
|
|
}
|
|
}
|
|
if ( rc )
|
|
return fail("Failed to obtain policy[%u]: %d - %s\n",
|
|
i, errno, strerror(errno));
|
|
}
|
|
|
|
dump_tsx_details(&host.policy, "Host:");
|
|
|
|
rc = xc_physinfo(xch, &physinfo);
|
|
if ( rc )
|
|
return fail("Failed to obtain physinfo: %d - %s\n",
|
|
errno, strerror(errno));
|
|
|
|
printf(" Got %u CPUs\n", physinfo.max_cpu_id + 1);
|
|
|
|
test_tsx_msrs();
|
|
test_rtm_behaviour();
|
|
test_def_max_policies();
|
|
test_guests();
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
printf("TSX tests\n");
|
|
|
|
xch = xc_interface_open(NULL, NULL, 0);
|
|
|
|
if ( !xch )
|
|
err(1, "xc_interface_open");
|
|
|
|
test_tsx();
|
|
|
|
return !!nr_failures;
|
|
}
|