#define _GNU_SOURCE
#include <err.h>
#include <errno.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/syscall.h>
#include <sys/wait.h>
#include "helpers.h"
#include "xstate.h"
#ifndef __x86_64__
# error This test is 64-bit only
#endif
#define fatal_error(msg, ...) err(1, "[FAIL]\t" msg, ##__VA_ARGS__)
#define XFEATURE_MASK_XTILECFG (1 << XFEATURE_XTILECFG)
#define XFEATURE_MASK_XTILEDATA (1 << XFEATURE_XTILEDATA)
#define XFEATURE_MASK_XTILE (XFEATURE_MASK_XTILECFG | XFEATURE_MASK_XTILEDATA)
struct xstate_info xtiledata;
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);
}
#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;
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;
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);
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);
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;
}
static inline bool xrstor_safe(struct xsave_buffer *xbuf, uint64_t mask)
{
noperm_signaled = false;
xrstor(xbuf, mask);
printf("%s", signal_message_buffer);
signal_message_buffer[0] = '\0';
if (noperm_errs)
fatal_error("saw %d errors in noperm signal handler\n", noperm_errs);
return !noperm_signaled;
}
static inline bool load_rand_tiledata(struct xsave_buffer *xbuf)
{
clear_xstate_header(xbuf);
set_xstatebv(xbuf, XFEATURE_MASK_XTILEDATA);
set_rand_data(&xtiledata, xbuf);
return xrstor_safe(xbuf, XFEATURE_MASK_XTILEDATA);
}
enum expected_result { FAIL_EXPECTED, SUCCESS_EXPECTED };
#define ARCH_GET_XCOMP_SUPP 0x1021
#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);
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);
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);
printf("\tAfter sigaltstack() with enough size (%u bytes).\n", enough_size);
setup_altstack(altstack, enough_size, SUCCESS_EXPECTED);
validate_req_xcomp_perm(SUCCESS_EXPECTED);
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) {
fatal_error("fork");
} else if (parent > 0) {
int status;
wait(&status);
if (!WIFEXITED(status) || WEXITSTATUS(status))
fatal_error("arch_prctl test parent exit");
return;
}
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);
}
printf("\tTest XCOMP_PERM at child.\n");
validate_xcomp_perm(FAIL_EXPECTED);
printf("\tTest dynamic sigaltstack at child:\n");
test_dynamic_sigaltstack();
printf("\tTest XCOMP_PERM again at child.\n");
validate_xcomp_perm(SUCCESS_EXPECTED);
printf("\tFork a grandchild.\n");
grandchild = fork();
if (grandchild < 0) {
fatal_error("fork");
} else if (!grandchild) {
printf("\tTest XCOMP_PERM at grandchild.\n");
validate_xcomp_perm(SUCCESS_EXPECTED);
} else {
int status;
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);
}
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_changed(struct xsave_buffer *xbuf)
{
int ret = __validate_tiledata_regs(xbuf);
if (ret == 0)
fatal_error("TILEDATA registers did not change");
}
static void test_fork(void)
{
pid_t child, grandchild;
child = fork();
if (child < 0) {
fatal_error("fork");
} else if (child > 0) {
int status;
wait(&status);
if (!WIFEXITED(status) || WEXITSTATUS(status))
fatal_error("fork test child");
return;
}
printf("[RUN]\tCheck tile data inheritance.\n\tBefore fork(), load tiledata\n");
load_rand_tiledata(stashed_xsave);
grandchild = fork();
if (grandchild < 0) {
fatal_error("fork");
} else if (grandchild > 0) {
int status;
wait(&status);
if (!WIFEXITED(status) || WEXITSTATUS(status))
fatal_error("fork test grand child");
_exit(0);
}
validate_tiledata_regs_changed(stashed_xsave);
_exit(0);
}
int main(void)
{
unsigned long features;
long rc;
rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_SUPP, &features);
if (rc || (features & XFEATURE_MASK_XTILE) != XFEATURE_MASK_XTILE) {
ksft_print_msg("no AMX support\n");
return KSFT_SKIP;
}
xtiledata = get_xstate_info(XFEATURE_XTILEDATA);
if (!xtiledata.size || !xtiledata.xbuf_offset) {
fatal_error("xstate cpuid: invalid tile data size/offset: %d/%d",
xtiledata.size, xtiledata.xbuf_offset);
}
init_stashed_xsave();
sethandler(SIGILL, handle_noperm, 0);
test_dynamic_state();
req_xtiledata_perm();
test_fork();
test_xstate(XFEATURE_XTILEDATA);
clearhandler(SIGILL);
free_stashed_xsave();
return 0;
}