956 lines
22 KiB
C
956 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#define _GNU_SOURCE
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <setjmp.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <x86intrin.h>
|
|
|
|
#include <sys/auxv.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/shm.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include "../kselftest.h" /* For __cpuid_count() */
|
|
|
|
#ifndef __x86_64__
|
|
# error This test is 64-bit only
|
|
#endif
|
|
|
|
#define XSAVE_HDR_OFFSET 512
|
|
#define XSAVE_HDR_SIZE 64
|
|
|
|
struct xsave_buffer {
|
|
union {
|
|
struct {
|
|
char legacy[XSAVE_HDR_OFFSET];
|
|
char header[XSAVE_HDR_SIZE];
|
|
char extended[0];
|
|
};
|
|
char bytes[0];
|
|
};
|
|
};
|
|
|
|
static inline uint64_t xgetbv(uint32_t index)
|
|
{
|
|
uint32_t eax, edx;
|
|
|
|
asm volatile("xgetbv;"
|
|
: "=a" (eax), "=d" (edx)
|
|
: "c" (index));
|
|
return eax + ((uint64_t)edx << 32);
|
|
}
|
|
|
|
static inline void xsave(struct xsave_buffer *xbuf, uint64_t rfbm)
|
|
{
|
|
uint32_t rfbm_lo = rfbm;
|
|
uint32_t rfbm_hi = rfbm >> 32;
|
|
|
|
asm volatile("xsave (%%rdi)"
|
|
: : "D" (xbuf), "a" (rfbm_lo), "d" (rfbm_hi)
|
|
: "memory");
|
|
}
|
|
|
|
static inline void xrstor(struct xsave_buffer *xbuf, uint64_t rfbm)
|
|
{
|
|
uint32_t rfbm_lo = rfbm;
|
|
uint32_t rfbm_hi = rfbm >> 32;
|
|
|
|
asm volatile("xrstor (%%rdi)"
|
|
: : "D" (xbuf), "a" (rfbm_lo), "d" (rfbm_hi));
|
|
}
|
|
|
|
/* err() exits and will not return */
|
|
#define fatal_error(msg, ...) err(1, "[FAIL]\t" msg, ##__VA_ARGS__)
|
|
|
|
static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
|
|
int flags)
|
|
{
|
|
struct sigaction sa;
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_sigaction = handler;
|
|
sa.sa_flags = SA_SIGINFO | flags;
|
|
sigemptyset(&sa.sa_mask);
|
|
if (sigaction(sig, &sa, 0))
|
|
fatal_error("sigaction");
|
|
}
|
|
|
|
static void clearhandler(int sig)
|
|
{
|
|
struct sigaction sa;
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = SIG_DFL;
|
|
sigemptyset(&sa.sa_mask);
|
|
if (sigaction(sig, &sa, 0))
|
|
fatal_error("sigaction");
|
|
}
|
|
|
|
#define XFEATURE_XTILECFG 17
|
|
#define XFEATURE_XTILEDATA 18
|
|
#define XFEATURE_MASK_XTILECFG (1 << XFEATURE_XTILECFG)
|
|
#define XFEATURE_MASK_XTILEDATA (1 << XFEATURE_XTILEDATA)
|
|
#define XFEATURE_MASK_XTILE (XFEATURE_MASK_XTILECFG | XFEATURE_MASK_XTILEDATA)
|
|
|
|
#define CPUID_LEAF1_ECX_XSAVE_MASK (1 << 26)
|
|
#define CPUID_LEAF1_ECX_OSXSAVE_MASK (1 << 27)
|
|
static inline void check_cpuid_xsave(void)
|
|
{
|
|
uint32_t eax, ebx, ecx, edx;
|
|
|
|
/*
|
|
* CPUID.1:ECX.XSAVE[bit 26] enumerates general
|
|
* support for the XSAVE feature set, including
|
|
* XGETBV.
|
|
*/
|
|
__cpuid_count(1, 0, eax, ebx, ecx, edx);
|
|
if (!(ecx & CPUID_LEAF1_ECX_XSAVE_MASK))
|
|
fatal_error("cpuid: no CPU xsave support");
|
|
if (!(ecx & CPUID_LEAF1_ECX_OSXSAVE_MASK))
|
|
fatal_error("cpuid: no OS xsave support");
|
|
}
|
|
|
|
static uint32_t xbuf_size;
|
|
|
|
static struct {
|
|
uint32_t xbuf_offset;
|
|
uint32_t size;
|
|
} xtiledata;
|
|
|
|
#define CPUID_LEAF_XSTATE 0xd
|
|
#define CPUID_SUBLEAF_XSTATE_USER 0x0
|
|
#define TILE_CPUID 0x1d
|
|
#define TILE_PALETTE_ID 0x1
|
|
|
|
static void check_cpuid_xtiledata(void)
|
|
{
|
|
uint32_t eax, ebx, ecx, edx;
|
|
|
|
__cpuid_count(CPUID_LEAF_XSTATE, CPUID_SUBLEAF_XSTATE_USER,
|
|
eax, ebx, ecx, edx);
|
|
|
|
/*
|
|
* EBX enumerates the size (in bytes) required by the XSAVE
|
|
* instruction for an XSAVE area containing all the user state
|
|
* components corresponding to bits currently set in XCR0.
|
|
*
|
|
* Stash that off so it can be used to allocate buffers later.
|
|
*/
|
|
xbuf_size = ebx;
|
|
|
|
__cpuid_count(CPUID_LEAF_XSTATE, XFEATURE_XTILEDATA,
|
|
eax, ebx, ecx, edx);
|
|
/*
|
|
* eax: XTILEDATA state component size
|
|
* ebx: XTILEDATA state component offset in user buffer
|
|
*/
|
|
if (!eax || !ebx)
|
|
fatal_error("xstate cpuid: invalid tile data size/offset: %d/%d",
|
|
eax, ebx);
|
|
|
|
xtiledata.size = eax;
|
|
xtiledata.xbuf_offset = ebx;
|
|
}
|
|
|
|
/* The helpers for managing XSAVE buffer and tile states: */
|
|
|
|
struct xsave_buffer *alloc_xbuf(void)
|
|
{
|
|
struct xsave_buffer *xbuf;
|
|
|
|
/* XSAVE buffer should be 64B-aligned. */
|
|
xbuf = aligned_alloc(64, xbuf_size);
|
|
if (!xbuf)
|
|
fatal_error("aligned_alloc()");
|
|
return xbuf;
|
|
}
|
|
|
|
static inline void clear_xstate_header(struct xsave_buffer *buffer)
|
|
{
|
|
memset(&buffer->header, 0, sizeof(buffer->header));
|
|
}
|
|
|
|
static inline uint64_t get_xstatebv(struct xsave_buffer *buffer)
|
|
{
|
|
/* XSTATE_BV is at the beginning of the header: */
|
|
return *(uint64_t *)&buffer->header;
|
|
}
|
|
|
|
static inline void set_xstatebv(struct xsave_buffer *buffer, uint64_t bv)
|
|
{
|
|
/* XSTATE_BV is at the beginning of the header: */
|
|
*(uint64_t *)(&buffer->header) = bv;
|
|
}
|
|
|
|
static void set_rand_tiledata(struct xsave_buffer *xbuf)
|
|
{
|
|
int *ptr = (int *)&xbuf->bytes[xtiledata.xbuf_offset];
|
|
int data;
|
|
int i;
|
|
|
|
/*
|
|
* Ensure that 'data' is never 0. This ensures that
|
|
* the registers are never in their initial configuration
|
|
* and thus never tracked as being in the init state.
|
|
*/
|
|
data = rand() | 1;
|
|
|
|
for (i = 0; i < xtiledata.size / sizeof(int); i++, ptr++)
|
|
*ptr = data;
|
|
}
|
|
|
|
struct xsave_buffer *stashed_xsave;
|
|
|
|
static void init_stashed_xsave(void)
|
|
{
|
|
stashed_xsave = alloc_xbuf();
|
|
if (!stashed_xsave)
|
|
fatal_error("failed to allocate stashed_xsave\n");
|
|
clear_xstate_header(stashed_xsave);
|
|
}
|
|
|
|
static void free_stashed_xsave(void)
|
|
{
|
|
free(stashed_xsave);
|
|
}
|
|
|
|
/* See 'struct _fpx_sw_bytes' at sigcontext.h */
|
|
#define SW_BYTES_OFFSET 464
|
|
/* N.B. The struct's field name varies so read from the offset. */
|
|
#define SW_BYTES_BV_OFFSET (SW_BYTES_OFFSET + 8)
|
|
|
|
static inline struct _fpx_sw_bytes *get_fpx_sw_bytes(void *buffer)
|
|
{
|
|
return (struct _fpx_sw_bytes *)(buffer + SW_BYTES_OFFSET);
|
|
}
|
|
|
|
static inline uint64_t get_fpx_sw_bytes_features(void *buffer)
|
|
{
|
|
return *(uint64_t *)(buffer + SW_BYTES_BV_OFFSET);
|
|
}
|
|
|
|
/* Work around printf() being unsafe in signals: */
|
|
#define SIGNAL_BUF_LEN 1000
|
|
char signal_message_buffer[SIGNAL_BUF_LEN];
|
|
void sig_print(char *msg)
|
|
{
|
|
int left = SIGNAL_BUF_LEN - strlen(signal_message_buffer) - 1;
|
|
|
|
strncat(signal_message_buffer, msg, left);
|
|
}
|
|
|
|
static volatile bool noperm_signaled;
|
|
static int noperm_errs;
|
|
/*
|
|
* Signal handler for when AMX is used but
|
|
* permission has not been obtained.
|
|
*/
|
|
static void handle_noperm(int sig, siginfo_t *si, void *ctx_void)
|
|
{
|
|
ucontext_t *ctx = (ucontext_t *)ctx_void;
|
|
void *xbuf = ctx->uc_mcontext.fpregs;
|
|
struct _fpx_sw_bytes *sw_bytes;
|
|
uint64_t features;
|
|
|
|
/* Reset the signal message buffer: */
|
|
signal_message_buffer[0] = '\0';
|
|
sig_print("\tAt SIGILL handler,\n");
|
|
|
|
if (si->si_code != ILL_ILLOPC) {
|
|
noperm_errs++;
|
|
sig_print("[FAIL]\tInvalid signal code.\n");
|
|
} else {
|
|
sig_print("[OK]\tValid signal code (ILL_ILLOPC).\n");
|
|
}
|
|
|
|
sw_bytes = get_fpx_sw_bytes(xbuf);
|
|
/*
|
|
* Without permission, the signal XSAVE buffer should not
|
|
* have room for AMX register state (aka. xtiledata).
|
|
* Check that the size does not overlap with where xtiledata
|
|
* will reside.
|
|
*
|
|
* This also implies that no state components *PAST*
|
|
* XTILEDATA (features >=19) can be present in the buffer.
|
|
*/
|
|
if (sw_bytes->xstate_size <= xtiledata.xbuf_offset) {
|
|
sig_print("[OK]\tValid xstate size\n");
|
|
} else {
|
|
noperm_errs++;
|
|
sig_print("[FAIL]\tInvalid xstate size\n");
|
|
}
|
|
|
|
features = get_fpx_sw_bytes_features(xbuf);
|
|
/*
|
|
* Without permission, the XTILEDATA feature
|
|
* bit should not be set.
|
|
*/
|
|
if ((features & XFEATURE_MASK_XTILEDATA) == 0) {
|
|
sig_print("[OK]\tValid xstate mask\n");
|
|
} else {
|
|
noperm_errs++;
|
|
sig_print("[FAIL]\tInvalid xstate mask\n");
|
|
}
|
|
|
|
noperm_signaled = true;
|
|
ctx->uc_mcontext.gregs[REG_RIP] += 3; /* Skip the faulting XRSTOR */
|
|
}
|
|
|
|
/* Return true if XRSTOR is successful; otherwise, false. */
|
|
static inline bool xrstor_safe(struct xsave_buffer *xbuf, uint64_t mask)
|
|
{
|
|
noperm_signaled = false;
|
|
xrstor(xbuf, mask);
|
|
|
|
/* Print any messages produced by the signal code: */
|
|
printf("%s", signal_message_buffer);
|
|
/*
|
|
* Reset the buffer to make sure any future printing
|
|
* only outputs new messages:
|
|
*/
|
|
signal_message_buffer[0] = '\0';
|
|
|
|
if (noperm_errs)
|
|
fatal_error("saw %d errors in noperm signal handler\n", noperm_errs);
|
|
|
|
return !noperm_signaled;
|
|
}
|
|
|
|
/*
|
|
* Use XRSTOR to populate the XTILEDATA registers with
|
|
* random data.
|
|
*
|
|
* Return true if successful; otherwise, false.
|
|
*/
|
|
static inline bool load_rand_tiledata(struct xsave_buffer *xbuf)
|
|
{
|
|
clear_xstate_header(xbuf);
|
|
set_xstatebv(xbuf, XFEATURE_MASK_XTILEDATA);
|
|
set_rand_tiledata(xbuf);
|
|
return xrstor_safe(xbuf, XFEATURE_MASK_XTILEDATA);
|
|
}
|
|
|
|
/* Return XTILEDATA to its initial configuration. */
|
|
static inline void init_xtiledata(void)
|
|
{
|
|
clear_xstate_header(stashed_xsave);
|
|
xrstor_safe(stashed_xsave, XFEATURE_MASK_XTILEDATA);
|
|
}
|
|
|
|
enum expected_result { FAIL_EXPECTED, SUCCESS_EXPECTED };
|
|
|
|
/* arch_prctl() and sigaltstack() test */
|
|
|
|
#define ARCH_GET_XCOMP_PERM 0x1022
|
|
#define ARCH_REQ_XCOMP_PERM 0x1023
|
|
|
|
static void req_xtiledata_perm(void)
|
|
{
|
|
syscall(SYS_arch_prctl, ARCH_REQ_XCOMP_PERM, XFEATURE_XTILEDATA);
|
|
}
|
|
|
|
static void validate_req_xcomp_perm(enum expected_result exp)
|
|
{
|
|
unsigned long bitmask, expected_bitmask;
|
|
long rc;
|
|
|
|
rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_PERM, &bitmask);
|
|
if (rc) {
|
|
fatal_error("prctl(ARCH_GET_XCOMP_PERM) error: %ld", rc);
|
|
} else if (!(bitmask & XFEATURE_MASK_XTILECFG)) {
|
|
fatal_error("ARCH_GET_XCOMP_PERM returns XFEATURE_XTILECFG off.");
|
|
}
|
|
|
|
rc = syscall(SYS_arch_prctl, ARCH_REQ_XCOMP_PERM, XFEATURE_XTILEDATA);
|
|
if (exp == FAIL_EXPECTED) {
|
|
if (rc) {
|
|
printf("[OK]\tARCH_REQ_XCOMP_PERM saw expected failure..\n");
|
|
return;
|
|
}
|
|
|
|
fatal_error("ARCH_REQ_XCOMP_PERM saw unexpected success.\n");
|
|
} else if (rc) {
|
|
fatal_error("ARCH_REQ_XCOMP_PERM saw unexpected failure.\n");
|
|
}
|
|
|
|
expected_bitmask = bitmask | XFEATURE_MASK_XTILEDATA;
|
|
|
|
rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_PERM, &bitmask);
|
|
if (rc) {
|
|
fatal_error("prctl(ARCH_GET_XCOMP_PERM) error: %ld", rc);
|
|
} else if (bitmask != expected_bitmask) {
|
|
fatal_error("ARCH_REQ_XCOMP_PERM set a wrong bitmask: %lx, expected: %lx.\n",
|
|
bitmask, expected_bitmask);
|
|
} else {
|
|
printf("\tARCH_REQ_XCOMP_PERM is successful.\n");
|
|
}
|
|
}
|
|
|
|
static void validate_xcomp_perm(enum expected_result exp)
|
|
{
|
|
bool load_success = load_rand_tiledata(stashed_xsave);
|
|
|
|
if (exp == FAIL_EXPECTED) {
|
|
if (load_success) {
|
|
noperm_errs++;
|
|
printf("[FAIL]\tLoad tiledata succeeded.\n");
|
|
} else {
|
|
printf("[OK]\tLoad tiledata failed.\n");
|
|
}
|
|
} else if (exp == SUCCESS_EXPECTED) {
|
|
if (load_success) {
|
|
printf("[OK]\tLoad tiledata succeeded.\n");
|
|
} else {
|
|
noperm_errs++;
|
|
printf("[FAIL]\tLoad tiledata failed.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef AT_MINSIGSTKSZ
|
|
# define AT_MINSIGSTKSZ 51
|
|
#endif
|
|
|
|
static void *alloc_altstack(unsigned int size)
|
|
{
|
|
void *altstack;
|
|
|
|
altstack = mmap(NULL, size, PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
|
|
|
|
if (altstack == MAP_FAILED)
|
|
fatal_error("mmap() for altstack");
|
|
|
|
return altstack;
|
|
}
|
|
|
|
static void setup_altstack(void *addr, unsigned long size, enum expected_result exp)
|
|
{
|
|
stack_t ss;
|
|
int rc;
|
|
|
|
memset(&ss, 0, sizeof(ss));
|
|
ss.ss_size = size;
|
|
ss.ss_sp = addr;
|
|
|
|
rc = sigaltstack(&ss, NULL);
|
|
|
|
if (exp == FAIL_EXPECTED) {
|
|
if (rc) {
|
|
printf("[OK]\tsigaltstack() failed.\n");
|
|
} else {
|
|
fatal_error("sigaltstack() succeeded unexpectedly.\n");
|
|
}
|
|
} else if (rc) {
|
|
fatal_error("sigaltstack()");
|
|
}
|
|
}
|
|
|
|
static void test_dynamic_sigaltstack(void)
|
|
{
|
|
unsigned int small_size, enough_size;
|
|
unsigned long minsigstksz;
|
|
void *altstack;
|
|
|
|
minsigstksz = getauxval(AT_MINSIGSTKSZ);
|
|
printf("\tAT_MINSIGSTKSZ = %lu\n", minsigstksz);
|
|
/*
|
|
* getauxval() itself can return 0 for failure or
|
|
* success. But, in this case, AT_MINSIGSTKSZ
|
|
* will always return a >=0 value if implemented.
|
|
* Just check for 0.
|
|
*/
|
|
if (minsigstksz == 0) {
|
|
printf("no support for AT_MINSIGSTKSZ, skipping sigaltstack tests\n");
|
|
return;
|
|
}
|
|
|
|
enough_size = minsigstksz * 2;
|
|
|
|
altstack = alloc_altstack(enough_size);
|
|
printf("\tAllocate memory for altstack (%u bytes).\n", enough_size);
|
|
|
|
/*
|
|
* Try setup_altstack() with a size which can not fit
|
|
* XTILEDATA. ARCH_REQ_XCOMP_PERM should fail.
|
|
*/
|
|
small_size = minsigstksz - xtiledata.size;
|
|
printf("\tAfter sigaltstack() with small size (%u bytes).\n", small_size);
|
|
setup_altstack(altstack, small_size, SUCCESS_EXPECTED);
|
|
validate_req_xcomp_perm(FAIL_EXPECTED);
|
|
|
|
/*
|
|
* Try setup_altstack() with a size derived from
|
|
* AT_MINSIGSTKSZ. It should be more than large enough
|
|
* and thus ARCH_REQ_XCOMP_PERM should succeed.
|
|
*/
|
|
printf("\tAfter sigaltstack() with enough size (%u bytes).\n", enough_size);
|
|
setup_altstack(altstack, enough_size, SUCCESS_EXPECTED);
|
|
validate_req_xcomp_perm(SUCCESS_EXPECTED);
|
|
|
|
/*
|
|
* Try to coerce setup_altstack() to again accept a
|
|
* too-small altstack. This ensures that big-enough
|
|
* sigaltstacks can not shrink to a too-small value
|
|
* once XTILEDATA permission is established.
|
|
*/
|
|
printf("\tThen, sigaltstack() with small size (%u bytes).\n", small_size);
|
|
setup_altstack(altstack, small_size, FAIL_EXPECTED);
|
|
}
|
|
|
|
static void test_dynamic_state(void)
|
|
{
|
|
pid_t parent, child, grandchild;
|
|
|
|
parent = fork();
|
|
if (parent < 0) {
|
|
/* fork() failed */
|
|
fatal_error("fork");
|
|
} else if (parent > 0) {
|
|
int status;
|
|
/* fork() succeeded. Now in the parent. */
|
|
|
|
wait(&status);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status))
|
|
fatal_error("arch_prctl test parent exit");
|
|
return;
|
|
}
|
|
/* fork() succeeded. Now in the child . */
|
|
|
|
printf("[RUN]\tCheck ARCH_REQ_XCOMP_PERM around process fork() and sigaltack() test.\n");
|
|
|
|
printf("\tFork a child.\n");
|
|
child = fork();
|
|
if (child < 0) {
|
|
fatal_error("fork");
|
|
} else if (child > 0) {
|
|
int status;
|
|
|
|
wait(&status);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status))
|
|
fatal_error("arch_prctl test child exit");
|
|
_exit(0);
|
|
}
|
|
|
|
/*
|
|
* The permission request should fail without an
|
|
* XTILEDATA-compatible signal stack
|
|
*/
|
|
printf("\tTest XCOMP_PERM at child.\n");
|
|
validate_xcomp_perm(FAIL_EXPECTED);
|
|
|
|
/*
|
|
* Set up an XTILEDATA-compatible signal stack and
|
|
* also obtain permission to populate XTILEDATA.
|
|
*/
|
|
printf("\tTest dynamic sigaltstack at child:\n");
|
|
test_dynamic_sigaltstack();
|
|
|
|
/* Ensure that XTILEDATA can be populated. */
|
|
printf("\tTest XCOMP_PERM again at child.\n");
|
|
validate_xcomp_perm(SUCCESS_EXPECTED);
|
|
|
|
printf("\tFork a grandchild.\n");
|
|
grandchild = fork();
|
|
if (grandchild < 0) {
|
|
/* fork() failed */
|
|
fatal_error("fork");
|
|
} else if (!grandchild) {
|
|
/* fork() succeeded. Now in the (grand)child. */
|
|
printf("\tTest XCOMP_PERM at grandchild.\n");
|
|
|
|
/*
|
|
* Ensure that the grandchild inherited
|
|
* permission and a compatible sigaltstack:
|
|
*/
|
|
validate_xcomp_perm(SUCCESS_EXPECTED);
|
|
} else {
|
|
int status;
|
|
/* fork() succeeded. Now in the parent. */
|
|
|
|
wait(&status);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status))
|
|
fatal_error("fork test grandchild");
|
|
}
|
|
|
|
_exit(0);
|
|
}
|
|
|
|
static inline int __compare_tiledata_state(struct xsave_buffer *xbuf1, struct xsave_buffer *xbuf2)
|
|
{
|
|
return memcmp(&xbuf1->bytes[xtiledata.xbuf_offset],
|
|
&xbuf2->bytes[xtiledata.xbuf_offset],
|
|
xtiledata.size);
|
|
}
|
|
|
|
/*
|
|
* Save current register state and compare it to @xbuf1.'
|
|
*
|
|
* Returns false if @xbuf1 matches the registers.
|
|
* Returns true if @xbuf1 differs from the registers.
|
|
*/
|
|
static inline bool __validate_tiledata_regs(struct xsave_buffer *xbuf1)
|
|
{
|
|
struct xsave_buffer *xbuf2;
|
|
int ret;
|
|
|
|
xbuf2 = alloc_xbuf();
|
|
if (!xbuf2)
|
|
fatal_error("failed to allocate XSAVE buffer\n");
|
|
|
|
xsave(xbuf2, XFEATURE_MASK_XTILEDATA);
|
|
ret = __compare_tiledata_state(xbuf1, xbuf2);
|
|
|
|
free(xbuf2);
|
|
|
|
if (ret == 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static inline void validate_tiledata_regs_same(struct xsave_buffer *xbuf)
|
|
{
|
|
int ret = __validate_tiledata_regs(xbuf);
|
|
|
|
if (ret != 0)
|
|
fatal_error("TILEDATA registers changed");
|
|
}
|
|
|
|
static inline void validate_tiledata_regs_changed(struct xsave_buffer *xbuf)
|
|
{
|
|
int ret = __validate_tiledata_regs(xbuf);
|
|
|
|
if (ret == 0)
|
|
fatal_error("TILEDATA registers did not change");
|
|
}
|
|
|
|
/* tiledata inheritance test */
|
|
|
|
static void test_fork(void)
|
|
{
|
|
pid_t child, grandchild;
|
|
|
|
child = fork();
|
|
if (child < 0) {
|
|
/* fork() failed */
|
|
fatal_error("fork");
|
|
} else if (child > 0) {
|
|
/* fork() succeeded. Now in the parent. */
|
|
int status;
|
|
|
|
wait(&status);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status))
|
|
fatal_error("fork test child");
|
|
return;
|
|
}
|
|
/* fork() succeeded. Now in the child. */
|
|
printf("[RUN]\tCheck tile data inheritance.\n\tBefore fork(), load tiledata\n");
|
|
|
|
load_rand_tiledata(stashed_xsave);
|
|
|
|
grandchild = fork();
|
|
if (grandchild < 0) {
|
|
/* fork() failed */
|
|
fatal_error("fork");
|
|
} else if (grandchild > 0) {
|
|
/* fork() succeeded. Still in the first child. */
|
|
int status;
|
|
|
|
wait(&status);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status))
|
|
fatal_error("fork test grand child");
|
|
_exit(0);
|
|
}
|
|
/* fork() succeeded. Now in the (grand)child. */
|
|
|
|
/*
|
|
* TILEDATA registers are not preserved across fork().
|
|
* Ensure that their value has changed:
|
|
*/
|
|
validate_tiledata_regs_changed(stashed_xsave);
|
|
|
|
_exit(0);
|
|
}
|
|
|
|
/* Context switching test */
|
|
|
|
static struct _ctxtswtest_cfg {
|
|
unsigned int iterations;
|
|
unsigned int num_threads;
|
|
} ctxtswtest_config;
|
|
|
|
struct futex_info {
|
|
pthread_t thread;
|
|
int nr;
|
|
pthread_mutex_t mutex;
|
|
struct futex_info *next;
|
|
};
|
|
|
|
static void *check_tiledata(void *info)
|
|
{
|
|
struct futex_info *finfo = (struct futex_info *)info;
|
|
struct xsave_buffer *xbuf;
|
|
int i;
|
|
|
|
xbuf = alloc_xbuf();
|
|
if (!xbuf)
|
|
fatal_error("unable to allocate XSAVE buffer");
|
|
|
|
/*
|
|
* Load random data into 'xbuf' and then restore
|
|
* it to the tile registers themselves.
|
|
*/
|
|
load_rand_tiledata(xbuf);
|
|
for (i = 0; i < ctxtswtest_config.iterations; i++) {
|
|
pthread_mutex_lock(&finfo->mutex);
|
|
|
|
/*
|
|
* Ensure the register values have not
|
|
* diverged from those recorded in 'xbuf'.
|
|
*/
|
|
validate_tiledata_regs_same(xbuf);
|
|
|
|
/* Load new, random values into xbuf and registers */
|
|
load_rand_tiledata(xbuf);
|
|
|
|
/*
|
|
* The last thread's last unlock will be for
|
|
* thread 0's mutex. However, thread 0 will
|
|
* have already exited the loop and the mutex
|
|
* will already be unlocked.
|
|
*
|
|
* Because this is not an ERRORCHECK mutex,
|
|
* that inconsistency will be silently ignored.
|
|
*/
|
|
pthread_mutex_unlock(&finfo->next->mutex);
|
|
}
|
|
|
|
free(xbuf);
|
|
/*
|
|
* Return this thread's finfo, which is
|
|
* a unique value for this thread.
|
|
*/
|
|
return finfo;
|
|
}
|
|
|
|
static int create_threads(int num, struct futex_info *finfo)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
int next_nr;
|
|
|
|
finfo[i].nr = i;
|
|
/*
|
|
* Thread 'i' will wait on this mutex to
|
|
* be unlocked. Lock it immediately after
|
|
* initialization:
|
|
*/
|
|
pthread_mutex_init(&finfo[i].mutex, NULL);
|
|
pthread_mutex_lock(&finfo[i].mutex);
|
|
|
|
next_nr = (i + 1) % num;
|
|
finfo[i].next = &finfo[next_nr];
|
|
|
|
if (pthread_create(&finfo[i].thread, NULL, check_tiledata, &finfo[i]))
|
|
fatal_error("pthread_create()");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void affinitize_cpu0(void)
|
|
{
|
|
cpu_set_t cpuset;
|
|
|
|
CPU_ZERO(&cpuset);
|
|
CPU_SET(0, &cpuset);
|
|
|
|
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
|
|
fatal_error("sched_setaffinity to CPU 0");
|
|
}
|
|
|
|
static void test_context_switch(void)
|
|
{
|
|
struct futex_info *finfo;
|
|
int i;
|
|
|
|
/* Affinitize to one CPU to force context switches */
|
|
affinitize_cpu0();
|
|
|
|
req_xtiledata_perm();
|
|
|
|
printf("[RUN]\tCheck tiledata context switches, %d iterations, %d threads.\n",
|
|
ctxtswtest_config.iterations,
|
|
ctxtswtest_config.num_threads);
|
|
|
|
|
|
finfo = malloc(sizeof(*finfo) * ctxtswtest_config.num_threads);
|
|
if (!finfo)
|
|
fatal_error("malloc()");
|
|
|
|
create_threads(ctxtswtest_config.num_threads, finfo);
|
|
|
|
/*
|
|
* This thread wakes up thread 0
|
|
* Thread 0 will wake up 1
|
|
* Thread 1 will wake up 2
|
|
* ...
|
|
* the last thread will wake up 0
|
|
*
|
|
* ... this will repeat for the configured
|
|
* number of iterations.
|
|
*/
|
|
pthread_mutex_unlock(&finfo[0].mutex);
|
|
|
|
/* Wait for all the threads to finish: */
|
|
for (i = 0; i < ctxtswtest_config.num_threads; i++) {
|
|
void *thread_retval;
|
|
int rc;
|
|
|
|
rc = pthread_join(finfo[i].thread, &thread_retval);
|
|
|
|
if (rc)
|
|
fatal_error("pthread_join() failed for thread %d err: %d\n",
|
|
i, rc);
|
|
|
|
if (thread_retval != &finfo[i])
|
|
fatal_error("unexpected thread retval for thread %d: %p\n",
|
|
i, thread_retval);
|
|
|
|
}
|
|
|
|
printf("[OK]\tNo incorrect case was found.\n");
|
|
|
|
free(finfo);
|
|
}
|
|
|
|
/* Ptrace test */
|
|
|
|
/*
|
|
* Make sure the ptracee has the expanded kernel buffer on the first
|
|
* use. Then, initialize the state before performing the state
|
|
* injection from the ptracer.
|
|
*/
|
|
static inline void ptracee_firstuse_tiledata(void)
|
|
{
|
|
load_rand_tiledata(stashed_xsave);
|
|
init_xtiledata();
|
|
}
|
|
|
|
/*
|
|
* Ptracer injects the randomized tile data state. It also reads
|
|
* before and after that, which will execute the kernel's state copy
|
|
* functions. So, the tester is advised to double-check any emitted
|
|
* kernel messages.
|
|
*/
|
|
static void ptracer_inject_tiledata(pid_t target)
|
|
{
|
|
struct xsave_buffer *xbuf;
|
|
struct iovec iov;
|
|
|
|
xbuf = alloc_xbuf();
|
|
if (!xbuf)
|
|
fatal_error("unable to allocate XSAVE buffer");
|
|
|
|
printf("\tRead the init'ed tiledata via ptrace().\n");
|
|
|
|
iov.iov_base = xbuf;
|
|
iov.iov_len = xbuf_size;
|
|
|
|
memset(stashed_xsave, 0, xbuf_size);
|
|
|
|
if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
|
|
fatal_error("PTRACE_GETREGSET");
|
|
|
|
if (!__compare_tiledata_state(stashed_xsave, xbuf))
|
|
printf("[OK]\tThe init'ed tiledata was read from ptracee.\n");
|
|
else
|
|
printf("[FAIL]\tThe init'ed tiledata was not read from ptracee.\n");
|
|
|
|
printf("\tInject tiledata via ptrace().\n");
|
|
|
|
load_rand_tiledata(xbuf);
|
|
|
|
memcpy(&stashed_xsave->bytes[xtiledata.xbuf_offset],
|
|
&xbuf->bytes[xtiledata.xbuf_offset],
|
|
xtiledata.size);
|
|
|
|
if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
|
|
fatal_error("PTRACE_SETREGSET");
|
|
|
|
if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
|
|
fatal_error("PTRACE_GETREGSET");
|
|
|
|
if (!__compare_tiledata_state(stashed_xsave, xbuf))
|
|
printf("[OK]\tTiledata was correctly written to ptracee.\n");
|
|
else
|
|
printf("[FAIL]\tTiledata was not correctly written to ptracee.\n");
|
|
}
|
|
|
|
static void test_ptrace(void)
|
|
{
|
|
pid_t child;
|
|
int status;
|
|
|
|
child = fork();
|
|
if (child < 0) {
|
|
err(1, "fork");
|
|
} else if (!child) {
|
|
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL))
|
|
err(1, "PTRACE_TRACEME");
|
|
|
|
ptracee_firstuse_tiledata();
|
|
|
|
raise(SIGTRAP);
|
|
_exit(0);
|
|
}
|
|
|
|
do {
|
|
wait(&status);
|
|
} while (WSTOPSIG(status) != SIGTRAP);
|
|
|
|
ptracer_inject_tiledata(child);
|
|
|
|
ptrace(PTRACE_DETACH, child, NULL, NULL);
|
|
wait(&status);
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status))
|
|
err(1, "ptrace test");
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
/* Check hardware availability at first */
|
|
check_cpuid_xsave();
|
|
check_cpuid_xtiledata();
|
|
|
|
init_stashed_xsave();
|
|
sethandler(SIGILL, handle_noperm, 0);
|
|
|
|
test_dynamic_state();
|
|
|
|
/* Request permission for the following tests */
|
|
req_xtiledata_perm();
|
|
|
|
test_fork();
|
|
|
|
ctxtswtest_config.iterations = 10;
|
|
ctxtswtest_config.num_threads = 5;
|
|
test_context_switch();
|
|
|
|
test_ptrace();
|
|
|
|
clearhandler(SIGILL);
|
|
free_stashed_xsave();
|
|
|
|
return 0;
|
|
}
|