/*
Copyright (C) 2017 Sergej Schumilo
This file is part of QEMU-PT (kAFL).
QEMU-PT is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QEMU-PT is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QEMU-PT. If not, see .
*/
#include "qemu/osdep.h"
#include
#include
#include
#include "exec/memory.h"
#include "sysemu/cpus.h"
#include "sysemu/kvm.h"
#include "sysemu/kvm_int.h"
#include "qemu-common.h"
#include "target/i386/cpu.h"
#include "nyx/debug.h"
#include "nyx/file_helper.h"
#include "nyx/helpers.h"
#include "nyx/hypercall/hypercall.h"
#include "nyx/interface.h"
#include "nyx/memory_access.h"
#include "nyx/page_cache.h"
#include "nyx/pt.h"
#include "nyx/redqueen_trace.h"
#include "nyx/state/state.h"
#include "nyx/trace_dump.h"
#ifdef CONFIG_REDQUEEN
#include "nyx/patcher.h"
#include "nyx/redqueen.h"
#include "nyx/redqueen_patch.h"
#endif
#define PT_BUFFER_MMAP_ADDR 0x3ffff0000000
static bool PT_ENABLED = false;
void set_global_pt_enabled(bool pt_enabled) {
PT_ENABLED = pt_enabled;
}
static void pt_set(CPUState *cpu, run_on_cpu_data arg)
{
asm volatile("" ::: "memory");
}
static inline int pt_cmd_hmp_context(CPUState *cpu, uint64_t cmd)
{
cpu->pt_ret = -1;
if (pt_hypercalls_enabled()) {
nyx_error("HMP commands are ignored if kafl tracing "
"mode is enabled (-kafl)!\n");
} else {
cpu->pt_cmd = cmd;
run_on_cpu(cpu, pt_set, RUN_ON_CPU_NULL);
}
return cpu->pt_ret;
}
static int pt_cmd(CPUState *cpu, uint64_t cmd, bool hmp_mode)
{
if (hmp_mode) {
return pt_cmd_hmp_context(cpu, cmd);
} else {
cpu->pt_cmd = cmd;
pt_pre_kvm_run(cpu);
return cpu->pt_ret;
}
}
static inline int pt_ioctl(int fd, unsigned long request, unsigned long arg)
{
if (!fd) {
return -EINVAL;
}
return ioctl(fd, request, arg);
}
void pt_dump(CPUState *cpu, int bytes)
{
nyx_debug("Dumping file...");
if (GET_GLOBAL_STATE()->in_fuzzing_mode && !GET_GLOBAL_STATE()->dump_page)
{
GET_GLOBAL_STATE()->pt_trace_size += bytes;
pt_write_pt_dump_file(cpu->pt_mmap, bytes);
}
}
int pt_enable(CPUState *cpu, bool hmp_mode)
{
if (!fast_reload_set_bitmap(get_fast_reload_snapshot())) {
coverage_bitmap_reset();
}
if (GET_GLOBAL_STATE()->trace_mode) {
redqueen_trace_reset();
alt_bitmap_reset();
}
nyx_debug("Truncating the dump file!\n");
pt_truncate_pt_dump_file();
return pt_cmd(cpu, KVM_VMX_PT_ENABLE, hmp_mode);
}
int pt_disable(CPUState *cpu, bool hmp_mode)
{
int r = pt_cmd(cpu, KVM_VMX_PT_DISABLE, hmp_mode);
return r;
}
int pt_set_cr3(CPUState *cpu, uint64_t val, bool hmp_mode)
{
int r = 0;
if (val == GET_GLOBAL_STATE()->pt_c3_filter) {
return 0; // nothing changed
}
if (cpu->pt_enabled) {
return -EINVAL;
}
if (GET_GLOBAL_STATE()->pt_c3_filter && GET_GLOBAL_STATE()->pt_c3_filter != val) {
// nyx_debug_p(PT_PREFIX, "Reconfigure CR3-Filtering!\n");
GET_GLOBAL_STATE()->pt_c3_filter = val;
r += pt_cmd(cpu, KVM_VMX_PT_CONFIGURE_CR3, hmp_mode);
r += pt_cmd(cpu, KVM_VMX_PT_ENABLE_CR3, hmp_mode);
return r;
}
GET_GLOBAL_STATE()->pt_c3_filter = val;
r += pt_cmd(cpu, KVM_VMX_PT_CONFIGURE_CR3, hmp_mode);
r += pt_cmd(cpu, KVM_VMX_PT_ENABLE_CR3, hmp_mode);
return r;
}
int pt_enable_ip_filtering(CPUState *cpu, uint8_t addrn, bool redqueen, bool hmp_mode)
{
int r = 0;
if (addrn > 3) {
return -1;
}
if (cpu->pt_enabled) {
return -EINVAL;
}
if (GET_GLOBAL_STATE()->pt_ip_filter_a[addrn] >
GET_GLOBAL_STATE()->pt_ip_filter_b[addrn])
{
nyx_debug_p(PT_PREFIX, "Error (ip_a > ip_b) 0x%lx-0x%lx\n",
GET_GLOBAL_STATE()->pt_ip_filter_a[addrn],
GET_GLOBAL_STATE()->pt_ip_filter_b[addrn]);
return -EINVAL;
}
if (GET_GLOBAL_STATE()->pt_ip_filter_enabled[addrn]) {
pt_disable_ip_filtering(cpu, addrn, hmp_mode);
}
nyx_debug_p(PT_PREFIX, "Configuring new trace region (addr%d, 0x%lx-0x%lx)\n",
addrn, GET_GLOBAL_STATE()->pt_ip_filter_a[addrn],
GET_GLOBAL_STATE()->pt_ip_filter_b[addrn]);
if (GET_GLOBAL_STATE()->pt_ip_filter_configured[addrn] &&
GET_GLOBAL_STATE()->pt_ip_filter_a[addrn] != 0 &&
GET_GLOBAL_STATE()->pt_ip_filter_b[addrn] != 0)
{
r += pt_cmd(cpu, KVM_VMX_PT_CONFIGURE_ADDR0 + addrn, hmp_mode);
r += pt_cmd(cpu, KVM_VMX_PT_ENABLE_ADDR0 + addrn, hmp_mode);
GET_GLOBAL_STATE()->pt_ip_filter_enabled[addrn] = true;
}
return r;
}
int pt_disable_ip_filtering(CPUState *cpu, uint8_t addrn, bool hmp_mode)
{
int r = 0;
switch (addrn) {
case 0:
case 1:
case 2:
case 3:
r = pt_cmd(cpu, KVM_VMX_PT_DISABLE_ADDR0 + addrn, hmp_mode);
if (GET_GLOBAL_STATE()->pt_ip_filter_enabled[addrn]) {
GET_GLOBAL_STATE()->pt_ip_filter_enabled[addrn] = false;
}
break;
default:
r = -EINVAL;
}
return r;
}
void pt_kvm_init(CPUState *cpu)
{
cpu->pt_cmd = 0;
cpu->pt_enabled = false;
cpu->pt_fd = 0;
cpu->pt_decoder_state = NULL;
cpu->reload_pending = false;
cpu->intel_pt_run_trashed = false;
}
struct vmx_pt_filter_iprs {
__u64 a;
__u64 b;
};
pthread_mutex_t pt_dump_mutex = PTHREAD_MUTEX_INITIALIZER;
void pt_pre_kvm_run(CPUState *cpu)
{
pthread_mutex_lock(&pt_dump_mutex);
int ret;
struct vmx_pt_filter_iprs filter_iprs;
if (GET_GLOBAL_STATE()->patches_disable_pending) {
// nyx_debug_p(REDQUEEN_PREFIX, "patches disable\n");
assert(false); /* remove this branch */
GET_GLOBAL_STATE()->patches_disable_pending = false;
}
if (GET_GLOBAL_STATE()->patches_enable_pending) {
// nyx_debug_p(REDQUEEN_PREFIX, "patches enable\n");
assert(false); /* remove this branch */
GET_GLOBAL_STATE()->patches_enable_pending = false;
}
if (GET_GLOBAL_STATE()->redqueen_enable_pending) {
// nyx_debug_p(REDQUEEN_PREFIX, "rq enable\n");
if (GET_GLOBAL_STATE()->redqueen_state) {
enable_rq_intercept_mode(GET_GLOBAL_STATE()->redqueen_state);
}
GET_GLOBAL_STATE()->redqueen_enable_pending = false;
}
if (GET_GLOBAL_STATE()->redqueen_disable_pending) {
// nyx_debug_p(REDQUEEN_PREFIX, "rq disable\n");
if (GET_GLOBAL_STATE()->redqueen_state) {
disable_rq_intercept_mode(GET_GLOBAL_STATE()->redqueen_state);
}
GET_GLOBAL_STATE()->redqueen_disable_pending = false;
}
if (PT_ENABLED && (GET_GLOBAL_STATE()->pt_trace_mode || GET_GLOBAL_STATE()->pt_trace_mode_force))
{
if (!cpu->pt_fd) {
cpu->pt_fd = kvm_vcpu_ioctl(cpu, KVM_VMX_PT_SETUP_FD, (unsigned long)0);
assert(cpu->pt_fd != -1);
ret = ioctl(cpu->pt_fd, KVM_VMX_PT_GET_TOPA_SIZE, (unsigned long)0x0);
if (ret == -1) {
nyx_abort("ToPA allocation failure. Check kernel logs.\n");
}
assert(ret % PAGE_SIZE == 0);
cpu->pt_mmap = mmap((void *)PT_BUFFER_MMAP_ADDR, ret,
PROT_READ | PROT_WRITE, MAP_SHARED, cpu->pt_fd, 0);
assert(cpu->pt_mmap != (void *)0xFFFFFFFFFFFFFFFF);
// add an extra page to have enough space for an additional PT_TRACE_END byte
assert(mmap(cpu->pt_mmap + ret, 0x1000, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, -1,
0) == (void *)(cpu->pt_mmap + ret));
nyx_debug("=> pt_mmap: %p - %p\n", cpu->pt_mmap, cpu->pt_mmap + ret);
memset(cpu->pt_mmap + ret, 0x55, 0x1000);
}
if (cpu->pt_cmd) {
switch (cpu->pt_cmd) {
case KVM_VMX_PT_ENABLE:
if (cpu->pt_fd) {
/* dump for the very last time before enabling VMX_PT ... just in case */
ioctl(cpu->pt_fd, KVM_VMX_PT_CHECK_TOPA_OVERFLOW,
(unsigned long)0);
if (!ioctl(cpu->pt_fd, cpu->pt_cmd, 0)) {
cpu->pt_enabled = true;
}
}
break;
case KVM_VMX_PT_DISABLE:
if (cpu->pt_fd) {
ret = ioctl(cpu->pt_fd, cpu->pt_cmd, 0);
if (ret > 0) {
// nyx_debug_p(PT_PREFIX, "KVM_VMX_PT_DISABLE %d\n", ret);
pt_dump(cpu, ret);
cpu->pt_enabled = false;
}
}
break;
/* ip filtering configuration */
case KVM_VMX_PT_CONFIGURE_ADDR0:
case KVM_VMX_PT_CONFIGURE_ADDR1:
case KVM_VMX_PT_CONFIGURE_ADDR2:
case KVM_VMX_PT_CONFIGURE_ADDR3:
filter_iprs.a =
GET_GLOBAL_STATE()
->pt_ip_filter_a[(cpu->pt_cmd) - KVM_VMX_PT_CONFIGURE_ADDR0];
filter_iprs.b =
GET_GLOBAL_STATE()
->pt_ip_filter_b[(cpu->pt_cmd) - KVM_VMX_PT_CONFIGURE_ADDR0];
ret = pt_ioctl(cpu->pt_fd, cpu->pt_cmd, (unsigned long)&filter_iprs);
break;
case KVM_VMX_PT_ENABLE_ADDR0:
case KVM_VMX_PT_ENABLE_ADDR1:
case KVM_VMX_PT_ENABLE_ADDR2:
case KVM_VMX_PT_ENABLE_ADDR3:
ret = pt_ioctl(cpu->pt_fd, cpu->pt_cmd, (unsigned long)0);
break;
case KVM_VMX_PT_CONFIGURE_CR3:
ret = pt_ioctl(cpu->pt_fd, cpu->pt_cmd,
GET_GLOBAL_STATE()->pt_c3_filter);
break;
case KVM_VMX_PT_ENABLE_CR3:
ret = pt_ioctl(cpu->pt_fd, cpu->pt_cmd, (unsigned long)0);
break;
default:
if (cpu->pt_fd) {
ioctl(cpu->pt_fd, cpu->pt_cmd, 0);
}
break;
}
cpu->pt_cmd = 0;
cpu->pt_ret = 0;
}
}
pthread_mutex_unlock(&pt_dump_mutex);
}
void pt_handle_overflow(CPUState *cpu)
{
pthread_mutex_lock(&pt_dump_mutex);
int overflow = ioctl(cpu->pt_fd, KVM_VMX_PT_CHECK_TOPA_OVERFLOW, (unsigned long)0);
if (overflow > 0) {
pt_dump(cpu, overflow);
}
pthread_mutex_unlock(&pt_dump_mutex);
}
void pt_post_kvm_run(CPUState *cpu)
{
if (PT_ENABLED && (GET_GLOBAL_STATE()->pt_trace_mode || GET_GLOBAL_STATE()->pt_trace_mode_force))
{
pt_handle_overflow(cpu);
}
}