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.
9087static 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+ __attribute__((used )) __attribute((__aligned__ (16 ))) static void 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])
100150static 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