Skip to content

Commit eba09fc

Browse files
committed
executor/kvm: add x86-64 SYZOS fuzzer
This commit adds the actual SyzOS fuzzer for x86-64 and two small tests.
1 parent 952fa82 commit eba09fc

File tree

5 files changed

+450
-7
lines changed

5 files changed

+450
-7
lines changed

executor/common_kvm_amd64.h

Lines changed: 282 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "kvm.h"
1111
#include "kvm_amd64.S.h"
12+
#include "common_kvm_amd64_syzos.h"
1213

1314
#ifndef KVM_SMI
1415
#define KVM_SMI _IO(KVMIO, 0xb7)
@@ -69,7 +70,9 @@ struct tss32 {
6970
uint16 trace;
7071
uint16 io_bitmap;
7172
} __attribute__((packed));
73+
#endif
7274

75+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu
7376
struct tss64 {
7477
uint32 reserved0;
7578
uint64 rsp[3];
@@ -79,9 +82,7 @@ struct tss64 {
7982
uint32 reserved3;
8083
uint32 io_bitmap;
8184
} __attribute__((packed));
82-
#endif
8385

84-
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu
8586
static void fill_segment_descriptor(uint64* dt, uint64* lt, struct kvm_segment* seg)
8687
{
8788
uint16 index = seg->selector >> 3;
@@ -200,7 +201,7 @@ static void setup_64bit_idt(struct kvm_sregs* sregs, char* host_mem, uintptr_t g
200201
}
201202
#endif
202203

203-
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu
204+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu
204205
struct kvm_text {
205206
uintptr_t typ;
206207
const void* text;
@@ -215,6 +216,125 @@ struct kvm_opt {
215216
};
216217
#endif
217218

219+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu
220+
#define GENMASK_ULL(h, l) (((~_UL(0)) - (_UL(1) << (l)) + 1) & \
221+
(~_UL(0) >> (__BITS_PER_LONG - 1 - (h))))
222+
#define PAGE_MASK GENMASK_ULL(51, 12)
223+
224+
// We assume a 4-level page table, in the future we could add support for
225+
// n-level if needed.
226+
static void setup_pg_table(void* host_mem)
227+
{
228+
uint64* pml4 = (uint64*) ((uint64)host_mem + X86_ADDR_PML4);
229+
uint64* pdp = (uint64*) ((uint64)host_mem + X86_ADDR_PDP);
230+
uint64* pd = (uint64*) ((uint64)host_mem + X86_ADDR_PD);
231+
uint64* pd_ioapic = (uint64*) ((uint64)host_mem + X86_ADDR_PD_IOAPIC);
232+
233+
pml4[0] = X86_PDE64_PRESENT | X86_PDE64_RW | (X86_ADDR_PDP & PAGE_MASK);
234+
pdp[0] = X86_PDE64_PRESENT | X86_PDE64_RW | (X86_ADDR_PD & PAGE_MASK);
235+
pdp[3] = X86_PDE64_PRESENT | X86_PDE64_RW | (X86_ADDR_PD_IOAPIC & PAGE_MASK);
236+
237+
pd[0] = X86_PDE64_PRESENT | X86_PDE64_RW | X86_PDE64_PS;
238+
pd_ioapic[502] = X86_PDE64_PRESENT | X86_PDE64_RW | X86_PDE64_PS;
239+
}
240+
241+
242+
// This only sets up a 64-bit VCPU.
243+
// TODO: Should add support for other modes.
244+
static void setup_gdt_ldt_pg(int cpufd, void* host_mem)
245+
{
246+
struct kvm_sregs sregs;
247+
ioctl(cpufd, KVM_GET_SREGS, &sregs);
248+
249+
sregs.gdt.base = X86_ADDR_GDT;
250+
sregs.gdt.limit = 256 * sizeof(uint64) - 1;
251+
uint64* gdt = (uint64*)((uint64)host_mem + sregs.gdt.base);
252+
253+
struct kvm_segment seg_ldt;
254+
memset(&seg_ldt, 0, sizeof(seg_ldt));
255+
seg_ldt.selector = X86_SEL_LDT;
256+
seg_ldt.type = 2;
257+
seg_ldt.base = X86_ADDR_LDT;
258+
seg_ldt.limit = 256 * sizeof(uint64) - 1;
259+
seg_ldt.present = 1;
260+
seg_ldt.dpl = 0;
261+
seg_ldt.s = 0;
262+
seg_ldt.g = 0;
263+
seg_ldt.db = 1;
264+
seg_ldt.l = 0;
265+
sregs.ldt = seg_ldt;
266+
uint64* ldt = (uint64*)((uint64)host_mem + sregs.ldt.base);
267+
268+
struct kvm_segment seg_cs64;
269+
memset(&seg_cs64, 0, sizeof(seg_cs64));
270+
seg_cs64.selector = X86_SEL_CS64;
271+
seg_cs64.type = 11;
272+
seg_cs64.base = 0;
273+
seg_cs64.limit = 0xFFFFFFFFu;
274+
seg_cs64.present = 1;
275+
seg_cs64.s = 1;
276+
seg_cs64.g = 1;
277+
seg_cs64.l = 1;
278+
279+
sregs.cs = seg_cs64;
280+
281+
struct kvm_segment seg_ds64;
282+
memset(&seg_ds64, 0, sizeof(struct kvm_segment));
283+
seg_ds64.selector = X86_SEL_DS64;
284+
seg_ds64.type = 3;
285+
seg_ds64.limit = 0xFFFFFFFFu;
286+
seg_ds64.present = 1;
287+
seg_ds64.s = 1;
288+
seg_ds64.g = 1;
289+
290+
sregs.ds = seg_ds64;
291+
sregs.es = seg_ds64;
292+
293+
struct kvm_segment seg_tss64;
294+
memset(&seg_tss64, 0, sizeof(seg_tss64));
295+
seg_tss64.selector = X86_SEL_TSS64;
296+
seg_tss64.base = X86_ADDR_VAR_TSS64;
297+
seg_tss64.limit = 0x1ff;
298+
seg_tss64.type = 9;
299+
seg_tss64.present = 1;
300+
301+
struct tss64 tss64;
302+
memset(&tss64, 0, sizeof(tss64));
303+
tss64.rsp[0] = X86_ADDR_STACK0;
304+
tss64.rsp[1] = X86_ADDR_STACK0;
305+
tss64.rsp[2] = X86_ADDR_STACK0;
306+
tss64.io_bitmap = offsetof(struct tss64, io_bitmap);
307+
struct tss64* tss64_addr = (struct tss64*)((uint64)host_mem + seg_tss64.base);
308+
memcpy(tss64_addr, &tss64, sizeof(tss64));
309+
310+
fill_segment_descriptor(gdt, ldt, &seg_ldt);
311+
fill_segment_descriptor(gdt, ldt, &seg_cs64);
312+
fill_segment_descriptor(gdt, ldt, &seg_ds64);
313+
fill_segment_descriptor_dword(gdt, ldt, &seg_tss64);
314+
315+
setup_pg_table(host_mem);
316+
317+
sregs.cr0 = X86_CR0_PE | X86_CR0_NE | X86_CR0_PG;
318+
sregs.cr4 |= X86_CR4_PAE | X86_CR4_OSFXSR;
319+
sregs.efer |= (X86_EFER_LME | X86_EFER_LMA | X86_EFER_NXE);
320+
sregs.cr3 = X86_ADDR_PML4;
321+
322+
ioctl(cpufd, KVM_SET_SREGS, &sregs);
323+
}
324+
325+
static void setup_cpuid(int cpufd)
326+
{
327+
int kvmfd = open("/dev/kvm", O_RDWR);
328+
char buf[sizeof(struct kvm_cpuid2) + 128 * sizeof(struct kvm_cpuid_entry2)];
329+
memset(buf, 0, sizeof(buf));
330+
struct kvm_cpuid2* cpuid = (struct kvm_cpuid2*)buf;
331+
cpuid->nent = 128;
332+
ioctl(kvmfd, KVM_GET_SUPPORTED_CPUID, cpuid);
333+
ioctl(cpufd, KVM_SET_CPUID2, cpuid);
334+
close(kvmfd);
335+
}
336+
#endif
337+
218338
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu
219339
#define KVM_SETUP_PAGING (1 << 0)
220340
#define KVM_SETUP_PAE (1 << 1)
@@ -764,18 +884,173 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat
764884
}
765885
#endif
766886

887+
#if SYZ_EXECUTOR || __NR_syz_kvm_add_vcpu
888+
static void reset_cpu_regs(int cpufd, int cpu_id, size_t text_size)
889+
{
890+
struct kvm_regs regs;
891+
memset(&regs, 0, sizeof(regs));
892+
893+
regs.rflags |= 2; // bit 1 is always set
894+
// PC points to the relative offset of guest_main() within the guest code.
895+
regs.rip = X86_ADDR_EXECUTOR_CODE + ((uint64)guest_main - (uint64)&__start_guest);
896+
regs.rsp = X86_ADDR_STACK0;
897+
// Pass parameters to guest_main().
898+
regs.rdi = text_size;
899+
regs.rsi = cpu_id;
900+
ioctl(cpufd, KVM_SET_REGS, &regs);
901+
}
902+
903+
static void install_user_code(int cpufd, void* user_text_slot, int cpu_id, const void* text, size_t text_size, void* host_mem)
904+
{
905+
if ((cpu_id < 0) || (cpu_id >= KVM_MAX_VCPU))
906+
return;
907+
if (!user_text_slot)
908+
return;
909+
if (text_size > KVM_PAGE_SIZE)
910+
text_size = KVM_PAGE_SIZE;
911+
void* target = (void*)((uint64)user_text_slot + (KVM_PAGE_SIZE * cpu_id));
912+
memcpy(target, text, text_size);
913+
setup_gdt_ldt_pg(cpufd, host_mem);
914+
setup_cpuid(cpufd);
915+
reset_cpu_regs(cpufd, cpu_id, text_size);
916+
}
917+
#endif
918+
919+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm
920+
struct addr_size {
921+
void* addr;
922+
size_t size;
923+
};
924+
925+
static struct addr_size alloc_guest_mem(struct addr_size* free, size_t size)
926+
{
927+
struct addr_size ret = {.addr = NULL, .size = 0};
928+
929+
if (free->size < size)
930+
return ret;
931+
ret.addr = free->addr;
932+
ret.size = size;
933+
free->addr = (void*)((char*)free->addr + size);
934+
free->size -= size;
935+
return ret;
936+
}
937+
938+
// Call KVM_SET_USER_MEMORY_REGION for the given pages.
939+
static void vm_set_user_memory_region(int vmfd, uint32 slot, uint32 flags, uint64 guest_phys_addr, uint64 memory_size, uint64 userspace_addr)
940+
{
941+
struct kvm_userspace_memory_region memreg;
942+
memreg.slot = slot;
943+
memreg.flags = flags;
944+
memreg.guest_phys_addr = guest_phys_addr;
945+
memreg.memory_size = memory_size;
946+
memreg.userspace_addr = userspace_addr;
947+
ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg);
948+
}
949+
950+
static void install_syzos_code(void* host_mem, size_t mem_size)
951+
{
952+
size_t size = (char*)&__stop_guest - (char*)&__start_guest;
953+
if (size > mem_size)
954+
fail("SyzOS size exceeds guest memory");
955+
memcpy(host_mem, &__start_guest, size);
956+
}
957+
958+
static void setup_vm(int vmfd, void* host_mem, void** text_slot)
959+
{
960+
// Guest virtual memory layout (must be in sync with executor/kvm.h):
961+
// 0x00000000 - AMD64 data structures (10 pages, see kvm.h)
962+
// 0x00030000 - SMRAM (10 pages)
963+
// 0x00040000 - unmapped region to trigger a page faults for uexits etc. (1 page)
964+
// 0x00041000 - writable region with KVM_MEM_LOG_DIRTY_PAGES to fuzz dirty ring (2 pages)
965+
// 0x00050000 - user code (4 pages)
966+
// 0x00054000 - executor guest code (4 pages)
967+
// 0000058000 - scratch memory for code generated at runtime (1 page)
968+
// 0xfec00000 - IOAPIC (1 page)
969+
struct addr_size allocator = {.addr = host_mem, .size = KVM_GUEST_MEM_SIZE};
970+
int slot = 0; // Slot numbers do not matter, they just have to be different.
971+
972+
// This *needs* to be the first allocation to avoid passing pointers
973+
// around for the gdt/ldt/page table setup.
974+
struct addr_size next = alloc_guest_mem(&allocator, 10 * KVM_PAGE_SIZE);
975+
vm_set_user_memory_region(vmfd, slot++, 0, 0, next.size, (uintptr_t)next.addr);
976+
977+
next = alloc_guest_mem(&allocator, 10 * KVM_PAGE_SIZE);
978+
vm_set_user_memory_region(vmfd, slot++, 0, X86_ADDR_SMRAM, next.size, (uintptr_t)next.addr);
979+
980+
next = alloc_guest_mem(&allocator, 2 * KVM_PAGE_SIZE);
981+
vm_set_user_memory_region(vmfd, slot++, KVM_MEM_LOG_DIRTY_PAGES, X86_ADDR_DIRTY_PAGES, next.size, (uintptr_t)next.addr);
982+
983+
next = alloc_guest_mem(&allocator, KVM_MAX_VCPU * KVM_PAGE_SIZE);
984+
vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, X86_ADDR_USER_CODE, next.size, (uintptr_t)next.addr);
985+
if (text_slot)
986+
*text_slot = next.addr;
987+
988+
struct addr_size host_text = alloc_guest_mem(&allocator, 4 * KVM_PAGE_SIZE);
989+
install_syzos_code(host_text.addr, host_text.size);
990+
vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, X86_ADDR_EXECUTOR_CODE, host_text.size, (uintptr_t)host_text.addr);
991+
992+
next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE);
993+
vm_set_user_memory_region(vmfd, slot++, 0, X86_ADDR_SCRATCH_CODE, next.size, (uintptr_t)next.addr);
994+
995+
next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE);
996+
vm_set_user_memory_region(vmfd, slot++, 0, X86_ADDR_IOAPIC, next.size, (uintptr_t)next.addr);
997+
998+
// Map the remaining pages at an unused address.
999+
next = alloc_guest_mem(&allocator, allocator.size);
1000+
vm_set_user_memory_region(vmfd, slot++, 0, X86_ADDR_UNUSED, next.size, (uintptr_t)next.addr);
1001+
}
1002+
#endif
1003+
1004+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm || __NR_syz_kvm_add_vcpu_amd64
1005+
struct kvm_syz_vm {
1006+
int vmfd;
1007+
int next_cpu_id;
1008+
void* user_text;
1009+
void* host_mem;
1010+
};
1011+
#endif
1012+
7671013
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm
7681014
static long syz_kvm_setup_syzos_vm(volatile long a0, volatile long a1)
7691015
{
770-
// Placeholder.
771-
return 0;
1016+
const int vmfd = a0;
1017+
void* host_mem = (void*)a1;
1018+
1019+
void* user_text_slot = NULL;
1020+
struct kvm_syz_vm* ret = (struct kvm_syz_vm*)host_mem;
1021+
host_mem = (void*)((uint64)host_mem + KVM_PAGE_SIZE);
1022+
setup_vm(vmfd, host_mem, &user_text_slot);
1023+
ret->vmfd = vmfd;
1024+
ret->next_cpu_id = 0;
1025+
ret->user_text = user_text_slot;
1026+
ret->host_mem = host_mem;
1027+
return (long)ret;
7721028
}
7731029
#endif
7741030

7751031
#if SYZ_EXECUTOR || __NR_syz_kvm_add_vcpu
7761032
static long syz_kvm_add_vcpu(volatile long a0, volatile long a1)
7771033
{
778-
// Placeholder.
779-
return 0;
1034+
struct kvm_syz_vm* vm = (struct kvm_syz_vm*)a0;
1035+
struct kvm_text* utext = (struct kvm_text*)a1;
1036+
const void* text = utext->text;
1037+
size_t text_size = utext->size;
1038+
1039+
if (!vm) {
1040+
errno = EINVAL;
1041+
return -1;
1042+
}
1043+
if (vm->next_cpu_id == KVM_MAX_VCPU) {
1044+
errno = ENOMEM;
1045+
return -1;
1046+
}
1047+
int cpu_id = vm->next_cpu_id;
1048+
int cpufd = ioctl(vm->vmfd, KVM_CREATE_VCPU, cpu_id);
1049+
if (cpufd == -1)
1050+
return -1;
1051+
// Only increment next_cpu_id if CPU creation succeeded.
1052+
vm->next_cpu_id++;
1053+
install_user_code(cpufd, vm->user_text, cpu_id, text, text_size, vm->host_mem);
1054+
return cpufd;
7801055
}
7811056
#endif

0 commit comments

Comments
 (0)