Skip to content

Commit 1052267

Browse files
committed
executor, sys/linux, pkg: enable syzos for riscv64
This patch enables syzos for riscv64 and implements the corresponding pseudo syscalls. Pseudo syscalls: - syz_kvm_setup_syzos_vm - syz_kvm_add_vcpu - syz_kvm_assert_syzos_uexit Syzos guest support: - guest_uexit - guest_execute_code - guest_handle_csrr and guest_handle_csrw Test seeds: - riscv64-syz_kvm_setup_syzos_vm - riscv64-syz_kvm_setup_syzos_vm-csrr - riscv64-syz_kvm_setup_syzos_vm-csrw
1 parent a16aed1 commit 1052267

File tree

9 files changed

+609
-12
lines changed

9 files changed

+609
-12
lines changed

executor/common_kvm_riscv64.h

Lines changed: 291 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88

99
// Implementation of syz_kvm_setup_cpu pseudo-syscall.
1010

11+
#include "common_kvm.h"
12+
#include "kvm.h"
1113
#include <stdint.h>
1214
#include <string.h>
1315
#include <sys/ioctl.h>
1416

15-
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu
16-
struct kvm_text {
17-
uintptr_t type;
18-
const void* text;
19-
uintptr_t size;
20-
};
17+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm || __NR_syz_kvm_add_vcpu
18+
#include "common_kvm_riscv64_syzos.h"
19+
#endif
2120

21+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu
2222
// Construct RISC-V register id for KVM.
2323
#define RISCV_CORE_REG(idx) (KVM_REG_RISCV | KVM_REG_SIZE_U64 | KVM_REG_RISCV_CORE | (idx))
2424
#define RISCV_CSR_REG(idx) (KVM_REG_RISCV | KVM_REG_SIZE_U64 | KVM_REG_RISCV_CSR | (idx))
@@ -83,9 +83,6 @@ enum riscv_core_index {
8383
// Indicate the Supervisor Interrupt Enable state.
8484
#define SSTATUS_SIE (1UL << 1)
8585

86-
// Define the starting physical address for the guest code.
87-
#define CODE_START 0x80000000ULL
88-
8986
// Set a single register value for the specified CPU file descriptor.
9087
static inline int kvm_set_reg(int cpufd, unsigned long id, unsigned long value)
9188
{
@@ -96,6 +93,59 @@ static inline int kvm_set_reg(int cpufd, unsigned long id, unsigned long value)
9693
return ioctl(cpufd, KVM_SET_ONE_REG, &reg);
9794
}
9895

96+
struct kvm_text {
97+
uintptr_t type;
98+
const void* text;
99+
uintptr_t size;
100+
};
101+
#endif
102+
103+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_setup_syzos_vm || __NR_syz_kvm_add_vcpu
104+
#define KVM_RISCV_SELFTESTS_SBI_EXT 0x08FFFFFF
105+
#define KVM_RISCV_SELFTESTS_SBI_UNEXP 1
106+
107+
struct sbiret {
108+
long error;
109+
long value;
110+
};
111+
112+
struct sbiret sbi_ecall(unsigned long arg0, unsigned long arg1,
113+
unsigned long arg2, unsigned long arg3,
114+
unsigned long arg4, unsigned long arg5,
115+
int fid, int ext)
116+
{
117+
struct sbiret ret;
118+
119+
register uintptr_t a0 asm("a0") = (uintptr_t)(arg0);
120+
register uintptr_t a1 asm("a1") = (uintptr_t)(arg1);
121+
register uintptr_t a2 asm("a2") = (uintptr_t)(arg2);
122+
register uintptr_t a3 asm("a3") = (uintptr_t)(arg3);
123+
register uintptr_t a4 asm("a4") = (uintptr_t)(arg4);
124+
register uintptr_t a5 asm("a5") = (uintptr_t)(arg5);
125+
register uintptr_t a6 asm("a6") = (uintptr_t)(fid);
126+
register uintptr_t a7 asm("a7") = (uintptr_t)(ext);
127+
asm volatile("ecall"
128+
: "+r"(a0), "+r"(a1)
129+
: "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7)
130+
: "memory");
131+
ret.error = a0;
132+
ret.value = a1;
133+
134+
return ret;
135+
}
136+
137+
static void __attribute((__aligned__(16))) guest_unexp_trap(void)
138+
{
139+
sbi_ecall(0, 0, 0, 0, 0, 0,
140+
KVM_RISCV_SELFTESTS_SBI_UNEXP,
141+
KVM_RISCV_SELFTESTS_SBI_EXT);
142+
}
143+
#endif
144+
145+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu
146+
// Define the starting physical address for the guest code.
147+
#define CODE_START 0x80000000ULL
148+
99149
// syz_kvm_setup_cpu$riscv64(fd fd_kvmvm, cpufd fd_kvmcpu, usermem vma[24], text ptr[in, array[kvm_text_riscv64, 1]], ntext len[text], flags const[0], opts ptr[in, array[kvm_setup_opt_riscv64, 1]], nopt len[opts])
100150
static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volatile long a2, volatile long a3, volatile long a4, volatile long a5, volatile long a6, volatile long a7)
101151
{
@@ -131,6 +181,7 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat
131181
if (size > guest_mem_size)
132182
size = guest_mem_size;
133183
memcpy(host_mem, text, size);
184+
memcpy(host_mem + page_size, (void*)guest_unexp_trap, KVM_PAGE_SIZE);
134185

135186
// Initialize VCPU registers.
136187
// Set PC (program counter) to start of code.
@@ -151,6 +202,14 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat
151202
unsigned long stvec = CODE_START + page_size;
152203
if (kvm_set_reg(cpufd, RISCV_CSR_REG(CSR_STVEC), stvec))
153204
return -1;
205+
// Set GP.
206+
unsigned long current_gp = 0;
207+
asm volatile("add %0, gp, zero"
208+
: "=r"(current_gp)
209+
:
210+
: "memory");
211+
if (kvm_set_reg(cpufd, RISCV_CORE_REG(CORE_GP), current_gp))
212+
return -1;
154213

155214
return 0;
156215
}
@@ -175,4 +234,226 @@ static long syz_kvm_assert_reg(volatile long a0, volatile long a1, volatile long
175234
}
176235
#endif
177236

178-
#endif // EXECUTOR_COMMON_KVM_RISCV64_H
237+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm || __NR_syz_kvm_add_vcpu
238+
struct kvm_syz_vm {
239+
int vmfd;
240+
int next_cpu_id;
241+
void* user_text;
242+
};
243+
#endif
244+
245+
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm
246+
struct addr_size {
247+
void* addr;
248+
size_t size;
249+
};
250+
251+
static struct addr_size alloc_guest_mem(struct addr_size* free, size_t size)
252+
{
253+
struct addr_size ret = {.addr = NULL, .size = 0};
254+
255+
if (free->size < size)
256+
return ret;
257+
ret.addr = free->addr;
258+
ret.size = size;
259+
free->addr = (void*)((char*)free->addr + size);
260+
free->size -= size;
261+
return ret;
262+
}
263+
264+
// Call KVM_SET_USER_MEMORY_REGION for the given pages.
265+
static void vm_set_user_memory_region(int vmfd, uint32 slot, uint32 flags, uint64 guest_phys_addr, uint64 memory_size, uint64 userspace_addr)
266+
{
267+
struct kvm_userspace_memory_region memreg;
268+
memreg.slot = slot;
269+
memreg.flags = flags;
270+
memreg.guest_phys_addr = guest_phys_addr;
271+
memreg.memory_size = memory_size;
272+
memreg.userspace_addr = userspace_addr;
273+
ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg);
274+
}
275+
276+
#define AUIPC_OPCODE 0x17
277+
#define AUIPC_OPCODE_MASK 0x7f
278+
279+
// Code loading SyzOS into guest memory does not handle data relocations (see
280+
// https://github.com/google/syzkaller/issues/5565), so SyzOS will crash soon after encountering an
281+
// AUIPC instruction. Detect these instructions to catch regressions early.
282+
// The most common reason for using data relocaions is accessing global variables and constants.
283+
// Sometimes the compiler may choose to emit a read-only constant to zero-initialize a structure
284+
// or to generate a jump table for a switch statement.
285+
static void validate_guest_code(void* mem, size_t size)
286+
{
287+
uint32* insns = (uint32*)mem;
288+
for (size_t i = 0; i < size / 4; i++) {
289+
if ((insns[i] & AUIPC_OPCODE_MASK) == AUIPC_OPCODE)
290+
fail("AUIPC instruction detected in SyzOS, exiting");
291+
}
292+
}
293+
294+
static void install_syzos_code(void* host_mem, size_t mem_size)
295+
{
296+
size_t size = (char*)&__stop_guest - (char*)&__start_guest;
297+
if (size > mem_size)
298+
fail("SyzOS size exceeds guest memory");
299+
memcpy(host_mem, &__start_guest, size);
300+
validate_guest_code(host_mem, size);
301+
}
302+
303+
static void setup_vm(int vmfd, void* host_mem, void** text_slot)
304+
{
305+
// Guest physical memory layout (must be in sync with executor/kvm.h):
306+
// 0x00000000 - unused pages
307+
// 0x00001000 - exception vector table
308+
// 0x02000000 - CLINT (MMIO, no memory allocated)
309+
// 0x0c000000 - PLIC (MMIO, no memory allocated)
310+
// 0x40000000 - unmapped region to trigger a page faults for uexits etc. (1 page)
311+
// 0x40001000 - writable region with KVM_MEM_LOG_DIRTY_PAGES to fuzz dirty ring (2 pages)
312+
// 0x80000000 - user code (4 pages)
313+
// 0x80008000 - executor guest code (4 pages)
314+
// 0x80010000 - scratch memory for code generated at runtime (1 page)
315+
// 0x80020000 - per-vCPU stacks (1 page each)
316+
struct addr_size allocator = {.addr = host_mem, .size = KVM_GUEST_MEM_SIZE};
317+
int slot = 0; // Slot numbers do not matter, they just have to be different.
318+
// Setup executor guest code.
319+
struct addr_size host_text = alloc_guest_mem(&allocator, 4 * KVM_PAGE_SIZE);
320+
install_syzos_code(host_text.addr, host_text.size);
321+
vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, SYZOS_ADDR_EXECUTOR_CODE, host_text.size, (uintptr_t)host_text.addr);
322+
// Setup writable region with KVM_MEM_LOG_DIRTY_PAGES to fuzz dirty ring.
323+
struct addr_size next = alloc_guest_mem(&allocator, 2 * KVM_PAGE_SIZE);
324+
vm_set_user_memory_region(vmfd, slot++, KVM_MEM_LOG_DIRTY_PAGES, RISCV64_ADDR_DIRTY_PAGES, next.size, (uintptr_t)next.addr);
325+
// Setup user code.
326+
next = alloc_guest_mem(&allocator, KVM_MAX_VCPU * KVM_PAGE_SIZE);
327+
vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, RISCV64_ADDR_USER_CODE, next.size, (uintptr_t)next.addr);
328+
if (text_slot)
329+
*text_slot = next.addr;
330+
// Setup per-vCPU stacks.
331+
next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE);
332+
vm_set_user_memory_region(vmfd, slot++, 0, RISCV64_ADDR_STACK_BASE, next.size, (uintptr_t)next.addr);
333+
// Setup scratch memory for code generated at runtime.
334+
next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE);
335+
vm_set_user_memory_region(vmfd, slot++, 0, RISCV64_ADDR_SCRATCH_CODE, next.size, (uintptr_t)next.addr);
336+
// Setup exception vector table.
337+
next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE);
338+
memcpy(next.addr, (void*)guest_unexp_trap, KVM_PAGE_SIZE);
339+
vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, RISCV64_ADDR_EXCEPTION_VECTOR, next.size, (uintptr_t)next.addr);
340+
// Setup remaining RAM.
341+
next = alloc_guest_mem(&allocator, allocator.size);
342+
vm_set_user_memory_region(vmfd, slot++, 0, 0, next.size, (uintptr_t)next.addr);
343+
}
344+
345+
static long syz_kvm_setup_syzos_vm(volatile long a0, volatile long a1)
346+
{
347+
const int vmfd = a0;
348+
void* host_mem = (void*)a1;
349+
350+
void* user_text_slot = NULL;
351+
struct kvm_syz_vm* ret = (struct kvm_syz_vm*)host_mem;
352+
host_mem = (void*)((uint64)host_mem + KVM_PAGE_SIZE);
353+
setup_vm(vmfd, host_mem, &user_text_slot);
354+
ret->vmfd = vmfd;
355+
ret->next_cpu_id = 0;
356+
ret->user_text = user_text_slot;
357+
return (long)ret;
358+
}
359+
#endif
360+
361+
#if SYZ_EXECUTOR || __NR_syz_kvm_add_vcpu
362+
// Set up CPU registers.
363+
static void reset_cpu_regs(int cpufd, int cpu_id, size_t text_size)
364+
{
365+
// PC points to the relative offset of guest_main() within the guest code.
366+
kvm_set_reg(cpufd, RISCV_CORE_REG(CORE_PC), executor_fn_guest_addr(guest_main));
367+
kvm_set_reg(cpufd, RISCV_CORE_REG(CORE_SP), RISCV64_ADDR_STACK_BASE + KVM_PAGE_SIZE - 128);
368+
kvm_set_reg(cpufd, RISCV_CORE_REG(CORE_TP), cpu_id);
369+
// Pass parameters to guest_main().
370+
kvm_set_reg(cpufd, RISCV_CORE_REG(CORE_A0), text_size);
371+
kvm_set_reg(cpufd, RISCV_CORE_REG(CORE_A1), cpu_id);
372+
// Set SSTATUS and MODE.
373+
kvm_set_reg(cpufd, RISCV_CORE_REG(CORE_MODE), 1);
374+
kvm_set_reg(cpufd, RISCV_CSR_REG(CSR_SSTATUS), SSTATUS_SPP | SSTATUS_SPIE);
375+
// Set GP.
376+
unsigned long current_gp = 0;
377+
asm volatile("add %0, gp, zero"
378+
: "=r"(current_gp)
379+
:
380+
: "memory");
381+
kvm_set_reg(cpufd, RISCV_CORE_REG(CORE_GP), current_gp);
382+
// Set STVEC.
383+
kvm_set_reg(cpufd, RISCV_CSR_REG(CSR_STVEC), RISCV64_ADDR_EXCEPTION_VECTOR);
384+
}
385+
386+
static void install_user_code(int cpufd, void* user_text_slot, int cpu_id, const void* text, size_t text_size)
387+
{
388+
if ((cpu_id < 0) || (cpu_id >= KVM_MAX_VCPU))
389+
return;
390+
if (!user_text_slot)
391+
return;
392+
if (text_size > KVM_PAGE_SIZE)
393+
text_size = KVM_PAGE_SIZE;
394+
void* target = (void*)((uint64)user_text_slot + (KVM_PAGE_SIZE * cpu_id));
395+
memcpy(target, text, text_size);
396+
reset_cpu_regs(cpufd, cpu_id, text_size);
397+
}
398+
399+
static long syz_kvm_add_vcpu(volatile long a0, volatile long a1, volatile long a2, volatile long a3)
400+
{
401+
struct kvm_syz_vm* vm = (struct kvm_syz_vm*)a0;
402+
struct kvm_text* utext = (struct kvm_text*)a1;
403+
const void* text = utext->text;
404+
size_t text_size = utext->size;
405+
406+
if (!vm) {
407+
errno = EINVAL;
408+
return -1;
409+
}
410+
if (vm->next_cpu_id == KVM_MAX_VCPU) {
411+
errno = ENOMEM;
412+
return -1;
413+
}
414+
int cpu_id = vm->next_cpu_id;
415+
int cpufd = ioctl(vm->vmfd, KVM_CREATE_VCPU, cpu_id);
416+
if (cpufd == -1)
417+
return -1;
418+
// Only increment next_cpu_id if CPU creation succeeded.
419+
vm->next_cpu_id++;
420+
install_user_code(cpufd, vm->user_text, cpu_id, text, text_size);
421+
return cpufd;
422+
}
423+
#endif
424+
425+
#if SYZ_EXECUTOR || __NR_syz_kvm_assert_syzos_uexit
426+
static long syz_kvm_assert_syzos_uexit(volatile long a0, volatile long a1,
427+
volatile long a2)
428+
{
429+
#if !SYZ_EXECUTOR
430+
int cpufd = (int)a0;
431+
#endif
432+
struct kvm_run* run = (struct kvm_run*)a1;
433+
uint64 expect = a2;
434+
435+
if (!run || (run->exit_reason != KVM_EXIT_MMIO) ||
436+
(run->mmio.phys_addr != RISCV64_ADDR_UEXIT)) {
437+
#if !SYZ_EXECUTOR
438+
fprintf(stderr, "[SYZOS-DEBUG] Assertion Triggered on VCPU %d\n", cpufd);
439+
#endif
440+
errno = EINVAL;
441+
return -1;
442+
}
443+
444+
uint64_t actual_code = ((uint64_t*)(run->mmio.data))[0];
445+
if (actual_code != expect) {
446+
#if !SYZ_EXECUTOR
447+
fprintf(stderr, "[SYZOS-DEBUG] Exit Code Mismatch on VCPU %d\n", cpufd);
448+
fprintf(stderr, " Expected: 0x%lx\n", (unsigned long)expect);
449+
fprintf(stderr, " Actual: 0x%lx\n",
450+
(unsigned long)actual_code);
451+
#endif
452+
errno = EDOM;
453+
return -1;
454+
}
455+
return 0;
456+
}
457+
#endif
458+
459+
#endif // EXECUTOR_COMMON_KVM_RISCV64_H

0 commit comments

Comments
 (0)