Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
*_32
*_64
single_step_syscall
sysret_ss_attrs
syscall_nt
ptrace_syscall
test_mremap_vdso
check_initial_reg_state
sigreturn
ldt_gdt
iopl
mpx-mini-test
ioperm
test_vdso
@@ -0,0 +1,140 @@
# SPDX-License-Identifier: GPL-2.0
all:
include ../lib.mk
.PHONY: all all_32 all_64 warn_32bit_failure clean
UNAME_M := $(shell uname -m)
CAN_BUILD_I386 := $(shell ./check_cc.sh "$(CC)" trivial_32bit_program.c -m32)
CAN_BUILD_X86_64 := $(shell ./check_cc.sh "$(CC)" trivial_64bit_program.c)
CAN_BUILD_WITH_NOPIE := $(shell ./check_cc.sh "$(CC)" trivial_program.c -no-pie)
TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap_vdso \
check_initial_reg_state sigreturn iopl ioperm \
test_vsyscall mov_ss_trap sigtrap_loop \
syscall_arg_fault fsgsbase_restore sigaltstack
TARGETS_C_BOTHBITS += nx_stack
TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \
test_FCMOV test_FCOMI test_FISTTP \
vdso_restorer
TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
corrupt_xstate_header amx lam test_shadow_stack avx apx
# Some selftests require 32bit support enabled also on 64bit systems
TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) $(TARGETS_C_32BIT_NEEDED)
TARGETS_C_64BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_64BIT_ONLY)
ifeq ($(CAN_BUILD_I386)$(CAN_BUILD_X86_64),11)
TARGETS_C_64BIT_ALL += $(TARGETS_C_32BIT_NEEDED)
endif
BINARIES_32 := $(TARGETS_C_32BIT_ALL:%=%_32)
BINARIES_64 := $(TARGETS_C_64BIT_ALL:%=%_64)
BINARIES_32 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_32))
BINARIES_64 := $(patsubst %,$(OUTPUT)/%,$(BINARIES_64))
CFLAGS := -O2 -g -std=gnu99 -pthread -Wall $(KHDR_INCLUDES)
CFLAGS += -I $(top_srcdir)/tools/testing/selftests/
# call32_from_64 in thunks.S uses absolute addresses.
ifeq ($(CAN_BUILD_WITH_NOPIE),1)
CFLAGS += -no-pie
ifneq ($(LLVM),)
# clang only wants to see -no-pie during linking. Here, we don't have a separate
# linking stage, so a compiler warning is unavoidable without (wastefully)
# restructuring the Makefile. Avoid this by simply disabling that warning.
CFLAGS += -Wno-unused-command-line-argument
endif
endif
define gen-target-rule-32
$(1) $(1)_32: $(OUTPUT)/$(1)_32
.PHONY: $(1) $(1)_32
endef
define gen-target-rule-64
$(1) $(1)_64: $(OUTPUT)/$(1)_64
.PHONY: $(1) $(1)_64
endef
ifeq ($(CAN_BUILD_I386),1)
all: all_32
TEST_PROGS += $(BINARIES_32)
EXTRA_CFLAGS += -DCAN_BUILD_32
$(foreach t,$(TARGETS_C_32BIT_ALL),$(eval $(call gen-target-rule-32,$(t))))
endif
ifeq ($(CAN_BUILD_X86_64),1)
all: all_64
TEST_PROGS += $(BINARIES_64)
EXTRA_CFLAGS += -DCAN_BUILD_64
$(foreach t,$(TARGETS_C_64BIT_ALL),$(eval $(call gen-target-rule-64,$(t))))
endif
all_32: $(BINARIES_32)
all_64: $(BINARIES_64)
EXTRA_CLEAN := $(BINARIES_32) $(BINARIES_64) srso
$(BINARIES_32): $(OUTPUT)/%_32: %.c helpers.h
$(CC) -m32 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $< $(EXTRA_FILES) -lrt -ldl -lm
$(BINARIES_64): $(OUTPUT)/%_64: %.c helpers.h
$(CC) -m64 -o $@ $(CFLAGS) $(EXTRA_CFLAGS) $< $(EXTRA_FILES) -lrt -ldl
# x86_64 users should be encouraged to install 32-bit libraries
ifeq ($(CAN_BUILD_I386)$(CAN_BUILD_X86_64),01)
all: warn_32bit_failure
warn_32bit_failure:
@echo "Warning: you seem to have a broken 32-bit build" 2>&1; \
echo "environment. This will reduce test coverage of 64-bit" 2>&1; \
echo "kernels. If you are using a Debian-like distribution," 2>&1; \
echo "try:"; 2>&1; \
echo ""; \
echo " apt-get install gcc-multilib libc6-i386 libc6-dev-i386"; \
echo ""; \
echo "If you are using a Fedora-like distribution, try:"; \
echo ""; \
echo " yum install glibc-devel.*i686"; \
echo ""; \
echo "If you are using a SUSE-like distribution, try:"; \
echo ""; \
echo " zypper install gcc-32bit glibc-devel-static-32bit"; \
exit 0;
endif
# Add an additional file to the source file list for a given target, and also
# add a Makefile dependency on that same file. However, do these separately, so
# that the compiler invocation ("$(CC) file1.c file2.S") is not combined with
# the dependencies ("header3.h"), because clang, unlike gcc, will not accept
# header files as an input to the compiler invocation.
define extra-files
$(OUTPUT)/$(1): EXTRA_FILES := $(2)
$(OUTPUT)/$(1): $(2)
endef
$(eval $(call extra-files,sysret_ss_attrs_64,thunks.S))
$(eval $(call extra-files,ptrace_syscall_32,raw_syscall_helper_32.S))
$(eval $(call extra-files,test_syscall_vdso_32,thunks_32.S))
$(eval $(call extra-files,fsgsbase_restore_64,clang_helpers_64.S))
$(eval $(call extra-files,fsgsbase_restore_32,clang_helpers_32.S))
$(eval $(call extra-files,sysret_rip_64,clang_helpers_64.S))
# check_initial_reg_state is special: it needs a custom entry, and it
# needs to be static so that its interpreter doesn't destroy its initial
# state.
$(OUTPUT)/check_initial_reg_state_32: CFLAGS += -Wl,-ereal_start -static
$(OUTPUT)/check_initial_reg_state_64: CFLAGS += -Wl,-ereal_start -static
$(OUTPUT)/nx_stack_32: CFLAGS += -Wl,-z,noexecstack
$(OUTPUT)/nx_stack_64: CFLAGS += -Wl,-z,noexecstack
$(OUTPUT)/avx_64: CFLAGS += -mno-avx -mno-avx512f
$(OUTPUT)/amx_64: EXTRA_FILES += xstate.c
$(OUTPUT)/avx_64: EXTRA_FILES += xstate.c
$(OUTPUT)/apx_64: EXTRA_FILES += xstate.c
@@ -0,0 +1,518 @@
// SPDX-License-Identifier: GPL-2.0
#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
/* err() exits and will not return */
#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;
/* The helpers for managing XSAVE buffer and tile states: */
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);
}
/* 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_data(&xtiledata, xbuf);
return xrstor_safe(xbuf, XFEATURE_MASK_XTILEDATA);
}
enum expected_result { FAIL_EXPECTED, SUCCESS_EXPECTED };
/* arch_prctl() and sigaltstack() test */
#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);
/*
* 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_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);
}
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();
/* Request permission for the following tests */
req_xtiledata_perm();
test_fork();
/*
* Perform generic xstate tests for context switching, ptrace,
* and signal.
*/
test_xstate(XFEATURE_XTILEDATA);
clearhandler(SIGILL);
free_stashed_xsave();
return 0;
}
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include "xstate.h"
int main(void)
{
test_xstate(XFEATURE_APX);
}
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE /* Required for inline xstate helpers */
#include "xstate.h"
int main(void)
{
test_xstate(XFEATURE_YMM);
test_xstate(XFEATURE_OPMASK);
test_xstate(XFEATURE_ZMM_Hi256);
test_xstate(XFEATURE_Hi16_ZMM);
}
@@ -0,0 +1,3 @@
TEST_PROGS := its_sysfs.py its_permutations.py its_indirect_alignment.py its_ret_alignment.py
TEST_FILES := common.py
include ../../lib.mk
+164
View File
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2025 Intel Corporation
#
# This contains kselftest framework adapted common functions for testing
# mitigation for x86 bugs.
import os, sys, re, shutil
sys.path.insert(0, '../../kselftest')
import ksft
def read_file(path):
if not os.path.exists(path):
return None
with open(path, 'r') as file:
return file.read().strip()
def cpuinfo_has(arg):
cpuinfo = read_file('/proc/cpuinfo')
if arg in cpuinfo:
return True
return False
def cmdline_has(arg):
cmdline = read_file('/proc/cmdline')
if arg in cmdline:
return True
return False
def cmdline_has_either(args):
cmdline = read_file('/proc/cmdline')
for arg in args:
if arg in cmdline:
return True
return False
def cmdline_has_none(args):
return not cmdline_has_either(args)
def cmdline_has_all(args):
cmdline = read_file('/proc/cmdline')
for arg in args:
if arg not in cmdline:
return False
return True
def get_sysfs(bug):
return read_file("/sys/devices/system/cpu/vulnerabilities/" + bug)
def sysfs_has(bug, mitigation):
status = get_sysfs(bug)
if mitigation in status:
return True
return False
def sysfs_has_either(bugs, mitigations):
for bug in bugs:
for mitigation in mitigations:
if sysfs_has(bug, mitigation):
return True
return False
def sysfs_has_none(bugs, mitigations):
return not sysfs_has_either(bugs, mitigations)
def sysfs_has_all(bugs, mitigations):
for bug in bugs:
for mitigation in mitigations:
if not sysfs_has(bug, mitigation):
return False
return True
def bug_check_pass(bug, found):
ksft.print_msg(f"\nFound: {found}")
# ksft.print_msg(f"\ncmdline: {read_file('/proc/cmdline')}")
ksft.test_result_pass(f'{bug}: {found}')
def bug_check_fail(bug, found, expected):
ksft.print_msg(f'\nFound:\t {found}')
ksft.print_msg(f'Expected:\t {expected}')
ksft.print_msg(f"\ncmdline: {read_file('/proc/cmdline')}")
ksft.test_result_fail(f'{bug}: {found}')
def bug_status_unknown(bug, found):
ksft.print_msg(f'\nUnknown status: {found}')
ksft.print_msg(f"\ncmdline: {read_file('/proc/cmdline')}")
ksft.test_result_fail(f'{bug}: {found}')
def basic_checks_sufficient(bug, mitigation):
if not mitigation:
bug_status_unknown(bug, "None")
return True
elif mitigation == "Not affected":
ksft.test_result_pass(bug)
return True
elif mitigation == "Vulnerable":
if cmdline_has_either([f'{bug}=off', 'mitigations=off']):
bug_check_pass(bug, mitigation)
return True
return False
def get_section_info(vmlinux, section_name):
from elftools.elf.elffile import ELFFile
with open(vmlinux, 'rb') as f:
elffile = ELFFile(f)
section = elffile.get_section_by_name(section_name)
if section is None:
ksft.print_msg("Available sections in vmlinux:")
for sec in elffile.iter_sections():
ksft.print_msg(sec.name)
raise ValueError(f"Section {section_name} not found in {vmlinux}")
return section['sh_addr'], section['sh_offset'], section['sh_size']
def get_patch_sites(vmlinux, offset, size):
import struct
output = []
with open(vmlinux, 'rb') as f:
f.seek(offset)
i = 0
while i < size:
data = f.read(4) # s32
if not data:
break
sym_offset = struct.unpack('<i', data)[0] + i
i += 4
output.append(sym_offset)
return output
def get_instruction_from_vmlinux(elffile, section, virtual_address, target_address):
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
section_start = section['sh_addr']
section_end = section_start + section['sh_size']
if not (section_start <= target_address < section_end):
return None
offset = target_address - section_start
code = section.data()[offset:offset + 16]
cap = init_capstone()
for instruction in cap.disasm(code, target_address):
if instruction.address == target_address:
return instruction
return None
def init_capstone():
from capstone import Cs, CS_ARCH_X86, CS_MODE_64, CS_OPT_SYNTAX_ATT
cap = Cs(CS_ARCH_X86, CS_MODE_64)
cap.syntax = CS_OPT_SYNTAX_ATT
return cap
def get_runtime_kernel():
import drgn
return drgn.program_from_kernel()
def check_dependencies_or_skip(modules, script_name="unknown test"):
for mod in modules:
try:
__import__(mod)
except ImportError:
ksft.test_result_skip(f"Skipping {script_name}: missing module '{mod}'")
ksft.finished()
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2025 Intel Corporation
#
# Test for indirect target selection (ITS) mitigation.
#
# Test if indirect CALL/JMP are correctly patched by evaluating
# the vmlinux .retpoline_sites in /proc/kcore.
# Install dependencies
# add-apt-repository ppa:michel-slm/kernel-utils
# apt update
# apt install -y python3-drgn python3-pyelftools python3-capstone
#
# Best to copy the vmlinux at a standard location:
# mkdir -p /usr/lib/debug/lib/modules/$(uname -r)
# cp $VMLINUX /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
#
# Usage: ./its_indirect_alignment.py [vmlinux]
import os, sys, argparse
from pathlib import Path
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, this_dir + '/../../kselftest')
import ksft
import common as c
bug = "indirect_target_selection"
mitigation = c.get_sysfs(bug)
if not mitigation or "Aligned branch/return thunks" not in mitigation:
ksft.test_result_skip("Skipping its_indirect_alignment.py: Aligned branch/return thunks not enabled")
ksft.finished()
if c.sysfs_has("spectre_v2", "Retpolines"):
ksft.test_result_skip("Skipping its_indirect_alignment.py: Retpolines deployed")
ksft.finished()
c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_indirect_alignment.py")
from elftools.elf.elffile import ELFFile
from drgn.helpers.common.memory import identify_address
cap = c.init_capstone()
if len(os.sys.argv) > 1:
arg_vmlinux = os.sys.argv[1]
if not os.path.exists(arg_vmlinux):
ksft.test_result_fail(f"its_indirect_alignment.py: vmlinux not found at argument path: {arg_vmlinux}")
ksft.exit_fail()
os.makedirs(f"/usr/lib/debug/lib/modules/{os.uname().release}", exist_ok=True)
os.system(f'cp {arg_vmlinux} /usr/lib/debug/lib/modules/$(uname -r)/vmlinux')
vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux"
if not os.path.exists(vmlinux):
ksft.test_result_fail(f"its_indirect_alignment.py: vmlinux not found at {vmlinux}")
ksft.exit_fail()
ksft.print_msg(f"Using vmlinux: {vmlinux}")
retpolines_start_vmlinux, retpolines_sec_offset, size = c.get_section_info(vmlinux, '.retpoline_sites')
ksft.print_msg(f"vmlinux: Section .retpoline_sites (0x{retpolines_start_vmlinux:x}) found at 0x{retpolines_sec_offset:x} with size 0x{size:x}")
sites_offset = c.get_patch_sites(vmlinux, retpolines_sec_offset, size)
total_retpoline_tests = len(sites_offset)
ksft.print_msg(f"Found {total_retpoline_tests} retpoline sites")
prog = c.get_runtime_kernel()
retpolines_start_kcore = prog.symbol('__retpoline_sites').address
ksft.print_msg(f'kcore: __retpoline_sites: 0x{retpolines_start_kcore:x}')
x86_indirect_its_thunk_r15 = prog.symbol('__x86_indirect_its_thunk_r15').address
ksft.print_msg(f'kcore: __x86_indirect_its_thunk_r15: 0x{x86_indirect_its_thunk_r15:x}')
tests_passed = 0
tests_failed = 0
tests_unknown = 0
with open(vmlinux, 'rb') as f:
elffile = ELFFile(f)
text_section = elffile.get_section_by_name('.text')
for i in range(0, len(sites_offset)):
site = retpolines_start_kcore + sites_offset[i]
vmlinux_site = retpolines_start_vmlinux + sites_offset[i]
passed = unknown = failed = False
try:
vmlinux_insn = c.get_instruction_from_vmlinux(elffile, text_section, text_section['sh_addr'], vmlinux_site)
kcore_insn = list(cap.disasm(prog.read(site, 16), site))[0]
operand = kcore_insn.op_str
insn_end = site + kcore_insn.size - 1 # TODO handle Jcc.32 __x86_indirect_thunk_\reg
safe_site = insn_end & 0x20
site_status = "" if safe_site else "(unsafe)"
ksft.print_msg(f"\nSite {i}: {identify_address(prog, site)} <0x{site:x}> {site_status}")
ksft.print_msg(f"\tvmlinux: 0x{vmlinux_insn.address:x}:\t{vmlinux_insn.mnemonic}\t{vmlinux_insn.op_str}")
ksft.print_msg(f"\tkcore: 0x{kcore_insn.address:x}:\t{kcore_insn.mnemonic}\t{kcore_insn.op_str}")
if (site & 0x20) ^ (insn_end & 0x20):
ksft.print_msg(f"\tSite at safe/unsafe boundary: {str(kcore_insn.bytes)} {kcore_insn.mnemonic} {operand}")
if safe_site:
tests_passed += 1
passed = True
ksft.print_msg(f"\tPASSED: At safe address")
continue
if operand.startswith('0xffffffff'):
thunk = int(operand, 16)
if thunk > x86_indirect_its_thunk_r15:
insn_at_thunk = list(cap.disasm(prog.read(thunk, 16), thunk))[0]
operand += ' -> ' + insn_at_thunk.mnemonic + ' ' + insn_at_thunk.op_str + ' <dynamic-thunk?>'
if 'jmp' in insn_at_thunk.mnemonic and thunk & 0x20:
ksft.print_msg(f"\tPASSED: Found {operand} at safe address")
passed = True
if not passed:
if kcore_insn.operands[0].type == capstone.CS_OP_IMM:
operand += ' <' + prog.symbol(int(operand, 16)) + '>'
if '__x86_indirect_its_thunk_' in operand:
ksft.print_msg(f"\tPASSED: Found {operand}")
else:
ksft.print_msg(f"\tPASSED: Found direct branch: {kcore_insn}, ITS thunk not required.")
passed = True
else:
unknown = True
if passed:
tests_passed += 1
elif unknown:
ksft.print_msg(f"UNKNOWN: unexpected operand: {kcore_insn}")
tests_unknown += 1
else:
ksft.print_msg(f'\t************* FAILED *************')
ksft.print_msg(f"\tFound {kcore_insn.bytes} {kcore_insn.mnemonic} {operand}")
ksft.print_msg(f'\t**********************************')
tests_failed += 1
except Exception as e:
ksft.print_msg(f"UNKNOWN: An unexpected error occurred: {e}")
tests_unknown += 1
ksft.print_msg(f"\n\nSummary:")
ksft.print_msg(f"PASS: \t{tests_passed} \t/ {total_retpoline_tests}")
ksft.print_msg(f"FAIL: \t{tests_failed} \t/ {total_retpoline_tests}")
ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_retpoline_tests}")
if tests_failed == 0:
ksft.test_result_pass("All ITS return thunk sites passed")
else:
ksft.test_result_fail(f"{tests_failed} ITS return thunk sites failed")
ksft.finished()
@@ -0,0 +1,109 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2025 Intel Corporation
#
# Test for indirect target selection (ITS) cmdline permutations with other bugs
# like spectre_v2 and retbleed.
import os, sys, subprocess, itertools, re, shutil
test_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, test_dir + '/../../kselftest')
import ksft
import common as c
bug = "indirect_target_selection"
mitigation = c.get_sysfs(bug)
if not mitigation or "Not affected" in mitigation:
ksft.test_result_skip("Skipping its_permutations.py: not applicable")
ksft.finished()
if shutil.which('vng') is None:
ksft.test_result_skip("Skipping its_permutations.py: virtme-ng ('vng') not found in PATH.")
ksft.finished()
TEST = f"{test_dir}/its_sysfs.py"
default_kparam = ['clearcpuid=hypervisor', 'panic=5', 'panic_on_warn=1', 'oops=panic', 'nmi_watchdog=1', 'hung_task_panic=1']
DEBUG = " -v "
# Install dependencies
# https://github.com/arighi/virtme-ng
# apt install virtme-ng
BOOT_CMD = f"vng --run {test_dir}/../../../../../arch/x86/boot/bzImage "
#BOOT_CMD += DEBUG
bug = "indirect_target_selection"
input_options = {
'indirect_target_selection' : ['off', 'on', 'stuff', 'vmexit'],
'retbleed' : ['off', 'stuff', 'auto'],
'spectre_v2' : ['off', 'on', 'eibrs', 'retpoline', 'ibrs', 'eibrs,retpoline'],
}
def pretty_print(output):
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
# Define patterns and their corresponding colors
patterns = {
r"^ok \d+": OKGREEN,
r"^not ok \d+": FAIL,
r"^# Testing .*": OKBLUE,
r"^# Found: .*": WARNING,
r"^# Totals: .*": BOLD,
r"pass:([1-9]\d*)": OKGREEN,
r"fail:([1-9]\d*)": FAIL,
r"skip:([1-9]\d*)": WARNING,
}
# Apply colors based on patterns
for pattern, color in patterns.items():
output = re.sub(pattern, lambda match: f"{color}{match.group(0)}{ENDC}", output, flags=re.MULTILINE)
print(output)
combinations = list(itertools.product(*input_options.values()))
ksft.print_header()
ksft.set_plan(len(combinations))
logs = ""
for combination in combinations:
append = ""
log = ""
for p in default_kparam:
append += f' --append={p}'
command = BOOT_CMD + append
test_params = ""
for i, key in enumerate(input_options.keys()):
param = f'{key}={combination[i]}'
test_params += f' {param}'
command += f" --append={param}"
command += f" -- {TEST}"
test_name = f"{bug} {test_params}"
pretty_print(f'# Testing {test_name}')
t = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
t.wait()
output, _ = t.communicate()
if t.returncode == 0:
ksft.test_result_pass(test_name)
else:
ksft.test_result_fail(test_name)
output = output.decode()
log += f" {output}"
pretty_print(log)
logs += output + "\n"
# Optionally use tappy to parse the output
# apt install python3-tappy
with open("logs.txt", "w") as f:
f.write(logs)
ksft.finished()
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2025 Intel Corporation
#
# Test for indirect target selection (ITS) mitigation.
#
# Tests if the RETs are correctly patched by evaluating the
# vmlinux .return_sites in /proc/kcore.
#
# Install dependencies
# add-apt-repository ppa:michel-slm/kernel-utils
# apt update
# apt install -y python3-drgn python3-pyelftools python3-capstone
#
# Run on target machine
# mkdir -p /usr/lib/debug/lib/modules/$(uname -r)
# cp $VMLINUX /usr/lib/debug/lib/modules/$(uname -r)/vmlinux
#
# Usage: ./its_ret_alignment.py
import os, sys, argparse
from pathlib import Path
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, this_dir + '/../../kselftest')
import ksft
import common as c
bug = "indirect_target_selection"
mitigation = c.get_sysfs(bug)
if not mitigation or "Aligned branch/return thunks" not in mitigation:
ksft.test_result_skip("Skipping its_ret_alignment.py: Aligned branch/return thunks not enabled")
ksft.finished()
c.check_dependencies_or_skip(['drgn', 'elftools', 'capstone'], script_name="its_ret_alignment.py")
from elftools.elf.elffile import ELFFile
from drgn.helpers.common.memory import identify_address
cap = c.init_capstone()
if len(os.sys.argv) > 1:
arg_vmlinux = os.sys.argv[1]
if not os.path.exists(arg_vmlinux):
ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at user-supplied path: {arg_vmlinux}")
ksft.exit_fail()
os.makedirs(f"/usr/lib/debug/lib/modules/{os.uname().release}", exist_ok=True)
os.system(f'cp {arg_vmlinux} /usr/lib/debug/lib/modules/$(uname -r)/vmlinux')
vmlinux = f"/usr/lib/debug/lib/modules/{os.uname().release}/vmlinux"
if not os.path.exists(vmlinux):
ksft.test_result_fail(f"its_ret_alignment.py: vmlinux not found at {vmlinux}")
ksft.exit_fail()
ksft.print_msg(f"Using vmlinux: {vmlinux}")
rethunks_start_vmlinux, rethunks_sec_offset, size = c.get_section_info(vmlinux, '.return_sites')
ksft.print_msg(f"vmlinux: Section .return_sites (0x{rethunks_start_vmlinux:x}) found at 0x{rethunks_sec_offset:x} with size 0x{size:x}")
sites_offset = c.get_patch_sites(vmlinux, rethunks_sec_offset, size)
total_rethunk_tests = len(sites_offset)
ksft.print_msg(f"Found {total_rethunk_tests} rethunk sites")
prog = c.get_runtime_kernel()
rethunks_start_kcore = prog.symbol('__return_sites').address
ksft.print_msg(f'kcore: __rethunk_sites: 0x{rethunks_start_kcore:x}')
its_return_thunk = prog.symbol('its_return_thunk').address
ksft.print_msg(f'kcore: its_return_thunk: 0x{its_return_thunk:x}')
tests_passed = 0
tests_failed = 0
tests_unknown = 0
tests_skipped = 0
with open(vmlinux, 'rb') as f:
elffile = ELFFile(f)
text_section = elffile.get_section_by_name('.text')
for i in range(len(sites_offset)):
site = rethunks_start_kcore + sites_offset[i]
vmlinux_site = rethunks_start_vmlinux + sites_offset[i]
try:
passed = unknown = failed = skipped = False
symbol = identify_address(prog, site)
vmlinux_insn = c.get_instruction_from_vmlinux(elffile, text_section, text_section['sh_addr'], vmlinux_site)
kcore_insn = list(cap.disasm(prog.read(site, 16), site))[0]
insn_end = site + kcore_insn.size - 1
safe_site = insn_end & 0x20
site_status = "" if safe_site else "(unsafe)"
ksft.print_msg(f"\nSite {i}: {symbol} <0x{site:x}> {site_status}")
ksft.print_msg(f"\tvmlinux: 0x{vmlinux_insn.address:x}:\t{vmlinux_insn.mnemonic}\t{vmlinux_insn.op_str}")
ksft.print_msg(f"\tkcore: 0x{kcore_insn.address:x}:\t{kcore_insn.mnemonic}\t{kcore_insn.op_str}")
if safe_site:
tests_passed += 1
passed = True
ksft.print_msg(f"\tPASSED: At safe address")
continue
if "jmp" in kcore_insn.mnemonic:
passed = True
elif "ret" not in kcore_insn.mnemonic:
skipped = True
if passed:
ksft.print_msg(f"\tPASSED: Found {kcore_insn.mnemonic} {kcore_insn.op_str}")
tests_passed += 1
elif skipped:
ksft.print_msg(f"\tSKIPPED: Found '{kcore_insn.mnemonic}'")
tests_skipped += 1
elif unknown:
ksft.print_msg(f"UNKNOWN: An unknown instruction: {kcore_insn}")
tests_unknown += 1
else:
ksft.print_msg(f'\t************* FAILED *************')
ksft.print_msg(f"\tFound {kcore_insn.mnemonic} {kcore_insn.op_str}")
ksft.print_msg(f'\t**********************************')
tests_failed += 1
except Exception as e:
ksft.print_msg(f"UNKNOWN: An unexpected error occurred: {e}")
tests_unknown += 1
ksft.print_msg(f"\n\nSummary:")
ksft.print_msg(f"PASSED: \t{tests_passed} \t/ {total_rethunk_tests}")
ksft.print_msg(f"FAILED: \t{tests_failed} \t/ {total_rethunk_tests}")
ksft.print_msg(f"SKIPPED: \t{tests_skipped} \t/ {total_rethunk_tests}")
ksft.print_msg(f"UNKNOWN: \t{tests_unknown} \t/ {total_rethunk_tests}")
if tests_failed == 0:
ksft.test_result_pass("All ITS return thunk sites passed.")
else:
ksft.test_result_fail(f"{tests_failed} failed sites need ITS return thunks.")
ksft.finished()
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2025 Intel Corporation
#
# Test for Indirect Target Selection(ITS) mitigation sysfs status.
import sys, os, re
this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, this_dir + '/../../kselftest')
import ksft
from common import *
bug = "indirect_target_selection"
mitigation = get_sysfs(bug)
ITS_MITIGATION_ALIGNED_THUNKS = "Mitigation: Aligned branch/return thunks"
ITS_MITIGATION_RETPOLINE_STUFF = "Mitigation: Retpolines, Stuffing RSB"
ITS_MITIGATION_VMEXIT_ONLY = "Mitigation: Vulnerable, KVM: Not affected"
ITS_MITIGATION_VULNERABLE = "Vulnerable"
def check_mitigation():
if mitigation == ITS_MITIGATION_ALIGNED_THUNKS:
if cmdline_has(f'{bug}=stuff') and sysfs_has("spectre_v2", "Retpolines"):
bug_check_fail(bug, ITS_MITIGATION_ALIGNED_THUNKS, ITS_MITIGATION_RETPOLINE_STUFF)
return
if cmdline_has(f'{bug}=vmexit') and cpuinfo_has('its_native_only'):
bug_check_fail(bug, ITS_MITIGATION_ALIGNED_THUNKS, ITS_MITIGATION_VMEXIT_ONLY)
return
bug_check_pass(bug, ITS_MITIGATION_ALIGNED_THUNKS)
return
if mitigation == ITS_MITIGATION_RETPOLINE_STUFF:
if cmdline_has(f'{bug}=stuff') and sysfs_has("spectre_v2", "Retpolines"):
bug_check_pass(bug, ITS_MITIGATION_RETPOLINE_STUFF)
return
if sysfs_has('retbleed', 'Stuffing'):
bug_check_pass(bug, ITS_MITIGATION_RETPOLINE_STUFF)
return
bug_check_fail(bug, ITS_MITIGATION_RETPOLINE_STUFF, ITS_MITIGATION_ALIGNED_THUNKS)
if mitigation == ITS_MITIGATION_VMEXIT_ONLY:
if cmdline_has(f'{bug}=vmexit') and cpuinfo_has('its_native_only'):
bug_check_pass(bug, ITS_MITIGATION_VMEXIT_ONLY)
return
bug_check_fail(bug, ITS_MITIGATION_VMEXIT_ONLY, ITS_MITIGATION_ALIGNED_THUNKS)
if mitigation == ITS_MITIGATION_VULNERABLE:
if sysfs_has("spectre_v2", "Vulnerable"):
bug_check_pass(bug, ITS_MITIGATION_VULNERABLE)
else:
bug_check_fail(bug, "Mitigation", ITS_MITIGATION_VULNERABLE)
bug_status_unknown(bug, mitigation)
return
ksft.print_header()
ksft.set_plan(1)
ksft.print_msg(f'{bug}: {mitigation} ...')
if not basic_checks_sufficient(bug, mitigation):
check_mitigation()
ksft.finished()
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
# check_cc.sh - Helper to test userspace compilation support
# Copyright (c) 2015 Andrew Lutomirski
CC="$1"
TESTPROG="$2"
shift 2
if [ -n "$CC" ] && $CC -o /dev/null "$TESTPROG" -O0 "$@" 2>/dev/null; then
echo 1
else
echo 0
fi
exit 0
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* check_initial_reg_state.c - check that execve sets the correct state
* Copyright (c) 2014-2016 Andrew Lutomirski
*/
#define _GNU_SOURCE
#include <stdio.h>
unsigned long ax, bx, cx, dx, si, di, bp, sp, flags;
unsigned long r8, r9, r10, r11, r12, r13, r14, r15;
asm (
".pushsection .text\n\t"
".type real_start, @function\n\t"
".global real_start\n\t"
"real_start:\n\t"
#ifdef __x86_64__
"mov %rax, ax\n\t"
"mov %rbx, bx\n\t"
"mov %rcx, cx\n\t"
"mov %rdx, dx\n\t"
"mov %rsi, si\n\t"
"mov %rdi, di\n\t"
"mov %rbp, bp\n\t"
"mov %rsp, sp\n\t"
"mov %r8, r8\n\t"
"mov %r9, r9\n\t"
"mov %r10, r10\n\t"
"mov %r11, r11\n\t"
"mov %r12, r12\n\t"
"mov %r13, r13\n\t"
"mov %r14, r14\n\t"
"mov %r15, r15\n\t"
"pushfq\n\t"
"popq flags\n\t"
#else
"mov %eax, ax\n\t"
"mov %ebx, bx\n\t"
"mov %ecx, cx\n\t"
"mov %edx, dx\n\t"
"mov %esi, si\n\t"
"mov %edi, di\n\t"
"mov %ebp, bp\n\t"
"mov %esp, sp\n\t"
"pushfl\n\t"
"popl flags\n\t"
#endif
"jmp _start\n\t"
".size real_start, . - real_start\n\t"
".popsection");
int main()
{
int nerrs = 0;
if (sp == 0) {
printf("[FAIL]\tTest was built incorrectly\n");
return 1;
}
if (ax || bx || cx || dx || si || di || bp
#ifdef __x86_64__
|| r8 || r9 || r10 || r11 || r12 || r13 || r14 || r15
#endif
) {
printf("[FAIL]\tAll GPRs except SP should be 0\n");
#define SHOW(x) printf("\t" #x " = 0x%lx\n", x);
SHOW(ax);
SHOW(bx);
SHOW(cx);
SHOW(dx);
SHOW(si);
SHOW(di);
SHOW(bp);
SHOW(sp);
#ifdef __x86_64__
SHOW(r8);
SHOW(r9);
SHOW(r10);
SHOW(r11);
SHOW(r12);
SHOW(r13);
SHOW(r14);
SHOW(r15);
#endif
nerrs++;
} else {
printf("[OK]\tAll GPRs except SP are 0\n");
}
if (flags != 0x202) {
printf("[FAIL]\tFLAGS is 0x%lx, but it should be 0x202\n", flags);
nerrs++;
} else {
printf("[OK]\tFLAGS is 0x202\n");
}
return nerrs ? 1 : 0;
}
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* 32-bit assembly helpers for asm operations that lack support in both gcc and
* clang. For example, clang asm does not support segment prefixes.
*/
.global dereference_seg_base
dereference_seg_base:
mov %fs:(0), %eax
ret
.section .note.GNU-stack,"",%progbits
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* 64-bit assembly helpers for asm operations that lack support in both gcc and
* clang. For example, clang asm does not support segment prefixes.
*/
.global dereference_seg_base
dereference_seg_base:
mov %gs:(0), %rax
ret
.global test_page
.global test_syscall_insn
.pushsection ".text", "ax"
.balign 4096
test_page: .globl test_page
.fill 4094,1,0xcc
test_syscall_insn:
syscall
.ifne . - test_page - 4096
.error "test page is not one page long"
.endif
.popsection
.section .note.GNU-stack,"",%progbits
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Corrupt the XSTATE header in a signal frame
*
* Based on analysis and a test case from Thomas Gleixner.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sched.h>
#include <signal.h>
#include <err.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/wait.h>
#include "kselftest.h" /* For __cpuid_count() */
#include "helpers.h"
static inline int xsave_enabled(void)
{
unsigned int eax, ebx, ecx, edx;
__cpuid_count(0x1, 0x0, eax, ebx, ecx, edx);
/* Is CR4.OSXSAVE enabled ? */
return ecx & (1U << 27);
}
static void sigusr1(int sig, siginfo_t *info, void *uc_void)
{
ucontext_t *uc = uc_void;
uint8_t *fpstate = (uint8_t *)uc->uc_mcontext.fpregs;
uint64_t *xfeatures = (uint64_t *)(fpstate + 512);
printf("\tWreck XSTATE header\n");
/* Wreck the first reserved bytes in the header */
*(xfeatures + 2) = 0xfffffff;
}
static void sigsegv(int sig, siginfo_t *info, void *uc_void)
{
printf("\tGot SIGSEGV\n");
}
int main(void)
{
cpu_set_t set;
sethandler(SIGUSR1, sigusr1, 0);
sethandler(SIGSEGV, sigsegv, 0);
if (!xsave_enabled()) {
printf("[SKIP] CR4.OSXSAVE disabled.\n");
return 0;
}
CPU_ZERO(&set);
CPU_SET(0, &set);
/*
* Enforce that the child runs on the same CPU
* which in turn forces a schedule.
*/
sched_setaffinity(getpid(), sizeof(set), &set);
printf("[RUN]\tSend ourselves a signal\n");
raise(SIGUSR1);
printf("[OK]\tBack from the signal. Now schedule.\n");
pid_t child = fork();
if (child < 0)
err(1, "fork");
if (child == 0)
return 0;
if (child)
waitpid(child, NULL, 0);
printf("[OK]\tBack in the main thread.\n");
/*
* We could try to confirm that extended state is still preserved
* when we schedule. For now, the only indication of failure is
* a warning in the kernel logs.
*/
return 0;
}
@@ -0,0 +1,328 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* entry_from_vm86.c - tests kernel entries from vm86 mode
* Copyright (c) 2014-2015 Andrew Lutomirski
*
* This exercises a few paths that need to special-case vm86 mode.
*/
#define _GNU_SOURCE
#include <assert.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/signal.h>
#include <sys/ucontext.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <err.h>
#include <stddef.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/vm86.h>
#include "helpers.h"
static unsigned long load_addr = 0x10000;
static int nerrs = 0;
static sig_atomic_t got_signal;
static void sighandler(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t*)ctx_void;
if (ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_VM ||
(ctx->uc_mcontext.gregs[REG_CS] & 3) != 3) {
printf("[FAIL]\tSignal frame should not reflect vm86 mode\n");
nerrs++;
}
const char *signame;
if (sig == SIGSEGV)
signame = "SIGSEGV";
else if (sig == SIGILL)
signame = "SIGILL";
else
signame = "unexpected signal";
printf("[INFO]\t%s: FLAGS = 0x%lx, CS = 0x%hx\n", signame,
(unsigned long)ctx->uc_mcontext.gregs[REG_EFL],
(unsigned short)ctx->uc_mcontext.gregs[REG_CS]);
got_signal = 1;
}
asm (
".pushsection .rodata\n\t"
".type vmcode_bound, @object\n\t"
"vmcode:\n\t"
"vmcode_bound:\n\t"
".code16\n\t"
"bound %ax, (2048)\n\t"
"int3\n\t"
"vmcode_sysenter:\n\t"
"sysenter\n\t"
"vmcode_syscall:\n\t"
"syscall\n\t"
"vmcode_sti:\n\t"
"sti\n\t"
"vmcode_int3:\n\t"
"int3\n\t"
"vmcode_int80:\n\t"
"int $0x80\n\t"
"vmcode_popf_hlt:\n\t"
"push %ax\n\t"
"popf\n\t"
"hlt\n\t"
"vmcode_umip:\n\t"
/* addressing via displacements */
"smsw (2052)\n\t"
"sidt (2054)\n\t"
"sgdt (2060)\n\t"
/* addressing via registers */
"mov $2066, %bx\n\t"
"smsw (%bx)\n\t"
"mov $2068, %bx\n\t"
"sidt (%bx)\n\t"
"mov $2074, %bx\n\t"
"sgdt (%bx)\n\t"
/* register operands, only for smsw */
"smsw %ax\n\t"
"mov %ax, (2080)\n\t"
"int3\n\t"
"vmcode_umip_str:\n\t"
"str %eax\n\t"
"vmcode_umip_sldt:\n\t"
"sldt %eax\n\t"
"int3\n\t"
".size vmcode, . - vmcode\n\t"
"end_vmcode:\n\t"
".code32\n\t"
".popsection"
);
extern unsigned char vmcode[], end_vmcode[];
extern unsigned char vmcode_bound[], vmcode_sysenter[], vmcode_syscall[],
vmcode_sti[], vmcode_int3[], vmcode_int80[], vmcode_popf_hlt[],
vmcode_umip[], vmcode_umip_str[], vmcode_umip_sldt[];
/* Returns false if the test was skipped. */
static bool do_test(struct vm86plus_struct *v86, unsigned long eip,
unsigned int rettype, unsigned int retarg,
const char *text)
{
long ret;
printf("[RUN]\t%s from vm86 mode\n", text);
v86->regs.eip = eip;
ret = vm86(VM86_ENTER, v86);
if (ret == -1 && (errno == ENOSYS || errno == EPERM)) {
printf("[SKIP]\tvm86 %s\n",
errno == ENOSYS ? "not supported" : "not allowed");
return false;
}
if (VM86_TYPE(ret) == VM86_INTx) {
char trapname[32];
int trapno = VM86_ARG(ret);
if (trapno == 13)
strcpy(trapname, "GP");
else if (trapno == 5)
strcpy(trapname, "BR");
else if (trapno == 14)
strcpy(trapname, "PF");
else
sprintf(trapname, "%d", trapno);
printf("[INFO]\tExited vm86 mode due to #%s\n", trapname);
} else if (VM86_TYPE(ret) == VM86_UNKNOWN) {
printf("[INFO]\tExited vm86 mode due to unhandled GP fault\n");
} else if (VM86_TYPE(ret) == VM86_TRAP) {
printf("[INFO]\tExited vm86 mode due to a trap (arg=%ld)\n",
VM86_ARG(ret));
} else if (VM86_TYPE(ret) == VM86_SIGNAL) {
printf("[INFO]\tExited vm86 mode due to a signal\n");
} else if (VM86_TYPE(ret) == VM86_STI) {
printf("[INFO]\tExited vm86 mode due to STI\n");
} else {
printf("[INFO]\tExited vm86 mode due to type %ld, arg %ld\n",
VM86_TYPE(ret), VM86_ARG(ret));
}
if (rettype == -1 ||
(VM86_TYPE(ret) == rettype && VM86_ARG(ret) == retarg)) {
printf("[OK]\tReturned correctly\n");
} else {
printf("[FAIL]\tIncorrect return reason (started at eip = 0x%lx, ended at eip = 0x%lx)\n", eip, v86->regs.eip);
nerrs++;
}
return true;
}
void do_umip_tests(struct vm86plus_struct *vm86, unsigned char *test_mem)
{
struct table_desc {
unsigned short limit;
unsigned long base;
} __attribute__((packed));
/* Initialize variables with arbitrary values */
struct table_desc gdt1 = { .base = 0x3c3c3c3c, .limit = 0x9999 };
struct table_desc gdt2 = { .base = 0x1a1a1a1a, .limit = 0xaeae };
struct table_desc idt1 = { .base = 0x7b7b7b7b, .limit = 0xf1f1 };
struct table_desc idt2 = { .base = 0x89898989, .limit = 0x1313 };
unsigned short msw1 = 0x1414, msw2 = 0x2525, msw3 = 3737;
/* UMIP -- exit with INT3 unless kernel emulation did not trap #GP */
do_test(vm86, vmcode_umip - vmcode, VM86_TRAP, 3, "UMIP tests");
/* Results from displacement-only addressing */
msw1 = *(unsigned short *)(test_mem + 2052);
memcpy(&idt1, test_mem + 2054, sizeof(idt1));
memcpy(&gdt1, test_mem + 2060, sizeof(gdt1));
/* Results from register-indirect addressing */
msw2 = *(unsigned short *)(test_mem + 2066);
memcpy(&idt2, test_mem + 2068, sizeof(idt2));
memcpy(&gdt2, test_mem + 2074, sizeof(gdt2));
/* Results when using register operands */
msw3 = *(unsigned short *)(test_mem + 2080);
printf("[INFO]\tResult from SMSW:[0x%04x]\n", msw1);
printf("[INFO]\tResult from SIDT: limit[0x%04x]base[0x%08lx]\n",
idt1.limit, idt1.base);
printf("[INFO]\tResult from SGDT: limit[0x%04x]base[0x%08lx]\n",
gdt1.limit, gdt1.base);
if (msw1 != msw2 || msw1 != msw3)
printf("[FAIL]\tAll the results of SMSW should be the same.\n");
else
printf("[PASS]\tAll the results from SMSW are identical.\n");
if (memcmp(&gdt1, &gdt2, sizeof(gdt1)))
printf("[FAIL]\tAll the results of SGDT should be the same.\n");
else
printf("[PASS]\tAll the results from SGDT are identical.\n");
if (memcmp(&idt1, &idt2, sizeof(idt1)))
printf("[FAIL]\tAll the results of SIDT should be the same.\n");
else
printf("[PASS]\tAll the results from SIDT are identical.\n");
sethandler(SIGILL, sighandler, 0);
do_test(vm86, vmcode_umip_str - vmcode, VM86_SIGNAL, 0,
"STR instruction");
clearhandler(SIGILL);
sethandler(SIGILL, sighandler, 0);
do_test(vm86, vmcode_umip_sldt - vmcode, VM86_SIGNAL, 0,
"SLDT instruction");
clearhandler(SIGILL);
}
int main(void)
{
struct vm86plus_struct v86;
unsigned char *addr = mmap((void *)load_addr, 4096,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE, -1,0);
if (addr != (unsigned char *)load_addr)
err(1, "mmap");
memcpy(addr, vmcode, end_vmcode - vmcode);
addr[2048] = 2;
addr[2050] = 3;
memset(&v86, 0, sizeof(v86));
v86.regs.cs = load_addr / 16;
v86.regs.ss = load_addr / 16;
v86.regs.ds = load_addr / 16;
v86.regs.es = load_addr / 16;
/* Use the end of the page as our stack. */
v86.regs.esp = 4096;
assert((v86.regs.cs & 3) == 0); /* Looks like RPL = 0 */
/* #BR -- should deliver SIG??? */
do_test(&v86, vmcode_bound - vmcode, VM86_INTx, 5, "#BR");
/*
* SYSENTER -- should cause #GP or #UD depending on CPU.
* Expected return type -1 means that we shouldn't validate
* the vm86 return value. This will avoid problems on non-SEP
* CPUs.
*/
sethandler(SIGILL, sighandler, 0);
do_test(&v86, vmcode_sysenter - vmcode, -1, 0, "SYSENTER");
clearhandler(SIGILL);
/*
* SYSCALL would be a disaster in VM86 mode. Fortunately,
* there is no kernel that both enables SYSCALL and sets
* EFER.SCE, so it's #UD on all systems. But vm86 is
* buggy (or has a "feature"), so the SIGILL will actually
* be delivered.
*/
sethandler(SIGILL, sighandler, 0);
do_test(&v86, vmcode_syscall - vmcode, VM86_SIGNAL, 0, "SYSCALL");
clearhandler(SIGILL);
/* STI with VIP set */
v86.regs.eflags |= X86_EFLAGS_VIP;
v86.regs.eflags &= ~X86_EFLAGS_IF;
do_test(&v86, vmcode_sti - vmcode, VM86_STI, 0, "STI with VIP set");
/* POPF with VIP set but IF clear: should not trap */
v86.regs.eflags = X86_EFLAGS_VIP;
v86.regs.eax = 0;
do_test(&v86, vmcode_popf_hlt - vmcode, VM86_UNKNOWN, 0, "POPF with VIP set and IF clear");
/* POPF with VIP set and IF set: should trap */
v86.regs.eflags = X86_EFLAGS_VIP;
v86.regs.eax = X86_EFLAGS_IF;
do_test(&v86, vmcode_popf_hlt - vmcode, VM86_STI, 0, "POPF with VIP and IF set");
/* POPF with VIP clear and IF set: should not trap */
v86.regs.eflags = 0;
v86.regs.eax = X86_EFLAGS_IF;
do_test(&v86, vmcode_popf_hlt - vmcode, VM86_UNKNOWN, 0, "POPF with VIP clear and IF set");
v86.regs.eflags = 0;
/* INT3 -- should cause #BP */
do_test(&v86, vmcode_int3 - vmcode, VM86_TRAP, 3, "INT3");
/* INT80 -- should exit with "INTx 0x80" */
v86.regs.eax = (unsigned int)-1;
do_test(&v86, vmcode_int80 - vmcode, VM86_INTx, 0x80, "int80");
/* UMIP -- should exit with INTx 0x80 unless UMIP was not disabled */
do_umip_tests(&v86, addr);
/* Execute a null pointer */
v86.regs.cs = 0;
v86.regs.ss = 0;
sethandler(SIGSEGV, sighandler, 0);
got_signal = 0;
if (do_test(&v86, 0, VM86_SIGNAL, 0, "Execute null pointer") &&
!got_signal) {
printf("[FAIL]\tDid not receive SIGSEGV\n");
nerrs++;
}
clearhandler(SIGSEGV);
/* Make sure nothing explodes if we fork. */
if (fork() == 0)
return 0;
return (nerrs == 0 ? 0 : 1);
}
@@ -0,0 +1,660 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fsgsbase.c, an fsgsbase test
* Copyright (c) 2014-2016 Andy Lutomirski
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <err.h>
#include <sys/user.h>
#include <asm/prctl.h>
#include <sys/prctl.h>
#include <signal.h>
#include <limits.h>
#include <sys/ucontext.h>
#include <sched.h>
#include <linux/futex.h>
#include <pthread.h>
#include <asm/ldt.h>
#include <sys/mman.h>
#include <stddef.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <setjmp.h>
#include "helpers.h"
#ifndef __x86_64__
# error This test is 64-bit only
#endif
static volatile sig_atomic_t want_segv;
static volatile unsigned long segv_addr;
static unsigned short *shared_scratch;
static int nerrs;
static void sigsegv(int sig, siginfo_t *si, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t*)ctx_void;
if (!want_segv) {
clearhandler(SIGSEGV);
return; /* Crash cleanly. */
}
want_segv = false;
segv_addr = (unsigned long)si->si_addr;
ctx->uc_mcontext.gregs[REG_RIP] += 4; /* Skip the faulting mov */
}
static jmp_buf jmpbuf;
static void sigill(int sig, siginfo_t *si, void *ctx_void)
{
siglongjmp(jmpbuf, 1);
}
static bool have_fsgsbase;
static inline unsigned long rdgsbase(void)
{
unsigned long gsbase;
asm volatile("rdgsbase %0" : "=r" (gsbase) :: "memory");
return gsbase;
}
static inline unsigned long rdfsbase(void)
{
unsigned long fsbase;
asm volatile("rdfsbase %0" : "=r" (fsbase) :: "memory");
return fsbase;
}
static inline void wrgsbase(unsigned long gsbase)
{
asm volatile("wrgsbase %0" :: "r" (gsbase) : "memory");
}
enum which_base { FS, GS };
static unsigned long read_base(enum which_base which)
{
unsigned long offset;
/*
* Unless we have FSGSBASE, there's no direct way to do this from
* user mode. We can get at it indirectly using signals, though.
*/
want_segv = true;
offset = 0;
if (which == FS) {
/* Use a constant-length instruction here. */
asm volatile ("mov %%fs:(%%rcx), %%rax" : : "c" (offset) : "rax");
} else {
asm volatile ("mov %%gs:(%%rcx), %%rax" : : "c" (offset) : "rax");
}
if (!want_segv)
return segv_addr + offset;
/*
* If that didn't segfault, try the other end of the address space.
* Unless we get really unlucky and run into the vsyscall page, this
* is guaranteed to segfault.
*/
offset = (ULONG_MAX >> 1) + 1;
if (which == FS) {
asm volatile ("mov %%fs:(%%rcx), %%rax"
: : "c" (offset) : "rax");
} else {
asm volatile ("mov %%gs:(%%rcx), %%rax"
: : "c" (offset) : "rax");
}
if (!want_segv)
return segv_addr + offset;
abort();
}
static void check_gs_value(unsigned long value)
{
unsigned long base;
unsigned short sel;
printf("[RUN]\tARCH_SET_GS to 0x%lx\n", value);
if (syscall(SYS_arch_prctl, ARCH_SET_GS, value) != 0)
err(1, "ARCH_SET_GS");
asm volatile ("mov %%gs, %0" : "=rm" (sel));
base = read_base(GS);
if (base == value) {
printf("[OK]\tGSBASE was set as expected (selector 0x%hx)\n",
sel);
} else {
nerrs++;
printf("[FAIL]\tGSBASE was not as expected: got 0x%lx (selector 0x%hx)\n",
base, sel);
}
if (syscall(SYS_arch_prctl, ARCH_GET_GS, &base) != 0)
err(1, "ARCH_GET_GS");
if (base == value) {
printf("[OK]\tARCH_GET_GS worked as expected (selector 0x%hx)\n",
sel);
} else {
nerrs++;
printf("[FAIL]\tARCH_GET_GS was not as expected: got 0x%lx (selector 0x%hx)\n",
base, sel);
}
}
static void mov_0_gs(unsigned long initial_base, bool schedule)
{
unsigned long base, arch_base;
printf("[RUN]\tARCH_SET_GS to 0x%lx then mov 0 to %%gs%s\n", initial_base, schedule ? " and schedule " : "");
if (syscall(SYS_arch_prctl, ARCH_SET_GS, initial_base) != 0)
err(1, "ARCH_SET_GS");
if (schedule)
usleep(10);
asm volatile ("mov %0, %%gs" : : "rm" (0));
base = read_base(GS);
if (syscall(SYS_arch_prctl, ARCH_GET_GS, &arch_base) != 0)
err(1, "ARCH_GET_GS");
if (base == arch_base) {
printf("[OK]\tGSBASE is 0x%lx\n", base);
} else {
nerrs++;
printf("[FAIL]\tGSBASE changed to 0x%lx but kernel reports 0x%lx\n", base, arch_base);
}
}
static volatile unsigned long remote_base;
static volatile unsigned int ftx;
/*
* ARCH_SET_FS/GS(0) may or may not program a selector of zero. HARD_ZERO
* means to force the selector to zero to improve test coverage.
*/
#define HARD_ZERO 0xa1fa5f343cb85fa4
static void do_remote_base()
{
unsigned long to_set = remote_base;
bool hard_zero = false;
if (to_set == HARD_ZERO) {
to_set = 0;
hard_zero = true;
}
if (syscall(SYS_arch_prctl, ARCH_SET_GS, to_set) != 0)
err(1, "ARCH_SET_GS");
if (hard_zero)
asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
unsigned short sel;
asm volatile ("mov %%gs, %0" : "=rm" (sel));
printf("\tother thread: ARCH_SET_GS(0x%lx)%s -- sel is 0x%hx\n",
to_set, hard_zero ? " and clear gs" : "", sel);
}
static __thread int set_thread_area_entry_number = -1;
static unsigned short load_gs(void)
{
/*
* Sets GS != 0 and GSBASE != 0 but arranges for the kernel to think
* that GSBASE == 0 (i.e. thread.gsbase == 0).
*/
/* Step 1: tell the kernel that we have GSBASE == 0. */
if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)
err(1, "ARCH_SET_GS");
/* Step 2: change GSBASE without telling the kernel. */
struct user_desc desc = {
.entry_number = 0,
.base_addr = 0xBAADF00D,
.limit = 0xfffff,
.seg_32bit = 1,
.contents = 0, /* Data, grow-up */
.read_exec_only = 0,
.limit_in_pages = 1,
.seg_not_present = 0,
.useable = 0
};
if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {
printf("\tusing LDT slot 0\n");
asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0x7));
return 0x7;
} else {
/* No modify_ldt for us (configured out, perhaps) */
struct user_desc *low_desc = mmap(
NULL, sizeof(desc),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
memcpy(low_desc, &desc, sizeof(desc));
low_desc->entry_number = set_thread_area_entry_number;
/* 32-bit set_thread_area */
long ret;
asm volatile ("int $0x80"
: "=a" (ret), "+m" (*low_desc)
: "a" (243), "b" (low_desc)
: "r8", "r9", "r10", "r11");
memcpy(&desc, low_desc, sizeof(desc));
munmap(low_desc, sizeof(desc));
if (ret != 0) {
printf("[NOTE]\tcould not create a segment -- test won't do anything\n");
return 0;
}
printf("\tusing GDT slot %d\n", desc.entry_number);
set_thread_area_entry_number = desc.entry_number;
unsigned short gs = (unsigned short)((desc.entry_number << 3) | 0x3);
asm volatile ("mov %0, %%gs" : : "rm" (gs));
return gs;
}
}
void test_wrbase(unsigned short index, unsigned long base)
{
unsigned short newindex;
unsigned long newbase;
printf("[RUN]\tGS = 0x%hx, GSBASE = 0x%lx\n", index, base);
asm volatile ("mov %0, %%gs" : : "rm" (index));
wrgsbase(base);
remote_base = 0;
ftx = 1;
syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
while (ftx != 0)
syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
asm volatile ("mov %%gs, %0" : "=rm" (newindex));
newbase = rdgsbase();
if (newindex == index && newbase == base) {
printf("[OK]\tIndex and base were preserved\n");
} else {
printf("[FAIL]\tAfter switch, GS = 0x%hx and GSBASE = 0x%lx\n",
newindex, newbase);
nerrs++;
}
}
static void *threadproc(void *ctx)
{
while (1) {
while (ftx == 0)
syscall(SYS_futex, &ftx, FUTEX_WAIT, 0, NULL, NULL, 0);
if (ftx == 3)
return NULL;
if (ftx == 1) {
do_remote_base();
} else if (ftx == 2) {
/*
* On AMD chips, this causes GSBASE != 0, GS == 0, and
* thread.gsbase == 0.
*/
load_gs();
asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
} else {
errx(1, "helper thread got bad command");
}
ftx = 0;
syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
}
}
static void set_gs_and_switch_to(unsigned long local,
unsigned short force_sel,
unsigned long remote)
{
unsigned long base;
unsigned short sel_pre_sched, sel_post_sched;
bool hard_zero = false;
if (local == HARD_ZERO) {
hard_zero = true;
local = 0;
}
printf("[RUN]\tARCH_SET_GS(0x%lx)%s, then schedule to 0x%lx\n",
local, hard_zero ? " and clear gs" : "", remote);
if (force_sel)
printf("\tBefore schedule, set selector to 0x%hx\n", force_sel);
if (syscall(SYS_arch_prctl, ARCH_SET_GS, local) != 0)
err(1, "ARCH_SET_GS");
if (hard_zero)
asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
if (read_base(GS) != local) {
nerrs++;
printf("[FAIL]\tGSBASE wasn't set as expected\n");
}
if (force_sel) {
asm volatile ("mov %0, %%gs" : : "rm" (force_sel));
sel_pre_sched = force_sel;
local = read_base(GS);
/*
* Signal delivery is quite likely to change a selector
* of 1, 2, or 3 back to 0 due to IRET being defective.
*/
asm volatile ("mov %0, %%gs" : : "rm" (force_sel));
} else {
asm volatile ("mov %%gs, %0" : "=rm" (sel_pre_sched));
}
remote_base = remote;
ftx = 1;
syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
while (ftx != 0)
syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
asm volatile ("mov %%gs, %0" : "=rm" (sel_post_sched));
base = read_base(GS);
if (base == local && sel_pre_sched == sel_post_sched) {
printf("[OK]\tGS/BASE remained 0x%hx/0x%lx\n",
sel_pre_sched, local);
} else if (base == local && sel_pre_sched >= 1 && sel_pre_sched <= 3 &&
sel_post_sched == 0) {
/*
* IRET is misdesigned and will squash selectors 1, 2, or 3
* to zero. Don't fail the test just because this happened.
*/
printf("[OK]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx because IRET is defective\n",
sel_pre_sched, local, sel_post_sched, base);
} else {
nerrs++;
printf("[FAIL]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx\n",
sel_pre_sched, local, sel_post_sched, base);
}
}
static void test_unexpected_base(void)
{
unsigned long base;
printf("[RUN]\tARCH_SET_GS(0), clear gs, then manipulate GSBASE in a different thread\n");
if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)
err(1, "ARCH_SET_GS");
asm volatile ("mov %0, %%gs" : : "rm" ((unsigned short)0));
ftx = 2;
syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
while (ftx != 0)
syscall(SYS_futex, &ftx, FUTEX_WAIT, 1, NULL, NULL, 0);
base = read_base(GS);
if (base == 0) {
printf("[OK]\tGSBASE remained 0\n");
} else {
nerrs++;
printf("[FAIL]\tGSBASE changed to 0x%lx\n", base);
}
}
#define USER_REGS_OFFSET(r) offsetof(struct user_regs_struct, r)
static void test_ptrace_write_gs_read_base(void)
{
int status;
pid_t child = fork();
if (child < 0)
err(1, "fork");
if (child == 0) {
printf("[RUN]\tPTRACE_POKE GS, read GSBASE back\n");
printf("[RUN]\tARCH_SET_GS to 1\n");
if (syscall(SYS_arch_prctl, ARCH_SET_GS, 1) != 0)
err(1, "ARCH_SET_GS");
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)
err(1, "PTRACE_TRACEME");
raise(SIGTRAP);
_exit(0);
}
wait(&status);
if (WSTOPSIG(status) == SIGTRAP) {
unsigned long base;
unsigned long gs_offset = USER_REGS_OFFSET(gs);
unsigned long base_offset = USER_REGS_OFFSET(gs_base);
/* Read the initial base. It should be 1. */
base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);
if (base == 1) {
printf("[OK]\tGSBASE started at 1\n");
} else {
nerrs++;
printf("[FAIL]\tGSBASE started at 0x%lx\n", base);
}
printf("[RUN]\tSet GS = 0x7, read GSBASE\n");
/* Poke an LDT selector into GS. */
if (ptrace(PTRACE_POKEUSER, child, gs_offset, 0x7) != 0)
err(1, "PTRACE_POKEUSER");
/* And read the base. */
base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);
if (base == 0 || base == 1) {
printf("[OK]\tGSBASE reads as 0x%lx with invalid GS\n", base);
} else {
nerrs++;
printf("[FAIL]\tGSBASE=0x%lx (should be 0 or 1)\n", base);
}
}
ptrace(PTRACE_CONT, child, NULL, NULL);
wait(&status);
if (!WIFEXITED(status))
printf("[WARN]\tChild didn't exit cleanly.\n");
}
static void test_ptrace_write_gsbase(void)
{
int status;
pid_t child = fork();
if (child < 0)
err(1, "fork");
if (child == 0) {
printf("[RUN]\tPTRACE_POKE(), write GSBASE from ptracer\n");
*shared_scratch = load_gs();
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)
err(1, "PTRACE_TRACEME");
raise(SIGTRAP);
_exit(0);
}
wait(&status);
if (WSTOPSIG(status) == SIGTRAP) {
unsigned long gs, base;
unsigned long gs_offset = USER_REGS_OFFSET(gs);
unsigned long base_offset = USER_REGS_OFFSET(gs_base);
gs = ptrace(PTRACE_PEEKUSER, child, gs_offset, NULL);
if (gs != *shared_scratch) {
nerrs++;
printf("[FAIL]\tGS is not prepared with nonzero\n");
goto END;
}
if (ptrace(PTRACE_POKEUSER, child, base_offset, 0xFF) != 0)
err(1, "PTRACE_POKEUSER");
gs = ptrace(PTRACE_PEEKUSER, child, gs_offset, NULL);
base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);
/*
* In a non-FSGSBASE system, the nonzero selector will load
* GSBASE (again). But what is tested here is whether the
* selector value is changed or not by the GSBASE write in
* a ptracer.
*/
if (gs != *shared_scratch) {
nerrs++;
printf("[FAIL]\tGS changed to %lx\n", gs);
/*
* On older kernels, poking a nonzero value into the
* base would zero the selector. On newer kernels,
* this behavior has changed -- poking the base
* changes only the base and, if FSGSBASE is not
* available, this may have no effect once the tracee
* is resumed.
*/
if (gs == 0)
printf("\tNote: this is expected behavior on older kernels.\n");
} else if (have_fsgsbase && (base != 0xFF)) {
nerrs++;
printf("[FAIL]\tGSBASE changed to %lx\n", base);
} else {
printf("[OK]\tGS remained 0x%hx", *shared_scratch);
if (have_fsgsbase)
printf(" and GSBASE changed to 0xFF");
printf("\n");
}
}
END:
ptrace(PTRACE_CONT, child, NULL, NULL);
wait(&status);
if (!WIFEXITED(status))
printf("[WARN]\tChild didn't exit cleanly.\n");
}
int main()
{
pthread_t thread;
shared_scratch = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
/* Do these tests before we have an LDT. */
test_ptrace_write_gs_read_base();
/* Probe FSGSBASE */
sethandler(SIGILL, sigill, 0);
if (sigsetjmp(jmpbuf, 1) == 0) {
rdfsbase();
have_fsgsbase = true;
printf("\tFSGSBASE instructions are enabled\n");
} else {
printf("\tFSGSBASE instructions are disabled\n");
}
clearhandler(SIGILL);
sethandler(SIGSEGV, sigsegv, 0);
check_gs_value(0);
check_gs_value(1);
check_gs_value(0x200000000);
check_gs_value(0);
check_gs_value(0x200000000);
check_gs_value(1);
for (int sched = 0; sched < 2; sched++) {
mov_0_gs(0, !!sched);
mov_0_gs(1, !!sched);
mov_0_gs(0x200000000, !!sched);
}
/* Set up for multithreading. */
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
err(1, "sched_setaffinity to CPU 0"); /* should never fail */
if (pthread_create(&thread, 0, threadproc, 0) != 0)
err(1, "pthread_create");
static unsigned long bases_with_hard_zero[] = {
0, HARD_ZERO, 1, 0x200000000,
};
for (int local = 0; local < 4; local++) {
for (int remote = 0; remote < 4; remote++) {
for (unsigned short s = 0; s < 5; s++) {
unsigned short sel = s;
if (s == 4)
asm ("mov %%ss, %0" : "=rm" (sel));
set_gs_and_switch_to(
bases_with_hard_zero[local],
sel,
bases_with_hard_zero[remote]);
}
}
}
test_unexpected_base();
if (have_fsgsbase) {
unsigned short ss;
asm volatile ("mov %%ss, %0" : "=rm" (ss));
test_wrbase(0, 0);
test_wrbase(0, 1);
test_wrbase(0, 0x200000000);
test_wrbase(0, 0xffffffffffffffff);
test_wrbase(ss, 0);
test_wrbase(ss, 1);
test_wrbase(ss, 0x200000000);
test_wrbase(ss, 0xffffffffffffffff);
}
ftx = 3; /* Kill the thread. */
syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
if (pthread_join(thread, NULL) != 0)
err(1, "pthread_join");
test_ptrace_write_gsbase();
return nerrs == 0 ? 0 : 1;
}
@@ -0,0 +1,244 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fsgsbase_restore.c, test ptrace vs fsgsbase
* Copyright (c) 2020 Andy Lutomirski
*
* This test case simulates a tracer redirecting tracee execution to
* a function and then restoring tracee state using PTRACE_GETREGS and
* PTRACE_SETREGS. This is similar to what gdb does when doing
* 'p func()'. The catch is that this test has the called function
* modify a segment register. This makes sure that ptrace correctly
* restores segment state when using PTRACE_SETREGS.
*
* This is not part of fsgsbase.c, because that test is 64-bit only.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <err.h>
#include <sys/user.h>
#include <asm/prctl.h>
#include <sys/prctl.h>
#include <asm/ldt.h>
#include <sys/mman.h>
#include <stddef.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <stdint.h>
#define EXPECTED_VALUE 0x1337f00d
#ifdef __x86_64__
# define SEG "%gs"
#else
# define SEG "%fs"
#endif
/*
* Defined in clang_helpers_[32|64].S, because unlike gcc, clang inline asm does
* not support segmentation prefixes.
*/
unsigned int dereference_seg_base(void);
static void init_seg(void)
{
unsigned int *target = mmap(
NULL, sizeof(unsigned int),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
if (target == MAP_FAILED)
err(1, "mmap");
*target = EXPECTED_VALUE;
printf("\tsegment base address = 0x%lx\n", (unsigned long)target);
struct user_desc desc = {
.entry_number = 0,
.base_addr = (unsigned int)(uintptr_t)target,
.limit = sizeof(unsigned int) - 1,
.seg_32bit = 1,
.contents = 0, /* Data, grow-up */
.read_exec_only = 0,
.limit_in_pages = 0,
.seg_not_present = 0,
.useable = 0
};
if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {
printf("\tusing LDT slot 0\n");
asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7));
} else {
/* No modify_ldt for us (configured out, perhaps) */
struct user_desc *low_desc = mmap(
NULL, sizeof(desc),
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
memcpy(low_desc, &desc, sizeof(desc));
low_desc->entry_number = -1;
/* 32-bit set_thread_area */
long ret;
asm volatile ("int $0x80"
: "=a" (ret), "+m" (*low_desc)
: "a" (243), "b" (low_desc)
#ifdef __x86_64__
: "r8", "r9", "r10", "r11"
#endif
);
memcpy(&desc, low_desc, sizeof(desc));
munmap(low_desc, sizeof(desc));
if (ret != 0) {
printf("[NOTE]\tcould not create a segment -- can't test anything\n");
exit(0);
}
printf("\tusing GDT slot %d\n", desc.entry_number);
unsigned short sel = (unsigned short)((desc.entry_number << 3) | 0x3);
asm volatile ("mov %0, %" SEG :: "rm" (sel));
}
}
static void tracee_zap_segment(void)
{
/*
* The tracer will redirect execution here. This is meant to
* work like gdb's 'p func()' feature. The tricky bit is that
* we modify a segment register in order to make sure that ptrace
* can correctly restore segment registers.
*/
printf("\tTracee: in tracee_zap_segment()\n");
/*
* Write a nonzero selector with base zero to the segment register.
* Using a null selector would defeat the test on AMD pre-Zen2
* CPUs, as such CPUs don't clear the base when loading a null
* selector.
*/
unsigned short sel;
asm volatile ("mov %%ss, %0\n\t"
"mov %0, %" SEG
: "=rm" (sel));
pid_t pid = getpid(), tid = syscall(SYS_gettid);
printf("\tTracee is going back to sleep\n");
syscall(SYS_tgkill, pid, tid, SIGSTOP);
/* Should not get here. */
while (true) {
printf("[FAIL]\tTracee hit unreachable code\n");
pause();
}
}
int main()
{
printf("\tSetting up a segment\n");
init_seg();
unsigned int val = dereference_seg_base();
if (val != EXPECTED_VALUE) {
printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
return 1;
}
printf("[OK]\tThe segment points to the right place.\n");
pid_t chld = fork();
if (chld < 0)
err(1, "fork");
if (chld == 0) {
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0, 0);
if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
err(1, "PTRACE_TRACEME");
pid_t pid = getpid(), tid = syscall(SYS_gettid);
printf("\tTracee will take a nap until signaled\n");
syscall(SYS_tgkill, pid, tid, SIGSTOP);
printf("\tTracee was resumed. Will re-check segment.\n");
val = dereference_seg_base();
if (val != EXPECTED_VALUE) {
printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
exit(1);
}
printf("[OK]\tThe segment points to the right place.\n");
exit(0);
}
int status;
/* Wait for SIGSTOP. */
if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
err(1, "waitpid");
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, chld, NULL, &regs) != 0)
err(1, "PTRACE_GETREGS");
#ifdef __x86_64__
printf("\tChild GS=0x%lx, GSBASE=0x%lx\n", (unsigned long)regs.gs, (unsigned long)regs.gs_base);
#else
printf("\tChild FS=0x%lx\n", (unsigned long)regs.xfs);
#endif
struct user_regs_struct regs2 = regs;
#ifdef __x86_64__
regs2.rip = (unsigned long)tracee_zap_segment;
regs2.rsp -= 128; /* Don't clobber the redzone. */
#else
regs2.eip = (unsigned long)tracee_zap_segment;
#endif
printf("\tTracer: redirecting tracee to tracee_zap_segment()\n");
if (ptrace(PTRACE_SETREGS, chld, NULL, &regs2) != 0)
err(1, "PTRACE_GETREGS");
if (ptrace(PTRACE_CONT, chld, NULL, NULL) != 0)
err(1, "PTRACE_GETREGS");
/* Wait for SIGSTOP. */
if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
err(1, "waitpid");
printf("\tTracer: restoring tracee state\n");
if (ptrace(PTRACE_SETREGS, chld, NULL, &regs) != 0)
err(1, "PTRACE_GETREGS");
if (ptrace(PTRACE_DETACH, chld, NULL, NULL) != 0)
err(1, "PTRACE_GETREGS");
/* Wait for SIGSTOP. */
if (waitpid(chld, &status, 0) != chld)
err(1, "waitpid");
if (WIFSIGNALED(status)) {
printf("[FAIL]\tTracee crashed\n");
return 1;
}
if (!WIFEXITED(status)) {
printf("[FAIL]\tTracee stopped for an unexpected reason: %d\n", status);
return 1;
}
int exitcode = WEXITSTATUS(status);
if (exitcode != 0) {
printf("[FAIL]\tTracee reported failure\n");
return 1;
}
printf("[OK]\tAll is well.\n");
return 0;
}
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef __SELFTESTS_X86_HELPERS_H
#define __SELFTESTS_X86_HELPERS_H
#include <signal.h>
#include <string.h>
#include <asm/processor-flags.h>
#include "kselftest.h"
static inline unsigned long get_eflags(void)
{
#ifdef __x86_64__
return __builtin_ia32_readeflags_u64();
#else
return __builtin_ia32_readeflags_u32();
#endif
}
static inline void set_eflags(unsigned long eflags)
{
#ifdef __x86_64__
__builtin_ia32_writeeflags_u64(eflags);
#else
__builtin_ia32_writeeflags_u32(eflags);
#endif
}
static inline 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))
ksft_exit_fail_msg("sigaction failed");
}
static inline 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))
ksft_exit_fail_msg("sigaction failed");
}
#endif /* __SELFTESTS_X86_HELPERS_H */
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ioperm.c - Test case for ioperm(2)
* Copyright (c) 2015 Andrew Lutomirski
*/
#define _GNU_SOURCE
#include <err.h>
#include <stdio.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <sched.h>
#include <sys/io.h>
#include "helpers.h"
static int nerrs = 0;
static jmp_buf jmpbuf;
static void sigsegv(int sig, siginfo_t *si, void *ctx_void)
{
siglongjmp(jmpbuf, 1);
}
static bool try_outb(unsigned short port)
{
sethandler(SIGSEGV, sigsegv, SA_RESETHAND);
if (sigsetjmp(jmpbuf, 1) != 0) {
return false;
} else {
asm volatile ("outb %%al, %w[port]"
: : [port] "Nd" (port), "a" (0));
return true;
}
clearhandler(SIGSEGV);
}
static void expect_ok(unsigned short port)
{
if (!try_outb(port)) {
printf("[FAIL]\toutb to 0x%02hx failed\n", port);
exit(1);
}
printf("[OK]\toutb to 0x%02hx worked\n", port);
}
static void expect_gp(unsigned short port)
{
if (try_outb(port)) {
printf("[FAIL]\toutb to 0x%02hx worked\n", port);
exit(1);
}
printf("[OK]\toutb to 0x%02hx failed\n", port);
}
int main(void)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
err(1, "sched_setaffinity to CPU 0");
expect_gp(0x80);
expect_gp(0xed);
/*
* Probe for ioperm support. Note that clearing ioperm bits
* works even as nonroot.
*/
printf("[RUN]\tenable 0x80\n");
if (ioperm(0x80, 1, 1) != 0) {
printf("[OK]\tioperm(0x80, 1, 1) failed (%d) -- try running as root\n",
errno);
return 0;
}
expect_ok(0x80);
expect_gp(0xed);
printf("[RUN]\tdisable 0x80\n");
if (ioperm(0x80, 1, 0) != 0) {
printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno);
return 1;
}
expect_gp(0x80);
expect_gp(0xed);
/* Make sure that fork() preserves ioperm. */
if (ioperm(0x80, 1, 1) != 0) {
printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno);
return 1;
}
pid_t child = fork();
if (child == -1)
err(1, "fork");
if (child == 0) {
printf("[RUN]\tchild: check that we inherited permissions\n");
expect_ok(0x80);
expect_gp(0xed);
printf("[RUN]\tchild: Extend permissions to 0x81\n");
if (ioperm(0x81, 1, 1) != 0) {
printf("[FAIL]\tioperm(0x81, 1, 1) failed (%d)", errno);
return 1;
}
printf("[RUN]\tchild: Drop permissions to 0x80\n");
if (ioperm(0x80, 1, 0) != 0) {
printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno);
return 1;
}
expect_gp(0x80);
return 0;
} else {
int status;
if (waitpid(child, &status, 0) != child ||
!WIFEXITED(status)) {
printf("[FAIL]\tChild died\n");
nerrs++;
} else if (WEXITSTATUS(status) != 0) {
printf("[FAIL]\tChild failed\n");
nerrs++;
} else {
printf("[OK]\tChild succeeded\n");
}
}
/* Verify that the child dropping 0x80 did not affect the parent */
printf("\tVerify that unsharing the bitmap worked\n");
expect_ok(0x80);
/* Test the capability checks. */
printf("\tDrop privileges\n");
if (setresuid(1, 1, 1) != 0) {
printf("[WARN]\tDropping privileges failed\n");
return 0;
}
printf("[RUN]\tdisable 0x80\n");
if (ioperm(0x80, 1, 0) != 0) {
printf("[FAIL]\tioperm(0x80, 1, 0) failed (%d)", errno);
return 1;
}
printf("[OK]\tit worked\n");
printf("[RUN]\tenable 0x80 again\n");
if (ioperm(0x80, 1, 1) == 0) {
printf("[FAIL]\tit succeeded but should have failed.\n");
return 1;
}
printf("[OK]\tit failed\n");
return 0;
}
@@ -0,0 +1,260 @@
// SPDX-License-Identifier: GPL-2.0
/*
* iopl.c - Test case for a Linux on Xen 64-bit bug
* Copyright (c) 2015 Andrew Lutomirski
*/
#define _GNU_SOURCE
#include <err.h>
#include <stdio.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <sched.h>
#include <sys/io.h>
#include "helpers.h"
static int nerrs = 0;
static jmp_buf jmpbuf;
static void sigsegv(int sig, siginfo_t *si, void *ctx_void)
{
siglongjmp(jmpbuf, 1);
}
static bool try_outb(unsigned short port)
{
sethandler(SIGSEGV, sigsegv, SA_RESETHAND);
if (sigsetjmp(jmpbuf, 1) != 0) {
return false;
} else {
asm volatile ("outb %%al, %w[port]"
: : [port] "Nd" (port), "a" (0));
return true;
}
clearhandler(SIGSEGV);
}
static void expect_ok_outb(unsigned short port)
{
if (!try_outb(port)) {
printf("[FAIL]\toutb to 0x%02hx failed\n", port);
exit(1);
}
printf("[OK]\toutb to 0x%02hx worked\n", port);
}
static void expect_gp_outb(unsigned short port)
{
if (try_outb(port)) {
printf("[FAIL]\toutb to 0x%02hx worked\n", port);
nerrs++;
}
printf("[OK]\toutb to 0x%02hx failed\n", port);
}
#define RET_FAULTED 0
#define RET_FAIL 1
#define RET_EMUL 2
static int try_cli(void)
{
unsigned long flags;
sethandler(SIGSEGV, sigsegv, SA_RESETHAND);
if (sigsetjmp(jmpbuf, 1) != 0) {
return RET_FAULTED;
} else {
asm volatile("cli; pushf; pop %[flags]"
: [flags] "=rm" (flags));
/* X86_FLAGS_IF */
if (!(flags & (1 << 9)))
return RET_FAIL;
else
return RET_EMUL;
}
clearhandler(SIGSEGV);
}
static int try_sti(bool irqs_off)
{
unsigned long flags;
sethandler(SIGSEGV, sigsegv, SA_RESETHAND);
if (sigsetjmp(jmpbuf, 1) != 0) {
return RET_FAULTED;
} else {
asm volatile("sti; pushf; pop %[flags]"
: [flags] "=rm" (flags));
/* X86_FLAGS_IF */
if (irqs_off && (flags & (1 << 9)))
return RET_FAIL;
else
return RET_EMUL;
}
clearhandler(SIGSEGV);
}
static void expect_gp_sti(bool irqs_off)
{
int ret = try_sti(irqs_off);
switch (ret) {
case RET_FAULTED:
printf("[OK]\tSTI faulted\n");
break;
case RET_EMUL:
printf("[OK]\tSTI NOPped\n");
break;
default:
printf("[FAIL]\tSTI worked\n");
nerrs++;
}
}
/*
* Returns whether it managed to disable interrupts.
*/
static bool test_cli(void)
{
int ret = try_cli();
switch (ret) {
case RET_FAULTED:
printf("[OK]\tCLI faulted\n");
break;
case RET_EMUL:
printf("[OK]\tCLI NOPped\n");
break;
default:
printf("[FAIL]\tCLI worked\n");
nerrs++;
return true;
}
return false;
}
int main(void)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
err(1, "sched_setaffinity to CPU 0");
/* Probe for iopl support. Note that iopl(0) works even as nonroot. */
switch(iopl(3)) {
case 0:
break;
case -ENOSYS:
printf("[OK]\tiopl() nor supported\n");
return 0;
default:
printf("[OK]\tiopl(3) failed (%d) -- try running as root\n",
errno);
return 0;
}
/* Make sure that CLI/STI are blocked even with IOPL level 3 */
expect_gp_sti(test_cli());
expect_ok_outb(0x80);
/* Establish an I/O bitmap to test the restore */
if (ioperm(0x80, 1, 1) != 0)
err(1, "ioperm(0x80, 1, 1) failed\n");
/* Restore our original state prior to starting the fork test. */
if (iopl(0) != 0)
err(1, "iopl(0)");
/*
* Verify that IOPL emulation is disabled and the I/O bitmap still
* works.
*/
expect_ok_outb(0x80);
expect_gp_outb(0xed);
/* Drop the I/O bitmap */
if (ioperm(0x80, 1, 0) != 0)
err(1, "ioperm(0x80, 1, 0) failed\n");
pid_t child = fork();
if (child == -1)
err(1, "fork");
if (child == 0) {
printf("\tchild: set IOPL to 3\n");
if (iopl(3) != 0)
err(1, "iopl");
printf("[RUN]\tchild: write to 0x80\n");
asm volatile ("outb %%al, $0x80" : : "a" (0));
return 0;
} else {
int status;
if (waitpid(child, &status, 0) != child ||
!WIFEXITED(status)) {
printf("[FAIL]\tChild died\n");
nerrs++;
} else if (WEXITSTATUS(status) != 0) {
printf("[FAIL]\tChild failed\n");
nerrs++;
} else {
printf("[OK]\tChild succeeded\n");
}
}
printf("[RUN]\tparent: write to 0x80 (should fail)\n");
expect_gp_outb(0x80);
expect_gp_sti(test_cli());
/* Test the capability checks. */
printf("\tiopl(3)\n");
if (iopl(3) != 0)
err(1, "iopl(3)");
printf("\tDrop privileges\n");
if (setresuid(1, 1, 1) != 0) {
printf("[WARN]\tDropping privileges failed\n");
goto done;
}
printf("[RUN]\tiopl(3) unprivileged but with IOPL==3\n");
if (iopl(3) != 0) {
printf("[FAIL]\tiopl(3) should work if iopl is already 3 even if unprivileged\n");
nerrs++;
}
printf("[RUN]\tiopl(0) unprivileged\n");
if (iopl(0) != 0) {
printf("[FAIL]\tiopl(0) should work if iopl is already 3 even if unprivileged\n");
nerrs++;
}
printf("[RUN]\tiopl(3) unprivileged\n");
if (iopl(3) == 0) {
printf("[FAIL]\tiopl(3) should fail if when unprivileged if iopl==0\n");
nerrs++;
} else {
printf("[OK]\tFailed as expected\n");
}
done:
return nerrs ? 1 : 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,917 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ldt_gdt.c - Test cases for LDT and GDT access
* Copyright (c) 2015 Andrew Lutomirski
*/
#define _GNU_SOURCE
#include <err.h>
#include <stdio.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <asm/ldt.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <pthread.h>
#include <sched.h>
#include <linux/futex.h>
#include <sys/mman.h>
#include <asm/prctl.h>
#include <sys/prctl.h>
#include "helpers.h"
#define AR_ACCESSED (1<<8)
#define AR_TYPE_RODATA (0 * (1<<9))
#define AR_TYPE_RWDATA (1 * (1<<9))
#define AR_TYPE_RODATA_EXPDOWN (2 * (1<<9))
#define AR_TYPE_RWDATA_EXPDOWN (3 * (1<<9))
#define AR_TYPE_XOCODE (4 * (1<<9))
#define AR_TYPE_XRCODE (5 * (1<<9))
#define AR_TYPE_XOCODE_CONF (6 * (1<<9))
#define AR_TYPE_XRCODE_CONF (7 * (1<<9))
#define AR_DPL3 (3 * (1<<13))
#define AR_S (1 << 12)
#define AR_P (1 << 15)
#define AR_AVL (1 << 20)
#define AR_L (1 << 21)
#define AR_DB (1 << 22)
#define AR_G (1 << 23)
#ifdef __x86_64__
# define INT80_CLOBBERS "r8", "r9", "r10", "r11"
#else
# define INT80_CLOBBERS
#endif
static int nerrs;
/* Points to an array of 1024 ints, each holding its own index. */
static const unsigned int *counter_page;
static struct user_desc *low_user_desc;
static struct user_desc *low_user_desc_clear; /* Use to delete GDT entry */
static int gdt_entry_num;
static void check_invalid_segment(uint16_t index, int ldt)
{
uint32_t has_limit = 0, has_ar = 0, limit, ar;
uint32_t selector = (index << 3) | (ldt << 2) | 3;
asm ("lsl %[selector], %[limit]\n\t"
"jnz 1f\n\t"
"movl $1, %[has_limit]\n\t"
"1:"
: [limit] "=r" (limit), [has_limit] "+rm" (has_limit)
: [selector] "r" (selector));
asm ("larl %[selector], %[ar]\n\t"
"jnz 1f\n\t"
"movl $1, %[has_ar]\n\t"
"1:"
: [ar] "=r" (ar), [has_ar] "+rm" (has_ar)
: [selector] "r" (selector));
if (has_limit || has_ar) {
printf("[FAIL]\t%s entry %hu is valid but should be invalid\n",
(ldt ? "LDT" : "GDT"), index);
nerrs++;
} else {
printf("[OK]\t%s entry %hu is invalid\n",
(ldt ? "LDT" : "GDT"), index);
}
}
static void check_valid_segment(uint16_t index, int ldt,
uint32_t expected_ar, uint32_t expected_limit,
bool verbose)
{
uint32_t has_limit = 0, has_ar = 0, limit, ar;
uint32_t selector = (index << 3) | (ldt << 2) | 3;
asm ("lsl %[selector], %[limit]\n\t"
"jnz 1f\n\t"
"movl $1, %[has_limit]\n\t"
"1:"
: [limit] "=r" (limit), [has_limit] "+rm" (has_limit)
: [selector] "r" (selector));
asm ("larl %[selector], %[ar]\n\t"
"jnz 1f\n\t"
"movl $1, %[has_ar]\n\t"
"1:"
: [ar] "=r" (ar), [has_ar] "+rm" (has_ar)
: [selector] "r" (selector));
if (!has_limit || !has_ar) {
printf("[FAIL]\t%s entry %hu is invalid but should be valid\n",
(ldt ? "LDT" : "GDT"), index);
nerrs++;
return;
}
/* The SDM says "bits 19:16 are undefined". Thanks. */
ar &= ~0xF0000;
/*
* NB: Different Linux versions do different things with the
* accessed bit in set_thread_area().
*/
if (ar != expected_ar && ar != (expected_ar | AR_ACCESSED)) {
printf("[FAIL]\t%s entry %hu has AR 0x%08X but expected 0x%08X\n",
(ldt ? "LDT" : "GDT"), index, ar, expected_ar);
nerrs++;
} else if (limit != expected_limit) {
printf("[FAIL]\t%s entry %hu has limit 0x%08X but expected 0x%08X\n",
(ldt ? "LDT" : "GDT"), index, limit, expected_limit);
nerrs++;
} else if (verbose) {
printf("[OK]\t%s entry %hu has AR 0x%08X and limit 0x%08X\n",
(ldt ? "LDT" : "GDT"), index, ar, limit);
}
}
static bool install_valid_mode(const struct user_desc *d, uint32_t ar,
bool oldmode, bool ldt)
{
struct user_desc desc = *d;
int ret;
if (!ldt) {
#ifndef __i386__
/* No point testing set_thread_area in a 64-bit build */
return false;
#endif
if (!gdt_entry_num)
return false;
desc.entry_number = gdt_entry_num;
ret = syscall(SYS_set_thread_area, &desc);
} else {
ret = syscall(SYS_modify_ldt, oldmode ? 1 : 0x11,
&desc, sizeof(desc));
if (ret < -1)
errno = -ret;
if (ret != 0 && errno == ENOSYS) {
printf("[OK]\tmodify_ldt returned -ENOSYS\n");
return false;
}
}
if (ret == 0) {
uint32_t limit = desc.limit;
if (desc.limit_in_pages)
limit = (limit << 12) + 4095;
check_valid_segment(desc.entry_number, ldt, ar, limit, true);
return true;
} else {
if (desc.seg_32bit) {
printf("[FAIL]\tUnexpected %s failure %d\n",
ldt ? "modify_ldt" : "set_thread_area",
errno);
nerrs++;
return false;
} else {
printf("[OK]\t%s rejected 16 bit segment\n",
ldt ? "modify_ldt" : "set_thread_area");
return false;
}
}
}
static bool install_valid(const struct user_desc *desc, uint32_t ar)
{
bool ret = install_valid_mode(desc, ar, false, true);
if (desc->contents <= 1 && desc->seg_32bit &&
!desc->seg_not_present) {
/* Should work in the GDT, too. */
install_valid_mode(desc, ar, false, false);
}
return ret;
}
static void install_invalid(const struct user_desc *desc, bool oldmode)
{
int ret = syscall(SYS_modify_ldt, oldmode ? 1 : 0x11,
desc, sizeof(*desc));
if (ret < -1)
errno = -ret;
if (ret == 0) {
check_invalid_segment(desc->entry_number, 1);
} else if (errno == ENOSYS) {
printf("[OK]\tmodify_ldt returned -ENOSYS\n");
} else {
if (desc->seg_32bit) {
printf("[FAIL]\tUnexpected modify_ldt failure %d\n",
errno);
nerrs++;
} else {
printf("[OK]\tmodify_ldt rejected 16 bit segment\n");
}
}
}
static int safe_modify_ldt(int func, struct user_desc *ptr,
unsigned long bytecount)
{
int ret = syscall(SYS_modify_ldt, 0x11, ptr, bytecount);
if (ret < -1)
errno = -ret;
return ret;
}
static void fail_install(struct user_desc *desc)
{
if (safe_modify_ldt(0x11, desc, sizeof(*desc)) == 0) {
printf("[FAIL]\tmodify_ldt accepted a bad descriptor\n");
nerrs++;
} else if (errno == ENOSYS) {
printf("[OK]\tmodify_ldt returned -ENOSYS\n");
} else {
printf("[OK]\tmodify_ldt failure %d\n", errno);
}
}
static void do_simple_tests(void)
{
struct user_desc desc = {
.entry_number = 0,
.base_addr = 0,
.limit = 10,
.seg_32bit = 1,
.contents = 2, /* Code, not conforming */
.read_exec_only = 0,
.limit_in_pages = 0,
.seg_not_present = 0,
.useable = 0
};
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | AR_S | AR_P | AR_DB);
desc.limit_in_pages = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE |
AR_S | AR_P | AR_DB | AR_G);
check_invalid_segment(1, 1);
desc.entry_number = 2;
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE |
AR_S | AR_P | AR_DB | AR_G);
check_invalid_segment(1, 1);
desc.base_addr = 0xf0000000;
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE |
AR_S | AR_P | AR_DB | AR_G);
desc.useable = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE |
AR_S | AR_P | AR_DB | AR_G | AR_AVL);
desc.seg_not_present = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE |
AR_S | AR_DB | AR_G | AR_AVL);
desc.seg_32bit = 0;
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE |
AR_S | AR_G | AR_AVL);
desc.seg_32bit = 1;
desc.contents = 0;
install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA |
AR_S | AR_DB | AR_G | AR_AVL);
desc.read_exec_only = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA |
AR_S | AR_DB | AR_G | AR_AVL);
desc.contents = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA_EXPDOWN |
AR_S | AR_DB | AR_G | AR_AVL);
desc.read_exec_only = 0;
desc.limit_in_pages = 0;
install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA_EXPDOWN |
AR_S | AR_DB | AR_AVL);
desc.contents = 3;
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE_CONF |
AR_S | AR_DB | AR_AVL);
desc.read_exec_only = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_XOCODE_CONF |
AR_S | AR_DB | AR_AVL);
desc.read_exec_only = 0;
desc.contents = 2;
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE |
AR_S | AR_DB | AR_AVL);
desc.read_exec_only = 1;
#ifdef __x86_64__
desc.lm = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_XOCODE |
AR_S | AR_DB | AR_AVL);
desc.lm = 0;
#endif
bool entry1_okay = install_valid(&desc, AR_DPL3 | AR_TYPE_XOCODE |
AR_S | AR_DB | AR_AVL);
if (entry1_okay) {
printf("[RUN]\tTest fork\n");
pid_t child = fork();
if (child == 0) {
nerrs = 0;
check_valid_segment(desc.entry_number, 1,
AR_DPL3 | AR_TYPE_XOCODE |
AR_S | AR_DB | AR_AVL, desc.limit,
true);
check_invalid_segment(1, 1);
exit(nerrs ? 1 : 0);
} else {
int status;
if (waitpid(child, &status, 0) != child ||
!WIFEXITED(status)) {
printf("[FAIL]\tChild died\n");
nerrs++;
} else if (WEXITSTATUS(status) != 0) {
printf("[FAIL]\tChild failed\n");
nerrs++;
} else {
printf("[OK]\tChild succeeded\n");
}
}
printf("[RUN]\tTest size\n");
int i;
for (i = 0; i < 8192; i++) {
desc.entry_number = i;
desc.limit = i;
if (safe_modify_ldt(0x11, &desc, sizeof(desc)) != 0) {
printf("[FAIL]\tFailed to install entry %d\n", i);
nerrs++;
break;
}
}
for (int j = 0; j < i; j++) {
check_valid_segment(j, 1, AR_DPL3 | AR_TYPE_XOCODE |
AR_S | AR_DB | AR_AVL, j, false);
}
printf("[DONE]\tSize test\n");
} else {
printf("[SKIP]\tSkipping fork and size tests because we have no LDT\n");
}
/* Test entry_number too high. */
desc.entry_number = 8192;
fail_install(&desc);
/* Test deletion and actions mistakeable for deletion. */
memset(&desc, 0, sizeof(desc));
install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | AR_S | AR_P);
desc.seg_not_present = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | AR_S);
desc.seg_not_present = 0;
desc.read_exec_only = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA | AR_S | AR_P);
desc.read_exec_only = 0;
desc.seg_not_present = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | AR_S);
desc.read_exec_only = 1;
desc.limit = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA | AR_S);
desc.limit = 0;
desc.base_addr = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA | AR_S);
desc.base_addr = 0;
install_invalid(&desc, false);
desc.seg_not_present = 0;
desc.seg_32bit = 1;
desc.read_exec_only = 0;
desc.limit = 0xfffff;
install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | AR_S | AR_P | AR_DB);
desc.limit_in_pages = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA | AR_S | AR_P | AR_DB | AR_G);
desc.read_exec_only = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA | AR_S | AR_P | AR_DB | AR_G);
desc.contents = 1;
desc.read_exec_only = 0;
install_valid(&desc, AR_DPL3 | AR_TYPE_RWDATA_EXPDOWN | AR_S | AR_P | AR_DB | AR_G);
desc.read_exec_only = 1;
install_valid(&desc, AR_DPL3 | AR_TYPE_RODATA_EXPDOWN | AR_S | AR_P | AR_DB | AR_G);
desc.limit = 0;
install_invalid(&desc, true);
}
/*
* 0: thread is idle
* 1: thread armed
* 2: thread should clear LDT entry 0
* 3: thread should exit
*/
static volatile unsigned int ftx;
static void *threadproc(void *ctx)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(1, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
err(1, "sched_setaffinity to CPU 1"); /* should never fail */
while (1) {
syscall(SYS_futex, &ftx, FUTEX_WAIT, 0, NULL, NULL, 0);
while (ftx != 2) {
if (ftx >= 3)
return NULL;
}
/* clear LDT entry 0 */
const struct user_desc desc = {};
if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) != 0)
err(1, "modify_ldt");
/* If ftx == 2, set it to zero. If ftx == 100, quit. */
unsigned int x = -2;
asm volatile ("lock xaddl %[x], %[ftx]" :
[x] "+r" (x), [ftx] "+m" (ftx));
if (x != 2)
return NULL;
}
}
#ifdef __i386__
#ifndef SA_RESTORE
#define SA_RESTORER 0x04000000
#endif
/*
* The UAPI header calls this 'struct sigaction', which conflicts with
* glibc. Sigh.
*/
struct fake_ksigaction {
void *handler; /* the real type is nasty */
unsigned long sa_flags;
void (*sa_restorer)(void);
unsigned char sigset[8];
};
static void fix_sa_restorer(int sig)
{
struct fake_ksigaction ksa;
if (syscall(SYS_rt_sigaction, sig, NULL, &ksa, 8) == 0) {
/*
* glibc has a nasty bug: it sometimes writes garbage to
* sa_restorer. This interacts quite badly with anything
* that fiddles with SS because it can trigger legacy
* stack switching. Patch it up. See:
*
* https://sourceware.org/bugzilla/show_bug.cgi?id=21269
*/
if (!(ksa.sa_flags & SA_RESTORER) && ksa.sa_restorer) {
ksa.sa_restorer = NULL;
if (syscall(SYS_rt_sigaction, sig, &ksa, NULL,
sizeof(ksa.sigset)) != 0)
err(1, "rt_sigaction");
}
}
}
#else
static void fix_sa_restorer(int sig)
{
/* 64-bit glibc works fine. */
}
#endif
static jmp_buf jmpbuf;
static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
{
siglongjmp(jmpbuf, 1);
}
static void do_multicpu_tests(void)
{
cpu_set_t cpuset;
pthread_t thread;
int failures = 0, iters = 5, i;
unsigned short orig_ss;
CPU_ZERO(&cpuset);
CPU_SET(1, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
printf("[SKIP]\tCannot set affinity to CPU 1\n");
return;
}
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
printf("[SKIP]\tCannot set affinity to CPU 0\n");
return;
}
sethandler(SIGSEGV, sigsegv, 0);
fix_sa_restorer(SIGSEGV);
#ifdef __i386__
/* True 32-bit kernels send SIGILL instead of SIGSEGV on IRET faults. */
sethandler(SIGILL, sigsegv, 0);
fix_sa_restorer(SIGILL);
#endif
printf("[RUN]\tCross-CPU LDT invalidation\n");
if (pthread_create(&thread, 0, threadproc, 0) != 0)
err(1, "pthread_create");
asm volatile ("mov %%ss, %0" : "=rm" (orig_ss));
for (i = 0; i < 5; i++) {
if (sigsetjmp(jmpbuf, 1) != 0)
continue;
/* Make sure the thread is ready after the last test. */
while (ftx != 0)
;
struct user_desc desc = {
.entry_number = 0,
.base_addr = 0,
.limit = 0xfffff,
.seg_32bit = 1,
.contents = 0, /* Data */
.read_exec_only = 0,
.limit_in_pages = 1,
.seg_not_present = 0,
.useable = 0
};
if (safe_modify_ldt(0x11, &desc, sizeof(desc)) != 0) {
if (errno != ENOSYS)
err(1, "modify_ldt");
printf("[SKIP]\tmodify_ldt unavailable\n");
break;
}
/* Arm the thread. */
ftx = 1;
syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
asm volatile ("mov %0, %%ss" : : "r" (0x7));
/* Go! */
ftx = 2;
while (ftx != 0)
;
/*
* On success, modify_ldt will segfault us synchronously,
* and we'll escape via siglongjmp.
*/
failures++;
asm volatile ("mov %0, %%ss" : : "rm" (orig_ss));
}
ftx = 100; /* Kill the thread. */
syscall(SYS_futex, &ftx, FUTEX_WAKE, 0, NULL, NULL, 0);
if (pthread_join(thread, NULL) != 0)
err(1, "pthread_join");
if (failures) {
printf("[FAIL]\t%d of %d iterations failed\n", failures, iters);
nerrs++;
} else {
printf("[OK]\tAll %d iterations succeeded\n", iters);
}
}
static int finish_exec_test(void)
{
/*
* Older kernel versions did inherit the LDT on exec() which is
* wrong because exec() starts from a clean state.
*/
check_invalid_segment(0, 1);
return nerrs ? 1 : 0;
}
static void do_exec_test(void)
{
printf("[RUN]\tTest exec\n");
struct user_desc desc = {
.entry_number = 0,
.base_addr = 0,
.limit = 42,
.seg_32bit = 1,
.contents = 2, /* Code, not conforming */
.read_exec_only = 0,
.limit_in_pages = 0,
.seg_not_present = 0,
.useable = 0
};
install_valid(&desc, AR_DPL3 | AR_TYPE_XRCODE | AR_S | AR_P | AR_DB);
pid_t child = fork();
if (child == 0) {
execl("/proc/self/exe", "ldt_gdt_test_exec", NULL);
printf("[FAIL]\tCould not exec self\n");
exit(1); /* exec failed */
} else {
int status;
if (waitpid(child, &status, 0) != child ||
!WIFEXITED(status)) {
printf("[FAIL]\tChild died\n");
nerrs++;
} else if (WEXITSTATUS(status) != 0) {
printf("[FAIL]\tChild failed\n");
nerrs++;
} else {
printf("[OK]\tChild succeeded\n");
}
}
}
static void setup_counter_page(void)
{
unsigned int *page = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
if (page == MAP_FAILED)
err(1, "mmap");
for (int i = 0; i < 1024; i++)
page[i] = i;
counter_page = page;
}
static int invoke_set_thread_area(void)
{
int ret;
asm volatile ("int $0x80"
: "=a" (ret), "+m" (low_user_desc) :
"a" (243), "b" (low_user_desc)
: INT80_CLOBBERS);
return ret;
}
static void setup_low_user_desc(void)
{
low_user_desc = mmap(NULL, 2 * sizeof(struct user_desc),
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_32BIT, -1, 0);
if (low_user_desc == MAP_FAILED)
err(1, "mmap");
low_user_desc->entry_number = -1;
low_user_desc->base_addr = (unsigned long)&counter_page[1];
low_user_desc->limit = 0xfffff;
low_user_desc->seg_32bit = 1;
low_user_desc->contents = 0; /* Data, grow-up*/
low_user_desc->read_exec_only = 0;
low_user_desc->limit_in_pages = 1;
low_user_desc->seg_not_present = 0;
low_user_desc->useable = 0;
if (invoke_set_thread_area() == 0) {
gdt_entry_num = low_user_desc->entry_number;
printf("[NOTE]\tset_thread_area is available; will use GDT index %d\n", gdt_entry_num);
} else {
printf("[NOTE]\tset_thread_area is unavailable\n");
}
low_user_desc_clear = low_user_desc + 1;
low_user_desc_clear->entry_number = gdt_entry_num;
low_user_desc_clear->read_exec_only = 1;
low_user_desc_clear->seg_not_present = 1;
}
static void test_gdt_invalidation(void)
{
if (!gdt_entry_num)
return; /* 64-bit only system -- we can't use set_thread_area */
unsigned short prev_sel;
unsigned short sel;
unsigned int eax;
const char *result;
#ifdef __x86_64__
unsigned long saved_base;
unsigned long new_base;
#endif
/* Test DS */
invoke_set_thread_area();
eax = 243;
sel = (gdt_entry_num << 3) | 3;
asm volatile ("movw %%ds, %[prev_sel]\n\t"
"movw %[sel], %%ds\n\t"
#ifdef __i386__
"pushl %%ebx\n\t"
#endif
"movl %[arg1], %%ebx\n\t"
"int $0x80\n\t" /* Should invalidate ds */
#ifdef __i386__
"popl %%ebx\n\t"
#endif
"movw %%ds, %[sel]\n\t"
"movw %[prev_sel], %%ds"
: [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
"+a" (eax)
: "m" (low_user_desc_clear),
[arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
: INT80_CLOBBERS);
if (sel != 0) {
result = "FAIL";
nerrs++;
} else {
result = "OK";
}
printf("[%s]\tInvalidate DS with set_thread_area: new DS = 0x%hx\n",
result, sel);
/* Test ES */
invoke_set_thread_area();
eax = 243;
sel = (gdt_entry_num << 3) | 3;
asm volatile ("movw %%es, %[prev_sel]\n\t"
"movw %[sel], %%es\n\t"
#ifdef __i386__
"pushl %%ebx\n\t"
#endif
"movl %[arg1], %%ebx\n\t"
"int $0x80\n\t" /* Should invalidate es */
#ifdef __i386__
"popl %%ebx\n\t"
#endif
"movw %%es, %[sel]\n\t"
"movw %[prev_sel], %%es"
: [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
"+a" (eax)
: "m" (low_user_desc_clear),
[arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
: INT80_CLOBBERS);
if (sel != 0) {
result = "FAIL";
nerrs++;
} else {
result = "OK";
}
printf("[%s]\tInvalidate ES with set_thread_area: new ES = 0x%hx\n",
result, sel);
/* Test FS */
invoke_set_thread_area();
eax = 243;
sel = (gdt_entry_num << 3) | 3;
#ifdef __x86_64__
syscall(SYS_arch_prctl, ARCH_GET_FS, &saved_base);
#endif
asm volatile ("movw %%fs, %[prev_sel]\n\t"
"movw %[sel], %%fs\n\t"
#ifdef __i386__
"pushl %%ebx\n\t"
#endif
"movl %[arg1], %%ebx\n\t"
"int $0x80\n\t" /* Should invalidate fs */
#ifdef __i386__
"popl %%ebx\n\t"
#endif
"movw %%fs, %[sel]\n\t"
: [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
"+a" (eax)
: "m" (low_user_desc_clear),
[arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
: INT80_CLOBBERS);
#ifdef __x86_64__
syscall(SYS_arch_prctl, ARCH_GET_FS, &new_base);
#endif
/* Restore FS/BASE for glibc */
asm volatile ("movw %[prev_sel], %%fs" : : [prev_sel] "rm" (prev_sel));
#ifdef __x86_64__
if (saved_base)
syscall(SYS_arch_prctl, ARCH_SET_FS, saved_base);
#endif
if (sel != 0) {
result = "FAIL";
nerrs++;
} else {
result = "OK";
}
printf("[%s]\tInvalidate FS with set_thread_area: new FS = 0x%hx\n",
result, sel);
#ifdef __x86_64__
if (sel == 0 && new_base != 0) {
nerrs++;
printf("[FAIL]\tNew FSBASE was 0x%lx\n", new_base);
} else {
printf("[OK]\tNew FSBASE was zero\n");
}
#endif
/* Test GS */
invoke_set_thread_area();
eax = 243;
sel = (gdt_entry_num << 3) | 3;
#ifdef __x86_64__
syscall(SYS_arch_prctl, ARCH_GET_GS, &saved_base);
#endif
asm volatile ("movw %%gs, %[prev_sel]\n\t"
"movw %[sel], %%gs\n\t"
#ifdef __i386__
"pushl %%ebx\n\t"
#endif
"movl %[arg1], %%ebx\n\t"
"int $0x80\n\t" /* Should invalidate gs */
#ifdef __i386__
"popl %%ebx\n\t"
#endif
"movw %%gs, %[sel]\n\t"
: [prev_sel] "=&r" (prev_sel), [sel] "+r" (sel),
"+a" (eax)
: "m" (low_user_desc_clear),
[arg1] "r" ((unsigned int)(unsigned long)low_user_desc_clear)
: INT80_CLOBBERS);
#ifdef __x86_64__
syscall(SYS_arch_prctl, ARCH_GET_GS, &new_base);
#endif
/* Restore GS/BASE for glibc */
asm volatile ("movw %[prev_sel], %%gs" : : [prev_sel] "rm" (prev_sel));
#ifdef __x86_64__
if (saved_base)
syscall(SYS_arch_prctl, ARCH_SET_GS, saved_base);
#endif
if (sel != 0) {
result = "FAIL";
nerrs++;
} else {
result = "OK";
}
printf("[%s]\tInvalidate GS with set_thread_area: new GS = 0x%hx\n",
result, sel);
#ifdef __x86_64__
if (sel == 0 && new_base != 0) {
nerrs++;
printf("[FAIL]\tNew GSBASE was 0x%lx\n", new_base);
} else {
printf("[OK]\tNew GSBASE was zero\n");
}
#endif
}
int main(int argc, char **argv)
{
if (argc == 1 && !strcmp(argv[0], "ldt_gdt_test_exec"))
return finish_exec_test();
setup_counter_page();
setup_low_user_desc();
do_simple_tests();
do_multicpu_tests();
do_exec_test();
test_gdt_invalidation();
return nerrs ? 1 : 0;
}
@@ -0,0 +1,274 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* mov_ss_trap.c: Exercise the bizarre side effects of a watchpoint on MOV SS
*
* This does MOV SS from a watchpointed address followed by various
* types of kernel entries. A MOV SS that hits a watchpoint will queue
* up a #DB trap but will not actually deliver that trap. The trap
* will be delivered after the next instruction instead. The CPU's logic
* seems to be:
*
* - Any fault: drop the pending #DB trap.
* - INT $N, INT3, INTO, SYSCALL, SYSENTER: enter the kernel and then
* deliver #DB.
* - ICEBP: enter the kernel but do not deliver the watchpoint trap
* - breakpoint: only one #DB is delivered (phew!)
*
* There are plenty of ways for a kernel to handle this incorrectly. This
* test tries to exercise all the cases.
*
* This should mostly cover CVE-2018-1087 and CVE-2018-8897.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <err.h>
#include <string.h>
#include <setjmp.h>
#include <sys/prctl.h>
#include "helpers.h"
#if __x86_64__
# define REG_IP REG_RIP
#else
# define REG_IP REG_EIP
#endif
unsigned short ss;
extern unsigned char breakpoint_insn[];
sigjmp_buf jmpbuf;
static void enable_watchpoint(void)
{
pid_t parent = getpid();
int status;
pid_t child = fork();
if (child < 0)
err(1, "fork");
if (child) {
if (waitpid(child, &status, 0) != child)
err(1, "waitpid for child");
} else {
unsigned long dr0, dr1, dr7;
dr0 = (unsigned long)&ss;
dr1 = (unsigned long)breakpoint_insn;
dr7 = ((1UL << 1) | /* G0 */
(3UL << 16) | /* RW0 = read or write */
(1UL << 18) | /* LEN0 = 2 bytes */
(1UL << 3)); /* G1, RW1 = insn */
if (ptrace(PTRACE_ATTACH, parent, NULL, NULL) != 0)
err(1, "PTRACE_ATTACH");
if (waitpid(parent, &status, 0) != parent)
err(1, "waitpid for child");
if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[0]), dr0) != 0)
err(1, "PTRACE_POKEUSER DR0");
if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[1]), dr1) != 0)
err(1, "PTRACE_POKEUSER DR1");
if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[7]), dr7) != 0)
err(1, "PTRACE_POKEUSER DR7");
printf("\tDR0 = %lx, DR1 = %lx, DR7 = %lx\n", dr0, dr1, dr7);
if (ptrace(PTRACE_DETACH, parent, NULL, NULL) != 0)
err(1, "PTRACE_DETACH");
exit(0);
}
}
static char const * const signames[] = {
[SIGSEGV] = "SIGSEGV",
[SIGBUS] = "SIBGUS",
[SIGTRAP] = "SIGTRAP",
[SIGILL] = "SIGILL",
};
static void sigtrap(int sig, siginfo_t *si, void *ctx_void)
{
ucontext_t *ctx = ctx_void;
printf("\tGot SIGTRAP with RIP=%lx, EFLAGS.RF=%d\n",
(unsigned long)ctx->uc_mcontext.gregs[REG_IP],
!!(ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_RF));
}
static void handle_and_return(int sig, siginfo_t *si, void *ctx_void)
{
ucontext_t *ctx = ctx_void;
printf("\tGot %s with RIP=%lx\n", signames[sig],
(unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
}
static void handle_and_longjmp(int sig, siginfo_t *si, void *ctx_void)
{
ucontext_t *ctx = ctx_void;
printf("\tGot %s with RIP=%lx\n", signames[sig],
(unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
siglongjmp(jmpbuf, 1);
}
int main()
{
unsigned long nr;
asm volatile ("mov %%ss, %[ss]" : [ss] "=m" (ss));
printf("\tSS = 0x%hx, &SS = 0x%p\n", ss, &ss);
if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0) == 0)
printf("\tPR_SET_PTRACER_ANY succeeded\n");
printf("\tSet up a watchpoint\n");
sethandler(SIGTRAP, sigtrap, 0);
enable_watchpoint();
printf("[RUN]\tRead from watched memory (should get SIGTRAP)\n");
asm volatile ("mov %[ss], %[tmp]" : [tmp] "=r" (nr) : [ss] "m" (ss));
printf("[RUN]\tMOV SS; INT3\n");
asm volatile ("mov %[ss], %%ss; int3" :: [ss] "m" (ss));
printf("[RUN]\tMOV SS; INT 3\n");
asm volatile ("mov %[ss], %%ss; .byte 0xcd, 0x3" :: [ss] "m" (ss));
printf("[RUN]\tMOV SS; CS CS INT3\n");
asm volatile ("mov %[ss], %%ss; .byte 0x2e, 0x2e; int3" :: [ss] "m" (ss));
printf("[RUN]\tMOV SS; CSx14 INT3\n");
asm volatile ("mov %[ss], %%ss; .fill 14,1,0x2e; int3" :: [ss] "m" (ss));
printf("[RUN]\tMOV SS; INT 4\n");
sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
asm volatile ("mov %[ss], %%ss; int $4" :: [ss] "m" (ss));
#ifdef __i386__
printf("[RUN]\tMOV SS; INTO\n");
sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
nr = -1;
asm volatile ("add $1, %[tmp]; mov %[ss], %%ss; into"
: [tmp] "+r" (nr) : [ss] "m" (ss));
#endif
if (sigsetjmp(jmpbuf, 1) == 0) {
printf("[RUN]\tMOV SS; ICEBP\n");
/* Some emulators (e.g. QEMU TCG) don't emulate ICEBP. */
sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
asm volatile ("mov %[ss], %%ss; .byte 0xf1" :: [ss] "m" (ss));
}
if (sigsetjmp(jmpbuf, 1) == 0) {
printf("[RUN]\tMOV SS; CLI\n");
sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
asm volatile ("mov %[ss], %%ss; cli" :: [ss] "m" (ss));
}
if (sigsetjmp(jmpbuf, 1) == 0) {
printf("[RUN]\tMOV SS; #PF\n");
sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
asm volatile ("mov %[ss], %%ss; mov (-1), %[tmp]"
: [tmp] "=r" (nr) : [ss] "m" (ss));
}
/*
* INT $1: if #DB has DPL=3 and there isn't special handling,
* then the kernel will die.
*/
if (sigsetjmp(jmpbuf, 1) == 0) {
printf("[RUN]\tMOV SS; INT 1\n");
sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
asm volatile ("mov %[ss], %%ss; int $1" :: [ss] "m" (ss));
}
#ifdef __x86_64__
/*
* In principle, we should test 32-bit SYSCALL as well, but
* the calling convention is so unpredictable that it's
* not obviously worth the effort.
*/
if (sigsetjmp(jmpbuf, 1) == 0) {
printf("[RUN]\tMOV SS; SYSCALL\n");
sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
nr = SYS_getpid;
/*
* Toggle the high bit of RSP to make it noncanonical to
* strengthen this test on non-SMAP systems.
*/
asm volatile ("btc $63, %%rsp\n\t"
"mov %[ss], %%ss; syscall\n\t"
"btc $63, %%rsp"
: "+a" (nr) : [ss] "m" (ss)
: "rcx"
#ifdef __x86_64__
, "r11"
#endif
);
}
#endif
printf("[RUN]\tMOV SS; breakpointed NOP\n");
asm volatile ("mov %[ss], %%ss; breakpoint_insn: nop" :: [ss] "m" (ss));
/*
* Invoking SYSENTER directly breaks all the rules. Just handle
* the SIGSEGV.
*/
if (sigsetjmp(jmpbuf, 1) == 0) {
printf("[RUN]\tMOV SS; SYSENTER\n");
stack_t stack = {
.ss_sp = malloc(sizeof(char) * SIGSTKSZ),
.ss_size = SIGSTKSZ,
};
if (sigaltstack(&stack, NULL) != 0)
err(1, "sigaltstack");
sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND | SA_ONSTACK);
nr = SYS_getpid;
free(stack.ss_sp);
/* Clear EBP first to make sure we segfault cleanly. */
asm volatile ("xorl %%ebp, %%ebp; mov %[ss], %%ss; SYSENTER" : "+a" (nr)
: [ss] "m" (ss) : "flags", "rcx"
#ifdef __x86_64__
, "r11"
#endif
);
/* We're unreachable here. SYSENTER forgets RIP. */
}
if (sigsetjmp(jmpbuf, 1) == 0) {
printf("[RUN]\tMOV SS; INT $0x80\n");
sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
nr = 20; /* compat getpid */
asm volatile ("mov %[ss], %%ss; int $0x80"
: "+a" (nr) : [ss] "m" (ss)
: "flags"
#ifdef __x86_64__
, "r8", "r9", "r10", "r11"
#endif
);
}
printf("[OK]\tI aten't dead\n");
return 0;
}
@@ -0,0 +1,212 @@
/*
* Copyright (c) 2023 Alexey Dobriyan <adobriyan@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Test that userspace stack is NX. Requires linking with -Wl,-z,noexecstack
* because I don't want to bother with PT_GNU_STACK detection.
*
* Fill the stack with INT3's and then try to execute some of them:
* SIGSEGV -- good, SIGTRAP -- bad.
*
* Regular stack is completely overwritten before testing.
* Test doesn't exit SIGSEGV handler after first fault at INT3.
*/
#undef _GNU_SOURCE
#define _GNU_SOURCE
#undef NDEBUG
#include <assert.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <unistd.h>
#define PAGE_SIZE 4096
/*
* This is memset(rsp, 0xcc, -1); but down.
* It will SIGSEGV when bottom of the stack is reached.
* Byte-size access is important! (see rdi tweak in the signal handler).
*/
void make_stack1(void);
asm(
".pushsection .text\n"
".globl make_stack1\n"
".align 16\n"
"make_stack1:\n"
"mov $0xcc, %al\n"
#if defined __amd64__
"mov %rsp, %rdi\n"
"mov $-1, %rcx\n"
#elif defined __i386__
"mov %esp, %edi\n"
"mov $-1, %ecx\n"
#else
#error
#endif
"std\n"
"rep stosb\n"
/* unreachable */
"hlt\n"
".type make_stack1,@function\n"
".size make_stack1,.-make_stack1\n"
".popsection\n"
);
/*
* memset(p, 0xcc, -1);
* It will SIGSEGV when top of the stack is reached.
*/
void make_stack2(uint64_t p);
asm(
".pushsection .text\n"
".globl make_stack2\n"
".align 16\n"
"make_stack2:\n"
"mov $0xcc, %al\n"
#if defined __amd64__
"mov $-1, %rcx\n"
#elif defined __i386__
"mov $-1, %ecx\n"
#else
#error
#endif
"cld\n"
"rep stosb\n"
/* unreachable */
"hlt\n"
".type make_stack2,@function\n"
".size make_stack2,.-make_stack2\n"
".popsection\n"
);
static volatile int test_state = 0;
static volatile unsigned long stack_min_addr;
#if defined __amd64__
#define RDI REG_RDI
#define RIP REG_RIP
#define RIP_STRING "rip"
#elif defined __i386__
#define RDI REG_EDI
#define RIP REG_EIP
#define RIP_STRING "eip"
#else
#error
#endif
static void sigsegv(int _, siginfo_t *__, void *uc_)
{
/*
* Some Linux versions didn't clear DF before entering signal
* handler. make_stack1() doesn't have a chance to clear DF
* either so we clear it by hand here.
*/
asm volatile ("cld" ::: "memory");
ucontext_t *uc = uc_;
if (test_state == 0) {
/* Stack is faulted and cleared from RSP to the lowest address. */
stack_min_addr = ++uc->uc_mcontext.gregs[RDI];
if (1) {
printf("stack min %lx\n", stack_min_addr);
}
uc->uc_mcontext.gregs[RIP] = (uintptr_t)&make_stack2;
test_state = 1;
} else if (test_state == 1) {
/* Stack has been cleared from top to bottom. */
unsigned long stack_max_addr = uc->uc_mcontext.gregs[RDI];
if (1) {
printf("stack max %lx\n", stack_max_addr);
}
/* Start faulting pages on stack and see what happens. */
uc->uc_mcontext.gregs[RIP] = stack_max_addr - PAGE_SIZE;
test_state = 2;
} else if (test_state == 2) {
/* Stack page is NX -- good, test next page. */
uc->uc_mcontext.gregs[RIP] -= PAGE_SIZE;
if (uc->uc_mcontext.gregs[RIP] == stack_min_addr) {
/* One more SIGSEGV and test ends. */
test_state = 3;
}
} else {
printf("PASS\tAll stack pages are NX\n");
_exit(EXIT_SUCCESS);
}
}
static void sigtrap(int _, siginfo_t *__, void *uc_)
{
const ucontext_t *uc = uc_;
unsigned long rip = uc->uc_mcontext.gregs[RIP];
printf("FAIL\texecutable page on the stack: " RIP_STRING " %lx\n", rip);
_exit(EXIT_FAILURE);
}
int main(void)
{
{
struct sigaction act = {};
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = &sigsegv;
int rv = sigaction(SIGSEGV, &act, NULL);
assert(rv == 0);
}
{
struct sigaction act = {};
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = &sigtrap;
int rv = sigaction(SIGTRAP, &act, NULL);
assert(rv == 0);
}
{
struct rlimit rlim;
int rv = getrlimit(RLIMIT_STACK, &rlim);
assert(rv == 0);
/* Cap stack at time-honored 8 MiB value. */
rlim.rlim_max = rlim.rlim_cur;
if (rlim.rlim_max > 8 * 1024 * 1024) {
rlim.rlim_max = 8 * 1024 * 1024;
}
rv = setrlimit(RLIMIT_STACK, &rlim);
assert(rv == 0);
}
{
/*
* We don't know now much stack SIGSEGV handler uses.
* Bump this by 1 page every time someone complains,
* or rewrite it in assembly.
*/
const size_t len = SIGSTKSZ;
void *p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
assert(p != MAP_FAILED);
stack_t ss = {};
ss.ss_sp = p;
ss.ss_size = len;
int rv = sigaltstack(&ss, NULL);
assert(rv == 0);
}
make_stack1();
/*
* Unreachable, but if _this_ INT3 is ever reached, it's a bug somewhere.
* Fold it into main SIGTRAP pathway.
*/
__builtin_trap();
}
@@ -0,0 +1,410 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <unistd.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <err.h>
#include <string.h>
#include <asm/ptrace-abi.h>
#include <sys/auxv.h>
#include "helpers.h"
/* Bitness-agnostic defines for user_regs_struct fields. */
#ifdef __x86_64__
# define user_syscall_nr orig_rax
# define user_arg0 rdi
# define user_arg1 rsi
# define user_arg2 rdx
# define user_arg3 r10
# define user_arg4 r8
# define user_arg5 r9
# define user_ip rip
# define user_ax rax
#else
# define user_syscall_nr orig_eax
# define user_arg0 ebx
# define user_arg1 ecx
# define user_arg2 edx
# define user_arg3 esi
# define user_arg4 edi
# define user_arg5 ebp
# define user_ip eip
# define user_ax eax
#endif
static int nerrs = 0;
struct syscall_args32 {
uint32_t nr, arg0, arg1, arg2, arg3, arg4, arg5;
};
#ifdef __i386__
extern void sys32_helper(struct syscall_args32 *, void *);
extern void int80_and_ret(void);
#endif
/*
* Helper to invoke int80 with controlled regs and capture the final regs.
*/
static void do_full_int80(struct syscall_args32 *args)
{
#ifdef __x86_64__
register unsigned long bp asm("bp") = args->arg5;
asm volatile ("int $0x80"
: "+a" (args->nr),
"+b" (args->arg0), "+c" (args->arg1), "+d" (args->arg2),
"+S" (args->arg3), "+D" (args->arg4), "+r" (bp)
: : "r8", "r9", "r10", "r11");
args->arg5 = bp;
#else
sys32_helper(args, int80_and_ret);
#endif
}
#ifdef __i386__
static void (*vsyscall32)(void);
/*
* Nasty helper to invoke AT_SYSINFO (i.e. __kernel_vsyscall) with
* controlled regs and capture the final regs. This is so nasty that it
* crashes my copy of gdb :)
*/
static void do_full_vsyscall32(struct syscall_args32 *args)
{
sys32_helper(args, vsyscall32);
}
#endif
static siginfo_t wait_trap(pid_t chld)
{
siginfo_t si;
if (waitid(P_PID, chld, &si, WEXITED|WSTOPPED) != 0)
err(1, "waitid");
if (si.si_pid != chld)
errx(1, "got unexpected pid in event\n");
if (si.si_code != CLD_TRAPPED)
errx(1, "got unexpected event type %d\n", si.si_code);
return si;
}
static void setsigign(int sig, int flags)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = (void *)SIG_IGN;
sa.sa_flags = flags;
sigemptyset(&sa.sa_mask);
if (sigaction(sig, &sa, 0))
err(1, "sigaction");
}
#ifdef __x86_64__
# define REG_BP REG_RBP
#else
# define REG_BP REG_EBP
#endif
static void empty_handler(int sig, siginfo_t *si, void *ctx_void)
{
}
static void test_sys32_regs(void (*do_syscall)(struct syscall_args32 *))
{
struct syscall_args32 args = {
.nr = 224, /* gettid */
.arg0 = 10, .arg1 = 11, .arg2 = 12,
.arg3 = 13, .arg4 = 14, .arg5 = 15,
};
do_syscall(&args);
if (args.nr != getpid() ||
args.arg0 != 10 || args.arg1 != 11 || args.arg2 != 12 ||
args.arg3 != 13 || args.arg4 != 14 || args.arg5 != 15) {
printf("[FAIL]\tgetpid() failed to preserve regs\n");
nerrs++;
} else {
printf("[OK]\tgetpid() preserves regs\n");
}
sethandler(SIGUSR1, empty_handler, 0);
args.nr = 37; /* kill */
args.arg0 = getpid();
args.arg1 = SIGUSR1;
do_syscall(&args);
if (args.nr != 0 ||
args.arg0 != getpid() || args.arg1 != SIGUSR1 || args.arg2 != 12 ||
args.arg3 != 13 || args.arg4 != 14 || args.arg5 != 15) {
printf("[FAIL]\tkill(getpid(), SIGUSR1) failed to preserve regs\n");
nerrs++;
} else {
printf("[OK]\tkill(getpid(), SIGUSR1) preserves regs\n");
}
clearhandler(SIGUSR1);
}
static void test_ptrace_syscall_restart(void)
{
printf("[RUN]\tptrace-induced syscall restart\n");
pid_t chld = fork();
if (chld < 0)
err(1, "fork");
if (chld == 0) {
if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
err(1, "PTRACE_TRACEME");
pid_t pid = getpid(), tid = syscall(SYS_gettid);
printf("\tChild will make one syscall\n");
syscall(SYS_tgkill, pid, tid, SIGSTOP);
syscall(SYS_gettid, 10, 11, 12, 13, 14, 15);
_exit(0);
}
int status;
/* Wait for SIGSTOP. */
if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
err(1, "waitpid");
struct user_regs_struct regs;
printf("[RUN]\tSYSEMU\n");
if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0)
err(1, "PTRACE_SYSEMU");
wait_trap(chld);
if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0)
err(1, "PTRACE_GETREGS");
if (regs.user_syscall_nr != SYS_gettid ||
regs.user_arg0 != 10 || regs.user_arg1 != 11 ||
regs.user_arg2 != 12 || regs.user_arg3 != 13 ||
regs.user_arg4 != 14 || regs.user_arg5 != 15) {
printf("[FAIL]\tInitial args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5);
nerrs++;
} else {
printf("[OK]\tInitial nr and args are correct\n");
}
printf("[RUN]\tRestart the syscall (ip = 0x%lx)\n",
(unsigned long)regs.user_ip);
/*
* This does exactly what it appears to do if syscall is int80 or
* SYSCALL64. For SYSCALL32 or SYSENTER, though, this is highly
* magical. It needs to work so that ptrace and syscall restart
* work as expected.
*/
regs.user_ax = regs.user_syscall_nr;
regs.user_ip -= 2;
if (ptrace(PTRACE_SETREGS, chld, 0, &regs) != 0)
err(1, "PTRACE_SETREGS");
if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0)
err(1, "PTRACE_SYSEMU");
wait_trap(chld);
if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0)
err(1, "PTRACE_GETREGS");
if (regs.user_syscall_nr != SYS_gettid ||
regs.user_arg0 != 10 || regs.user_arg1 != 11 ||
regs.user_arg2 != 12 || regs.user_arg3 != 13 ||
regs.user_arg4 != 14 || regs.user_arg5 != 15) {
printf("[FAIL]\tRestart nr or args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5);
nerrs++;
} else {
printf("[OK]\tRestarted nr and args are correct\n");
}
printf("[RUN]\tChange nr and args and restart the syscall (ip = 0x%lx)\n",
(unsigned long)regs.user_ip);
regs.user_ax = SYS_getpid;
regs.user_arg0 = 20;
regs.user_arg1 = 21;
regs.user_arg2 = 22;
regs.user_arg3 = 23;
regs.user_arg4 = 24;
regs.user_arg5 = 25;
regs.user_ip -= 2;
if (ptrace(PTRACE_SETREGS, chld, 0, &regs) != 0)
err(1, "PTRACE_SETREGS");
if (ptrace(PTRACE_SYSEMU, chld, 0, 0) != 0)
err(1, "PTRACE_SYSEMU");
wait_trap(chld);
if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0)
err(1, "PTRACE_GETREGS");
if (regs.user_syscall_nr != SYS_getpid ||
regs.user_arg0 != 20 || regs.user_arg1 != 21 || regs.user_arg2 != 22 ||
regs.user_arg3 != 23 || regs.user_arg4 != 24 || regs.user_arg5 != 25) {
printf("[FAIL]\tRestart nr or args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5);
nerrs++;
} else {
printf("[OK]\tReplacement nr and args are correct\n");
}
if (ptrace(PTRACE_CONT, chld, 0, 0) != 0)
err(1, "PTRACE_CONT");
if (waitpid(chld, &status, 0) != chld)
err(1, "waitpid");
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
printf("[FAIL]\tChild failed\n");
nerrs++;
} else {
printf("[OK]\tChild exited cleanly\n");
}
}
static void test_restart_under_ptrace(void)
{
printf("[RUN]\tkernel syscall restart under ptrace\n");
pid_t chld = fork();
if (chld < 0)
err(1, "fork");
if (chld == 0) {
if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
err(1, "PTRACE_TRACEME");
pid_t pid = getpid(), tid = syscall(SYS_gettid);
printf("\tChild will take a nap until signaled\n");
setsigign(SIGUSR1, SA_RESTART);
syscall(SYS_tgkill, pid, tid, SIGSTOP);
syscall(SYS_pause, 0, 0, 0, 0, 0, 0);
_exit(0);
}
int status;
/* Wait for SIGSTOP. */
if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
err(1, "waitpid");
struct user_regs_struct regs;
printf("[RUN]\tSYSCALL\n");
if (ptrace(PTRACE_SYSCALL, chld, 0, 0) != 0)
err(1, "PTRACE_SYSCALL");
wait_trap(chld);
/* We should be stopped at pause(2) entry. */
if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0)
err(1, "PTRACE_GETREGS");
if (regs.user_syscall_nr != SYS_pause ||
regs.user_arg0 != 0 || regs.user_arg1 != 0 ||
regs.user_arg2 != 0 || regs.user_arg3 != 0 ||
regs.user_arg4 != 0 || regs.user_arg5 != 0) {
printf("[FAIL]\tInitial args are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5);
nerrs++;
} else {
printf("[OK]\tInitial nr and args are correct\n");
}
/* Interrupt it. */
kill(chld, SIGUSR1);
/* Advance. We should be stopped at exit. */
printf("[RUN]\tSYSCALL\n");
if (ptrace(PTRACE_SYSCALL, chld, 0, 0) != 0)
err(1, "PTRACE_SYSCALL");
wait_trap(chld);
if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0)
err(1, "PTRACE_GETREGS");
if (regs.user_syscall_nr != SYS_pause ||
regs.user_arg0 != 0 || regs.user_arg1 != 0 ||
regs.user_arg2 != 0 || regs.user_arg3 != 0 ||
regs.user_arg4 != 0 || regs.user_arg5 != 0) {
printf("[FAIL]\tArgs after SIGUSR1 are wrong (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5);
nerrs++;
} else {
printf("[OK]\tArgs after SIGUSR1 are correct (ax = %ld)\n",
(long)regs.user_ax);
}
/* Poke the regs back in. This must not break anything. */
if (ptrace(PTRACE_SETREGS, chld, 0, &regs) != 0)
err(1, "PTRACE_SETREGS");
/* Catch the (ignored) SIGUSR1. */
if (ptrace(PTRACE_CONT, chld, 0, 0) != 0)
err(1, "PTRACE_CONT");
if (waitpid(chld, &status, 0) != chld)
err(1, "waitpid");
if (!WIFSTOPPED(status)) {
printf("[FAIL]\tChild was stopped for SIGUSR1 (status = 0x%x)\n", status);
nerrs++;
} else {
printf("[OK]\tChild got SIGUSR1\n");
}
/* The next event should be pause(2) again. */
printf("[RUN]\tStep again\n");
if (ptrace(PTRACE_SYSCALL, chld, 0, 0) != 0)
err(1, "PTRACE_SYSCALL");
wait_trap(chld);
/* We should be stopped at pause(2) entry. */
if (ptrace(PTRACE_GETREGS, chld, 0, &regs) != 0)
err(1, "PTRACE_GETREGS");
if (regs.user_syscall_nr != SYS_pause ||
regs.user_arg0 != 0 || regs.user_arg1 != 0 ||
regs.user_arg2 != 0 || regs.user_arg3 != 0 ||
regs.user_arg4 != 0 || regs.user_arg5 != 0) {
printf("[FAIL]\tpause did not restart (nr=%lu, args=%lu %lu %lu %lu %lu %lu)\n", (unsigned long)regs.user_syscall_nr, (unsigned long)regs.user_arg0, (unsigned long)regs.user_arg1, (unsigned long)regs.user_arg2, (unsigned long)regs.user_arg3, (unsigned long)regs.user_arg4, (unsigned long)regs.user_arg5);
nerrs++;
} else {
printf("[OK]\tpause(2) restarted correctly\n");
}
/* Kill it. */
kill(chld, SIGKILL);
if (waitpid(chld, &status, 0) != chld)
err(1, "waitpid");
}
int main()
{
printf("[RUN]\tCheck int80 return regs\n");
test_sys32_regs(do_full_int80);
#if defined(__i386__) && (!defined(__GLIBC__) || __GLIBC__ > 2 || __GLIBC_MINOR__ >= 16)
vsyscall32 = (void *)getauxval(AT_SYSINFO);
if (vsyscall32) {
printf("[RUN]\tCheck AT_SYSINFO return regs\n");
test_sys32_regs(do_full_vsyscall32);
} else {
printf("[SKIP]\tAT_SYSINFO is not available\n");
}
#endif
test_ptrace_syscall_restart();
test_restart_under_ptrace();
return 0;
}
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0 */
.global sys32_helper
sys32_helper:
/* Args: syscall_args_32*, function pointer */
pushl %ebp
pushl %ebx
pushl %esi
pushl %edi
movl 5*4(%esp), %eax /* pointer to args struct */
movl 1*4(%eax), %ebx
movl 2*4(%eax), %ecx
movl 3*4(%eax), %edx
movl 4*4(%eax), %esi
movl 5*4(%eax), %edi
movl 6*4(%eax), %ebp
movl 0*4(%eax), %eax
call *(6*4)(%esp) /* Do the syscall */
/* Now we need to recover without losing any reg values */
pushl %eax
movl 6*4(%esp), %eax
popl 0*4(%eax)
movl %ebx, 1*4(%eax)
movl %ecx, 2*4(%eax)
movl %edx, 3*4(%eax)
movl %esi, 4*4(%eax)
movl %edi, 5*4(%eax)
movl %ebp, 6*4(%eax)
popl %edi
popl %esi
popl %ebx
popl %ebp
ret
.type sys32_helper, @function
.size sys32_helper, .-sys32_helper
.global int80_and_ret
int80_and_ret:
int $0x80
ret
.type int80_and_ret, @function
.size int80_and_ret, .-int80_and_ret
.section .note.GNU-stack,"",%progbits
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: GPL-2.0-only
#define _GNU_SOURCE
#include <signal.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <sys/mman.h>
#include <sys/auxv.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <setjmp.h>
#include "helpers.h"
/* sigaltstack()-enforced minimum stack */
#define ENFORCED_MINSIGSTKSZ 2048
#ifndef AT_MINSIGSTKSZ
# define AT_MINSIGSTKSZ 51
#endif
static int nerrs;
static bool sigalrm_expected;
static unsigned long at_minstack_size;
static int setup_altstack(void *start, unsigned long size)
{
stack_t ss;
memset(&ss, 0, sizeof(ss));
ss.ss_size = size;
ss.ss_sp = start;
return sigaltstack(&ss, NULL);
}
static jmp_buf jmpbuf;
static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
{
if (sigalrm_expected) {
printf("[FAIL]\tWrong signal delivered: SIGSEGV (expected SIGALRM).");
nerrs++;
} else {
printf("[OK]\tSIGSEGV signal delivered.\n");
}
siglongjmp(jmpbuf, 1);
}
static void sigalrm(int sig, siginfo_t *info, void *ctx_void)
{
if (!sigalrm_expected) {
printf("[FAIL]\tWrong signal delivered: SIGALRM (expected SIGSEGV).");
nerrs++;
} else {
printf("[OK]\tSIGALRM signal delivered.\n");
}
}
static void test_sigaltstack(void *altstack, unsigned long size)
{
if (setup_altstack(altstack, size))
err(1, "sigaltstack()");
sigalrm_expected = (size > at_minstack_size) ? true : false;
sethandler(SIGSEGV, sigsegv, 0);
sethandler(SIGALRM, sigalrm, SA_ONSTACK);
if (!sigsetjmp(jmpbuf, 1)) {
printf("[RUN]\tTest an alternate signal stack of %ssufficient size.\n",
sigalrm_expected ? "" : "in");
printf("\tRaise SIGALRM. %s is expected to be delivered.\n",
sigalrm_expected ? "It" : "SIGSEGV");
raise(SIGALRM);
}
clearhandler(SIGALRM);
clearhandler(SIGSEGV);
}
int main(void)
{
void *altstack;
at_minstack_size = getauxval(AT_MINSIGSTKSZ);
altstack = mmap(NULL, at_minstack_size + SIGSTKSZ, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
if (altstack == MAP_FAILED)
err(1, "mmap()");
if ((ENFORCED_MINSIGSTKSZ + 1) < at_minstack_size)
test_sigaltstack(altstack, ENFORCED_MINSIGSTKSZ + 1);
test_sigaltstack(altstack, at_minstack_size + SIGSTKSZ);
return nerrs == 0 ? 0 : 1;
}
@@ -0,0 +1,855 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sigreturn.c - tests for x86 sigreturn(2) and exit-to-userspace
* Copyright (c) 2014-2015 Andrew Lutomirski
*
* This is a series of tests that exercises the sigreturn(2) syscall and
* the IRET / SYSRET paths in the kernel.
*
* For now, this focuses on the effects of unusual CS and SS values,
* and it has a bunch of tests to make sure that ESP/RSP is restored
* properly.
*
* The basic idea behind these tests is to raise(SIGUSR1) to create a
* sigcontext frame, plug in the values to be tested, and then return,
* which implicitly invokes sigreturn(2) and programs the user context
* as desired.
*
* For tests for which we expect sigreturn and the subsequent return to
* user mode to succeed, we return to a short trampoline that generates
* SIGTRAP so that the meat of the tests can be ordinary C code in a
* SIGTRAP handler.
*
* The inner workings of each test is documented below.
*
* Do not run on outdated, unpatched kernels at risk of nasty crashes.
*/
#define _GNU_SOURCE
#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/signal.h>
#include <sys/ucontext.h>
#include <asm/ldt.h>
#include <err.h>
#include <setjmp.h>
#include <stddef.h>
#include <stdbool.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include "helpers.h"
/* Pull in AR_xyz defines. */
typedef unsigned int u32;
typedef unsigned short u16;
#include "../../../../arch/x86/include/asm/desc_defs.h"
/*
* Copied from asm/ucontext.h, as asm/ucontext.h conflicts badly with the glibc
* headers.
*/
#ifdef __x86_64__
/*
* UC_SIGCONTEXT_SS will be set when delivering 64-bit or x32 signals on
* kernels that save SS in the sigcontext. All kernels that set
* UC_SIGCONTEXT_SS will correctly restore at least the low 32 bits of esp
* regardless of SS (i.e. they implement espfix).
*
* Kernels that set UC_SIGCONTEXT_SS will also set UC_STRICT_RESTORE_SS
* when delivering a signal that came from 64-bit code.
*
* Sigreturn restores SS as follows:
*
* if (saved SS is valid || UC_STRICT_RESTORE_SS is set ||
* saved CS is not 64-bit)
* new SS = saved SS (will fail IRET and signal if invalid)
* else
* new SS = a flat 32-bit data segment
*/
#define UC_SIGCONTEXT_SS 0x2
#define UC_STRICT_RESTORE_SS 0x4
#endif
/*
* In principle, this test can run on Linux emulation layers (e.g.
* Illumos "LX branded zones"). Solaris-based kernels reserve LDT
* entries 0-5 for their own internal purposes, so start our LDT
* allocations above that reservation. (The tests don't pass on LX
* branded zones, but at least this lets them run.)
*/
#define LDT_OFFSET 6
/* An aligned stack accessible through some of our segments. */
static unsigned char stack16[65536] __attribute__((aligned(4096)));
/*
* An aligned int3 instruction used as a trampoline. Some of the tests
* want to fish out their ss values, so this trampoline copies ss to eax
* before the int3.
*/
asm (".pushsection .text\n\t"
".type int3, @function\n\t"
".align 4096\n\t"
"int3:\n\t"
"mov %ss,%ecx\n\t"
"int3\n\t"
".size int3, . - int3\n\t"
".align 4096, 0xcc\n\t"
".popsection");
extern char int3[4096];
/*
* At startup, we prepapre:
*
* - ldt_nonexistent_sel: An LDT entry that doesn't exist (all-zero
* descriptor or out of bounds).
* - code16_sel: A 16-bit LDT code segment pointing to int3.
* - data16_sel: A 16-bit LDT data segment pointing to stack16.
* - npcode32_sel: A 32-bit not-present LDT code segment pointing to int3.
* - npdata32_sel: A 32-bit not-present LDT data segment pointing to stack16.
* - gdt_data16_idx: A 16-bit GDT data segment pointing to stack16.
* - gdt_npdata32_idx: A 32-bit not-present GDT data segment pointing to
* stack16.
*
* For no particularly good reason, xyz_sel is a selector value with the
* RPL and LDT bits filled in, whereas xyz_idx is just an index into the
* descriptor table. These variables will be zero if their respective
* segments could not be allocated.
*/
static unsigned short ldt_nonexistent_sel;
static unsigned short code16_sel, data16_sel, npcode32_sel, npdata32_sel;
static unsigned short gdt_data16_idx, gdt_npdata32_idx;
static unsigned short GDT3(int idx)
{
return (idx << 3) | 3;
}
static unsigned short LDT3(int idx)
{
return (idx << 3) | 7;
}
static void add_ldt(const struct user_desc *desc, unsigned short *var,
const char *name)
{
if (syscall(SYS_modify_ldt, 1, desc, sizeof(*desc)) == 0) {
*var = LDT3(desc->entry_number);
} else {
printf("[NOTE]\tFailed to create %s segment\n", name);
*var = 0;
}
}
static void setup_ldt(void)
{
if ((unsigned long)stack16 > (1ULL << 32) - sizeof(stack16))
errx(1, "stack16 is too high\n");
if ((unsigned long)int3 > (1ULL << 32) - sizeof(int3))
errx(1, "int3 is too high\n");
ldt_nonexistent_sel = LDT3(LDT_OFFSET + 2);
const struct user_desc code16_desc = {
.entry_number = LDT_OFFSET + 0,
.base_addr = (unsigned long)int3,
.limit = 4095,
.seg_32bit = 0,
.contents = 2, /* Code, not conforming */
.read_exec_only = 0,
.limit_in_pages = 0,
.seg_not_present = 0,
.useable = 0
};
add_ldt(&code16_desc, &code16_sel, "code16");
const struct user_desc data16_desc = {
.entry_number = LDT_OFFSET + 1,
.base_addr = (unsigned long)stack16,
.limit = 0xffff,
.seg_32bit = 0,
.contents = 0, /* Data, grow-up */
.read_exec_only = 0,
.limit_in_pages = 0,
.seg_not_present = 0,
.useable = 0
};
add_ldt(&data16_desc, &data16_sel, "data16");
const struct user_desc npcode32_desc = {
.entry_number = LDT_OFFSET + 3,
.base_addr = (unsigned long)int3,
.limit = 4095,
.seg_32bit = 1,
.contents = 2, /* Code, not conforming */
.read_exec_only = 0,
.limit_in_pages = 0,
.seg_not_present = 1,
.useable = 0
};
add_ldt(&npcode32_desc, &npcode32_sel, "npcode32");
const struct user_desc npdata32_desc = {
.entry_number = LDT_OFFSET + 4,
.base_addr = (unsigned long)stack16,
.limit = 0xffff,
.seg_32bit = 1,
.contents = 0, /* Data, grow-up */
.read_exec_only = 0,
.limit_in_pages = 0,
.seg_not_present = 1,
.useable = 0
};
add_ldt(&npdata32_desc, &npdata32_sel, "npdata32");
struct user_desc gdt_data16_desc = {
.entry_number = -1,
.base_addr = (unsigned long)stack16,
.limit = 0xffff,
.seg_32bit = 0,
.contents = 0, /* Data, grow-up */
.read_exec_only = 0,
.limit_in_pages = 0,
.seg_not_present = 0,
.useable = 0
};
if (syscall(SYS_set_thread_area, &gdt_data16_desc) == 0) {
/*
* This probably indicates vulnerability to CVE-2014-8133.
* Merely getting here isn't definitive, though, and we'll
* diagnose the problem for real later on.
*/
printf("[WARN]\tset_thread_area allocated data16 at index %d\n",
gdt_data16_desc.entry_number);
gdt_data16_idx = gdt_data16_desc.entry_number;
} else {
printf("[OK]\tset_thread_area refused 16-bit data\n");
}
struct user_desc gdt_npdata32_desc = {
.entry_number = -1,
.base_addr = (unsigned long)stack16,
.limit = 0xffff,
.seg_32bit = 1,
.contents = 0, /* Data, grow-up */
.read_exec_only = 0,
.limit_in_pages = 0,
.seg_not_present = 1,
.useable = 0
};
if (syscall(SYS_set_thread_area, &gdt_npdata32_desc) == 0) {
/*
* As a hardening measure, newer kernels don't allow this.
*/
printf("[WARN]\tset_thread_area allocated npdata32 at index %d\n",
gdt_npdata32_desc.entry_number);
gdt_npdata32_idx = gdt_npdata32_desc.entry_number;
} else {
printf("[OK]\tset_thread_area refused 16-bit data\n");
}
}
/* State used by our signal handlers. */
static gregset_t initial_regs, requested_regs, resulting_regs;
/* Instructions for the SIGUSR1 handler. */
static volatile unsigned short sig_cs, sig_ss;
static volatile sig_atomic_t sig_trapped, sig_err, sig_trapno;
#ifdef __x86_64__
static volatile sig_atomic_t sig_corrupt_final_ss;
#endif
/* Abstractions for some 32-bit vs 64-bit differences. */
#ifdef __x86_64__
# define REG_IP REG_RIP
# define REG_SP REG_RSP
# define REG_CX REG_RCX
struct selectors {
unsigned short cs, gs, fs, ss;
};
static unsigned short *ssptr(ucontext_t *ctx)
{
struct selectors *sels = (void *)&ctx->uc_mcontext.gregs[REG_CSGSFS];
return &sels->ss;
}
static unsigned short *csptr(ucontext_t *ctx)
{
struct selectors *sels = (void *)&ctx->uc_mcontext.gregs[REG_CSGSFS];
return &sels->cs;
}
#else
# define REG_IP REG_EIP
# define REG_SP REG_ESP
# define REG_CX REG_ECX
static greg_t *ssptr(ucontext_t *ctx)
{
return &ctx->uc_mcontext.gregs[REG_SS];
}
static greg_t *csptr(ucontext_t *ctx)
{
return &ctx->uc_mcontext.gregs[REG_CS];
}
#endif
/*
* Checks a given selector for its code bitness or returns -1 if it's not
* a usable code segment selector.
*/
int cs_bitness(unsigned short cs)
{
uint32_t valid = 0, ar;
asm ("lar %[cs], %[ar]\n\t"
"jnz 1f\n\t"
"mov $1, %[valid]\n\t"
"1:"
: [ar] "=r" (ar), [valid] "+rm" (valid)
: [cs] "r" (cs));
if (!valid)
return -1;
bool db = (ar & (1 << 22));
bool l = (ar & (1 << 21));
if (!(ar & (1<<11)))
return -1; /* Not code. */
if (l && !db)
return 64;
else if (!l && db)
return 32;
else if (!l && !db)
return 16;
else
return -1; /* Unknown bitness. */
}
/*
* Checks a given selector for its code bitness or returns -1 if it's not
* a usable code segment selector.
*/
bool is_valid_ss(unsigned short cs)
{
uint32_t valid = 0, ar;
asm ("lar %[cs], %[ar]\n\t"
"jnz 1f\n\t"
"mov $1, %[valid]\n\t"
"1:"
: [ar] "=r" (ar), [valid] "+rm" (valid)
: [cs] "r" (cs));
if (!valid)
return false;
if ((ar & AR_TYPE_MASK) != AR_TYPE_RWDATA &&
(ar & AR_TYPE_MASK) != AR_TYPE_RWDATA_EXPDOWN)
return false;
return (ar & AR_P);
}
/* Number of errors in the current test case. */
static volatile sig_atomic_t nerrs;
static void validate_signal_ss(int sig, ucontext_t *ctx)
{
#ifdef __x86_64__
bool was_64bit = (cs_bitness(*csptr(ctx)) == 64);
if (!(ctx->uc_flags & UC_SIGCONTEXT_SS)) {
printf("[FAIL]\tUC_SIGCONTEXT_SS was not set\n");
nerrs++;
/*
* This happens on Linux 4.1. The rest will fail, too, so
* return now to reduce the noise.
*/
return;
}
/* UC_STRICT_RESTORE_SS is set iff we came from 64-bit mode. */
if (!!(ctx->uc_flags & UC_STRICT_RESTORE_SS) != was_64bit) {
printf("[FAIL]\tUC_STRICT_RESTORE_SS was wrong in signal %d\n",
sig);
nerrs++;
}
if (is_valid_ss(*ssptr(ctx))) {
/*
* DOSEMU was written before 64-bit sigcontext had SS, and
* it tries to figure out the signal source SS by looking at
* the physical register. Make sure that keeps working.
*/
unsigned short hw_ss;
asm ("mov %%ss, %0" : "=rm" (hw_ss));
if (hw_ss != *ssptr(ctx)) {
printf("[FAIL]\tHW SS didn't match saved SS\n");
nerrs++;
}
}
#endif
}
/*
* SIGUSR1 handler. Sets CS and SS as requested and points IP to the
* int3 trampoline. Sets SP to a large known value so that we can see
* whether the value round-trips back to user mode correctly.
*/
static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t*)ctx_void;
validate_signal_ss(sig, ctx);
memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
*csptr(ctx) = sig_cs;
*ssptr(ctx) = sig_ss;
ctx->uc_mcontext.gregs[REG_IP] =
sig_cs == code16_sel ? 0 : (unsigned long)&int3;
ctx->uc_mcontext.gregs[REG_SP] = (unsigned long)0x8badf00d5aadc0deULL;
ctx->uc_mcontext.gregs[REG_CX] = 0;
#ifdef __i386__
/*
* Make sure the kernel doesn't inadvertently use DS or ES-relative
* accesses in a region where user DS or ES is loaded.
*
* Skip this for 64-bit builds because long mode doesn't care about
* DS and ES and skipping it increases test coverage a little bit,
* since 64-bit kernels can still run the 32-bit build.
*/
ctx->uc_mcontext.gregs[REG_DS] = 0;
ctx->uc_mcontext.gregs[REG_ES] = 0;
#endif
memcpy(&requested_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
requested_regs[REG_CX] = *ssptr(ctx); /* The asm code does this. */
return;
}
/*
* Called after a successful sigreturn (via int3) or from a failed
* sigreturn (directly by kernel). Restores our state so that the
* original raise(SIGUSR1) returns.
*/
static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t*)ctx_void;
validate_signal_ss(sig, ctx);
sig_err = ctx->uc_mcontext.gregs[REG_ERR];
sig_trapno = ctx->uc_mcontext.gregs[REG_TRAPNO];
unsigned short ss;
asm ("mov %%ss,%0" : "=r" (ss));
greg_t asm_ss = ctx->uc_mcontext.gregs[REG_CX];
if (asm_ss != sig_ss && sig == SIGTRAP) {
/* Sanity check failure. */
printf("[FAIL]\tSIGTRAP: ss = %hx, frame ss = %x, ax = %llx\n",
ss, *ssptr(ctx), (unsigned long long)asm_ss);
nerrs++;
}
memcpy(&resulting_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
#ifdef __x86_64__
if (sig_corrupt_final_ss) {
if (ctx->uc_flags & UC_STRICT_RESTORE_SS) {
printf("[FAIL]\tUC_STRICT_RESTORE_SS was set inappropriately\n");
nerrs++;
} else {
/*
* DOSEMU transitions from 32-bit to 64-bit mode by
* adjusting sigcontext, and it requires that this work
* even if the saved SS is bogus.
*/
printf("\tCorrupting SS on return to 64-bit mode\n");
*ssptr(ctx) = 0;
}
}
#endif
sig_trapped = sig;
}
#ifdef __x86_64__
/* Tests recovery if !UC_STRICT_RESTORE_SS */
static void sigusr2(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t*)ctx_void;
if (!(ctx->uc_flags & UC_STRICT_RESTORE_SS)) {
printf("[FAIL]\traise(2) didn't set UC_STRICT_RESTORE_SS\n");
nerrs++;
return; /* We can't do the rest. */
}
ctx->uc_flags &= ~UC_STRICT_RESTORE_SS;
*ssptr(ctx) = 0;
/* Return. The kernel should recover without sending another signal. */
}
static int test_nonstrict_ss(void)
{
clearhandler(SIGUSR1);
clearhandler(SIGTRAP);
clearhandler(SIGSEGV);
clearhandler(SIGILL);
sethandler(SIGUSR2, sigusr2, 0);
nerrs = 0;
printf("[RUN]\tClear UC_STRICT_RESTORE_SS and corrupt SS\n");
raise(SIGUSR2);
if (!nerrs)
printf("[OK]\tIt worked\n");
return nerrs;
}
#endif
/* Finds a usable code segment of the requested bitness. */
int find_cs(int bitness)
{
unsigned short my_cs;
asm ("mov %%cs,%0" : "=r" (my_cs));
if (cs_bitness(my_cs) == bitness)
return my_cs;
if (cs_bitness(my_cs + (2 << 3)) == bitness)
return my_cs + (2 << 3);
if (my_cs > (2<<3) && cs_bitness(my_cs - (2 << 3)) == bitness)
return my_cs - (2 << 3);
if (cs_bitness(code16_sel) == bitness)
return code16_sel;
printf("[WARN]\tCould not find %d-bit CS\n", bitness);
return -1;
}
static int test_valid_sigreturn(int cs_bits, bool use_16bit_ss, int force_ss)
{
int cs = find_cs(cs_bits);
if (cs == -1) {
printf("[SKIP]\tCode segment unavailable for %d-bit CS, %d-bit SS\n",
cs_bits, use_16bit_ss ? 16 : 32);
return 0;
}
if (force_ss != -1) {
sig_ss = force_ss;
} else {
if (use_16bit_ss) {
if (!data16_sel) {
printf("[SKIP]\tData segment unavailable for %d-bit CS, 16-bit SS\n",
cs_bits);
return 0;
}
sig_ss = data16_sel;
} else {
asm volatile ("mov %%ss,%0" : "=r" (sig_ss));
}
}
sig_cs = cs;
printf("[RUN]\tValid sigreturn: %d-bit CS (%hx), %d-bit SS (%hx%s)\n",
cs_bits, sig_cs, use_16bit_ss ? 16 : 32, sig_ss,
(sig_ss & 4) ? "" : ", GDT");
raise(SIGUSR1);
nerrs = 0;
/*
* Check that each register had an acceptable value when the
* int3 trampoline was invoked.
*/
for (int i = 0; i < NGREG; i++) {
greg_t req = requested_regs[i], res = resulting_regs[i];
if (i == REG_TRAPNO || i == REG_IP)
continue; /* don't care */
if (i == REG_SP) {
/*
* If we were using a 16-bit stack segment, then
* the kernel is a bit stuck: IRET only restores
* the low 16 bits of ESP/RSP if SS is 16-bit.
* The kernel uses a hack to restore bits 31:16,
* but that hack doesn't help with bits 63:32.
* On Intel CPUs, bits 63:32 end up zeroed, and, on
* AMD CPUs, they leak the high bits of the kernel
* espfix64 stack pointer. There's very little that
* the kernel can do about it.
*
* Similarly, if we are returning to a 32-bit context,
* the CPU will often lose the high 32 bits of RSP.
*/
if (res == req)
continue;
if (cs_bits != 64 && ((res ^ req) & 0xFFFFFFFF) == 0) {
printf("[NOTE]\tSP: %llx -> %llx\n",
(unsigned long long)req,
(unsigned long long)res);
continue;
}
printf("[FAIL]\tSP mismatch: requested 0x%llx; got 0x%llx\n",
(unsigned long long)requested_regs[i],
(unsigned long long)resulting_regs[i]);
nerrs++;
continue;
}
bool ignore_reg = false;
#if __i386__
if (i == REG_UESP)
ignore_reg = true;
#else
if (i == REG_CSGSFS) {
struct selectors *req_sels =
(void *)&requested_regs[REG_CSGSFS];
struct selectors *res_sels =
(void *)&resulting_regs[REG_CSGSFS];
if (req_sels->cs != res_sels->cs) {
printf("[FAIL]\tCS mismatch: requested 0x%hx; got 0x%hx\n",
req_sels->cs, res_sels->cs);
nerrs++;
}
if (req_sels->ss != res_sels->ss) {
printf("[FAIL]\tSS mismatch: requested 0x%hx; got 0x%hx\n",
req_sels->ss, res_sels->ss);
nerrs++;
}
continue;
}
#endif
/* Sanity check on the kernel */
if (i == REG_CX && req != res) {
printf("[FAIL]\tCX (saved SP) mismatch: requested 0x%llx; got 0x%llx\n",
(unsigned long long)req,
(unsigned long long)res);
nerrs++;
continue;
}
if (req != res && !ignore_reg) {
printf("[FAIL]\tReg %d mismatch: requested 0x%llx; got 0x%llx\n",
i, (unsigned long long)req,
(unsigned long long)res);
nerrs++;
}
}
if (nerrs == 0)
printf("[OK]\tall registers okay\n");
return nerrs;
}
static int test_bad_iret(int cs_bits, unsigned short ss, int force_cs)
{
int cs = force_cs == -1 ? find_cs(cs_bits) : force_cs;
if (cs == -1)
return 0;
sig_cs = cs;
sig_ss = ss;
printf("[RUN]\t%d-bit CS (%hx), bogus SS (%hx)\n",
cs_bits, sig_cs, sig_ss);
sig_trapped = 0;
raise(SIGUSR1);
if (sig_trapped) {
char errdesc[32] = "";
if (sig_err) {
const char *src = (sig_err & 1) ? " EXT" : "";
const char *table;
if ((sig_err & 0x6) == 0x0)
table = "GDT";
else if ((sig_err & 0x6) == 0x4)
table = "LDT";
else if ((sig_err & 0x6) == 0x2)
table = "IDT";
else
table = "???";
sprintf(errdesc, "%s%s index %d, ",
table, src, sig_err >> 3);
}
char trapname[32];
if (sig_trapno == 13)
strcpy(trapname, "GP");
else if (sig_trapno == 11)
strcpy(trapname, "NP");
else if (sig_trapno == 12)
strcpy(trapname, "SS");
else if (sig_trapno == 32)
strcpy(trapname, "IRET"); /* X86_TRAP_IRET */
else
sprintf(trapname, "%d", sig_trapno);
printf("[OK]\tGot #%s(0x%lx) (i.e. %s%s)\n",
trapname, (unsigned long)sig_err,
errdesc, strsignal(sig_trapped));
return 0;
} else {
/*
* This also implicitly tests UC_STRICT_RESTORE_SS:
* We check that these signals set UC_STRICT_RESTORE_SS and,
* if UC_STRICT_RESTORE_SS doesn't cause strict behavior,
* then we won't get SIGSEGV.
*/
printf("[FAIL]\tDid not get SIGSEGV\n");
return 1;
}
}
int main()
{
int total_nerrs = 0;
unsigned short my_cs, my_ss;
asm volatile ("mov %%cs,%0" : "=r" (my_cs));
asm volatile ("mov %%ss,%0" : "=r" (my_ss));
setup_ldt();
stack_t stack = {
/* Our sigaltstack scratch space. */
.ss_sp = malloc(sizeof(char) * SIGSTKSZ),
.ss_size = SIGSTKSZ,
};
if (sigaltstack(&stack, NULL) != 0)
err(1, "sigaltstack");
sethandler(SIGUSR1, sigusr1, 0);
sethandler(SIGTRAP, sigtrap, SA_ONSTACK);
/* Easy cases: return to a 32-bit SS in each possible CS bitness. */
total_nerrs += test_valid_sigreturn(64, false, -1);
total_nerrs += test_valid_sigreturn(32, false, -1);
total_nerrs += test_valid_sigreturn(16, false, -1);
/*
* Test easy espfix cases: return to a 16-bit LDT SS in each possible
* CS bitness. NB: with a long mode CS, the SS bitness is irrelevant.
*
* This catches the original missing-espfix-on-64-bit-kernels issue
* as well as CVE-2014-8134.
*/
total_nerrs += test_valid_sigreturn(64, true, -1);
total_nerrs += test_valid_sigreturn(32, true, -1);
total_nerrs += test_valid_sigreturn(16, true, -1);
if (gdt_data16_idx) {
/*
* For performance reasons, Linux skips espfix if SS points
* to the GDT. If we were able to allocate a 16-bit SS in
* the GDT, see if it leaks parts of the kernel stack pointer.
*
* This tests for CVE-2014-8133.
*/
total_nerrs += test_valid_sigreturn(64, true,
GDT3(gdt_data16_idx));
total_nerrs += test_valid_sigreturn(32, true,
GDT3(gdt_data16_idx));
total_nerrs += test_valid_sigreturn(16, true,
GDT3(gdt_data16_idx));
}
#ifdef __x86_64__
/* Nasty ABI case: check SS corruption handling. */
sig_corrupt_final_ss = 1;
total_nerrs += test_valid_sigreturn(32, false, -1);
total_nerrs += test_valid_sigreturn(32, true, -1);
sig_corrupt_final_ss = 0;
#endif
/*
* We're done testing valid sigreturn cases. Now we test states
* for which sigreturn itself will succeed but the subsequent
* entry to user mode will fail.
*
* Depending on the failure mode and the kernel bitness, these
* entry failures can generate SIGSEGV, SIGBUS, or SIGILL.
*/
clearhandler(SIGTRAP);
sethandler(SIGSEGV, sigtrap, SA_ONSTACK);
sethandler(SIGBUS, sigtrap, SA_ONSTACK);
sethandler(SIGILL, sigtrap, SA_ONSTACK); /* 32-bit kernels do this */
/* Easy failures: invalid SS, resulting in #GP(0) */
test_bad_iret(64, ldt_nonexistent_sel, -1);
test_bad_iret(32, ldt_nonexistent_sel, -1);
test_bad_iret(16, ldt_nonexistent_sel, -1);
/* These fail because SS isn't a data segment, resulting in #GP(SS) */
test_bad_iret(64, my_cs, -1);
test_bad_iret(32, my_cs, -1);
test_bad_iret(16, my_cs, -1);
/* Try to return to a not-present code segment, triggering #NP(SS). */
test_bad_iret(32, my_ss, npcode32_sel);
/*
* Try to return to a not-present but otherwise valid data segment.
* This will cause IRET to fail with #SS on the espfix stack. This
* exercises CVE-2014-9322.
*
* Note that, if espfix is enabled, 64-bit Linux will lose track
* of the actual cause of failure and report #GP(0) instead.
* This would be very difficult for Linux to avoid, because
* espfix64 causes IRET failures to be promoted to #DF, so the
* original exception frame is never pushed onto the stack.
*/
test_bad_iret(32, npdata32_sel, -1);
/*
* Try to return to a not-present but otherwise valid data
* segment without invoking espfix. Newer kernels don't allow
* this to happen in the first place. On older kernels, though,
* this can trigger CVE-2014-9322.
*/
if (gdt_npdata32_idx)
test_bad_iret(32, GDT3(gdt_npdata32_idx), -1);
#ifdef __x86_64__
total_nerrs += test_nonstrict_ss();
#endif
free(stack.ss_sp);
return total_nerrs ? 1 : 0;
}
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Intel Corporation
*/
#define _GNU_SOURCE
#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ucontext.h>
#ifdef __x86_64__
# define REG_IP REG_RIP
#else
# define REG_IP REG_EIP
#endif
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))
err(1, "sigaction");
return;
}
static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t *)ctx_void;
static unsigned int loop_count_on_same_ip;
static unsigned long last_trap_ip;
if (last_trap_ip == ctx->uc_mcontext.gregs[REG_IP]) {
printf("\tTrapped at %016lx\n", last_trap_ip);
/*
* If the same IP is hit more than 10 times in a row, it is
* _considered_ an infinite loop.
*/
if (++loop_count_on_same_ip > 10) {
printf("[FAIL]\tDetected SIGTRAP infinite loop\n");
exit(1);
}
return;
}
loop_count_on_same_ip = 0;
last_trap_ip = ctx->uc_mcontext.gregs[REG_IP];
printf("\tTrapped at %016lx\n", last_trap_ip);
}
int main(int argc, char *argv[])
{
sethandler(SIGTRAP, sigtrap, 0);
/*
* Set the Trap Flag (TF) to single-step the test code, therefore to
* trigger a SIGTRAP signal after each instruction until the TF is
* cleared.
*
* Because the arithmetic flags are not significant here, the TF is
* set by pushing 0x302 onto the stack and then popping it into the
* flags register.
*
* Four instructions in the following asm code are executed with the
* TF set, thus the SIGTRAP handler is expected to run four times.
*/
printf("[RUN]\tSIGTRAP infinite loop detection\n");
asm volatile(
#ifdef __x86_64__
/*
* Avoid clobbering the redzone
*
* Equivalent to "sub $128, %rsp", however -128 can be encoded
* in a single byte immediate while 128 uses 4 bytes.
*/
"add $-128, %rsp\n\t"
#endif
"push $0x302\n\t"
"popf\n\t"
"nop\n\t"
"nop\n\t"
"push $0x202\n\t"
"popf\n\t"
#ifdef __x86_64__
"sub $-128, %rsp\n\t"
#endif
);
printf("[OK]\tNo SIGTRAP infinite loop detected\n");
return 0;
}
@@ -0,0 +1,220 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* single_step_syscall.c - single-steps various x86 syscalls
* Copyright (c) 2014-2015 Andrew Lutomirski
*
* This is a very simple series of tests that makes system calls with
* the TF flag set. This exercises some nasty kernel code in the
* SYSENTER case: SYSENTER does not clear TF, so SYSENTER with TF set
* immediately issues #DB from CPL 0. This requires special handling in
* the kernel.
*/
#define _GNU_SOURCE
#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/signal.h>
#include <sys/ucontext.h>
#include <asm/ldt.h>
#include <err.h>
#include <setjmp.h>
#include <stddef.h>
#include <stdbool.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include "helpers.h"
static volatile sig_atomic_t sig_traps, sig_eflags;
sigjmp_buf jmpbuf;
#ifdef __x86_64__
# define REG_IP REG_RIP
# define WIDTH "q"
# define INT80_CLOBBERS "r8", "r9", "r10", "r11"
#else
# define REG_IP REG_EIP
# define WIDTH "l"
# define INT80_CLOBBERS
#endif
static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t*)ctx_void;
if (get_eflags() & X86_EFLAGS_TF) {
set_eflags(get_eflags() & ~X86_EFLAGS_TF);
printf("[WARN]\tSIGTRAP handler had TF set\n");
_exit(1);
}
sig_traps++;
if (sig_traps == 10000 || sig_traps == 10001) {
printf("[WARN]\tHit %d SIGTRAPs with si_addr 0x%lx, ip 0x%lx\n",
(int)sig_traps,
(unsigned long)info->si_addr,
(unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
}
}
static char const * const signames[] = {
[SIGSEGV] = "SIGSEGV",
[SIGBUS] = "SIBGUS",
[SIGTRAP] = "SIGTRAP",
[SIGILL] = "SIGILL",
};
static void print_and_longjmp(int sig, siginfo_t *si, void *ctx_void)
{
ucontext_t *ctx = ctx_void;
printf("\tGot %s with RIP=%lx, TF=%ld\n", signames[sig],
(unsigned long)ctx->uc_mcontext.gregs[REG_IP],
(unsigned long)ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_TF);
sig_eflags = (unsigned long)ctx->uc_mcontext.gregs[REG_EFL];
siglongjmp(jmpbuf, 1);
}
static void check_result(void)
{
unsigned long new_eflags = get_eflags();
set_eflags(new_eflags & ~X86_EFLAGS_TF);
if (!sig_traps) {
printf("[FAIL]\tNo SIGTRAP\n");
exit(1);
}
if (!(new_eflags & X86_EFLAGS_TF)) {
printf("[FAIL]\tTF was cleared\n");
exit(1);
}
printf("[OK]\tSurvived with TF set and %d traps\n", (int)sig_traps);
sig_traps = 0;
}
static void fast_syscall_no_tf(void)
{
sig_traps = 0;
printf("[RUN]\tFast syscall with TF cleared\n");
fflush(stdout); /* Force a syscall */
if (get_eflags() & X86_EFLAGS_TF) {
printf("[FAIL]\tTF is now set\n");
exit(1);
}
if (sig_traps) {
printf("[FAIL]\tGot SIGTRAP\n");
exit(1);
}
printf("[OK]\tNothing unexpected happened\n");
}
int main()
{
#ifdef CAN_BUILD_32
int tmp;
#endif
sethandler(SIGTRAP, sigtrap, 0);
printf("[RUN]\tSet TF and check nop\n");
set_eflags(get_eflags() | X86_EFLAGS_TF);
asm volatile ("nop");
check_result();
#ifdef __x86_64__
printf("[RUN]\tSet TF and check syscall-less opportunistic sysret\n");
set_eflags(get_eflags() | X86_EFLAGS_TF);
extern unsigned char post_nop[];
asm volatile ("pushf" WIDTH "\n\t"
"pop" WIDTH " %%r11\n\t"
"nop\n\t"
"post_nop:"
: : "c" (post_nop) : "r11");
check_result();
#endif
#ifdef CAN_BUILD_32
printf("[RUN]\tSet TF and check int80\n");
set_eflags(get_eflags() | X86_EFLAGS_TF);
asm volatile ("int $0x80" : "=a" (tmp) : "a" (SYS_getpid)
: INT80_CLOBBERS);
check_result();
#endif
/*
* This test is particularly interesting if fast syscalls use
* SYSENTER: it triggers a nasty design flaw in SYSENTER.
* Specifically, SYSENTER does not clear TF, so either SYSENTER
* or the next instruction traps at CPL0. (Of course, Intel
* mostly forgot to document exactly what happens here.) So we
* get a CPL0 fault with usergs (on 64-bit kernels) and possibly
* no stack. The only sane way the kernel can possibly handle
* it is to clear TF on return from the #DB handler, but this
* happens way too early to set TF in the saved pt_regs, so the
* kernel has to do something clever to avoid losing track of
* the TF bit.
*
* Needless to say, we've had bugs in this area.
*/
syscall(SYS_getpid); /* Force symbol binding without TF set. */
printf("[RUN]\tSet TF and check a fast syscall\n");
set_eflags(get_eflags() | X86_EFLAGS_TF);
syscall(SYS_getpid);
check_result();
/* Now make sure that another fast syscall doesn't set TF again. */
fast_syscall_no_tf();
/*
* And do a forced SYSENTER to make sure that this works even if
* fast syscalls don't use SYSENTER.
*
* Invoking SYSENTER directly breaks all the rules. Just handle
* the SIGSEGV.
*/
if (sigsetjmp(jmpbuf, 1) == 0) {
unsigned long nr = SYS_getpid;
printf("[RUN]\tSet TF and check SYSENTER\n");
stack_t stack = {
.ss_sp = malloc(sizeof(char) * SIGSTKSZ),
.ss_size = SIGSTKSZ,
};
if (sigaltstack(&stack, NULL) != 0)
err(1, "sigaltstack");
sethandler(SIGSEGV, print_and_longjmp,
SA_RESETHAND | SA_ONSTACK);
sethandler(SIGILL, print_and_longjmp, SA_RESETHAND);
set_eflags(get_eflags() | X86_EFLAGS_TF);
free(stack.ss_sp);
/* Clear EBP first to make sure we segfault cleanly. */
asm volatile ("xorl %%ebp, %%ebp; SYSENTER" : "+a" (nr) :: "flags", "rcx"
#ifdef __x86_64__
, "r11"
#endif
);
/* We're unreachable here. SYSENTER forgets RIP. */
}
clearhandler(SIGSEGV);
clearhandler(SIGILL);
if (!(sig_eflags & X86_EFLAGS_TF)) {
printf("[FAIL]\tTF was cleared\n");
exit(1);
}
/* Now make sure that another fast syscall doesn't set TF again. */
fast_syscall_no_tf();
return 0;
}
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/perf_event.h>
#include <cpuid.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <unistd.h>
int main(void)
{
struct perf_event_attr ret_attr, mret_attr;
long long count_rets, count_rets_mispred;
int rrets_fd, mrrets_fd;
unsigned int cpuid1_eax, b, c, d;
__cpuid(1, cpuid1_eax, b, c, d);
if (cpuid1_eax < 0x00800f00 ||
cpuid1_eax > 0x00afffff) {
fprintf(stderr, "This needs to run on a Zen[1-4] machine (CPUID(1).EAX: 0x%x). Exiting...\n", cpuid1_eax);
exit(EXIT_FAILURE);
}
memset(&ret_attr, 0, sizeof(struct perf_event_attr));
memset(&mret_attr, 0, sizeof(struct perf_event_attr));
ret_attr.type = mret_attr.type = PERF_TYPE_RAW;
ret_attr.size = mret_attr.size = sizeof(struct perf_event_attr);
ret_attr.config = 0xc8;
mret_attr.config = 0xc9;
ret_attr.disabled = mret_attr.disabled = 1;
ret_attr.exclude_user = mret_attr.exclude_user = 1;
ret_attr.exclude_hv = mret_attr.exclude_hv = 1;
rrets_fd = syscall(SYS_perf_event_open, &ret_attr, 0, -1, -1, 0);
if (rrets_fd == -1) {
perror("opening retired RETs fd");
exit(EXIT_FAILURE);
}
mrrets_fd = syscall(SYS_perf_event_open, &mret_attr, 0, -1, -1, 0);
if (mrrets_fd == -1) {
perror("opening retired mispredicted RETs fd");
exit(EXIT_FAILURE);
}
ioctl(rrets_fd, PERF_EVENT_IOC_RESET, 0);
ioctl(mrrets_fd, PERF_EVENT_IOC_RESET, 0);
ioctl(rrets_fd, PERF_EVENT_IOC_ENABLE, 0);
ioctl(mrrets_fd, PERF_EVENT_IOC_ENABLE, 0);
printf("Sleeping for 10 seconds\n");
sleep(10);
ioctl(rrets_fd, PERF_EVENT_IOC_DISABLE, 0);
ioctl(mrrets_fd, PERF_EVENT_IOC_DISABLE, 0);
read(rrets_fd, &count_rets, sizeof(long long));
read(mrrets_fd, &count_rets_mispred, sizeof(long long));
printf("RETs: (%lld retired <-> %lld mispredicted)\n",
count_rets, count_rets_mispred);
printf("SRSO Safe-RET mitigation works correctly if both counts are almost equal.\n");
return 0;
}
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* syscall_arg_fault.c - tests faults 32-bit fast syscall stack args
* Copyright (c) 2015 Andrew Lutomirski
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/signal.h>
#include <sys/ucontext.h>
#include <err.h>
#include <setjmp.h>
#include <errno.h>
#include "helpers.h"
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t n_errs;
#ifdef __x86_64__
#define REG_AX REG_RAX
#define REG_IP REG_RIP
#else
#define REG_AX REG_EAX
#define REG_IP REG_EIP
#endif
static void sigsegv_or_sigbus(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t*)ctx_void;
long ax = (long)ctx->uc_mcontext.gregs[REG_AX];
if (ax != -EFAULT && ax != -ENOSYS) {
printf("[FAIL]\tAX had the wrong value: 0x%lx\n",
(unsigned long)ax);
printf("\tIP = 0x%lx\n", (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
n_errs++;
} else {
printf("[OK]\tSeems okay\n");
}
siglongjmp(jmpbuf, 1);
}
static volatile sig_atomic_t sigtrap_consecutive_syscalls;
static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
{
/*
* KVM has some bugs that can cause us to stop making progress.
* detect them and complain, but don't infinite loop or fail the
* test.
*/
ucontext_t *ctx = (ucontext_t*)ctx_void;
unsigned short *ip = (unsigned short *)ctx->uc_mcontext.gregs[REG_IP];
if (*ip == 0x340f || *ip == 0x050f) {
/* The trap was on SYSCALL or SYSENTER */
sigtrap_consecutive_syscalls++;
if (sigtrap_consecutive_syscalls > 3) {
printf("[WARN]\tGot stuck single-stepping -- you probably have a KVM bug\n");
siglongjmp(jmpbuf, 1);
}
} else {
sigtrap_consecutive_syscalls = 0;
}
}
static void sigill(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t*)ctx_void;
unsigned short *ip = (unsigned short *)ctx->uc_mcontext.gregs[REG_IP];
if (*ip == 0x0b0f) {
/* one of the ud2 instructions faulted */
printf("[OK]\tSYSCALL returned normally\n");
} else {
printf("[SKIP]\tIllegal instruction\n");
}
siglongjmp(jmpbuf, 1);
}
int main()
{
stack_t stack = {
/* Our sigaltstack scratch space. */
.ss_sp = malloc(sizeof(char) * SIGSTKSZ),
.ss_size = SIGSTKSZ,
};
if (sigaltstack(&stack, NULL) != 0)
err(1, "sigaltstack");
sethandler(SIGSEGV, sigsegv_or_sigbus, SA_ONSTACK);
/*
* The actual exception can vary. On Atom CPUs, we get #SS
* instead of #PF when the vDSO fails to access the stack when
* ESP is too close to 2^32, and #SS causes SIGBUS.
*/
sethandler(SIGBUS, sigsegv_or_sigbus, SA_ONSTACK);
sethandler(SIGILL, sigill, SA_ONSTACK);
/*
* Exercise another nasty special case. The 32-bit SYSCALL
* and SYSENTER instructions (even in compat mode) each
* clobber one register. A Linux system call has a syscall
* number and six arguments, and the user stack pointer
* needs to live in some register on return. That means
* that we need eight registers, but SYSCALL and SYSENTER
* only preserve seven registers. As a result, one argument
* ends up on the stack. The stack is user memory, which
* means that the kernel can fail to read it.
*
* The 32-bit fast system calls don't have a defined ABI:
* we're supposed to invoke them through the vDSO. So we'll
* fudge it: we set all regs to invalid pointer values and
* invoke the entry instruction. The return will fail no
* matter what, and we completely lose our program state,
* but we can fix it up with a signal handler.
*/
printf("[RUN]\tSYSENTER with invalid state\n");
if (sigsetjmp(jmpbuf, 1) == 0) {
asm volatile (
"movl $-1, %%eax\n\t"
"movl $-1, %%ebx\n\t"
"movl $-1, %%ecx\n\t"
"movl $-1, %%edx\n\t"
"movl $-1, %%esi\n\t"
"movl $-1, %%edi\n\t"
"movl $-1, %%ebp\n\t"
"movl $-1, %%esp\n\t"
"sysenter"
: : : "memory", "flags");
}
printf("[RUN]\tSYSCALL with invalid state\n");
if (sigsetjmp(jmpbuf, 1) == 0) {
asm volatile (
"movl $-1, %%eax\n\t"
"movl $-1, %%ebx\n\t"
"movl $-1, %%ecx\n\t"
"movl $-1, %%edx\n\t"
"movl $-1, %%esi\n\t"
"movl $-1, %%edi\n\t"
"movl $-1, %%ebp\n\t"
"movl $-1, %%esp\n\t"
"syscall\n\t"
"ud2" /* make sure we recover cleanly */
: : : "memory", "flags");
}
printf("[RUN]\tSYSENTER with TF and invalid state\n");
sethandler(SIGTRAP, sigtrap, SA_ONSTACK);
if (sigsetjmp(jmpbuf, 1) == 0) {
sigtrap_consecutive_syscalls = 0;
set_eflags(get_eflags() | X86_EFLAGS_TF);
asm volatile (
"movl $-1, %%eax\n\t"
"movl $-1, %%ebx\n\t"
"movl $-1, %%ecx\n\t"
"movl $-1, %%edx\n\t"
"movl $-1, %%esi\n\t"
"movl $-1, %%edi\n\t"
"movl $-1, %%ebp\n\t"
"movl $-1, %%esp\n\t"
"sysenter"
: : : "memory", "flags");
}
set_eflags(get_eflags() & ~X86_EFLAGS_TF);
printf("[RUN]\tSYSCALL with TF and invalid state\n");
if (sigsetjmp(jmpbuf, 1) == 0) {
sigtrap_consecutive_syscalls = 0;
set_eflags(get_eflags() | X86_EFLAGS_TF);
asm volatile (
"movl $-1, %%eax\n\t"
"movl $-1, %%ebx\n\t"
"movl $-1, %%ecx\n\t"
"movl $-1, %%edx\n\t"
"movl $-1, %%esi\n\t"
"movl $-1, %%edi\n\t"
"movl $-1, %%ebp\n\t"
"movl $-1, %%esp\n\t"
"syscall\n\t"
"ud2" /* make sure we recover cleanly */
: : : "memory", "flags");
}
set_eflags(get_eflags() & ~X86_EFLAGS_TF);
#ifdef __x86_64__
printf("[RUN]\tSYSENTER with TF, invalid state, and GSBASE < 0\n");
if (sigsetjmp(jmpbuf, 1) == 0) {
sigtrap_consecutive_syscalls = 0;
asm volatile ("wrgsbase %%rax\n\t"
:: "a" (0xffffffffffff0000UL));
set_eflags(get_eflags() | X86_EFLAGS_TF);
asm volatile (
"movl $-1, %%eax\n\t"
"movl $-1, %%ebx\n\t"
"movl $-1, %%ecx\n\t"
"movl $-1, %%edx\n\t"
"movl $-1, %%esi\n\t"
"movl $-1, %%edi\n\t"
"movl $-1, %%ebp\n\t"
"movl $-1, %%esp\n\t"
"sysenter"
: : : "memory", "flags");
}
set_eflags(get_eflags() & ~X86_EFLAGS_TF);
#endif
free(stack.ss_sp);
return 0;
}
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* syscall_nt.c - checks syscalls with NT set
* Copyright (c) 2014-2015 Andrew Lutomirski
*
* Some obscure user-space code requires the ability to make system calls
* with FLAGS.NT set. Make sure it works.
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <err.h>
#include <sys/syscall.h>
#include "helpers.h"
static unsigned int nerrs;
static void sigtrap(int sig, siginfo_t *si, void *ctx_void)
{
}
static void do_it(unsigned long extraflags)
{
unsigned long flags;
set_eflags(get_eflags() | extraflags);
syscall(SYS_getpid);
flags = get_eflags();
set_eflags(X86_EFLAGS_IF | X86_EFLAGS_FIXED);
if ((flags & extraflags) == extraflags) {
printf("[OK]\tThe syscall worked and flags are still set\n");
} else {
printf("[FAIL]\tThe syscall worked but flags were cleared (flags = 0x%lx but expected 0x%lx set)\n",
flags, extraflags);
nerrs++;
}
}
int main(void)
{
printf("[RUN]\tSet NT and issue a syscall\n");
do_it(X86_EFLAGS_NT);
printf("[RUN]\tSet AC and issue a syscall\n");
do_it(X86_EFLAGS_AC);
printf("[RUN]\tSet NT|AC and issue a syscall\n");
do_it(X86_EFLAGS_NT | X86_EFLAGS_AC);
/*
* Now try it again with TF set -- TF forces returns via IRET in all
* cases except non-ptregs-using 64-bit full fast path syscalls.
*/
sethandler(SIGTRAP, sigtrap, 0);
printf("[RUN]\tSet TF and issue a syscall\n");
do_it(X86_EFLAGS_TF);
printf("[RUN]\tSet NT|TF and issue a syscall\n");
do_it(X86_EFLAGS_NT | X86_EFLAGS_TF);
printf("[RUN]\tSet AC|TF and issue a syscall\n");
do_it(X86_EFLAGS_AC | X86_EFLAGS_TF);
printf("[RUN]\tSet NT|AC|TF and issue a syscall\n");
do_it(X86_EFLAGS_NT | X86_EFLAGS_AC | X86_EFLAGS_TF);
/*
* Now try DF. This is evil and it's plausible that we will crash
* glibc, but glibc would have to do something rather surprising
* for this to happen.
*/
printf("[RUN]\tSet DF and issue a syscall\n");
do_it(X86_EFLAGS_DF);
printf("[RUN]\tSet TF|DF and issue a syscall\n");
do_it(X86_EFLAGS_TF | X86_EFLAGS_DF);
return nerrs == 0 ? 0 : 1;
}
@@ -0,0 +1,483 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* syscall_numbering.c - test calling the x86-64 kernel with various
* valid and invalid system call numbers.
*
* Copyright (c) 2018 Andrew Lutomirski
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <sysexits.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <linux/ptrace.h>
#include "kselftest.h"
/* Common system call numbers */
#define SYS_READ 0
#define SYS_WRITE 1
#define SYS_GETPID 39
/* x64-only system call numbers */
#define X64_IOCTL 16
#define X64_READV 19
#define X64_WRITEV 20
/* x32-only system call numbers (without X32_BIT) */
#define X32_IOCTL 514
#define X32_READV 515
#define X32_WRITEV 516
#define X32_BIT 0x40000000
static int nullfd = -1; /* File descriptor for /dev/null */
static bool with_x32; /* x32 supported on this kernel? */
enum ptrace_pass {
PTP_NOTHING,
PTP_GETREGS,
PTP_WRITEBACK,
PTP_FUZZRET,
PTP_FUZZHIGH,
PTP_INTNUM,
PTP_DONE
};
static const char * const ptrace_pass_name[] =
{
[PTP_NOTHING] = "just stop, no data read",
[PTP_GETREGS] = "only getregs",
[PTP_WRITEBACK] = "getregs, unmodified setregs",
[PTP_FUZZRET] = "modifying the default return",
[PTP_FUZZHIGH] = "clobbering the top 32 bits",
[PTP_INTNUM] = "sign-extending the syscall number",
};
/*
* Shared memory block between tracer and test
*/
struct shared {
unsigned int nerr; /* Total error count */
unsigned int indent; /* Message indentation level */
enum ptrace_pass ptrace_pass;
bool probing_syscall; /* In probe_syscall() */
};
static volatile struct shared *sh;
static inline unsigned int offset(void)
{
unsigned int level = sh ? sh->indent : 0;
return 8 + level * 4;
}
#define msg(lvl, fmt, ...) printf("%-*s" fmt, offset(), "[" #lvl "]", \
## __VA_ARGS__)
#define run(fmt, ...) msg(RUN, fmt, ## __VA_ARGS__)
#define info(fmt, ...) msg(INFO, fmt, ## __VA_ARGS__)
#define ok(fmt, ...) msg(OK, fmt, ## __VA_ARGS__)
#define fail(fmt, ...) \
do { \
msg(FAIL, fmt, ## __VA_ARGS__); \
sh->nerr++; \
} while (0)
#define crit(fmt, ...) \
do { \
sh->indent = 0; \
msg(FAIL, fmt, ## __VA_ARGS__); \
msg(SKIP, "Unable to run test\n"); \
exit(EX_OSERR); \
} while (0)
/* Sentinel for ptrace-modified return value */
#define MODIFIED_BY_PTRACE -9999
/*
* Directly invokes the given syscall with nullfd as the first argument
* and the rest zero. Avoids involving glibc wrappers in case they ever
* end up intercepting some system calls for some reason, or modify
* the system call number itself.
*/
static long long probe_syscall(int msb, int lsb)
{
register long long arg1 asm("rdi") = nullfd;
register long long arg2 asm("rsi") = 0;
register long long arg3 asm("rdx") = 0;
register long long arg4 asm("r10") = 0;
register long long arg5 asm("r8") = 0;
register long long arg6 asm("r9") = 0;
long long nr = ((long long)msb << 32) | (unsigned int)lsb;
long long ret;
/*
* We pass in an extra copy of the extended system call number
* in %rbx, so we can examine it from the ptrace handler without
* worrying about it being possibly modified. This is to test
* the validity of struct user regs.orig_rax a.k.a.
* struct pt_regs.orig_ax.
*/
sh->probing_syscall = true;
asm volatile("syscall"
: "=a" (ret)
: "a" (nr), "b" (nr),
"r" (arg1), "r" (arg2), "r" (arg3),
"r" (arg4), "r" (arg5), "r" (arg6)
: "rcx", "r11", "memory", "cc");
sh->probing_syscall = false;
return ret;
}
static const char *syscall_str(int msb, int start, int end)
{
static char buf[64];
const char * const type = (start & X32_BIT) ? "x32" : "x64";
int lsb = start;
/*
* Improve readability by stripping the x32 bit, but round
* toward zero so we don't display -1 as -1073741825.
*/
if (lsb < 0)
lsb |= X32_BIT;
else
lsb &= ~X32_BIT;
if (start == end)
snprintf(buf, sizeof buf, "%s syscall %d:%d",
type, msb, lsb);
else
snprintf(buf, sizeof buf, "%s syscalls %d:%d..%d",
type, msb, lsb, lsb + (end-start));
return buf;
}
static unsigned int _check_for(int msb, int start, int end, long long expect,
const char *expect_str)
{
unsigned int err = 0;
sh->indent++;
if (start != end)
sh->indent++;
for (int nr = start; nr <= end; nr++) {
long long ret = probe_syscall(msb, nr);
if (ret != expect) {
fail("%s returned %lld, but it should have returned %s\n",
syscall_str(msb, nr, nr),
ret, expect_str);
err++;
}
}
if (start != end)
sh->indent--;
if (err) {
if (start != end)
fail("%s had %u failure%s\n",
syscall_str(msb, start, end),
err, err == 1 ? "s" : "");
} else {
ok("%s returned %s as expected\n",
syscall_str(msb, start, end), expect_str);
}
sh->indent--;
return err;
}
#define check_for(msb,start,end,expect) \
_check_for(msb,start,end,expect,#expect)
static bool check_zero(int msb, int nr)
{
return check_for(msb, nr, nr, 0);
}
static bool check_enosys(int msb, int nr)
{
return check_for(msb, nr, nr, -ENOSYS);
}
/*
* Anyone diagnosing a failure will want to know whether the kernel
* supports x32. Tell them. This can also be used to conditionalize
* tests based on existence or nonexistence of x32.
*/
static bool test_x32(void)
{
long long ret;
pid_t mypid = getpid();
run("Checking for x32 by calling x32 getpid()\n");
ret = probe_syscall(0, SYS_GETPID | X32_BIT);
sh->indent++;
if (ret == mypid) {
info("x32 is supported\n");
with_x32 = true;
} else if (ret == -ENOSYS) {
info("x32 is not supported\n");
with_x32 = false;
} else {
fail("x32 getpid() returned %lld, but it should have returned either %lld or -ENOSYS\n", ret, (long long)mypid);
with_x32 = false;
}
sh->indent--;
return with_x32;
}
static void test_syscalls_common(int msb)
{
enum ptrace_pass pass = sh->ptrace_pass;
run("Checking some common syscalls as 64 bit\n");
check_zero(msb, SYS_READ);
check_zero(msb, SYS_WRITE);
run("Checking some 64-bit only syscalls as 64 bit\n");
check_zero(msb, X64_READV);
check_zero(msb, X64_WRITEV);
run("Checking out of range system calls\n");
check_for(msb, -64, -2, -ENOSYS);
if (pass >= PTP_FUZZRET)
check_for(msb, -1, -1, MODIFIED_BY_PTRACE);
else
check_for(msb, -1, -1, -ENOSYS);
check_for(msb, X32_BIT-64, X32_BIT-1, -ENOSYS);
check_for(msb, -64-X32_BIT, -1-X32_BIT, -ENOSYS);
check_for(msb, INT_MAX-64, INT_MAX-1, -ENOSYS);
}
static void test_syscalls_with_x32(int msb)
{
/*
* Syscalls 512-547 are "x32" syscalls. They are
* intended to be called with the x32 (0x40000000) bit
* set. Calling them without the x32 bit set is
* nonsense and should not work.
*/
run("Checking x32 syscalls as 64 bit\n");
check_for(msb, 512, 547, -ENOSYS);
run("Checking some common syscalls as x32\n");
check_zero(msb, SYS_READ | X32_BIT);
check_zero(msb, SYS_WRITE | X32_BIT);
run("Checking some x32 syscalls as x32\n");
check_zero(msb, X32_READV | X32_BIT);
check_zero(msb, X32_WRITEV | X32_BIT);
run("Checking some 64-bit syscalls as x32\n");
check_enosys(msb, X64_IOCTL | X32_BIT);
check_enosys(msb, X64_READV | X32_BIT);
check_enosys(msb, X64_WRITEV | X32_BIT);
}
static void test_syscalls_without_x32(int msb)
{
run("Checking for absence of x32 system calls\n");
check_for(msb, 0 | X32_BIT, 999 | X32_BIT, -ENOSYS);
}
static void test_syscall_numbering(void)
{
static const int msbs[] = {
0, 1, -1, X32_BIT-1, X32_BIT, X32_BIT-1, -X32_BIT, INT_MAX,
INT_MIN, INT_MIN+1
};
sh->indent++;
/*
* The MSB is supposed to be ignored, so we loop over a few
* to test that out.
*/
for (size_t i = 0; i < ARRAY_SIZE(msbs); i++) {
int msb = msbs[i];
run("Checking system calls with msb = %d (0x%x)\n",
msb, msb);
sh->indent++;
test_syscalls_common(msb);
if (with_x32)
test_syscalls_with_x32(msb);
else
test_syscalls_without_x32(msb);
sh->indent--;
}
sh->indent--;
}
static void syscall_numbering_tracee(void)
{
enum ptrace_pass pass;
if (ptrace(PTRACE_TRACEME, 0, 0, 0)) {
crit("Failed to request tracing\n");
return;
}
raise(SIGSTOP);
for (sh->ptrace_pass = pass = PTP_NOTHING; pass < PTP_DONE;
sh->ptrace_pass = ++pass) {
run("Running tests under ptrace: %s\n", ptrace_pass_name[pass]);
test_syscall_numbering();
}
}
static void mess_with_syscall(pid_t testpid, enum ptrace_pass pass)
{
struct user_regs_struct regs;
sh->probing_syscall = false; /* Do this on entry only */
/* For these, don't even getregs */
if (pass == PTP_NOTHING || pass == PTP_DONE)
return;
ptrace(PTRACE_GETREGS, testpid, NULL, &regs);
if (regs.orig_rax != regs.rbx) {
fail("orig_rax %#llx doesn't match syscall number %#llx\n",
(unsigned long long)regs.orig_rax,
(unsigned long long)regs.rbx);
}
switch (pass) {
case PTP_GETREGS:
/* Just read, no writeback */
return;
case PTP_WRITEBACK:
/* Write back the same register state verbatim */
break;
case PTP_FUZZRET:
regs.rax = MODIFIED_BY_PTRACE;
break;
case PTP_FUZZHIGH:
regs.rax = MODIFIED_BY_PTRACE;
regs.orig_rax = regs.orig_rax | 0xffffffff00000000ULL;
break;
case PTP_INTNUM:
regs.rax = MODIFIED_BY_PTRACE;
regs.orig_rax = (int)regs.orig_rax;
break;
default:
crit("invalid ptrace_pass\n");
break;
}
ptrace(PTRACE_SETREGS, testpid, NULL, &regs);
}
static void syscall_numbering_tracer(pid_t testpid)
{
int wstatus;
do {
pid_t wpid = waitpid(testpid, &wstatus, 0);
if (wpid < 0 && errno != EINTR)
break;
if (wpid != testpid)
continue;
if (!WIFSTOPPED(wstatus))
break; /* Thread exited? */
if (sh->probing_syscall && WSTOPSIG(wstatus) == SIGTRAP)
mess_with_syscall(testpid, sh->ptrace_pass);
} while (sh->ptrace_pass != PTP_DONE &&
!ptrace(PTRACE_SYSCALL, testpid, NULL, NULL));
ptrace(PTRACE_DETACH, testpid, NULL, NULL);
/* Wait for the child process to terminate */
while (waitpid(testpid, &wstatus, 0) != testpid || !WIFEXITED(wstatus))
/* wait some more */;
}
static void test_traced_syscall_numbering(void)
{
pid_t testpid;
/* Launch the test thread; this thread continues as the tracer thread */
testpid = fork();
if (testpid < 0) {
crit("Unable to launch tracer process\n");
} else if (testpid == 0) {
syscall_numbering_tracee();
_exit(0);
} else {
syscall_numbering_tracer(testpid);
}
}
int main(void)
{
unsigned int nerr;
/*
* It is quite likely to get a segfault on a failure, so make
* sure the message gets out by setting stdout to nonbuffered.
*/
setvbuf(stdout, NULL, _IONBF, 0);
/*
* Harmless file descriptor to work on...
*/
nullfd = open("/dev/null", O_RDWR);
if (nullfd < 0) {
crit("Unable to open /dev/null: %s\n", strerror(errno));
}
/*
* Set up a block of shared memory...
*/
sh = mmap(NULL, sysconf(_SC_PAGE_SIZE), PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_SHARED, 0, 0);
if (sh == MAP_FAILED) {
crit("Unable to allocated shared memory block: %s\n",
strerror(errno));
}
with_x32 = test_x32();
run("Running tests without ptrace...\n");
test_syscall_numbering();
test_traced_syscall_numbering();
nerr = sh->nerr;
if (!nerr) {
ok("All system calls succeeded or failed as expected\n");
return 0;
} else {
fail("A total of %u system call%s had incorrect behavior\n",
nerr, nerr != 1 ? "s" : "");
return 1;
}
}
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sigreturn.c - tests that x86 avoids Intel SYSRET pitfalls
* Copyright (c) 2014-2016 Andrew Lutomirski
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <sys/signal.h>
#include <sys/ucontext.h>
#include <sys/syscall.h>
#include <err.h>
#include <stddef.h>
#include <stdbool.h>
#include <setjmp.h>
#include <sys/user.h>
#include <sys/mman.h>
#include <assert.h>
#include "helpers.h"
/*
* These items are in clang_helpers_64.S, in order to avoid clang inline asm
* limitations:
*/
void test_syscall_ins(void);
extern const char test_page[];
static const void *current_test_page_addr = test_page;
/* State used by our signal handlers. */
static gregset_t initial_regs;
static volatile unsigned long rip;
static void sigsegv_for_sigreturn_test(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t *)ctx_void;
if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
printf("[FAIL]\tRequested RIP=0x%lx but got RIP=0x%lx\n",
rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
fflush(stdout);
_exit(1);
}
memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t));
printf("[OK]\tGot SIGSEGV at RIP=0x%lx\n", rip);
}
static void sigusr1(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t *)ctx_void;
memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t));
/* Set IP and CX to match so that SYSRET can happen. */
ctx->uc_mcontext.gregs[REG_RIP] = rip;
ctx->uc_mcontext.gregs[REG_RCX] = rip;
/* R11 and EFLAGS should already match. */
assert(ctx->uc_mcontext.gregs[REG_EFL] ==
ctx->uc_mcontext.gregs[REG_R11]);
sethandler(SIGSEGV, sigsegv_for_sigreturn_test, SA_RESETHAND);
}
static void test_sigreturn_to(unsigned long ip)
{
rip = ip;
printf("[RUN]\tsigreturn to 0x%lx\n", ip);
raise(SIGUSR1);
}
static jmp_buf jmpbuf;
static void sigsegv_for_fallthrough(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t *)ctx_void;
if (rip != ctx->uc_mcontext.gregs[REG_RIP]) {
printf("[FAIL]\tExpected SIGSEGV at 0x%lx but got RIP=0x%lx\n",
rip, (unsigned long)ctx->uc_mcontext.gregs[REG_RIP]);
fflush(stdout);
_exit(1);
}
siglongjmp(jmpbuf, 1);
}
static void test_syscall_fallthrough_to(unsigned long ip)
{
void *new_address = (void *)(ip - 4096);
void *ret;
printf("[RUN]\tTrying a SYSCALL that falls through to 0x%lx\n", ip);
ret = mremap((void *)current_test_page_addr, 4096, 4096,
MREMAP_MAYMOVE | MREMAP_FIXED, new_address);
if (ret == MAP_FAILED) {
if (ip <= (1UL << 47) - PAGE_SIZE) {
err(1, "mremap to %p", new_address);
} else {
printf("[OK]\tmremap to %p failed\n", new_address);
return;
}
}
if (ret != new_address)
errx(1, "mremap malfunctioned: asked for %p but got %p\n",
new_address, ret);
current_test_page_addr = new_address;
rip = ip;
if (sigsetjmp(jmpbuf, 1) == 0) {
asm volatile ("call *%[syscall_insn]" :: "a" (SYS_getpid),
[syscall_insn] "rm" (ip - 2));
errx(1, "[FAIL]\tSyscall trampoline returned");
}
printf("[OK]\tWe survived\n");
}
int main(void)
{
/*
* When the kernel returns from a slow-path syscall, it will
* detect whether SYSRET is appropriate. If it incorrectly
* thinks that SYSRET is appropriate when RIP is noncanonical,
* it'll crash on Intel CPUs.
*/
sethandler(SIGUSR1, sigusr1, 0);
for (int i = 47; i < 64; i++)
test_sigreturn_to(1UL<<i);
clearhandler(SIGUSR1);
sethandler(SIGSEGV, sigsegv_for_fallthrough, 0);
/* One extra test to check that we didn't screw up the mremap logic. */
test_syscall_fallthrough_to((1UL << 47) - 2*PAGE_SIZE);
/* These are the interesting cases. */
for (int i = 47; i < 64; i++) {
test_syscall_fallthrough_to((1UL<<i) - PAGE_SIZE);
test_syscall_fallthrough_to(1UL<<i);
}
return 0;
}
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* sysret_ss_attrs.c - test that syscalls return valid hidden SS attributes
* Copyright (c) 2015 Andrew Lutomirski
*
* On AMD CPUs, SYSRET can return with a valid SS descriptor with with
* the hidden attributes set to an unusable state. Make sure the kernel
* doesn't let this happen.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <err.h>
#include <stddef.h>
#include <stdbool.h>
#include <pthread.h>
static void *threadproc(void *ctx)
{
/*
* Do our best to cause sleeps on this CPU to exit the kernel and
* re-enter with SS = 0.
*/
while (true)
;
return NULL;
}
#ifdef __x86_64__
extern unsigned long call32_from_64(void *stack, void (*function)(void));
asm (".pushsection .text\n\t"
".code32\n\t"
"test_ss:\n\t"
"pushl $0\n\t"
"popl %eax\n\t"
"ret\n\t"
".code64");
extern void test_ss(void);
#endif
int main()
{
/*
* Start a busy-looping thread on the same CPU we're on.
* For simplicity, just stick everything to CPU 0. This will
* fail in some containers, but that's probably okay.
*/
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
printf("[WARN]\tsched_setaffinity failed\n");
pthread_t thread;
if (pthread_create(&thread, 0, threadproc, 0) != 0)
err(1, "pthread_create");
#ifdef __x86_64__
unsigned char *stack32 = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_32BIT | MAP_ANONYMOUS | MAP_PRIVATE,
-1, 0);
if (stack32 == MAP_FAILED)
err(1, "mmap");
#endif
printf("[RUN]\tSyscalls followed by SS validation\n");
for (int i = 0; i < 1000; i++) {
/*
* Go to sleep and return using sysret (if we're 64-bit
* or we're 32-bit on AMD on a 64-bit kernel). On AMD CPUs,
* SYSRET doesn't fix up the cached SS descriptor, so the
* kernel needs some kind of workaround to make sure that we
* end the system call with a valid stack segment. This
* can be a confusing failure because the SS *selector*
* is the same regardless.
*/
usleep(2);
#ifdef __x86_64__
/*
* On 32-bit, just doing a syscall through glibc is enough
* to cause a crash if our cached SS descriptor is invalid.
* On 64-bit, it's not, so try extra hard.
*/
call32_from_64(stack32 + 4088, test_ss);
#endif
}
printf("[OK]\tWe survived\n");
#ifdef __x86_64__
munmap(stack32, 4096);
#endif
return 0;
}
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0
#undef _GNU_SOURCE
#define _GNU_SOURCE 1
#undef __USE_GNU
#define __USE_GNU 1
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/wait.h>
#define TEST(insn) \
long double __attribute__((noinline)) insn(long flags) \
{ \
long double out; \
asm ("\n" \
" push %1""\n" \
" popf""\n" \
" fldpi""\n" \
" fld1""\n" \
" " #insn " %%st(1), %%st" "\n" \
" ffree %%st(1)" "\n" \
: "=t" (out) \
: "r" (flags) \
); \
return out; \
}
TEST(fcmovb)
TEST(fcmove)
TEST(fcmovbe)
TEST(fcmovu)
TEST(fcmovnb)
TEST(fcmovne)
TEST(fcmovnbe)
TEST(fcmovnu)
enum {
CF = 1 << 0,
PF = 1 << 2,
ZF = 1 << 6,
};
void sighandler(int sig)
{
printf("[FAIL]\tGot signal %d, exiting\n", sig);
exit(1);
}
int main(int argc, char **argv, char **envp)
{
int err = 0;
/* SIGILL triggers on 32-bit kernels w/o fcomi emulation
* when run with "no387 nofxsr". Other signals are caught
* just in case.
*/
signal(SIGILL, sighandler);
signal(SIGFPE, sighandler);
signal(SIGSEGV, sighandler);
printf("[RUN]\tTesting fcmovCC instructions\n");
/* If fcmovCC() returns 1.0, the move wasn't done */
err |= !(fcmovb(0) == 1.0); err |= !(fcmovnb(0) != 1.0);
err |= !(fcmove(0) == 1.0); err |= !(fcmovne(0) != 1.0);
err |= !(fcmovbe(0) == 1.0); err |= !(fcmovnbe(0) != 1.0);
err |= !(fcmovu(0) == 1.0); err |= !(fcmovnu(0) != 1.0);
err |= !(fcmovb(CF) != 1.0); err |= !(fcmovnb(CF) == 1.0);
err |= !(fcmove(CF) == 1.0); err |= !(fcmovne(CF) != 1.0);
err |= !(fcmovbe(CF) != 1.0); err |= !(fcmovnbe(CF) == 1.0);
err |= !(fcmovu(CF) == 1.0); err |= !(fcmovnu(CF) != 1.0);
err |= !(fcmovb(ZF) == 1.0); err |= !(fcmovnb(ZF) != 1.0);
err |= !(fcmove(ZF) != 1.0); err |= !(fcmovne(ZF) == 1.0);
err |= !(fcmovbe(ZF) != 1.0); err |= !(fcmovnbe(ZF) == 1.0);
err |= !(fcmovu(ZF) == 1.0); err |= !(fcmovnu(ZF) != 1.0);
err |= !(fcmovb(PF) == 1.0); err |= !(fcmovnb(PF) != 1.0);
err |= !(fcmove(PF) == 1.0); err |= !(fcmovne(PF) != 1.0);
err |= !(fcmovbe(PF) == 1.0); err |= !(fcmovnbe(PF) != 1.0);
err |= !(fcmovu(PF) != 1.0); err |= !(fcmovnu(PF) == 1.0);
if (!err)
printf("[OK]\tfcmovCC\n");
else
printf("[FAIL]\tfcmovCC errors: %d\n", err);
return err;
}
@@ -0,0 +1,332 @@
// SPDX-License-Identifier: GPL-2.0
#undef _GNU_SOURCE
#define _GNU_SOURCE 1
#undef __USE_GNU
#define __USE_GNU 1
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <fenv.h>
enum {
CF = 1 << 0,
PF = 1 << 2,
ZF = 1 << 6,
ARITH = CF | PF | ZF,
};
long res_fcomi_pi_1;
long res_fcomi_1_pi;
long res_fcomi_1_1;
long res_fcomi_nan_1;
/* sNaN is s|111 1111 1|1xx xxxx xxxx xxxx xxxx xxxx */
/* qNaN is s|111 1111 1|0xx xxxx xxxx xxxx xxxx xxxx (some x must be nonzero) */
int snan = 0x7fc11111;
int qnan = 0x7f811111;
unsigned short snan1[5];
/* sNaN80 is s|111 1111 1111 1111 |10xx xx...xx (some x must be nonzero) */
unsigned short snan80[5] = { 0x1111, 0x1111, 0x1111, 0x8111, 0x7fff };
int test(long flags)
{
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm ("\n"
" push %0""\n"
" popf""\n"
" fld1""\n"
" fldpi""\n"
" fcomi %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" ffree %%st(1)" "\n"
" pushf""\n"
" pop res_fcomi_1_pi""\n"
" push %0""\n"
" popf""\n"
" fldpi""\n"
" fld1""\n"
" fcomi %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" ffree %%st(1)" "\n"
" pushf""\n"
" pop res_fcomi_pi_1""\n"
" push %0""\n"
" popf""\n"
" fld1""\n"
" fld1""\n"
" fcomi %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" ffree %%st(1)" "\n"
" pushf""\n"
" pop res_fcomi_1_1""\n"
:
: "r" (flags)
);
if ((res_fcomi_1_pi & ARITH) != (0)) {
printf("[BAD]\tfcomi_1_pi with flags:%lx\n", flags);
return 1;
}
if ((res_fcomi_pi_1 & ARITH) != (CF)) {
printf("[BAD]\tfcomi_pi_1 with flags:%lx->%lx\n", flags, res_fcomi_pi_1 & ARITH);
return 1;
}
if ((res_fcomi_1_1 & ARITH) != (ZF)) {
printf("[BAD]\tfcomi_1_1 with flags:%lx\n", flags);
return 1;
}
if (fetestexcept(FE_INVALID) != 0) {
printf("[BAD]\tFE_INVALID is set in %s\n", __func__);
return 1;
}
return 0;
}
int test_qnan(long flags)
{
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm ("\n"
" push %0""\n"
" popf""\n"
" flds qnan""\n"
" fld1""\n"
" fnclex""\n" // fld of a qnan raised FE_INVALID, clear it
" fcomi %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" ffree %%st(1)" "\n"
" pushf""\n"
" pop res_fcomi_nan_1""\n"
:
: "r" (flags)
);
if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) {
printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags);
return 1;
}
if (fetestexcept(FE_INVALID) != FE_INVALID) {
printf("[BAD]\tFE_INVALID is not set in %s\n", __func__);
return 1;
}
return 0;
}
int testu_qnan(long flags)
{
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm ("\n"
" push %0""\n"
" popf""\n"
" flds qnan""\n"
" fld1""\n"
" fnclex""\n" // fld of a qnan raised FE_INVALID, clear it
" fucomi %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" ffree %%st(1)" "\n"
" pushf""\n"
" pop res_fcomi_nan_1""\n"
:
: "r" (flags)
);
if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) {
printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags);
return 1;
}
if (fetestexcept(FE_INVALID) != 0) {
printf("[BAD]\tFE_INVALID is set in %s\n", __func__);
return 1;
}
return 0;
}
int testu_snan(long flags)
{
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm ("\n"
" push %0""\n"
" popf""\n"
// " flds snan""\n" // WRONG, this will convert 32-bit fp snan to a *qnan* in 80-bit fp register!
// " fstpt snan1""\n" // if uncommented, it prints "snan1:7fff c111 1100 0000 0000" - c111, not 8111!
// " fnclex""\n" // flds of a snan raised FE_INVALID, clear it
" fldt snan80""\n" // fldt never raise FE_INVALID
" fld1""\n"
" fucomi %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" ffree %%st(1)" "\n"
" pushf""\n"
" pop res_fcomi_nan_1""\n"
:
: "r" (flags)
);
if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) {
printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags);
return 1;
}
// printf("snan:%x snan1:%04x %04x %04x %04x %04x\n", snan, snan1[4], snan1[3], snan1[2], snan1[1], snan1[0]);
if (fetestexcept(FE_INVALID) != FE_INVALID) {
printf("[BAD]\tFE_INVALID is not set in %s\n", __func__);
return 1;
}
return 0;
}
int testp(long flags)
{
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm ("\n"
" push %0""\n"
" popf""\n"
" fld1""\n"
" fldpi""\n"
" fcomip %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" pushf""\n"
" pop res_fcomi_1_pi""\n"
" push %0""\n"
" popf""\n"
" fldpi""\n"
" fld1""\n"
" fcomip %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" pushf""\n"
" pop res_fcomi_pi_1""\n"
" push %0""\n"
" popf""\n"
" fld1""\n"
" fld1""\n"
" fcomip %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" pushf""\n"
" pop res_fcomi_1_1""\n"
:
: "r" (flags)
);
if ((res_fcomi_1_pi & ARITH) != (0)) {
printf("[BAD]\tfcomi_1_pi with flags:%lx\n", flags);
return 1;
}
if ((res_fcomi_pi_1 & ARITH) != (CF)) {
printf("[BAD]\tfcomi_pi_1 with flags:%lx->%lx\n", flags, res_fcomi_pi_1 & ARITH);
return 1;
}
if ((res_fcomi_1_1 & ARITH) != (ZF)) {
printf("[BAD]\tfcomi_1_1 with flags:%lx\n", flags);
return 1;
}
if (fetestexcept(FE_INVALID) != 0) {
printf("[BAD]\tFE_INVALID is set in %s\n", __func__);
return 1;
}
return 0;
}
int testp_qnan(long flags)
{
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm ("\n"
" push %0""\n"
" popf""\n"
" flds qnan""\n"
" fld1""\n"
" fnclex""\n" // fld of a qnan raised FE_INVALID, clear it
" fcomip %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" pushf""\n"
" pop res_fcomi_nan_1""\n"
:
: "r" (flags)
);
if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) {
printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags);
return 1;
}
if (fetestexcept(FE_INVALID) != FE_INVALID) {
printf("[BAD]\tFE_INVALID is not set in %s\n", __func__);
return 1;
}
return 0;
}
int testup_qnan(long flags)
{
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm ("\n"
" push %0""\n"
" popf""\n"
" flds qnan""\n"
" fld1""\n"
" fnclex""\n" // fld of a qnan raised FE_INVALID, clear it
" fucomip %%st(1), %%st" "\n"
" ffree %%st(0)" "\n"
" pushf""\n"
" pop res_fcomi_nan_1""\n"
:
: "r" (flags)
);
if ((res_fcomi_nan_1 & ARITH) != (ZF|CF|PF)) {
printf("[BAD]\tfcomi_qnan_1 with flags:%lx\n", flags);
return 1;
}
if (fetestexcept(FE_INVALID) != 0) {
printf("[BAD]\tFE_INVALID is set in %s\n", __func__);
return 1;
}
return 0;
}
void sighandler(int sig)
{
printf("[FAIL]\tGot signal %d, exiting\n", sig);
exit(1);
}
int main(int argc, char **argv, char **envp)
{
int err = 0;
/* SIGILL triggers on 32-bit kernels w/o fcomi emulation
* when run with "no387 nofxsr". Other signals are caught
* just in case.
*/
signal(SIGILL, sighandler);
signal(SIGFPE, sighandler);
signal(SIGSEGV, sighandler);
printf("[RUN]\tTesting f[u]comi[p] instructions\n");
err |= test(0);
err |= test_qnan(0);
err |= testu_qnan(0);
err |= testu_snan(0);
err |= test(CF|ZF|PF);
err |= test_qnan(CF|ZF|PF);
err |= testu_qnan(CF|ZF|PF);
err |= testu_snan(CF|ZF|PF);
err |= testp(0);
err |= testp_qnan(0);
err |= testup_qnan(0);
err |= testp(CF|ZF|PF);
err |= testp_qnan(CF|ZF|PF);
err |= testup_qnan(CF|ZF|PF);
if (!err)
printf("[OK]\tf[u]comi[p]\n");
else
printf("[FAIL]\tf[u]comi[p] errors: %d\n", err);
return err;
}
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-2.0
#undef _GNU_SOURCE
#define _GNU_SOURCE 1
#undef __USE_GNU
#define __USE_GNU 1
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <fenv.h>
unsigned long long res64 = -1;
unsigned int res32 = -1;
unsigned short res16 = -1;
int test(void)
{
int ex;
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm volatile ("\n"
" fld1""\n"
" fisttps res16""\n"
" fld1""\n"
" fisttpl res32""\n"
" fld1""\n"
" fisttpll res64""\n"
: : : "memory"
);
if (res16 != 1 || res32 != 1 || res64 != 1) {
printf("[BAD]\tfisttp 1\n");
return 1;
}
ex = fetestexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
if (ex != 0) {
printf("[BAD]\tfisttp 1: wrong exception state\n");
return 1;
}
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm volatile ("\n"
" fldpi""\n"
" fisttps res16""\n"
" fldpi""\n"
" fisttpl res32""\n"
" fldpi""\n"
" fisttpll res64""\n"
: : : "memory"
);
if (res16 != 3 || res32 != 3 || res64 != 3) {
printf("[BAD]\tfisttp pi\n");
return 1;
}
ex = fetestexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
if (ex != FE_INEXACT) {
printf("[BAD]\tfisttp pi: wrong exception state\n");
return 1;
}
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm volatile ("\n"
" fldpi""\n"
" fchs""\n"
" fisttps res16""\n"
" fldpi""\n"
" fchs""\n"
" fisttpl res32""\n"
" fldpi""\n"
" fchs""\n"
" fisttpll res64""\n"
: : : "memory"
);
if (res16 != 0xfffd || res32 != 0xfffffffd || res64 != 0xfffffffffffffffdULL) {
printf("[BAD]\tfisttp -pi\n");
return 1;
}
ex = fetestexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
if (ex != FE_INEXACT) {
printf("[BAD]\tfisttp -pi: wrong exception state\n");
return 1;
}
feclearexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
asm volatile ("\n"
" fldln2""\n"
" fisttps res16""\n"
" fldln2""\n"
" fisttpl res32""\n"
" fldln2""\n"
" fisttpll res64""\n"
: : : "memory"
);
/* Test truncation to zero (round-to-nearest would give 1 here) */
if (res16 != 0 || res32 != 0 || res64 != 0) {
printf("[BAD]\tfisttp ln2\n");
return 1;
}
ex = fetestexcept(FE_DIVBYZERO|FE_INEXACT|FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW);
if (ex != FE_INEXACT) {
printf("[BAD]\tfisttp ln2: wrong exception state\n");
return 1;
}
return 0;
}
void sighandler(int sig)
{
printf("[FAIL]\tGot signal %d, exiting\n", sig);
exit(1);
}
int main(int argc, char **argv, char **envp)
{
int err = 0;
/* SIGILL triggers on 32-bit kernels w/o fisttp emulation
* when run with "no387 nofxsr". Other signals are caught
* just in case.
*/
signal(SIGILL, sighandler);
signal(SIGFPE, sighandler);
signal(SIGSEGV, sighandler);
printf("[RUN]\tTesting fisttp instructions\n");
err |= test();
if (!err)
printf("[OK]\tfisttp\n");
else
printf("[FAIL]\tfisttp errors: %d\n", err);
return err;
}
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 32-bit test to check vDSO mremap.
*
* Copyright (c) 2016 Dmitry Safonov
* Suggested-by: Andrew Lutomirski
*/
/*
* Can be built statically:
* gcc -Os -Wall -static -m32 test_mremap_vdso.c
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <sys/auxv.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include "kselftest.h"
#define PAGE_SIZE 4096
static int try_to_remap(void *vdso_addr, unsigned long size)
{
void *dest_addr, *new_addr;
/* Searching for memory location where to remap */
dest_addr = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (dest_addr == MAP_FAILED) {
ksft_print_msg("WARN: mmap failed (%d): %m\n", errno);
return 0;
}
ksft_print_msg("Moving vDSO: [%p, %#lx] -> [%p, %#lx]\n",
vdso_addr, (unsigned long)vdso_addr + size,
dest_addr, (unsigned long)dest_addr + size);
fflush(stdout);
new_addr = mremap(vdso_addr, size, size,
MREMAP_FIXED|MREMAP_MAYMOVE, dest_addr);
if ((unsigned long)new_addr == (unsigned long)-1) {
munmap(dest_addr, size);
if (errno == EINVAL) {
ksft_print_msg("vDSO partial move failed, will try with bigger size\n");
return -1; /* Retry with larger */
}
ksft_print_msg("[FAIL]\tmremap failed (%d): %m\n", errno);
return 1;
}
return 0;
}
#define VDSO_NAME "[vdso]"
#define VMFLAGS "VmFlags:"
#define MSEAL_FLAGS "sl"
#define MAX_LINE_LEN 512
bool vdso_sealed(FILE *maps)
{
char line[MAX_LINE_LEN];
bool has_vdso = false;
while (fgets(line, sizeof(line), maps)) {
if (strstr(line, VDSO_NAME))
has_vdso = true;
if (has_vdso && !strncmp(line, VMFLAGS, strlen(VMFLAGS))) {
if (strstr(line, MSEAL_FLAGS))
return true;
return false;
}
}
return false;
}
int main(int argc, char **argv, char **envp)
{
pid_t child;
FILE *maps;
ksft_print_header();
ksft_set_plan(1);
maps = fopen("/proc/self/smaps", "r");
if (!maps) {
ksft_test_result_skip(
"Could not open /proc/self/smaps, errno=%d\n",
errno);
return 0;
}
if (vdso_sealed(maps)) {
ksft_test_result_skip("vdso is sealed\n");
return 0;
}
fclose(maps);
child = fork();
if (child == -1)
ksft_exit_fail_msg("failed to fork (%d): %m\n", errno);
if (child == 0) {
unsigned long vdso_size = PAGE_SIZE;
unsigned long auxval;
int ret = -1;
auxval = getauxval(AT_SYSINFO_EHDR);
ksft_print_msg("AT_SYSINFO_EHDR is %#lx\n", auxval);
if (!auxval || auxval == -ENOENT) {
ksft_print_msg("WARN: getauxval failed\n");
return 0;
}
/* Simpler than parsing ELF header */
while (ret < 0) {
ret = try_to_remap((void *)auxval, vdso_size);
vdso_size += PAGE_SIZE;
}
#ifdef __i386__
/* Glibc is likely to explode now - exit with raw syscall */
asm volatile ("int $0x80" : : "a" (__NR_exit), "b" (!!ret));
#else /* __x86_64__ */
syscall(SYS_exit, ret);
#endif
} else {
int status;
if (waitpid(child, &status, 0) != child ||
!WIFEXITED(status))
ksft_test_result_fail("mremap() of the vDSO does not work on this kernel!\n");
else if (WEXITSTATUS(status) != 0)
ksft_test_result_fail("Child failed with %d\n", WEXITSTATUS(status));
else
ksft_test_result_pass("%s\n", __func__);
}
ksft_finished();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,400 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 32-bit syscall ABI conformance test.
*
* Copyright (c) 2015 Denys Vlasenko
*/
/*
* Can be built statically:
* gcc -Os -Wall -static -m32 test_syscall_vdso.c thunks_32.S
*/
#undef _GNU_SOURCE
#define _GNU_SOURCE 1
#undef __USE_GNU
#define __USE_GNU 1
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <elf.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#if !defined(__i386__)
int main(int argc, char **argv, char **envp)
{
printf("[SKIP]\tNot a 32-bit x86 userspace\n");
return 0;
}
#else
long syscall_addr;
long get_syscall(char **envp)
{
Elf32_auxv_t *auxv;
while (*envp++ != NULL)
continue;
for (auxv = (void *)envp; auxv->a_type != AT_NULL; auxv++)
if (auxv->a_type == AT_SYSINFO)
return auxv->a_un.a_val;
printf("[WARN]\tAT_SYSINFO not supplied\n");
return 0;
}
asm (
" .pushsection .text\n"
" .global int80\n"
"int80:\n"
" int $0x80\n"
" ret\n"
" .popsection\n"
);
extern char int80;
struct regs64 {
uint64_t rax, rbx, rcx, rdx;
uint64_t rsi, rdi, rbp, rsp;
uint64_t r8, r9, r10, r11;
uint64_t r12, r13, r14, r15;
};
struct regs64 regs64;
int kernel_is_64bit;
asm (
" .pushsection .text\n"
" .code64\n"
"get_regs64:\n"
" push %rax\n"
" mov $regs64, %eax\n"
" pop 0*8(%rax)\n"
" movq %rbx, 1*8(%rax)\n"
" movq %rcx, 2*8(%rax)\n"
" movq %rdx, 3*8(%rax)\n"
" movq %rsi, 4*8(%rax)\n"
" movq %rdi, 5*8(%rax)\n"
" movq %rbp, 6*8(%rax)\n"
" movq %rsp, 7*8(%rax)\n"
" movq %r8, 8*8(%rax)\n"
" movq %r9, 9*8(%rax)\n"
" movq %r10, 10*8(%rax)\n"
" movq %r11, 11*8(%rax)\n"
" movq %r12, 12*8(%rax)\n"
" movq %r13, 13*8(%rax)\n"
" movq %r14, 14*8(%rax)\n"
" movq %r15, 15*8(%rax)\n"
" ret\n"
"poison_regs64:\n"
" movq $0x7f7f7f7f, %r8\n"
" shl $32, %r8\n"
" orq $0x7f7f7f7f, %r8\n"
" movq %r8, %r9\n"
" incq %r9\n"
" movq %r9, %r10\n"
" incq %r10\n"
" movq %r10, %r11\n"
" incq %r11\n"
" movq %r11, %r12\n"
" incq %r12\n"
" movq %r12, %r13\n"
" incq %r13\n"
" movq %r13, %r14\n"
" incq %r14\n"
" movq %r14, %r15\n"
" incq %r15\n"
" ret\n"
" .code32\n"
" .popsection\n"
);
extern void get_regs64(void);
extern void poison_regs64(void);
extern unsigned long call64_from_32(void (*function)(void));
void print_regs64(void)
{
if (!kernel_is_64bit)
return;
printf("ax:%016llx bx:%016llx cx:%016llx dx:%016llx\n", regs64.rax, regs64.rbx, regs64.rcx, regs64.rdx);
printf("si:%016llx di:%016llx bp:%016llx sp:%016llx\n", regs64.rsi, regs64.rdi, regs64.rbp, regs64.rsp);
printf(" 8:%016llx 9:%016llx 10:%016llx 11:%016llx\n", regs64.r8 , regs64.r9 , regs64.r10, regs64.r11);
printf("12:%016llx 13:%016llx 14:%016llx 15:%016llx\n", regs64.r12, regs64.r13, regs64.r14, regs64.r15);
}
int check_regs64(void)
{
int err = 0;
int num = 8;
uint64_t *r64 = &regs64.r8;
uint64_t expected = 0x7f7f7f7f7f7f7f7fULL;
if (!kernel_is_64bit)
return 0;
do {
if (*r64 == expected++)
continue; /* register did not change */
if (syscall_addr != (long)&int80) {
/*
* Non-INT80 syscall entrypoints are allowed to clobber R8+ regs:
* either clear them to 0, or for R11, load EFLAGS.
*/
if (*r64 == 0)
continue;
if (num == 11) {
printf("[NOTE]\tR11 has changed:%016llx - assuming clobbered by SYSRET insn\n", *r64);
continue;
}
} else {
/*
* INT80 syscall entrypoint can be used by
* 64-bit programs too, unlike SYSCALL/SYSENTER.
* Therefore it must preserve R12+
* (they are callee-saved registers in 64-bit C ABI).
*
* Starting in Linux 4.17 (and any kernel that
* backports the change), R8..11 are preserved.
* Historically (and probably unintentionally), they
* were clobbered or zeroed.
*/
}
printf("[FAIL]\tR%d has changed:%016llx\n", num, *r64);
err++;
} while (r64++, ++num < 16);
if (!err)
printf("[OK]\tR8..R15 did not leak kernel data\n");
return err;
}
int nfds;
fd_set rfds;
fd_set wfds;
fd_set efds;
struct timespec timeout;
sigset_t sigmask;
struct {
sigset_t *sp;
int sz;
} sigmask_desc;
void prep_args()
{
nfds = 42;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
FD_SET(0, &rfds);
FD_SET(1, &wfds);
FD_SET(2, &efds);
timeout.tv_sec = 0;
timeout.tv_nsec = 123;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGINT);
sigaddset(&sigmask, SIGUSR2);
sigaddset(&sigmask, SIGRTMAX);
sigmask_desc.sp = &sigmask;
sigmask_desc.sz = 8; /* bytes */
}
static void print_flags(const char *name, unsigned long r)
{
static const char *bitarray[] = {
"\n" ,"c\n" ,/* Carry Flag */
"0 " ,"1 " ,/* Bit 1 - always on */
"" ,"p " ,/* Parity Flag */
"0 " ,"3? " ,
"" ,"a " ,/* Auxiliary carry Flag */
"0 " ,"5? " ,
"" ,"z " ,/* Zero Flag */
"" ,"s " ,/* Sign Flag */
"" ,"t " ,/* Trap Flag */
"" ,"i " ,/* Interrupt Flag */
"" ,"d " ,/* Direction Flag */
"" ,"o " ,/* Overflow Flag */
"0 " ,"1 " ,/* I/O Privilege Level (2 bits) */
"0" ,"1" ,/* I/O Privilege Level (2 bits) */
"" ,"n " ,/* Nested Task */
"0 " ,"15? ",
"" ,"r " ,/* Resume Flag */
"" ,"v " ,/* Virtual Mode */
"" ,"ac " ,/* Alignment Check/Access Control */
"" ,"vif ",/* Virtual Interrupt Flag */
"" ,"vip ",/* Virtual Interrupt Pending */
"" ,"id " ,/* CPUID detection */
NULL
};
const char **bitstr;
int bit;
printf("%s=%016lx ", name, r);
bitstr = bitarray + 42;
bit = 21;
if ((r >> 22) != 0)
printf("(extra bits are set) ");
do {
if (bitstr[(r >> bit) & 1][0])
fputs(bitstr[(r >> bit) & 1], stdout);
bitstr -= 2;
bit--;
} while (bit >= 0);
}
int run_syscall(void)
{
long flags, bad_arg;
prep_args();
if (kernel_is_64bit)
call64_from_32(poison_regs64);
/*print_regs64();*/
asm("\n"
/* Try 6-arg syscall: pselect. It should return quickly */
" push %%ebp\n"
" mov $308, %%eax\n" /* PSELECT */
" mov nfds, %%ebx\n" /* ebx arg1 */
" mov $rfds, %%ecx\n" /* ecx arg2 */
" mov $wfds, %%edx\n" /* edx arg3 */
" mov $efds, %%esi\n" /* esi arg4 */
" mov $timeout, %%edi\n" /* edi arg5 */
" mov $sigmask_desc, %%ebp\n" /* %ebp arg6 */
" push $0x200ed7\n" /* set almost all flags */
" popf\n" /* except TF, IOPL, NT, RF, VM, AC, VIF, VIP */
" call *syscall_addr\n"
/* Check that registers are not clobbered */
" pushf\n"
" pop %%eax\n"
" cld\n"
" cmp nfds, %%ebx\n" /* ebx arg1 */
" mov $1, %%ebx\n"
" jne 1f\n"
" cmp $rfds, %%ecx\n" /* ecx arg2 */
" mov $2, %%ebx\n"
" jne 1f\n"
" cmp $wfds, %%edx\n" /* edx arg3 */
" mov $3, %%ebx\n"
" jne 1f\n"
" cmp $efds, %%esi\n" /* esi arg4 */
" mov $4, %%ebx\n"
" jne 1f\n"
" cmp $timeout, %%edi\n" /* edi arg5 */
" mov $5, %%ebx\n"
" jne 1f\n"
" cmpl $sigmask_desc, %%ebp\n" /* %ebp arg6 */
" mov $6, %%ebx\n"
" jne 1f\n"
" mov $0, %%ebx\n"
"1:\n"
" pop %%ebp\n"
: "=a" (flags), "=b" (bad_arg)
:
: "cx", "dx", "si", "di"
);
if (kernel_is_64bit) {
memset(&regs64, 0x77, sizeof(regs64));
call64_from_32(get_regs64);
/*print_regs64();*/
}
/*
* On paravirt kernels, flags are not preserved across syscalls.
* Thus, we do not consider it a bug if some are changed.
* We just show ones which do.
*/
if ((0x200ed7 ^ flags) != 0) {
print_flags("[WARN]\tFlags before", 0x200ed7);
print_flags("[WARN]\tFlags after", flags);
print_flags("[WARN]\tFlags change", (0x200ed7 ^ flags));
}
if (bad_arg) {
printf("[FAIL]\targ#%ld clobbered\n", bad_arg);
return 1;
}
printf("[OK]\tArguments are preserved across syscall\n");
return check_regs64();
}
int run_syscall_twice()
{
int exitcode = 0;
long sv;
if (syscall_addr) {
printf("[RUN]\tExecuting 6-argument 32-bit syscall via VDSO\n");
exitcode = run_syscall();
}
sv = syscall_addr;
syscall_addr = (long)&int80;
printf("[RUN]\tExecuting 6-argument 32-bit syscall via INT 80\n");
exitcode += run_syscall();
syscall_addr = sv;
return exitcode;
}
void ptrace_me()
{
pid_t pid;
fflush(NULL);
pid = fork();
if (pid < 0)
exit(1);
if (pid == 0) {
/* child */
if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) != 0)
exit(0);
raise(SIGSTOP);
return;
}
/* parent */
printf("[RUN]\tRunning tests under ptrace\n");
while (1) {
int status;
pid = waitpid(-1, &status, __WALL);
if (WIFEXITED(status))
exit(WEXITSTATUS(status));
if (WIFSIGNALED(status))
exit(WTERMSIG(status));
if (pid <= 0 || !WIFSTOPPED(status)) /* paranoia */
exit(255);
/*
* Note: we do not inject sig = WSTOPSIG(status).
* We probably should, but careful: do not inject SIGTRAP
* generated by syscall entry/exit stops.
* That kills the child.
*/
ptrace(PTRACE_SYSCALL, pid, 0L, 0L /*sig*/);
}
}
int main(int argc, char **argv, char **envp)
{
int exitcode = 0;
int cs;
asm("\n"
" movl %%cs, %%eax\n"
: "=a" (cs)
);
kernel_is_64bit = (cs == 0x23);
if (!kernel_is_64bit)
printf("[NOTE]\tNot a 64-bit kernel, won't test R8..R15 leaks\n");
/* This only works for non-static builds:
* syscall_addr = dlsym(dlopen("linux-gate.so.1", RTLD_NOW), "__kernel_vsyscall");
*/
syscall_addr = get_syscall(envp);
exitcode += run_syscall_twice();
ptrace_me();
exitcode += run_syscall_twice();
return exitcode;
}
#endif
@@ -0,0 +1,535 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <dlfcn.h>
#include <string.h>
#include <inttypes.h>
#include <signal.h>
#include <sys/ucontext.h>
#include <errno.h>
#include <err.h>
#include <sched.h>
#include <stdbool.h>
#include <setjmp.h>
#include <sys/uio.h>
#include "helpers.h"
#include "kselftest.h"
#ifdef __x86_64__
#define TOTAL_TESTS 13
#else
#define TOTAL_TESTS 8
#endif
#ifdef __x86_64__
# define VSYS(x) (x)
#else
# define VSYS(x) 0
#endif
#ifndef SYS_getcpu
# ifdef __x86_64__
# define SYS_getcpu 309
# else
# define SYS_getcpu 318
# endif
#endif
/* max length of lines in /proc/self/maps - anything longer is skipped here */
#define MAPS_LINE_LEN 128
/* vsyscalls and vDSO */
bool vsyscall_map_r = false, vsyscall_map_x = false;
typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz);
const gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000);
gtod_t vdso_gtod;
typedef int (*vgettime_t)(clockid_t, struct timespec *);
vgettime_t vdso_gettime;
typedef long (*time_func_t)(time_t *t);
const time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400);
time_func_t vdso_time;
typedef long (*getcpu_t)(unsigned *, unsigned *, void *);
const getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800);
getcpu_t vdso_getcpu;
static void init_vdso(void)
{
void *vdso = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
if (!vdso)
vdso = dlopen("linux-gate.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
if (!vdso) {
ksft_print_msg("[WARN] failed to find vDSO\n");
return;
}
vdso_gtod = (gtod_t)dlsym(vdso, "__vdso_gettimeofday");
if (!vdso_gtod)
ksft_print_msg("[WARN] failed to find gettimeofday in vDSO\n");
vdso_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime");
if (!vdso_gettime)
ksft_print_msg("[WARN] failed to find clock_gettime in vDSO\n");
vdso_time = (time_func_t)dlsym(vdso, "__vdso_time");
if (!vdso_time)
ksft_print_msg("[WARN] failed to find time in vDSO\n");
vdso_getcpu = (getcpu_t)dlsym(vdso, "__vdso_getcpu");
if (!vdso_getcpu)
ksft_print_msg("[WARN] failed to find getcpu in vDSO\n");
}
/* syscalls */
static inline long sys_gtod(struct timeval *tv, struct timezone *tz)
{
return syscall(SYS_gettimeofday, tv, tz);
}
static inline long sys_time(time_t *t)
{
return syscall(SYS_time, t);
}
static inline long sys_getcpu(unsigned * cpu, unsigned * node,
void* cache)
{
return syscall(SYS_getcpu, cpu, node, cache);
}
static double tv_diff(const struct timeval *a, const struct timeval *b)
{
return (double)(a->tv_sec - b->tv_sec) +
(double)((int)a->tv_usec - (int)b->tv_usec) * 1e-6;
}
static void check_gtod(const struct timeval *tv_sys1,
const struct timeval *tv_sys2,
const struct timezone *tz_sys,
const char *which,
const struct timeval *tv_other,
const struct timezone *tz_other)
{
double d1, d2;
if (tz_other && (tz_sys->tz_minuteswest != tz_other->tz_minuteswest ||
tz_sys->tz_dsttime != tz_other->tz_dsttime))
ksft_print_msg("%s tz mismatch\n", which);
d1 = tv_diff(tv_other, tv_sys1);
d2 = tv_diff(tv_sys2, tv_other);
ksft_print_msg("%s time offsets: %lf %lf\n", which, d1, d2);
ksft_test_result(!(d1 < 0 || d2 < 0), "%s gettimeofday()'s timeval\n", which);
}
static void test_gtod(void)
{
struct timeval tv_sys1, tv_sys2, tv_vdso, tv_vsys;
struct timezone tz_sys, tz_vdso, tz_vsys;
long ret_vdso = -1;
long ret_vsys = -1;
ksft_print_msg("test gettimeofday()\n");
if (sys_gtod(&tv_sys1, &tz_sys) != 0)
ksft_exit_fail_msg("syscall gettimeofday: %s\n", strerror(errno));
if (vdso_gtod)
ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso);
if (vsyscall_map_x)
ret_vsys = vgtod(&tv_vsys, &tz_vsys);
if (sys_gtod(&tv_sys2, &tz_sys) != 0)
ksft_exit_fail_msg("syscall gettimeofday: %s\n", strerror(errno));
if (vdso_gtod) {
if (ret_vdso == 0)
check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vDSO", &tv_vdso, &tz_vdso);
else
ksft_test_result_fail("vDSO gettimeofday() failed: %ld\n", ret_vdso);
} else {
ksft_test_result_skip("vdso_gtod isn't set\n");
}
if (vsyscall_map_x) {
if (ret_vsys == 0)
check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vsyscall", &tv_vsys, &tz_vsys);
else
ksft_test_result_fail("vsys gettimeofday() failed: %ld\n", ret_vsys);
} else {
ksft_test_result_skip("vsyscall_map_x isn't set\n");
}
}
static void test_time(void)
{
long t_sys1, t_sys2, t_vdso = 0, t_vsys = 0;
long t2_sys1 = -1, t2_sys2 = -1, t2_vdso = -1, t2_vsys = -1;
ksft_print_msg("test time()\n");
t_sys1 = sys_time(&t2_sys1);
if (vdso_time)
t_vdso = vdso_time(&t2_vdso);
if (vsyscall_map_x)
t_vsys = vtime(&t2_vsys);
t_sys2 = sys_time(&t2_sys2);
if (t_sys1 < 0 || t_sys1 != t2_sys1 || t_sys2 < 0 || t_sys2 != t2_sys2) {
ksft_print_msg("syscall failed (ret1:%ld output1:%ld ret2:%ld output2:%ld)\n",
t_sys1, t2_sys1, t_sys2, t2_sys2);
ksft_test_result_skip("vdso_time\n");
ksft_test_result_skip("vdso_time\n");
return;
}
if (vdso_time) {
if (t_vdso < 0 || t_vdso != t2_vdso)
ksft_test_result_fail("vDSO failed (ret:%ld output:%ld)\n",
t_vdso, t2_vdso);
else if (t_vdso < t_sys1 || t_vdso > t_sys2)
ksft_test_result_fail("vDSO returned the wrong time (%ld %ld %ld)\n",
t_sys1, t_vdso, t_sys2);
else
ksft_test_result_pass("vDSO time() is okay\n");
} else {
ksft_test_result_skip("vdso_time isn't set\n");
}
if (vsyscall_map_x) {
if (t_vsys < 0 || t_vsys != t2_vsys)
ksft_test_result_fail("vsyscall failed (ret:%ld output:%ld)\n",
t_vsys, t2_vsys);
else if (t_vsys < t_sys1 || t_vsys > t_sys2)
ksft_test_result_fail("vsyscall returned the wrong time (%ld %ld %ld)\n",
t_sys1, t_vsys, t_sys2);
else
ksft_test_result_pass("vsyscall time() is okay\n");
} else {
ksft_test_result_skip("vsyscall_map_x isn't set\n");
}
}
static void test_getcpu(int cpu)
{
unsigned int cpu_sys, cpu_vdso, cpu_vsys, node_sys, node_vdso, node_vsys;
long ret_sys, ret_vdso = -1, ret_vsys = -1;
unsigned int node = 0;
bool have_node = false;
cpu_set_t cpuset;
ksft_print_msg("getcpu() on CPU %d\n", cpu);
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) {
ksft_print_msg("failed to force CPU %d\n", cpu);
ksft_test_result_skip("vdso_getcpu\n");
ksft_test_result_skip("vsyscall_map_x\n");
return;
}
ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0);
if (vdso_getcpu)
ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0);
if (vsyscall_map_x)
ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0);
if (ret_sys == 0) {
if (cpu_sys != cpu)
ksft_print_msg("syscall reported CPU %u but should be %d\n",
cpu_sys, cpu);
have_node = true;
node = node_sys;
}
if (vdso_getcpu) {
if (ret_vdso) {
ksft_test_result_fail("vDSO getcpu() failed\n");
} else {
if (!have_node) {
have_node = true;
node = node_vdso;
}
if (cpu_vdso != cpu || node_vdso != node) {
if (cpu_vdso != cpu)
ksft_print_msg("vDSO reported CPU %u but should be %d\n",
cpu_vdso, cpu);
if (node_vdso != node)
ksft_print_msg("vDSO reported node %u but should be %u\n",
node_vdso, node);
ksft_test_result_fail("Wrong values\n");
} else {
ksft_test_result_pass("vDSO reported correct CPU and node\n");
}
}
} else {
ksft_test_result_skip("vdso_getcpu isn't set\n");
}
if (vsyscall_map_x) {
if (ret_vsys) {
ksft_test_result_fail("vsyscall getcpu() failed\n");
} else {
if (!have_node) {
have_node = true;
node = node_vsys;
}
if (cpu_vsys != cpu || node_vsys != node) {
if (cpu_vsys != cpu)
ksft_print_msg("vsyscall reported CPU %u but should be %d\n",
cpu_vsys, cpu);
if (node_vsys != node)
ksft_print_msg("vsyscall reported node %u but should be %u\n",
node_vsys, node);
ksft_test_result_fail("Wrong values\n");
} else {
ksft_test_result_pass("vsyscall reported correct CPU and node\n");
}
}
} else {
ksft_test_result_skip("vsyscall_map_x isn't set\n");
}
}
#ifdef __x86_64__
static jmp_buf jmpbuf;
static volatile unsigned long segv_err, segv_trapno;
static void sigsegv(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t *)ctx_void;
segv_trapno = ctx->uc_mcontext.gregs[REG_TRAPNO];
segv_err = ctx->uc_mcontext.gregs[REG_ERR];
siglongjmp(jmpbuf, 1);
}
static void test_vsys_r(void)
{
ksft_print_msg("Checking read access to the vsyscall page\n");
bool can_read;
if (sigsetjmp(jmpbuf, 1) == 0) {
*(volatile int *)0xffffffffff600000;
can_read = true;
} else {
can_read = false;
}
if (can_read && !vsyscall_map_r)
ksft_test_result_fail("We have read access, but we shouldn't\n");
else if (!can_read && vsyscall_map_r)
ksft_test_result_fail("We don't have read access, but we should\n");
else if (can_read)
ksft_test_result_pass("We have read access\n");
else
ksft_test_result_pass("We do not have read access (trap=%ld, error=0x%lx)\n",
segv_trapno, segv_err);
}
static void test_vsys_x(void)
{
if (vsyscall_map_x) {
/* We already tested this adequately. */
ksft_test_result_pass("vsyscall_map_x is true\n");
return;
}
ksft_print_msg("Make sure that vsyscalls really cause a fault\n");
bool can_exec;
if (sigsetjmp(jmpbuf, 1) == 0) {
vgtod(NULL, NULL);
can_exec = true;
} else {
can_exec = false;
}
if (can_exec)
ksft_test_result_fail("Executing the vsyscall did not fault\n");
/* #GP or #PF (with X86_PF_INSTR) */
else if ((segv_trapno == 13) || ((segv_trapno == 14) && (segv_err & (1 << 4))))
ksft_test_result_pass("Executing the vsyscall page failed (trap=%ld, error=0x%lx)\n",
segv_trapno, segv_err);
else
ksft_test_result_fail("Execution failed with the wrong error (trap=%ld, error=0x%lx)\n",
segv_trapno, segv_err);
}
/*
* Debuggers expect ptrace() to be able to peek at the vsyscall page.
* Use process_vm_readv() as a proxy for ptrace() to test this. We
* want it to work in the vsyscall=emulate case and to fail in the
* vsyscall=xonly case.
*
* It's worth noting that this ABI is a bit nutty. write(2) can't
* read from the vsyscall page on any kernel version or mode. The
* fact that ptrace() ever worked was a nice courtesy of old kernels,
* but the code to support it is fairly gross.
*/
static void test_process_vm_readv(void)
{
char buf[4096];
struct iovec local, remote;
int ret;
ksft_print_msg("process_vm_readv() from vsyscall page\n");
local.iov_base = buf;
local.iov_len = 4096;
remote.iov_base = (void *)0xffffffffff600000;
remote.iov_len = 4096;
ret = process_vm_readv(getpid(), &local, 1, &remote, 1, 0);
if (ret != 4096) {
/*
* We expect process_vm_readv() to work if and only if the
* vsyscall page is readable.
*/
ksft_test_result(!vsyscall_map_r,
"process_vm_readv() failed (ret = %d, errno = %d)\n", ret, errno);
return;
}
if (vsyscall_map_r)
ksft_test_result(!memcmp(buf, remote.iov_base, sizeof(buf)), "Read data\n");
else
ksft_test_result_fail("process_rm_readv() succeeded, but it should have failed in this configuration\n");
}
static void init_vsys(void)
{
int nerrs = 0;
FILE *maps;
char line[MAPS_LINE_LEN];
bool found = false;
maps = fopen("/proc/self/maps", "r");
if (!maps) {
ksft_test_result_skip("Could not open /proc/self/maps -- assuming vsyscall is r-x\n");
vsyscall_map_r = true;
return;
}
while (fgets(line, MAPS_LINE_LEN, maps)) {
char r, x;
void *start, *end;
char name[MAPS_LINE_LEN];
/* sscanf() is safe here as strlen(name) >= strlen(line) */
if (sscanf(line, "%p-%p %c-%cp %*x %*x:%*x %*u %s",
&start, &end, &r, &x, name) != 5)
continue;
if (strcmp(name, "[vsyscall]"))
continue;
ksft_print_msg("vsyscall map: %s", line);
if (start != (void *)0xffffffffff600000 ||
end != (void *)0xffffffffff601000) {
ksft_print_msg("address range is nonsense\n");
nerrs++;
}
ksft_print_msg("vsyscall permissions are %c-%c\n", r, x);
vsyscall_map_r = (r == 'r');
vsyscall_map_x = (x == 'x');
found = true;
break;
}
fclose(maps);
if (!found) {
ksft_print_msg("no vsyscall map in /proc/self/maps\n");
vsyscall_map_r = false;
vsyscall_map_x = false;
}
ksft_test_result(!nerrs, "vsyscall map\n");
}
static volatile sig_atomic_t num_vsyscall_traps;
static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t *)ctx_void;
unsigned long ip = ctx->uc_mcontext.gregs[REG_RIP];
if (((ip ^ 0xffffffffff600000UL) & ~0xfffUL) == 0)
num_vsyscall_traps++;
}
static void test_emulation(void)
{
time_t tmp;
bool is_native;
if (!vsyscall_map_x) {
ksft_test_result_skip("vsyscall_map_x isn't set\n");
return;
}
ksft_print_msg("checking that vsyscalls are emulated\n");
sethandler(SIGTRAP, sigtrap, 0);
set_eflags(get_eflags() | X86_EFLAGS_TF);
vtime(&tmp);
set_eflags(get_eflags() & ~X86_EFLAGS_TF);
/*
* If vsyscalls are emulated, we expect a single trap in the
* vsyscall page -- the call instruction will trap with RIP
* pointing to the entry point before emulation takes over.
* In native mode, we expect two traps, since whatever code
* the vsyscall page contains will be more than just a ret
* instruction.
*/
is_native = (num_vsyscall_traps > 1);
ksft_test_result(!is_native, "vsyscalls are %s (%d instructions in vsyscall page)\n",
(is_native ? "native" : "emulated"), (int)num_vsyscall_traps);
}
#endif
int main(int argc, char **argv)
{
int total_tests = TOTAL_TESTS;
ksft_print_header();
ksft_set_plan(total_tests);
init_vdso();
#ifdef __x86_64__
init_vsys();
#endif
test_gtod();
test_time();
test_getcpu(0);
test_getcpu(1);
#ifdef __x86_64__
sethandler(SIGSEGV, sigsegv, 0);
test_vsys_r();
test_vsys_x();
test_process_vm_readv();
test_emulation();
#endif
ksft_finished();
}
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* thunks.S - assembly helpers for mixed-bitness code
* Copyright (c) 2015 Andrew Lutomirski
*
* These are little helpers that make it easier to switch bitness on
* the fly.
*/
.text
.global call32_from_64
.type call32_from_64, @function
call32_from_64:
// rdi: stack to use
// esi: function to call
// Save registers
pushq %rbx
pushq %rbp
pushq %r12
pushq %r13
pushq %r14
pushq %r15
pushfq
// Switch stacks
mov %rsp,(%rdi)
mov %rdi,%rsp
// Switch to compatibility mode
pushq $0x23 /* USER32_CS */
pushq $1f
lretq
1:
.code32
// Call the function
call *%esi
// Switch back to long mode
jmp $0x33,$1f
.code64
1:
// Restore the stack
mov (%rsp),%rsp
// Restore registers
popfq
popq %r15
popq %r14
popq %r13
popq %r12
popq %rbp
popq %rbx
ret
.size call32_from_64, .-call32_from_64
.section .note.GNU-stack,"",%progbits
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* thunks_32.S - assembly helpers for mixed-bitness code
* Copyright (c) 2015 Denys Vlasenko
*
* These are little helpers that make it easier to switch bitness on
* the fly.
*/
.text
.code32
.global call64_from_32
.type call32_from_64, @function
// 4(%esp): function to call
call64_from_32:
// Fetch function address
mov 4(%esp), %eax
// Save registers which are callee-clobbered by 64-bit ABI
push %ecx
push %edx
push %esi
push %edi
// Switch to long mode
jmp $0x33,$1f
1: .code64
// Call the function
call *%rax
// Switch to compatibility mode
push $0x23 /* USER32_CS */
.code32; push $1f; .code64 /* hack: can't have X86_64_32S relocation in 32-bit ELF */
lretq
1: .code32
pop %edi
pop %esi
pop %edx
pop %ecx
ret
.size call64_from_32, .-call64_from_32
.section .note.GNU-stack,"",%progbits
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Trivial program to check that we have a valid 32-bit build environment.
* Copyright (c) 2015 Andy Lutomirski
*/
#ifndef __i386__
# error wrong architecture
#endif
#include <stdio.h>
int main()
{
printf("\n");
return 0;
}
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Trivial program to check that we have a valid 64-bit build environment.
* Copyright (c) 2015 Andy Lutomirski
*/
#ifndef __x86_64__
# error wrong architecture
#endif
#include <stdio.h>
int main()
{
printf("\n");
return 0;
}
@@ -0,0 +1,10 @@
/* Trivial program to check that compilation with certain flags is working. */
#include <stdio.h>
int
main(void)
{
puts("");
return 0;
}
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* unwind_vdso.c - tests unwind info for AT_SYSINFO in the vDSO
* Copyright (c) 2014-2015 Andrew Lutomirski
*
* This tests __kernel_vsyscall's unwind info.
*/
#define _GNU_SOURCE
#include <features.h>
#include <stdio.h>
#include "helpers.h"
#if defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16
int main()
{
/* We need getauxval(). */
printf("[SKIP]\tGLIBC before 2.16 cannot compile this test\n");
return 0;
}
#else
#include <sys/time.h>
#include <stdlib.h>
#include <syscall.h>
#include <unistd.h>
#include <string.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <signal.h>
#include <sys/ucontext.h>
#include <err.h>
#include <stddef.h>
#include <stdbool.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <link.h>
#include <sys/auxv.h>
#include <dlfcn.h>
#include <unwind.h>
static volatile sig_atomic_t nerrs;
static unsigned long sysinfo;
static bool got_sysinfo = false;
static unsigned long return_address;
struct unwind_state {
unsigned long ip; /* trap source */
int depth; /* -1 until we hit the trap source */
};
_Unwind_Reason_Code trace_fn(struct _Unwind_Context * ctx, void *opaque)
{
struct unwind_state *state = opaque;
unsigned long ip = _Unwind_GetIP(ctx);
if (state->depth == -1) {
if (ip == state->ip)
state->depth = 0;
else
return _URC_NO_REASON; /* Not there yet */
}
printf("\t 0x%lx\n", ip);
if (ip == return_address) {
/* Here we are. */
unsigned long eax = _Unwind_GetGR(ctx, 0);
unsigned long ecx = _Unwind_GetGR(ctx, 1);
unsigned long edx = _Unwind_GetGR(ctx, 2);
unsigned long ebx = _Unwind_GetGR(ctx, 3);
unsigned long ebp = _Unwind_GetGR(ctx, 5);
unsigned long esi = _Unwind_GetGR(ctx, 6);
unsigned long edi = _Unwind_GetGR(ctx, 7);
bool ok = (eax == SYS_getpid || eax == getpid()) &&
ebx == 1 && ecx == 2 && edx == 3 &&
esi == 4 && edi == 5 && ebp == 6;
if (!ok)
nerrs++;
printf("[%s]\t NR = %ld, args = %ld, %ld, %ld, %ld, %ld, %ld\n",
(ok ? "OK" : "FAIL"),
eax, ebx, ecx, edx, esi, edi, ebp);
return _URC_NORMAL_STOP;
} else {
state->depth++;
return _URC_NO_REASON;
}
}
static void sigtrap(int sig, siginfo_t *info, void *ctx_void)
{
ucontext_t *ctx = (ucontext_t *)ctx_void;
struct unwind_state state;
unsigned long ip = ctx->uc_mcontext.gregs[REG_EIP];
if (!got_sysinfo && ip == sysinfo) {
got_sysinfo = true;
/* Find the return address. */
return_address = *(unsigned long *)(unsigned long)ctx->uc_mcontext.gregs[REG_ESP];
printf("\tIn vsyscall at 0x%lx, returning to 0x%lx\n",
ip, return_address);
}
if (!got_sysinfo)
return; /* Not there yet */
if (ip == return_address) {
ctx->uc_mcontext.gregs[REG_EFL] &= ~X86_EFLAGS_TF;
printf("\tVsyscall is done\n");
return;
}
printf("\tSIGTRAP at 0x%lx\n", ip);
state.ip = ip;
state.depth = -1;
_Unwind_Backtrace(trace_fn, &state);
}
int main()
{
sysinfo = getauxval(AT_SYSINFO);
printf("\tAT_SYSINFO is 0x%lx\n", sysinfo);
Dl_info info;
if (!dladdr((void *)sysinfo, &info)) {
printf("[WARN]\tdladdr failed on AT_SYSINFO\n");
} else {
printf("[OK]\tAT_SYSINFO maps to %s, loaded at 0x%p\n",
info.dli_fname, info.dli_fbase);
}
sethandler(SIGTRAP, sigtrap, 0);
syscall(SYS_getpid); /* Force symbol binding without TF set. */
printf("[RUN]\tSet TF and check a fast syscall\n");
set_eflags(get_eflags() | X86_EFLAGS_TF);
syscall(SYS_getpid, 1, 2, 3, 4, 5, 6);
if (!got_sysinfo) {
set_eflags(get_eflags() & ~X86_EFLAGS_TF);
/*
* The most likely cause of this is that you're on Debian or
* a Debian-based distro, you're missing libc6-i686, and you're
* affected by libc/19006 (https://sourceware.org/PR19006).
*/
printf("[WARN]\tsyscall(2) didn't enter AT_SYSINFO\n");
}
if (get_eflags() & X86_EFLAGS_TF) {
printf("[FAIL]\tTF is still set\n");
nerrs++;
}
if (nerrs) {
printf("[FAIL]\tThere were errors\n");
return 1;
} else {
printf("[OK]\tAll is well\n");
return 0;
}
}
#endif /* New enough libc */
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vdso_restorer.c - tests vDSO-based signal restore
* Copyright (c) 2015 Andrew Lutomirski
*
* This makes sure that sa_restorer == NULL keeps working on 32-bit
* configurations. Modern glibc doesn't use it under any circumstances,
* so it's easy to overlook breakage.
*
* 64-bit userspace has never supported sa_restorer == NULL, so this is
* 32-bit only.
*/
#define _GNU_SOURCE
#include <err.h>
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <syscall.h>
#include <sys/syscall.h>
/* Open-code this -- the headers are too messy to easily use them. */
struct real_sigaction {
void *handler;
unsigned long flags;
void *restorer;
unsigned int mask[2];
};
static volatile sig_atomic_t handler_called;
static void handler_with_siginfo(int sig, siginfo_t *info, void *ctx_void)
{
handler_called = 1;
}
static void handler_without_siginfo(int sig)
{
handler_called = 1;
}
int main()
{
int nerrs = 0;
struct real_sigaction sa;
void *vdso = dlopen("linux-vdso.so.1",
RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
if (!vdso)
vdso = dlopen("linux-gate.so.1",
RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
if (!vdso) {
printf("[SKIP]\tFailed to find vDSO. Tests are not expected to work.\n");
return 0;
}
memset(&sa, 0, sizeof(sa));
sa.handler = handler_with_siginfo;
sa.flags = SA_SIGINFO;
sa.restorer = NULL; /* request kernel-provided restorer */
printf("[RUN]\tRaise a signal, SA_SIGINFO, sa.restorer == NULL\n");
if (syscall(SYS_rt_sigaction, SIGUSR1, &sa, NULL, 8) != 0)
err(1, "raw rt_sigaction syscall");
raise(SIGUSR1);
if (handler_called) {
printf("[OK]\tSA_SIGINFO handler returned successfully\n");
} else {
printf("[FAIL]\tSA_SIGINFO handler was not called\n");
nerrs++;
}
printf("[RUN]\tRaise a signal, !SA_SIGINFO, sa.restorer == NULL\n");
sa.flags = 0;
sa.handler = handler_without_siginfo;
if (syscall(SYS_sigaction, SIGUSR1, &sa, 0) != 0)
err(1, "raw sigaction syscall");
handler_called = 0;
raise(SIGUSR1);
if (handler_called) {
printf("[OK]\t!SA_SIGINFO handler returned successfully\n");
} else {
printf("[FAIL]\t!SA_SIGINFO handler was not called\n");
nerrs++;
}
return nerrs;
}
@@ -0,0 +1,478 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <elf.h>
#include <pthread.h>
#include <stdbool.h>
#include <asm/prctl.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include "helpers.h"
#include "xstate.h"
/*
* The userspace xstate test suite is designed to be generic and operates
* with randomized xstate data. However, some states require special handling:
*
* - PKRU and XTILECFG need specific adjustments, such as modifying
* randomization behavior or using fixed values.
* - But, PKRU already has a dedicated test suite in /tools/selftests/mm.
* - Legacy states (FP and SSE) are excluded, as they are not considered
* part of extended states (xstates) and their usage is already deeply
* integrated into user-space libraries.
*/
#define XFEATURE_MASK_TEST_SUPPORTED \
((1 << XFEATURE_YMM) | \
(1 << XFEATURE_OPMASK) | \
(1 << XFEATURE_ZMM_Hi256) | \
(1 << XFEATURE_Hi16_ZMM) | \
(1 << XFEATURE_XTILEDATA) | \
(1 << XFEATURE_APX))
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 uint64_t get_xstatebv(struct xsave_buffer *xbuf)
{
return *(uint64_t *)(&xbuf->header);
}
static struct xstate_info xstate;
struct futex_info {
unsigned int iterations;
struct futex_info *next;
pthread_mutex_t mutex;
pthread_t thread;
bool valid;
int nr;
};
static inline void load_rand_xstate(struct xstate_info *xstate, struct xsave_buffer *xbuf)
{
clear_xstate_header(xbuf);
set_xstatebv(xbuf, xstate->mask);
set_rand_data(xstate, xbuf);
xrstor(xbuf, xstate->mask);
}
static inline void load_init_xstate(struct xstate_info *xstate, struct xsave_buffer *xbuf)
{
clear_xstate_header(xbuf);
xrstor(xbuf, xstate->mask);
}
static inline void copy_xstate(struct xsave_buffer *xbuf_dst, struct xsave_buffer *xbuf_src)
{
memcpy(&xbuf_dst->bytes[xstate.xbuf_offset],
&xbuf_src->bytes[xstate.xbuf_offset],
xstate.size);
}
static inline bool validate_xstate_same(struct xsave_buffer *xbuf1, struct xsave_buffer *xbuf2)
{
int ret;
ret = memcmp(&xbuf1->bytes[xstate.xbuf_offset],
&xbuf2->bytes[xstate.xbuf_offset],
xstate.size);
return ret == 0;
}
static inline bool validate_xregs_same(struct xsave_buffer *xbuf1)
{
struct xsave_buffer *xbuf2;
bool ret;
xbuf2 = alloc_xbuf();
if (!xbuf2)
ksft_exit_fail_msg("failed to allocate XSAVE buffer\n");
xsave(xbuf2, xstate.mask);
ret = validate_xstate_same(xbuf1, xbuf2);
free(xbuf2);
return ret;
}
/* Context switching test */
static void *check_xstate(void *info)
{
struct futex_info *finfo = (struct futex_info *)info;
struct xsave_buffer *xbuf;
int i;
xbuf = alloc_xbuf();
if (!xbuf)
ksft_exit_fail_msg("unable to allocate XSAVE buffer\n");
/*
* Load random data into 'xbuf' and then restore it to the xstate
* registers.
*/
load_rand_xstate(&xstate, xbuf);
finfo->valid = true;
for (i = 0; i < finfo->iterations; i++) {
pthread_mutex_lock(&finfo->mutex);
/*
* Ensure the register values have not diverged from the
* record. Then reload a new random value. If it failed
* ever before, skip it.
*/
if (finfo->valid) {
finfo->valid = validate_xregs_same(xbuf);
load_rand_xstate(&xstate, 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 finfo;
}
static void create_threads(uint32_t num_threads, uint32_t iterations, struct futex_info *finfo)
{
int i;
for (i = 0; i < num_threads; i++) {
int next_nr;
finfo[i].nr = i;
finfo[i].iterations = iterations;
/*
* 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_threads;
finfo[i].next = &finfo[next_nr];
if (pthread_create(&finfo[i].thread, NULL, check_xstate, &finfo[i]))
ksft_exit_fail_msg("pthread_create() failed\n");
}
}
static bool checkout_threads(uint32_t num_threads, struct futex_info *finfo)
{
void *thread_retval;
bool valid = true;
int err, i;
for (i = 0; i < num_threads; i++) {
err = pthread_join(finfo[i].thread, &thread_retval);
if (err)
ksft_exit_fail_msg("pthread_join() failed for thread %d err: %d\n", i, err);
if (thread_retval != &finfo[i]) {
ksft_exit_fail_msg("unexpected thread retval for thread %d: %p\n",
i, thread_retval);
}
valid &= finfo[i].valid;
}
return valid;
}
static void affinitize_cpu0(void)
{
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
ksft_exit_fail_msg("sched_setaffinity to CPU 0 failed\n");
}
static void test_context_switch(uint32_t num_threads, uint32_t iterations)
{
struct futex_info *finfo;
/* Affinitize to one CPU to force context switches */
affinitize_cpu0();
printf("[RUN]\t%s: check context switches, %d iterations, %d threads.\n",
xstate.name, iterations, num_threads);
finfo = malloc(sizeof(*finfo) * num_threads);
if (!finfo)
ksft_exit_fail_msg("unable allocate memory\n");
create_threads(num_threads, iterations, 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: */
if (checkout_threads(num_threads, finfo))
printf("[OK]\tNo incorrect case was found.\n");
else
printf("[FAIL]\tFailed with context switching test.\n");
free(finfo);
}
/*
* Ptrace test for the ABI format as described in arch/x86/include/asm/user.h
*/
/*
* 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. For non-dynamic states, this is benign.
*/
static inline void ptracee_touch_xstate(void)
{
struct xsave_buffer *xbuf;
xbuf = alloc_xbuf();
load_rand_xstate(&xstate, xbuf);
load_init_xstate(&xstate, xbuf);
free(xbuf);
}
/*
* Ptracer injects the randomized xstate data. It also reads before and
* after that, which will execute the kernel's state copy functions.
*/
static void ptracer_inject_xstate(pid_t target)
{
uint32_t xbuf_size = get_xbuf_size();
struct xsave_buffer *xbuf1, *xbuf2;
struct iovec iov;
/*
* Allocate buffers to keep data while ptracer can write the
* other buffer
*/
xbuf1 = alloc_xbuf();
xbuf2 = alloc_xbuf();
if (!xbuf1 || !xbuf2)
ksft_exit_fail_msg("unable to allocate XSAVE buffer\n");
iov.iov_base = xbuf1;
iov.iov_len = xbuf_size;
if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
ksft_exit_fail_msg("PTRACE_GETREGSET failed\n");
printf("[RUN]\t%s: inject xstate via ptrace().\n", xstate.name);
load_rand_xstate(&xstate, xbuf1);
copy_xstate(xbuf2, xbuf1);
if (ptrace(PTRACE_SETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
ksft_exit_fail_msg("PTRACE_SETREGSET failed\n");
if (ptrace(PTRACE_GETREGSET, target, (uint32_t)NT_X86_XSTATE, &iov))
ksft_exit_fail_msg("PTRACE_GETREGSET failed\n");
if (*(uint64_t *)get_fpx_sw_bytes(xbuf1) == xgetbv(0))
printf("[OK]\t'xfeatures' in SW reserved area was correctly written\n");
else
printf("[FAIL]\t'xfeatures' in SW reserved area was not correctly written\n");
if (validate_xstate_same(xbuf2, xbuf1))
printf("[OK]\txstate was correctly updated.\n");
else
printf("[FAIL]\txstate was not correctly updated.\n");
free(xbuf1);
free(xbuf2);
}
static void test_ptrace(void)
{
pid_t child;
int status;
child = fork();
if (child < 0) {
ksft_exit_fail_msg("fork() failed\n");
} else if (!child) {
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL))
ksft_exit_fail_msg("PTRACE_TRACEME failed\n");
ptracee_touch_xstate();
raise(SIGTRAP);
_exit(0);
}
do {
wait(&status);
} while (WSTOPSIG(status) != SIGTRAP);
ptracer_inject_xstate(child);
ptrace(PTRACE_DETACH, child, NULL, NULL);
wait(&status);
if (!WIFEXITED(status) || WEXITSTATUS(status))
ksft_exit_fail_msg("ptracee exit error\n");
}
/*
* Test signal delivery for the ABI compatibility.
* See the ABI format: arch/x86/include/uapi/asm/sigcontext.h
*/
/*
* Avoid using printf() in signal handlers as it is not
* async-signal-safe.
*/
#define SIGNAL_BUF_LEN 1000
static char signal_message_buffer[SIGNAL_BUF_LEN];
static void sig_print(char *msg)
{
int left = SIGNAL_BUF_LEN - strlen(signal_message_buffer) - 1;
strncat(signal_message_buffer, msg, left);
}
static struct xsave_buffer *stashed_xbuf;
static void validate_sigfpstate(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;
uint32_t magic2;
/* Reset the signal message buffer: */
signal_message_buffer[0] = '\0';
sw_bytes = get_fpx_sw_bytes(xbuf);
if (sw_bytes->magic1 == FP_XSTATE_MAGIC1)
sig_print("[OK]\t'magic1' is valid\n");
else
sig_print("[FAIL]\t'magic1' is not valid\n");
if (get_fpx_sw_bytes_features(xbuf) & xstate.mask)
sig_print("[OK]\t'xfeatures' in SW reserved area is valid\n");
else
sig_print("[FAIL]\t'xfeatures' in SW reserved area is not valid\n");
if (get_xstatebv(xbuf) & xstate.mask)
sig_print("[OK]\t'xfeatures' in XSAVE header is valid\n");
else
sig_print("[FAIL]\t'xfeatures' in XSAVE header is not valid\n");
if (validate_xstate_same(stashed_xbuf, xbuf))
sig_print("[OK]\txstate delivery was successful\n");
else
sig_print("[FAIL]\txstate delivery was not successful\n");
magic2 = *(uint32_t *)(xbuf + sw_bytes->xstate_size);
if (magic2 == FP_XSTATE_MAGIC2)
sig_print("[OK]\t'magic2' is valid\n");
else
sig_print("[FAIL]\t'magic2' is not valid\n");
set_rand_data(&xstate, xbuf);
copy_xstate(stashed_xbuf, xbuf);
}
static void test_signal(void)
{
bool valid_xstate;
/*
* The signal handler will access this to verify xstate context
* preservation.
*/
stashed_xbuf = alloc_xbuf();
if (!stashed_xbuf)
ksft_exit_fail_msg("unable to allocate XSAVE buffer\n");
printf("[RUN]\t%s: load xstate and raise SIGUSR1\n", xstate.name);
sethandler(SIGUSR1, validate_sigfpstate, 0);
load_rand_xstate(&xstate, stashed_xbuf);
raise(SIGUSR1);
/*
* Immediately record the test result, deferring printf() to
* prevent unintended state contamination by that.
*/
valid_xstate = validate_xregs_same(stashed_xbuf);
printf("%s", signal_message_buffer);
printf("[RUN]\t%s: load new xstate from sighandler and check it after sigreturn\n",
xstate.name);
if (valid_xstate)
printf("[OK]\txstate was restored correctly\n");
else
printf("[FAIL]\txstate restoration failed\n");
clearhandler(SIGUSR1);
free(stashed_xbuf);
}
void test_xstate(uint32_t feature_num)
{
const unsigned int ctxtsw_num_threads = 5, ctxtsw_iterations = 10;
unsigned long features;
long rc;
if (!(XFEATURE_MASK_TEST_SUPPORTED & (1 << feature_num))) {
ksft_print_msg("The xstate test does not fully support the component %u, yet.\n",
feature_num);
return;
}
rc = syscall(SYS_arch_prctl, ARCH_GET_XCOMP_SUPP, &features);
if (rc || !(features & (1 << feature_num))) {
ksft_print_msg("The kernel does not support feature number: %u\n", feature_num);
return;
}
xstate = get_xstate_info(feature_num);
if (!xstate.size || !xstate.xbuf_offset) {
ksft_exit_fail_msg("invalid state size/offset (%d/%d)\n",
xstate.size, xstate.xbuf_offset);
}
test_context_switch(ctxtsw_num_threads, ctxtsw_iterations);
test_ptrace();
test_signal();
}
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef __SELFTESTS_X86_XSTATE_H
#define __SELFTESTS_X86_XSTATE_H
#include <stdint.h>
#include "kselftest.h"
#define XSAVE_HDR_OFFSET 512
#define XSAVE_HDR_SIZE 64
/*
* List of XSAVE features Linux knows about. Copied from
* arch/x86/include/asm/fpu/types.h
*/
enum xfeature {
XFEATURE_FP,
XFEATURE_SSE,
XFEATURE_YMM,
XFEATURE_BNDREGS,
XFEATURE_BNDCSR,
XFEATURE_OPMASK,
XFEATURE_ZMM_Hi256,
XFEATURE_Hi16_ZMM,
XFEATURE_PT_UNIMPLEMENTED_SO_FAR,
XFEATURE_PKRU,
XFEATURE_PASID,
XFEATURE_CET_USER,
XFEATURE_CET_KERNEL_UNUSED,
XFEATURE_RSRVD_COMP_13,
XFEATURE_RSRVD_COMP_14,
XFEATURE_LBR,
XFEATURE_RSRVD_COMP_16,
XFEATURE_XTILECFG,
XFEATURE_XTILEDATA,
XFEATURE_APX,
XFEATURE_MAX,
};
/* Copied from arch/x86/kernel/fpu/xstate.c */
static const char *xfeature_names[] =
{
"x87 floating point registers",
"SSE registers",
"AVX registers",
"MPX bounds registers",
"MPX CSR",
"AVX-512 opmask",
"AVX-512 Hi256",
"AVX-512 ZMM_Hi256",
"Processor Trace (unused)",
"Protection Keys User registers",
"PASID state",
"Control-flow User registers",
"Control-flow Kernel registers (unused)",
"unknown xstate feature",
"unknown xstate feature",
"unknown xstate feature",
"unknown xstate feature",
"AMX Tile config",
"AMX Tile data",
"APX registers",
"unknown xstate feature",
};
struct xsave_buffer {
union {
struct {
char legacy[XSAVE_HDR_OFFSET];
char header[XSAVE_HDR_SIZE];
char extended[0];
};
char bytes[0];
};
};
static inline void xsave(struct xsave_buffer *xbuf, uint64_t rfbm)
{
uint32_t rfbm_hi = rfbm >> 32;
uint32_t rfbm_lo = rfbm;
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_hi = rfbm >> 32;
uint32_t rfbm_lo = rfbm;
asm volatile("xrstor (%%rdi)"
: : "D" (xbuf), "a" (rfbm_lo), "d" (rfbm_hi));
}
#define CPUID_LEAF_XSTATE 0xd
#define CPUID_SUBLEAF_XSTATE_USER 0x0
static inline uint32_t get_xbuf_size(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.
*/
return ebx;
}
struct xstate_info {
const char *name;
uint32_t num;
uint32_t mask;
uint32_t xbuf_offset;
uint32_t size;
};
static inline struct xstate_info get_xstate_info(uint32_t xfeature_num)
{
struct xstate_info xstate = { };
uint32_t eax, ebx, ecx, edx;
if (xfeature_num >= XFEATURE_MAX) {
ksft_print_msg("unknown state\n");
return xstate;
}
xstate.name = xfeature_names[xfeature_num];
xstate.num = xfeature_num;
xstate.mask = 1 << xfeature_num;
__cpuid_count(CPUID_LEAF_XSTATE, xfeature_num,
eax, ebx, ecx, edx);
xstate.size = eax;
xstate.xbuf_offset = ebx;
return xstate;
}
static inline struct xsave_buffer *alloc_xbuf(void)
{
uint32_t xbuf_size = get_xbuf_size();
/* XSAVE buffer should be 64B-aligned. */
return aligned_alloc(64, xbuf_size);
}
static inline void clear_xstate_header(struct xsave_buffer *xbuf)
{
memset(&xbuf->header, 0, sizeof(xbuf->header));
}
static inline void set_xstatebv(struct xsave_buffer *xbuf, uint64_t bv)
{
/* XSTATE_BV is at the beginning of the header: */
*(uint64_t *)(&xbuf->header) = bv;
}
/* 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 *xbuf)
{
return xbuf + SW_BYTES_OFFSET;
}
static inline uint64_t get_fpx_sw_bytes_features(void *buffer)
{
return *(uint64_t *)(buffer + SW_BYTES_BV_OFFSET);
}
static inline void set_rand_data(struct xstate_info *xstate, struct xsave_buffer *xbuf)
{
int *ptr = (int *)&xbuf->bytes[xstate->xbuf_offset];
int data, 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 < xstate->size / sizeof(int); i++, ptr++)
*ptr = data;
}
/* Testing kernel's context switching and ABI support for the xstate. */
void test_xstate(uint32_t feature_num);
#endif /* __SELFTESTS_X86_XSTATE_H */