Files
xen/tools/misc/xen-cpuid.c
T
Andrew Cooper 8202b9cf84 x86/shskt: Disable CET-SS on parts susceptible to fractured updates
Refer to Intel SDM Rev 70 (Dec 2022), Vol3 17.2.3 "Supervisor Shadow Stack
Token".

Architecturally, an event delivery which starts in CPL<3 and switches shadow
stack will first validate the Supervisor Shadow Stack Token (setting the busy
bit), then pushes CS/LIP/SSP.  One example of this is an NMI interrupting Xen.

Some CPUs suffer from an issue called fracturing, whereby a fault/vmexit/etc
between setting the busy bit and completing the event injection renders the
action non-restartable, because when it comes time to restart, the busy bit is
found to be already set.

This is far more easily encountered under virt, yet it is not the fault of the
hypervisor, nor the fault of the guest kernel.  The fault lies somewhere
between the architectural specification, and the uarch behaviour.

Intel have allocated CPUID.7[1].ecx[18] CET_SSS to enumerate that supervisor
shadow stacks are safe to use.  Because of how Xen lays out its shadow stacks,
fracturing is not expected to be a problem on native.

Detect this case on boot and default to not using shstk if virtualised.
Specifying `cet=shstk` on the command line will override this heuristic and
enable shadow stacks irrespective.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
master commit: 01e7477d1b
master date: 2023-02-09 18:26:17 +0000
2023-03-03 07:56:16 +01:00

640 lines
18 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <inttypes.h>
#include <xenctrl.h>
#include <xenguest.h>
#include <xen-tools/libs.h>
static uint32_t nr_features;
static const char *const str_1d[32] =
{
[ 0] = "fpu", [ 1] = "vme",
[ 2] = "de", [ 3] = "pse",
[ 4] = "tsc", [ 5] = "msr",
[ 6] = "pae", [ 7] = "mce",
[ 8] = "cx8", [ 9] = "apic",
/* [10] */ [11] = "sysenter",
[12] = "mtrr", [13] = "pge",
[14] = "mca", [15] = "cmov",
[16] = "pat", [17] = "pse36",
[18] = "psn", [19] = "clflush",
/* [20] */ [21] = "ds",
[22] = "acpi", [23] = "mmx",
[24] = "fxsr", [25] = "sse",
[26] = "sse2", [27] = "ss",
[28] = "htt", [29] = "tm",
[30] = "ia64", [31] = "pbe",
};
static const char *const str_1c[32] =
{
[ 0] = "sse3", [ 1] = "pclmulqdq",
[ 2] = "dtes64", [ 3] = "monitor",
[ 4] = "ds-cpl", [ 5] = "vmx",
[ 6] = "smx", [ 7] = "est",
[ 8] = "tm2", [ 9] = "ssse3",
[10] = "cntx-id", [11] = "sdgb",
[12] = "fma", [13] = "cx16",
[14] = "xtpr", [15] = "pdcm",
/* [16] */ [17] = "pcid",
[18] = "dca", [19] = "sse41",
[20] = "sse42", [21] = "x2apic",
[22] = "movebe", [23] = "popcnt",
[24] = "tsc-dl", [25] = "aesni",
[26] = "xsave", [27] = "osxsave",
[28] = "avx", [29] = "f16c",
[30] = "rdrnd", [31] = "hyper",
};
static const char *const str_e1d[32] =
{
[ 0] = "fpu", [ 1] = "vme",
[ 2] = "de", [ 3] = "pse",
[ 4] = "tsc", [ 5] = "msr",
[ 6] = "pae", [ 7] = "mce",
[ 8] = "cx8", [ 9] = "apic",
/* [10] */ [11] = "syscall",
[12] = "mtrr", [13] = "pge",
[14] = "mca", [15] = "cmov",
[16] = "fcmov", [17] = "pse36",
/* [18] */ [19] = "mp",
[20] = "nx", /* [21] */
[22] = "mmx+", [23] = "mmx",
[24] = "fxsr", [25] = "fxsr+",
[26] = "pg1g", [27] = "rdtscp",
/* [28] */ [29] = "lm",
[30] = "3dnow+", [31] = "3dnow",
};
static const char *const str_e1c[32] =
{
[ 0] = "lahf-lm", [ 1] = "cmp",
[ 2] = "svm", [ 3] = "extapic",
[ 4] = "cr8d", [ 5] = "lzcnt",
[ 6] = "sse4a", [ 7] = "msse",
[ 8] = "3dnowpf", [ 9] = "osvw",
[10] = "ibs", [11] = "xop",
[12] = "skinit", [13] = "wdt",
/* [14] */ [15] = "lwp",
[16] = "fma4", [17] = "tce",
/* [18] */ [19] = "nodeid",
/* [20] */ [21] = "tbm",
[22] = "topoext", [23] = "perfctr-core",
[24] = "perfctr-nb", /* [25] */
[26] = "dbx", [27] = "perftsc",
[28] = "pcx-l2i", [29] = "monitorx",
};
static const char *const str_7b0[32] =
{
[ 0] = "fsgsbase", [ 1] = "tsc-adj",
[ 2] = "sgx", [ 3] = "bmi1",
[ 4] = "hle", [ 5] = "avx2",
[ 6] = "fdp-exn", [ 7] = "smep",
[ 8] = "bmi2", [ 9] = "erms",
[10] = "invpcid", [11] = "rtm",
[12] = "pqm", [13] = "depfpp",
[14] = "mpx", [15] = "pqe",
[16] = "avx512f", [17] = "avx512dq",
[18] = "rdseed", [19] = "adx",
[20] = "smap", [21] = "avx512-ifma",
[22] = "pcommit", [23] = "clflushopt",
[24] = "clwb", [25] = "proc-trace",
[26] = "avx512pf", [27] = "avx512er",
[28] = "avx512cd", [29] = "sha",
[30] = "avx512bw", [31] = "avx512vl",
};
static const char *const str_Da1[32] =
{
[ 0] = "xsaveopt", [ 1] = "xsavec",
[ 2] = "xgetbv1", [ 3] = "xsaves",
};
static const char *const str_7c0[32] =
{
[ 0] = "prefetchwt1", [ 1] = "avx512-vbmi",
[ 2] = "umip", [ 3] = "pku",
[ 4] = "ospke", [ 5] = "waitpkg",
[ 6] = "avx512-vbmi2", [ 7] = "cet-ss",
[ 8] = "gfni", [ 9] = "vaes",
[10] = "vpclmulqdq", [11] = "avx512-vnni",
[12] = "avx512-bitalg",
[14] = "avx512-vpopcntdq",
[22] = "rdpid",
/* 24 */ [25] = "cldemote",
/* 26 */ [27] = "movdiri",
[28] = "movdir64b", [29] = "enqcmd",
[30] = "sgx-lc",
};
static const char *const str_e7d[32] =
{
/* 6 */ [ 7] = "hw-pstate",
[ 8] = "itsc", [ 9] = "cpb",
[10] = "efro",
};
static const char *const str_e8b[32] =
{
[ 0] = "clzero",
[ 2] = "rstr-fp-err-ptrs",
/* [ 8] */ [ 9] = "wbnoinvd",
[12] = "ibpb",
[14] = "ibrs", [15] = "amd-stibp",
[16] = "ibrs-always", [17] = "stibp-always",
[18] = "ibrs-fast", [19] = "ibrs-same-mode",
[20] = "no-lmsl",
/* [22] */ [23] = "ppin",
[24] = "amd-ssbd", [25] = "virt-ssbd",
[26] = "ssb-no",
[28] = "psfd", [29] = "btc-no",
[30] = "ibpb-ret",
};
static const char *const str_7d0[32] =
{
[ 2] = "avx512-4vnniw", [ 3] = "avx512-4fmaps",
[ 4] = "fsrm",
[ 8] = "avx512-vp2intersect", [ 9] = "srbds-ctrl",
[10] = "md-clear", [11] = "rtm-always-abort",
/* 12 */ [13] = "tsx-force-abort",
[14] = "serialize",
[16] = "tsxldtrk",
[18] = "pconfig",
[20] = "cet-ibt",
/* 22 */ [23] = "avx512-fp16",
[26] = "ibrsb", [27] = "stibp",
[28] = "l1d-flush", [29] = "arch-caps",
[30] = "core-caps", [31] = "ssbd",
};
static const char *const str_7a1[32] =
{
[ 4] = "avx-vnni", [ 5] = "avx512-bf16",
[10] = "fzrm", [11] = "fsrs",
[12] = "fsrcs",
};
static const char *const str_e21a[32] =
{
[ 2] = "lfence+",
[ 6] = "nscb",
};
static const char *const str_7b1[32] =
{
[ 0] = "ppin",
};
static const char *const str_7c1[32] =
{
};
static const char *const str_7d1[32] =
{
[18] = "cet-sss",
};
static const char *const str_7d2[32] =
{
[ 0] = "intel-psfd",
/* 4 */ [ 5] = "mcdt-no",
};
static const struct {
const char *name;
const char *abbr;
const char *const *strs;
} decodes[] =
{
{ "0x00000001.edx", "1d", str_1d },
{ "0x00000001.ecx", "1c", str_1c },
{ "0x80000001.edx", "e1d", str_e1d },
{ "0x80000001.ecx", "e1c", str_e1c },
{ "0x0000000d:1.eax", "Da1", str_Da1 },
{ "0x00000007:0.ebx", "7b0", str_7b0 },
{ "0x00000007:0.ecx", "7c0", str_7c0 },
{ "0x80000007.edx", "e7d", str_e7d },
{ "0x80000008.ebx", "e8b", str_e8b },
{ "0x00000007:0.edx", "7d0", str_7d0 },
{ "0x00000007:1.eax", "7a1", str_7a1 },
{ "0x80000021.eax", "e21a", str_e21a },
{ "0x00000007:1.ebx", "7b1", str_7b1 },
{ "0x00000007:2.edx", "7d2", str_7d2 },
{ "0x00000007:1.ecx", "7c1", str_7c1 },
{ "0x00000007:1.edx", "7d1", str_7d1 },
};
#define COL_ALIGN "18"
static struct fsinfo {
const char *name;
uint32_t len;
uint32_t *fs;
} featuresets[] =
{
[XEN_SYSCTL_cpu_featureset_host] = { "Host", 0, NULL },
[XEN_SYSCTL_cpu_featureset_raw] = { "Raw", 0, NULL },
[XEN_SYSCTL_cpu_featureset_pv] = { "PV", 0, NULL },
[XEN_SYSCTL_cpu_featureset_hvm] = { "HVM", 0, NULL },
};
static void dump_leaf(uint32_t leaf, const char *const *strs)
{
unsigned i;
if ( !strs )
{
printf(" ???");
return;
}
for ( i = 0; i < 32; ++i )
if ( leaf & (1u << i) )
{
if ( strs[i] )
printf(" %s", strs[i]);
else
printf(" <%u>", i);
}
}
static void decode_featureset(const uint32_t *features,
const uint32_t length,
const char *name,
bool detail)
{
unsigned int i;
printf("%-"COL_ALIGN"s ", name);
for ( i = 0; i < length; ++i )
printf("%08x%c", features[i],
i < length - 1 ? ':' : '\n');
if ( !detail )
return;
for ( i = 0; i < length && i < ARRAY_SIZE(decodes); ++i )
{
printf(" [%02u] %-"COL_ALIGN"s", i, decodes[i].name ?: "<UNKNOWN>");
if ( decodes[i].name )
dump_leaf(features[i], decodes[i].strs);
printf("\n");
}
}
static int get_featureset(xc_interface *xch, unsigned int idx)
{
struct fsinfo *f = &featuresets[idx];
f->len = nr_features;
f->fs = calloc(nr_features, sizeof(*f->fs));
if ( !f->fs )
err(1, "calloc(, featureset)");
return xc_get_cpu_featureset(xch, idx, &f->len, f->fs);
}
static void dump_info(xc_interface *xch, bool detail)
{
unsigned int i;
printf("nr_features: %u\n", nr_features);
if ( !detail )
{
printf(" %"COL_ALIGN"s ", "KEY");
for ( i = 0; i < ARRAY_SIZE(decodes); ++i )
printf("%-8s ", decodes[i].abbr ?: "???");
printf("\n");
}
printf("\nStatic sets:\n");
decode_featureset(xc_get_static_cpu_featuremask(XC_FEATUREMASK_KNOWN),
nr_features, "Known", detail);
decode_featureset(xc_get_static_cpu_featuremask(XC_FEATUREMASK_SPECIAL),
nr_features, "Special", detail);
decode_featureset(xc_get_static_cpu_featuremask(XC_FEATUREMASK_PV_MAX),
nr_features, "PV Max", detail);
decode_featureset(xc_get_static_cpu_featuremask(XC_FEATUREMASK_PV_DEF),
nr_features, "PV Default", detail);
decode_featureset(xc_get_static_cpu_featuremask(XC_FEATUREMASK_HVM_SHADOW_MAX),
nr_features, "HVM Shadow Max", detail);
decode_featureset(xc_get_static_cpu_featuremask(XC_FEATUREMASK_HVM_SHADOW_DEF),
nr_features, "HVM Shadow Default", detail);
decode_featureset(xc_get_static_cpu_featuremask(XC_FEATUREMASK_HVM_HAP_MAX),
nr_features, "HVM Hap Max", detail);
decode_featureset(xc_get_static_cpu_featuremask(XC_FEATUREMASK_HVM_HAP_DEF),
nr_features, "HVM Hap Default", detail);
printf("\nDynamic sets:\n");
for ( i = 0; i < ARRAY_SIZE(featuresets); ++i )
{
if ( get_featureset(xch, i) )
{
if ( errno == EOPNOTSUPP )
{
printf("%s featureset not supported by Xen\n",
featuresets[i].name);
continue;
}
err(1, "xc_get_featureset()");
}
decode_featureset(featuresets[i].fs, featuresets[i].len,
featuresets[i].name, detail);
}
for ( i = 0; i < ARRAY_SIZE(featuresets); ++i )
free(featuresets[i].fs);
}
static void print_policy(const char *name,
xen_cpuid_leaf_t *leaves, uint32_t nr_leaves,
xen_msr_entry_t *msrs, uint32_t nr_msrs)
{
unsigned int l;
printf("%s policy: %u leaves, %u MSRs\n", name, nr_leaves, nr_msrs);
printf(" CPUID:\n");
printf(" %-8s %-8s -> %-8s %-8s %-8s %-8s\n",
"leaf", "subleaf", "eax", "ebx", "ecx", "edx");
for ( l = 0; l < nr_leaves; ++l )
{
/* Skip empty leaves. */
if ( !leaves[l].a && !leaves[l].b && !leaves[l].c && !leaves[l].d )
continue;
printf(" %08x:%08x -> %08x:%08x:%08x:%08x\n",
leaves[l].leaf, leaves[l].subleaf,
leaves[l].a, leaves[l].b, leaves[l].c, leaves[l].d);
}
printf(" MSRs:\n");
printf(" %-8s -> %-16s\n", "index", "value");
for ( l = 0; l < nr_msrs; ++l )
printf(" %08x -> %016"PRIx64"\n",
msrs[l].idx, msrs[l].val);
}
int main(int argc, char **argv)
{
enum { MODE_UNKNOWN, MODE_INFO, MODE_DETAIL, MODE_INTERPRET, MODE_POLICY }
mode = MODE_UNKNOWN;
int domid = -1;
nr_features = xc_get_cpu_featureset_size();
for ( ;; )
{
const char *tmp_optarg;
int option_index = 0, c;
static const struct option long_options[] =
{
{ "help", no_argument, NULL, 'h' },
{ "info", no_argument, NULL, 'i' },
{ "detail", no_argument, NULL, 'd' },
{ "verbose", no_argument, NULL, 'v' },
{ "policy", optional_argument, NULL, 'p' },
{ NULL, 0, NULL, 0 },
};
c = getopt_long(argc, argv, "hidvp::", long_options, &option_index);
if ( c == -1 )
break;
switch ( c )
{
default:
printf("Bad option '%c'\n", c);
/* Fallthough */
case 'h':
printf("Usage: %s [ info | detail | <featureset>* ]\n", argv[0]);
return 0;
case 'i':
mode = MODE_INFO;
break;
case 'p':
mode = MODE_POLICY;
tmp_optarg = optarg;
/* Make "--policy $DOMID" and "-p $DOMID" work. */
if ( !optarg && optind < argc &&
argv[optind] != NULL && argv[optind][0] != '\0' &&
argv[optind][0] != '-' )
tmp_optarg = argv[optind++];
if ( tmp_optarg )
{
char *endptr;
errno = 0;
domid = strtol(tmp_optarg, &endptr, 0);
if ( errno || endptr == tmp_optarg )
err(1, "strtol(%s,,)", tmp_optarg);
}
break;
case 'd':
case 'v':
mode = MODE_DETAIL;
break;
}
}
if ( mode == MODE_UNKNOWN )
{
if ( optind == argc )
mode = MODE_INFO;
else if ( optind < argc )
{
if ( !strcmp(argv[optind], "info") )
{
mode = MODE_INFO;
optind++;
}
else if ( !strcmp(argv[optind], "detail") )
{
mode = MODE_DETAIL;
optind++;
}
else
mode = MODE_INTERPRET;
}
else
mode = MODE_INTERPRET;
}
if ( mode == MODE_POLICY )
{
static const char *const sys_policies[] = {
[ XEN_SYSCTL_cpu_policy_raw ] = "Raw",
[ XEN_SYSCTL_cpu_policy_host ] = "Host",
[ XEN_SYSCTL_cpu_policy_pv_max ] = "PV Max",
[ XEN_SYSCTL_cpu_policy_hvm_max ] = "HVM Max",
[ XEN_SYSCTL_cpu_policy_pv_default ] = "PV Default",
[ XEN_SYSCTL_cpu_policy_hvm_default ] = "HVM Default",
};
xen_cpuid_leaf_t *leaves;
xen_msr_entry_t *msrs;
uint32_t i, max_leaves, max_msrs;
xc_interface *xch = xc_interface_open(0, 0, 0);
xc_cpu_policy_t *policy = xc_cpu_policy_init();
if ( !xch )
err(1, "xc_interface_open");
if ( !policy )
err(1, "xc_cpu_policy_init");
if ( xc_cpu_policy_get_size(xch, &max_leaves, &max_msrs) )
err(1, "xc_get_cpu_policy_size(...)");
if ( domid == -1 )
printf("Xen reports there are maximum %u leaves and %u MSRs\n",
max_leaves, max_msrs);
leaves = calloc(max_leaves, sizeof(xen_cpuid_leaf_t));
if ( !leaves )
err(1, "calloc(max_leaves)");
msrs = calloc(max_msrs, sizeof(xen_msr_entry_t));
if ( !msrs )
err(1, "calloc(max_msrs)");
if ( domid != -1 )
{
char name[20];
uint32_t nr_leaves = max_leaves;
uint32_t nr_msrs = max_msrs;
if ( xc_cpu_policy_get_domain(xch, domid, policy) )
err(1, "xc_cpu_policy_get_domain(, %d, )", domid);
if ( xc_cpu_policy_serialise(xch, policy, leaves, &nr_leaves,
msrs, &nr_msrs) )
err(1, "xc_cpu_policy_serialise");
snprintf(name, sizeof(name), "Domain %d", domid);
print_policy(name, leaves, nr_leaves, msrs, nr_msrs);
}
else
{
/* Get system policies */
for ( i = 0; i < ARRAY_SIZE(sys_policies); ++i )
{
uint32_t nr_leaves = max_leaves;
uint32_t nr_msrs = max_msrs;
if ( xc_cpu_policy_get_system(xch, i, policy) )
{
if ( errno == EOPNOTSUPP )
{
printf("%s policy not supported by Xen\n",
sys_policies[i]);
continue;
}
err(1, "xc_cpu_policy_get_system(, %s, )", sys_policies[i]);
}
if ( xc_cpu_policy_serialise(xch, policy, leaves, &nr_leaves,
msrs, &nr_msrs) )
err(1, "xc_cpu_policy_serialise");
print_policy(sys_policies[i], leaves, nr_leaves,
msrs, nr_msrs);
}
}
xc_cpu_policy_destroy(policy);
free(leaves);
free(msrs);
xc_interface_close(xch);
}
else if ( mode == MODE_INFO || mode == MODE_DETAIL )
{
xc_interface *xch = xc_interface_open(0, 0, 0);
if ( !xch )
err(1, "xc_interface_open");
if ( xc_get_cpu_featureset(xch, 0, &nr_features, NULL) )
err(1, "xc_get_featureset(, NULL)");
dump_info(xch, mode == MODE_DETAIL);
xc_interface_close(xch);
}
else
{
uint32_t fs[nr_features + 1];
while ( optind < argc )
{
char *ptr = argv[optind++];
unsigned int i = 0;
int offset;
memset(fs, 0, sizeof(fs));
while ( sscanf(ptr, "%x%n", &fs[i], &offset) == 1 )
{
i++;
ptr += offset;
if ( i == nr_features )
break;
if ( *ptr == ':' )
{
ptr++; continue;
}
break;
}
if ( !i )
{
fprintf(stderr, "'%s' unrecognized - skipping\n", ptr);
continue;
}
if ( *ptr )
fprintf(stderr, "'%s' unrecognized - ignoring\n", ptr);
decode_featureset(fs, i, "Raw", true);
}
}
return 0;
}
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/