Romain Malmain d8460d14a2
More Nyx hypercalls supported in libafl qemu. add tests for filters. (#2825)
* more nyx hypercalls implemented, among them:
- panic hypercall
- range filtering hypercall

* fixed some nyx hypercalls behavior.

* added generic read / write to qemu memory

* port linux kernel example to also have nyx API, add better filtering as well.

* make nyx api structs volatile to avoid optimization issues

* Introduce a method create a Vec in place, using a closure.

* use new vec_init function in relevant places.

* removed unused unsafe keywork

* add more allocated memory r/w callbacks

* add more safety notes

* move emulator hooks to separate struct

* update QEMU version
2025-01-13 17:32:23 +01:00

349 lines
9.3 KiB
C

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/kallsyms.h>
#include "x509-parser.h"
#if defined(USE_LQEMU)
#include "libafl_qemu.h"
#elif defined(USE_NYX)
#include "nyx_api.h"
#endif
#define PAYLOAD_MAX_SIZE 65536
static int harness_open(struct inode *inode, struct file *file);
static int harness_release(struct inode *inode, struct file *file);
static const struct file_operations harness_fops = {
.owner = THIS_MODULE,
.open = harness_open,
.release = harness_release,
};
struct mychar_device_data {
struct cdev cdev;
};
static int dev_major = 0;
static struct class *harness_class = NULL;
static struct mychar_device_data harness_data;
#define KPROBE_PRE_HANDLER(fname) \
static int __kprobes fname(struct kprobe *p, struct pt_regs *regs)
// kallsyms_lookup_name function address
static long unsigned int kallsyms_lookup_name_addr = 0;
// kallsyms_lookup_name function
static unsigned long (*kall_syms_lookup_name_fn)(const char *name) = NULL;
static struct kprobe kp0, kp1;
KPROBE_PRE_HANDLER(handler_pre0) {
kallsyms_lookup_name_addr = (--regs->ip);
return 0;
}
KPROBE_PRE_HANDLER(handler_pre1) {
return 0;
}
static int do_register_kprobe(struct kprobe *kp, char *symbol_name,
void *handler) {
int ret;
kp->symbol_name = symbol_name;
kp->pre_handler = handler;
ret = register_kprobe(kp);
if (ret < 0) {
pr_err("register_probe() for symbol %s failed, returned %d\n", symbol_name,
ret);
return ret;
}
pr_info("Planted kprobe for symbol %s at %p\n", symbol_name, kp->addr);
return ret;
}
// Find kallsyms_lookup_name
// taken from
// https://github.com/zizzu0/LinuxKernelModules/blob/main/FindKallsymsLookupName.c
static int harness_find_kallsyms_lookup(void) {
int ret;
ret = do_register_kprobe(&kp0, "kallsyms_lookup_name", handler_pre0);
if (ret < 0) return ret;
ret = do_register_kprobe(&kp1, "kallsyms_lookup_name", handler_pre1);
if (ret < 0) {
unregister_kprobe(&kp0);
return ret;
}
unregister_kprobe(&kp0);
unregister_kprobe(&kp1);
#ifdef USE_NYX
hprintf("kallsyms_lookup_name address = 0x%lx\n", kallsyms_lookup_name_addr);
#elif DEFINED(USE_LQEMU)
lqprintf("kallsyms_lookup_name address = 0x%lx\n", kallsyms_lookup_name_addr);
#endif
if (kallsyms_lookup_name_addr == 0) { return -1; }
kall_syms_lookup_name_fn =
(unsigned long (*)(const char *name))kallsyms_lookup_name_addr;
return ret;
}
static int harness_uevent(const struct device *dev,
struct kobj_uevent_env *env) {
add_uevent_var(env, "DEVMODE=%#o", 0666);
return 0;
}
#ifdef USE_NYX
/**
* Allocate page-aligned memory
*/
static void *malloc_resident_pages(size_t num_pages) {
size_t data_size = PAGE_SIZE * num_pages;
void *ptr = NULL;
if ((ptr = kzalloc(data_size, GFP_KERNEL)) == NULL) {
printk("Allocation failure\n");
goto err_out;
}
// ensure pages are aligned and resident
memset(ptr, 0x42, data_size);
// if (mlock(ptr, data_size) == -1) {
// printk("Error locking scratch buffer\n");
// goto err_out;
// }
// assert(((uintptr_t)ptr % PAGE_SIZE) == 0);
return ptr;
err_out:
// free(ptr);
return NULL;
}
static volatile __attribute__((aligned(PAGE_SIZE))) uint64_t range_args[3];
static void hrange_submit(unsigned id, unsigned long start, unsigned long end) {
range_args[0] = start;
range_args[1] = end;
range_args[2] = id;
kAFL_hypercall(HYPERCALL_KAFL_RANGE_SUBMIT, (unsigned long)range_args);
}
static int agent_init(int verbose) {
host_config_t host_config;
hprintf("Nyx agent init");
// set ready state
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
kAFL_hypercall(HYPERCALL_KAFL_GET_HOST_CONFIG, (uintptr_t)&host_config);
if (verbose) {
printk("GET_HOST_CONFIG\n");
printk("\thost magic: 0x%x, version: 0x%x\n", host_config.host_magic,
host_config.host_version);
printk("\tbitmap size: 0x%x, ijon: 0x%x\n", host_config.bitmap_size,
host_config.ijon_bitmap_size);
printk("\tpayload size: %u KB\n", host_config.payload_buffer_size / 1024);
printk("\tworker id: %d\n", host_config.worker_id);
}
if (host_config.host_magic != NYX_HOST_MAGIC) {
hprintf("HOST_MAGIC mismatch: %08x != %08x", host_config.host_magic,
NYX_HOST_MAGIC);
habort("HOST_MAGIC mismatch!");
return -1;
}
if (host_config.host_version != NYX_HOST_VERSION) {
hprintf("HOST_VERSION mismatch: %08x != %08x\n", host_config.host_version,
NYX_HOST_VERSION);
habort("HOST_VERSION mismatch!");
return -1;
}
if (host_config.payload_buffer_size > PAYLOAD_MAX_SIZE) {
hprintf("Fuzzer payload size too large: %lu > %lu\n",
host_config.payload_buffer_size, PAYLOAD_MAX_SIZE);
habort("Host payload size too large!");
return -1;
}
agent_config_t agent_config = {0};
agent_config.agent_magic = NYX_AGENT_MAGIC;
agent_config.agent_version = NYX_AGENT_VERSION;
// agent_config.agent_timeout_detection = 0; // timeout by host
// agent_config.agent_tracing = 0; // trace by host
// agent_config.agent_ijon_tracing = 0; // no IJON
agent_config.agent_non_reload_mode = 0; // no persistent mode
// agent_config.trace_buffer_vaddr = 0xdeadbeef;
// agent_config.ijon_trace_buffer_vaddr = 0xdeadbeef;
agent_config.coverage_bitmap_size = host_config.bitmap_size;
// agent_config.input_buffer_size;
// agent_config.dump_payloads; // set by hypervisor (??)
kAFL_hypercall(HYPERCALL_KAFL_SET_AGENT_CONFIG, (uintptr_t)&agent_config);
return 0;
}
#endif
static int __init harness_init(void) {
int err;
dev_t dev;
err = alloc_chrdev_region(&dev, 0, 1, "harness");
if (err < 0) { return err; }
dev_major = MAJOR(dev);
harness_class = class_create("harness");
harness_class->dev_uevent = harness_uevent;
cdev_init(&harness_data.cdev, &harness_fops);
harness_data.cdev.owner = THIS_MODULE;
cdev_add(&harness_data.cdev, MKDEV(dev_major, 0), 1);
device_create(harness_class, NULL, MKDEV(dev_major, 0), NULL, "harness");
err = harness_find_kallsyms_lookup();
if (err < 0) {
habort("error while trying to find kallsyms");
return err;
}
return 0;
}
static void __exit harness_exit(void) {
device_destroy(harness_class, MKDEV(dev_major, 0));
class_unregister(harness_class);
class_destroy(harness_class);
unregister_chrdev_region(MKDEV(dev_major, 0), MINORMASK);
}
static int harness_open(struct inode *inode, struct file *file) {
unsigned long x509_fn_addr = kall_syms_lookup_name_fn("x509_cert_parse");
unsigned long asn1_ber_decoder_addr =
kall_syms_lookup_name_fn("asn1_ber_decoder");
unsigned long x509_get_sig_params_addr =
kall_syms_lookup_name_fn("x509_get_sig_params");
// hprintf("action 0: %p", x509_decoder.actions[0]);
#if defined(USE_LQEMU)
lqprintf("harness: Device open\n");
// TODO: better filtering...
libafl_qemu_trace_vaddr_size(x509_fn_addr, 0x1000);
libafl_qemu_test();
char *input_buf = kzalloc(PAYLOAD_MAX_SIZE, GFP_KERNEL);
input_buf[0] = 0xff; // init page
#elif defined(USE_NYX)
hprintf("harness: Device open.");
hprintf("\tx509_cert_parse: %p", x509_fn_addr);
hprintf("\tasn1_ber_decoder: %p", asn1_ber_decoder_addr);
hprintf("\tx509_get_sig_params: %p", x509_get_sig_params_addr);
if (!x509_fn_addr || !asn1_ber_decoder_addr) { habort("Invalid ranges"); }
kAFL_payload *pbuf = malloc_resident_pages(PAYLOAD_MAX_SIZE / PAGE_SIZE);
agent_init(1);
// kAFL_hypercall(HYPERCALL_KAFL_SUBMIT_CR3, 0);
hrange_submit(0, x509_fn_addr, x509_fn_addr + 0x1000);
hrange_submit(1, asn1_ber_decoder_addr, asn1_ber_decoder_addr + 0x1000);
hrange_submit(2, x509_get_sig_params_addr, x509_get_sig_params_addr + 0x1000);
kAFL_hypercall(HYPERCALL_KAFL_GET_PAYLOAD, (uintptr_t)pbuf);
hprintf("payload size addr: %p", &pbuf->size);
hprintf("payload addr: %p", &pbuf->data);
#else
#error No API specified.
#endif
// int ret;
// uintptr_t start_addr = 0, end_addr = 0;
// ret = lqemu_symfinder_widen_range("x509_cert_parse", &start_addr,
// &end_addr); if (ret) {
// printk("error while handling range");
// return ret;
// }
while (true) {
#if defined(USE_LQEMU)
uint8_t *data = input_buf;
size_t size = libafl_qemu_start_virt(data, PAYLOAD_MAX_SIZE);
#elif defined(USE_NYX)
kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
size_t size = pbuf->size;
const volatile uint8_t *data = pbuf->data;
#endif
__maybe_unused struct x509_certificate *cert_ret =
x509_cert_parse((const void *)data, size);
#if defined(USE_LQEMU)
libafl_qemu_end(LIBAFL_QEMU_END_OK);
#elif defined(USE_NYX)
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
#endif
}
return 0;
}
static int harness_release(struct inode *inode, struct file *file) {
#if defined(USE_LQEMU)
lqprintf("harness: Device close\n");
#elif defined(USE_NYX)
hprintf("harness: Device close");
#endif
return 0;
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Slasti Mormanti");
module_init(harness_init);
module_exit(harness_exit);