David Venhoff cfcea9ee67 Initial commit
Based off b80915eb99
and compacted into a single commit so that it will fit on the uni git server
2025-08-11 13:05:09 +02:00

369 lines
10 KiB
C

/*
* Kernel AFL Fast Dirty Page Logging Driver (KVM Extension)
* (c) Sergej Schumilo 2017 - sergej@schumilo.de
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
*/
#include "vmx_fdl.h"
#include <linux/types.h>
#include <asm/nmi.h>
#include <asm/page.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/anon_inodes.h>
#include <linux/vmalloc.h>
#include <linux/kvm_host.h>
/*===========================================================================*
* Fast Reload Mechanism *
*===========================================================================*/
//#define DEBUG_FDL
#define FAST_IN_RANGE(address, start, end) (address < end && address >= start)
#define PAGE_ALIGNED_SIZE(x) ((x) + (0x1000-((x)%0x1000)))
#define FDL_BITMAP_SIZE(x) ((x/0x1000)/8)
#define FDL_STACK_SIZE(x) ((x/0x1000)*sizeof(uint64_t))
#define FDL_MAX_AREAS 8
struct fdl_area{
uint64_t base_address;
uint64_t size;
uint64_t mmap_bitmap_offset; /* set by kernel */
uint64_t mmap_stack_offset; /* set by kernel */
uint64_t mmap_bitmap_size; /* set by kernel */
uint64_t mmap_stack_size; /* set by kernel */
};
struct fdl_conf{
uint8_t num;
uint64_t mmap_size; /* set by kernel */
struct fdl_area areas[FDL_MAX_AREAS];
};
struct fdl_result{
uint8_t num;
uint64_t values[FDL_MAX_AREAS];
};
struct vmx_fdl_area{
uint64_t base; /* base address of this ram area */
uint64_t size; /* size of this ram area */
uint64_t* stack; /* pointer to fdl stack */
uint8_t* bitmap; /* pointer to bitmap */
uint64_t stack_max; /* max stack slots */
uint64_t stack_index; /* current stack position */
};
struct vm_vmx_fdl{
bool configured;
void* alloc_buf; /* pointer to the allocated mmapped buffer */
uint64_t total_alloc_size; /* total size of the mmapped buffer (page aligend) */
uint8_t num_areas; /* number of tracked ram arease */
struct vmx_fdl_area areas[FDL_MAX_AREAS]; /* our ram areas */
};
static void vmx_fdl_set_addr(struct vm_vmx_fdl* data, u64 gpfn){
struct vmx_fdl_area* area;
if(data->alloc_buf){
uint8_t ram_area = 0xff;
switch(FDL_MAX_AREAS-data->num_areas){
case 0:
ram_area = FAST_IN_RANGE(gpfn, data->areas[7].base, data->areas[7].base+(data->areas[7].size-1ULL)) ? 7 : ram_area;
// fall through
case 1:
ram_area = FAST_IN_RANGE(gpfn, data->areas[6].base, data->areas[6].base+(data->areas[6].size-1ULL)) ? 6 : ram_area;
// fall through
case 2:
ram_area = FAST_IN_RANGE(gpfn, data->areas[5].base, data->areas[5].base+(data->areas[5].size-1ULL)) ? 5 : ram_area;
// fall through
case 3:
ram_area = FAST_IN_RANGE(gpfn, data->areas[4].base, data->areas[4].base+(data->areas[4].size-1ULL)) ? 4 : ram_area;
// fall through
case 4:
ram_area = FAST_IN_RANGE(gpfn, data->areas[3].base, data->areas[3].base+(data->areas[3].size-1ULL)) ? 3 : ram_area;
// fall through
case 5:
ram_area = FAST_IN_RANGE(gpfn, data->areas[2].base, data->areas[2].base+(data->areas[2].size-1ULL)) ? 2 : ram_area;
// fall through
case 6:
ram_area = FAST_IN_RANGE(gpfn, data->areas[1].base, data->areas[1].base+(data->areas[1].size-1ULL)) ? 1 : ram_area;
// fall through
case 7:
ram_area = FAST_IN_RANGE(gpfn, data->areas[0].base, data->areas[0].base+(data->areas[0].size-1ULL)) ? 0 : ram_area;
// fall through
default:
break;
}
if(ram_area == 0xff){
printk("ERROR: %s %llx [%d]\n", __func__, gpfn, ram_area);
return;
}
area = &data->areas[ram_area];
if(!test_and_set_bit_le((gpfn-area->base)>>12, area->bitmap)){
//printk("%s %llx [%d]\n", __func__, gpfn, ram_area);
if(area->stack_index >= area->stack_max){
printk("ERROR stack_max reached\n");
return;
}
area->stack[area->stack_index] = gpfn;
area->stack_index += 1;
}
}
}
void vmx_fdl_set_addr_kvm(void* data, u64 gpa){
//printk("%s %llx\n", __func__, gpa);
struct vm_vmx_fdl* fdl_data = ((struct kvm*)data)->arch.fdl_opaque;
struct kvm_memory_slot* slot = gfn_to_memslot(((struct kvm*)data), gpa>>12);
//printk("%p\n", slot);
if (slot && slot->dirty_bitmap) {
//printk("---> %p\n", slot);
vmx_fdl_set_addr(fdl_data, gpa&0xFFFFFFFFFFFFF000);
}
}
void vmx_fdl_set_addr_vpcu(void* data, u64 gpa){
vmx_fdl_set_addr_kvm((void*)(((struct kvm_vcpu*)data)->kvm), gpa);
}
static int vmx_fdl_release(struct inode *inode, struct file *filp)
{
#ifdef DEBUG
PRINT_INFO("fast_reload_release: file closed ...");
#endif
return 0;
}
static long vmx_fdl_realloc_memory(struct vm_vmx_fdl* data, unsigned long arg){
long r = -EINVAL;
struct fdl_conf configuration;
void __user *argp;
uint8_t i;
if(data->configured){
/* configuration is only allowed once */
return r;
}
argp = (void __user *)arg;
if (!copy_from_user(&configuration, argp, sizeof(struct fdl_conf))){
data->num_areas = 0;
data->total_alloc_size = 0;
if(configuration.num){
#ifdef DEBUG_FDL
printk("configuration.num: %d\n", configuration.num);
#endif
for(i = 0; i < configuration.num; i++){
data->areas[i].base = configuration.areas[i].base_address;
data->areas[i].size = configuration.areas[i].size;
data->areas[i].stack_index = 0;
data->areas[i].stack_max = (data->areas[i].size/0x1000);
configuration.areas[i].mmap_bitmap_offset = data->total_alloc_size;
configuration.areas[i].mmap_bitmap_size = PAGE_ALIGNED_SIZE(FDL_BITMAP_SIZE(data->areas[i].size));
data->total_alloc_size += configuration.areas[i].mmap_bitmap_size;
configuration.areas[i].mmap_stack_offset = data->total_alloc_size;
configuration.areas[i].mmap_stack_size = PAGE_ALIGNED_SIZE(FDL_STACK_SIZE(data->areas[i].size));
data->total_alloc_size += configuration.areas[i].mmap_stack_size;
data->num_areas++;
}
data->alloc_buf = vmalloc(data->total_alloc_size);
memset(data->alloc_buf, 0, data->total_alloc_size);
for(i = 0; i < data->num_areas; i++){
data->areas[i].bitmap = data->alloc_buf + configuration.areas[i].mmap_bitmap_offset;
data->areas[i].stack = data->alloc_buf + configuration.areas[i].mmap_stack_offset;
}
configuration.mmap_size = data->total_alloc_size;
data->configured = true;
#ifdef DEBUG_FDL
printk("Total Alloc Size: 0x%llx\n", data->total_alloc_size);
for(i = 0; i < data->num_areas; i++){
printk("area[%d]\n", i);
printk("\tbase:\t0x%llx\n", data->areas[i].base);
printk("\tsize:\t0x%llx\n", data->areas[i].size);
printk("\tstack_index:\t0x%llx\n", data->areas[i].stack_index);
printk("\tstack_max:\t0x%llx\n", data->areas[i].stack_max);
printk("\tbitmap:\t0x%p\n", data->areas[i].bitmap);
printk("\tstack:\t0x%p\n", data->areas[i].stack);
}
#endif
if(copy_to_user(argp, &configuration, sizeof(struct fdl_conf))){
printk("%s: copy_to_user failed\n", __func__);
}
else{
r = 0;
}
}
}
else{
printk("%s: copy_from_user failed\n", __func__);
}
return r;
}
static long vmx_fdl_get_index(struct vm_vmx_fdl* data, unsigned long arg){
long r = -EINVAL;
uint8_t i;
void __user *argp;
struct fdl_result result;
if(data->configured){
argp = (void __user *)arg;
result.num = data->num_areas;
for(i = 0; i < result.num; i++){
//printk("[%d] -> stack_index: %lld\n", i, data->areas[i].stack_index);
result.values[i] = data->areas[i].stack_index;
data->areas[i].stack_index = 0; /* reset */
}
if(copy_to_user(argp, &result, sizeof(struct fdl_result))){
printk("%s: copy_to_user failed\n", __func__);
}
else{
r = 0;
}
}
return r;
}
static long vmx_fdl_flush(struct vm_vmx_fdl* data){
long r = -EINVAL;
uint8_t i;
if(data->configured){
for(i = 0; i < data->num_areas; i++){
data->areas[i].stack_index = 0; /* reset */
}
r = 0;
}
return r;
}
static long vmx_fdl_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg){
long r = -EINVAL;
struct vm_vmx_fdl *data = filp->private_data;
if(!data){
return r;
}
switch (ioctl) {
case KVM_VMX_FDL_SET:
r = vmx_fdl_realloc_memory(data, arg);
break;
case KVM_VMX_FDL_FLUSH:
/* reset the index to zero */
vmx_fdl_flush(data);
r = 0;
break;
case KVM_VMX_FDL_GET_INDEX:
r = vmx_fdl_get_index(data, arg);
break;
default:
break;
}
return r;
}
static int vmx_fdl_mmap(struct file *filp, struct vm_area_struct *vma)
{
u64 uaddr, vaddr;
struct vm_vmx_fdl *data = filp->private_data;
struct page * pageptr;
if ((vma->vm_end-vma->vm_start) > (data->total_alloc_size)){
return -EINVAL;
}
vma->vm_flags = (VM_READ | VM_SHARED | VM_WRITE); // VM_DENYWRITE);
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
uaddr = (u64)vma->vm_start;
vaddr = (u64)data->alloc_buf;
do {
pageptr = vmalloc_to_page((void*)vaddr);
vm_insert_page(vma, uaddr, pageptr);
//printk("REMAPPING: %llx - %llx\n", vaddr, uaddr);
vaddr += PAGE_SIZE;
uaddr += PAGE_SIZE;
} while(uaddr < vma->vm_end || vaddr < data->total_alloc_size);
return 0;
}
static struct file_operations vmx_fdl_fops = {
.release = vmx_fdl_release,
.unlocked_ioctl = vmx_fdl_ioctl,
.mmap = vmx_fdl_mmap,
.llseek = noop_llseek,
};
int vmx_fdl_create_fd(void* vmx_fdl_opaque){
if(vmx_fdl_opaque){
//printk("vmx-fdl\n");
return anon_inode_getfd("vmx-fdl", &vmx_fdl_fops, vmx_fdl_opaque, O_RDWR | O_CLOEXEC);
}
return 0;
}
void vmx_fdl_setup(void** vmx_fdl_opaque){
//printk("%s\n", __func__);
if(!(*vmx_fdl_opaque)){
*vmx_fdl_opaque = (void*)kmalloc(sizeof(struct vm_vmx_fdl), GFP_KERNEL);
memset(*vmx_fdl_opaque, 0, sizeof(struct vm_vmx_fdl));
((struct vm_vmx_fdl*)(*vmx_fdl_opaque))->configured = false;
}
}
void vmx_fdl_destroy(void* vmx_fdl_opaque){
//printk("%s\n", __func__);
if(vmx_fdl_opaque){
if(((struct vm_vmx_fdl*)(vmx_fdl_opaque))->alloc_buf){
#ifdef DEBUG_FDL
printk("vfree alloc_buf\n");
#endif
vfree(((struct vm_vmx_fdl*)(vmx_fdl_opaque))->alloc_buf);
}
kfree(vmx_fdl_opaque);
}
}