177 lines
4.8 KiB
C
177 lines
4.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2018 Andrew Lutomirski
|
|
* Copyright (C) 2020 SUSE LLC <mdoucha@suse.cz>
|
|
*
|
|
* CVE-2018-1000199
|
|
*
|
|
* Test error handling when ptrace(POKEUSER) modified x86 debug registers even
|
|
* when the call returned error.
|
|
*
|
|
* When the bug was present we could create breakpoint in the kernel code,
|
|
* which shoudn't be possible at all. The original CVE caused a kernel crash by
|
|
* setting a breakpoint on do_debug kernel function which, when triggered,
|
|
* caused an infinite loop. However we do not have to crash the kernel in order
|
|
* to assert if kernel has been fixed or not.
|
|
*
|
|
* On newer kernels all we have to do is to try to set a breakpoint, on any
|
|
* kernel address, then read it back and check if the value has been set or
|
|
* not.
|
|
*
|
|
* The original fix to the CVE however disabled a breakpoint on address change
|
|
* and the check was deffered to write dr7 that enabled the breakpoint again.
|
|
* So on older kernels we have to write to dr7 which should fail instead.
|
|
*
|
|
* Kernel crash partially fixed in:
|
|
*
|
|
* commit f67b15037a7a50c57f72e69a6d59941ad90a0f0f
|
|
* Author: Linus Torvalds <torvalds@linux-foundation.org>
|
|
* Date: Mon Mar 26 15:39:07 2018 -1000
|
|
*
|
|
* perf/hwbp: Simplify the perf-hwbp code, fix documentation
|
|
*
|
|
* On Centos7, this is also a regression test for
|
|
* commit 27747f8bc355 ("perf/x86/hw_breakpoints: Fix check for kernel-space breakpoints").
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/user.h>
|
|
#include <signal.h>
|
|
#include "tst_test.h"
|
|
#include "tst_safe_stdio.h"
|
|
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
|
|
static pid_t child_pid;
|
|
|
|
#if defined(__x86_64__)
|
|
# define KERN_ADDR_MIN 0xffff800000000000
|
|
# define KERN_ADDR_MAX 0xffffffffffffffff
|
|
# define KERN_ADDR_BITS 64
|
|
#elif defined(__i386__)
|
|
# define KERN_ADDR_MIN 0xc0000000
|
|
# define KERN_ADDR_MAX 0xffffffff
|
|
# define KERN_ADDR_BITS 32
|
|
#endif
|
|
|
|
static int deffered_check;
|
|
|
|
static struct tst_kern_exv kvers[] = {
|
|
{"RHEL8", "4.18.0-49"},
|
|
{NULL, NULL},
|
|
};
|
|
|
|
static void setup(void)
|
|
{
|
|
/*
|
|
* When running in compat mode we can't pass 64 address to ptrace so we
|
|
* have to skip the test.
|
|
*/
|
|
if (tst_kernel_bits() != KERN_ADDR_BITS)
|
|
tst_brk(TCONF, "Cannot pass 64bit kernel address in compat mode");
|
|
|
|
|
|
/*
|
|
* The original fix for the kernel haven't rejected the kernel address
|
|
* right away when breakpoint was modified from userspace it was
|
|
* disabled instead and the EINVAL was returned when dr7 was written to
|
|
* enable it again. On RHEL8, it has introduced the right fix since
|
|
* 4.18.0-49.
|
|
*/
|
|
if (tst_kvercmp2(4, 19, 0, kvers) < 0)
|
|
deffered_check = 1;
|
|
}
|
|
|
|
static void child_main(void)
|
|
{
|
|
raise(SIGSTOP);
|
|
exit(0);
|
|
}
|
|
|
|
static void ptrace_try_kern_addr(unsigned long kern_addr)
|
|
{
|
|
int status;
|
|
unsigned long addr;
|
|
|
|
tst_res(TINFO, "Trying address 0x%lx", kern_addr);
|
|
|
|
child_pid = SAFE_FORK();
|
|
|
|
if (!child_pid)
|
|
child_main();
|
|
|
|
if (SAFE_WAITPID(child_pid, &status, WUNTRACED) != child_pid)
|
|
tst_brk(TBROK, "Received event from unexpected PID");
|
|
|
|
SAFE_PTRACE(PTRACE_ATTACH, child_pid, NULL, NULL);
|
|
SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
|
|
(void *)offsetof(struct user, u_debugreg[0]), (void *)1);
|
|
SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
|
|
(void *)offsetof(struct user, u_debugreg[7]), (void *)1);
|
|
|
|
TEST(ptrace(PTRACE_POKEUSER, child_pid,
|
|
(void *)offsetof(struct user, u_debugreg[0]),
|
|
(void *)kern_addr));
|
|
|
|
if (deffered_check) {
|
|
TEST(ptrace(PTRACE_POKEUSER, child_pid,
|
|
(void *)offsetof(struct user, u_debugreg[7]), (void *)1));
|
|
}
|
|
|
|
if (TST_RET != -1) {
|
|
tst_res(TFAIL, "ptrace() breakpoint with kernel addr succeeded");
|
|
} else {
|
|
if (TST_ERR == EINVAL) {
|
|
tst_res(TPASS | TTERRNO,
|
|
"ptrace() breakpoint with kernel addr failed");
|
|
} else {
|
|
tst_res(TFAIL | TTERRNO,
|
|
"ptrace() breakpoint on kernel addr should return EINVAL, got");
|
|
}
|
|
}
|
|
|
|
addr = ptrace(PTRACE_PEEKUSER, child_pid,
|
|
(void*)offsetof(struct user, u_debugreg[0]), NULL);
|
|
|
|
if (!deffered_check && addr == kern_addr)
|
|
tst_res(TFAIL, "Was able to set breakpoint on kernel addr");
|
|
|
|
SAFE_PTRACE(PTRACE_DETACH, child_pid, NULL, NULL);
|
|
SAFE_KILL(child_pid, SIGCONT);
|
|
child_pid = 0;
|
|
tst_reap_children();
|
|
}
|
|
|
|
static void run(void)
|
|
{
|
|
ptrace_try_kern_addr(KERN_ADDR_MIN);
|
|
ptrace_try_kern_addr(KERN_ADDR_MAX);
|
|
ptrace_try_kern_addr(KERN_ADDR_MIN + (KERN_ADDR_MAX - KERN_ADDR_MIN)/2);
|
|
}
|
|
|
|
static void cleanup(void)
|
|
{
|
|
/* Main process terminated by tst_brk() with child still paused */
|
|
if (child_pid)
|
|
SAFE_KILL(child_pid, SIGKILL);
|
|
}
|
|
|
|
static struct tst_test test = {
|
|
.test_all = run,
|
|
.setup = setup,
|
|
.cleanup = cleanup,
|
|
.forks_child = 1,
|
|
.tags = (const struct tst_tag[]) {
|
|
{"linux-git", "f67b15037a7a"},
|
|
{"CVE", "2018-1000199"},
|
|
{"linux-git", "27747f8bc355"},
|
|
{}
|
|
}
|
|
};
|
|
#else
|
|
TST_TEST_TCONF("This test is only supported on x86 systems");
|
|
#endif
|