selftests/x86: Test signal frame XSTATE header corruption handling
This is very heavily based on some code from Thomas Gleixner. On a system without XSAVES, it triggers the WARN_ON(): Bad FPU state detected at copy_kernel_to_fpregs+0x2f/0x40, reinitializing FPU registers. [ bp: Massage in nitpicks. ] Signed-off-by: Andy Lutomirski <luto@kernel.org> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Borislav Petkov <bp@suse.de> Acked-by: Dave Hansen <dave.hansen@linux.intel.com> Acked-by: Rik van Riel <riel@surriel.com> Link: https://lkml.kernel.org/r/20210608144346.234764986@linutronix.de
This commit is contained in:
parent
f72a249b0b
commit
b7c11876d2
|
@ -17,7 +17,8 @@ TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt test_mremap
|
|||
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
|
||||
TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \
|
||||
corrupt_xstate_header
|
||||
# Some selftests require 32bit support enabled also on 64bit systems
|
||||
TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall
|
||||
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
// 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>
|
||||
|
||||
static inline void __cpuid(unsigned int *eax, unsigned int *ebx,
|
||||
unsigned int *ecx, unsigned int *edx)
|
||||
{
|
||||
asm volatile(
|
||||
"cpuid;"
|
||||
: "=a" (*eax),
|
||||
"=b" (*ebx),
|
||||
"=c" (*ecx),
|
||||
"=d" (*edx)
|
||||
: "0" (*eax), "2" (*ecx));
|
||||
}
|
||||
|
||||
static inline int xsave_enabled(void)
|
||||
{
|
||||
unsigned int eax, ebx, ecx, edx;
|
||||
|
||||
eax = 0x1;
|
||||
ecx = 0x0;
|
||||
__cpuid(&eax, &ebx, &ecx, &edx);
|
||||
|
||||
/* Is CR4.OSXSAVE enabled ? */
|
||||
return ecx & (1U << 27);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
Loading…
Reference in New Issue