| // 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; |
| } |