1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// This file is shared between executor and csource package.
// Implementation of syz_kvm_setup_cpu pseudo-syscall.
#include "common_kvm_arm64_syzos.h"
#include "kvm.h"
// Register encodings from https://docs.kernel.org/virt/kvm/api.html.
#define KVM_ARM64_REGS_X0 0x6030000000100000UL
#define KVM_ARM64_REGS_PC 0x6030000000100040UL
#define KVM_ARM64_REGS_SP_EL1 0x6030000000100044UL
struct kvm_text {
uintptr_t typ;
const void* text;
uintptr_t size;
};
struct kvm_opt {
uint64 typ;
uint64 val;
};
// Call KVM_SET_USER_MEMORY_REGION for the given pages.
static void vm_set_user_memory_region(int vmfd, uint32 slot, uint32 flags, uint64 guest_phys_addr, uint64 memory_size, uint64 userspace_addr)
{
struct kvm_userspace_memory_region memreg;
memreg.slot = slot;
memreg.flags = flags;
memreg.guest_phys_addr = guest_phys_addr;
memreg.memory_size = memory_size;
memreg.userspace_addr = userspace_addr;
ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg);
}
// Set the value of the specified register.
static void vcpu_set_reg(int vcpu_fd, uint64 id, uint64 val)
{
struct kvm_one_reg reg = {.id = id, .addr = (uint64)&val};
ioctl(vcpu_fd, KVM_SET_ONE_REG, ®);
}
struct addr_size {
void* addr;
size_t size;
};
static struct addr_size alloc_guest_mem(struct addr_size* free, size_t size)
{
struct addr_size ret = {.addr = NULL, .size = 0};
if (free->size < size)
return ret;
ret.addr = free->addr;
ret.size = size;
free->addr = (void*)((char*)free->addr + size);
free->size -= size;
return ret;
}
struct api_fn {
int index;
void* fn;
};
// syz_kvm_setup_cpu(fd fd_kvmvm, cpufd fd_kvmcpu, usermem vma[24], text ptr[in, array[kvm_text, 1]], ntext len[text], flags flags[kvm_setup_flags], opts ptr[in, array[kvm_setup_opt, 0:2]], nopt len[opts])
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)
{
const int vmfd = a0;
const int cpufd = a1;
char* const host_mem = (char*)a2;
const struct kvm_text* const text_array_ptr = (struct kvm_text*)a3;
const uintptr_t text_count = a4;
const uintptr_t flags = a5;
const struct kvm_opt* const opt_array_ptr = (struct kvm_opt*)a6;
uintptr_t opt_count = a7;
(void)flags;
(void)opt_count;
const uintptr_t page_size = 4 << 10;
const uintptr_t guest_mem_size = 24 * page_size;
(void)text_count; // fuzzer can spoof count and we need just 1 text, so ignore text_count
int text_type = text_array_ptr[0].typ;
const void* text = text_array_ptr[0].text;
size_t text_size = text_array_ptr[0].size;
(void)text_type;
(void)opt_array_ptr;
uint32 features = 0;
if (opt_count > 1)
opt_count = 1;
for (uintptr_t i = 0; i < opt_count; i++) {
uint64 typ = opt_array_ptr[i].typ;
uint64 val = opt_array_ptr[i].val;
switch (typ) {
case 1:
features = val;
break;
}
}
// Guest physical memory layout:
// 0x00000000 - unused pages
// 0xdddd0000 - unmapped region to trigger a page faults for uexits etc. (1 page)
// 0xeeee0000 - user code (1 page)
// 0xeeee8000 - executor guest code (4 pages)
// 0xffff1000 - EL1 stack (1 page)
struct addr_size allocator = {.addr = host_mem, .size = guest_mem_size};
int slot = 0; // Slot numbers do not matter, they just have to be different.
struct addr_size host_text = alloc_guest_mem(&allocator, 4 * page_size);
memcpy(host_text.addr, &__start_guest, (char*)&__stop_guest - (char*)&__start_guest);
vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, ARM64_ADDR_EXECUTOR_CODE, host_text.size, (uintptr_t)host_text.addr);
struct addr_size next = alloc_guest_mem(&allocator, page_size);
if (text_size > next.size)
text_size = next.size;
memcpy(next.addr, text, text_size);
vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, ARM64_ADDR_USER_CODE, next.size, (uintptr_t)next.addr);
next = alloc_guest_mem(&allocator, page_size);
vm_set_user_memory_region(vmfd, slot++, 0, ARM64_ADDR_EL1_STACK_BOTTOM, next.size, (uintptr_t)next.addr);
next = alloc_guest_mem(&allocator, page_size);
vm_set_user_memory_region(vmfd, slot++, 0, ARM64_ADDR_SCRATCH_CODE, next.size, (uintptr_t)next.addr);
// Map the remaining pages at address 0.
next = alloc_guest_mem(&allocator, allocator.size);
vm_set_user_memory_region(vmfd, slot++, 0, 0, next.size, (uintptr_t)next.addr);
struct kvm_vcpu_init init;
// Queries KVM for preferred CPU target type.
ioctl(vmfd, KVM_ARM_PREFERRED_TARGET, &init);
init.features[0] = features;
// Use the modified struct kvm_vcpu_init to initialize the virtual CPU.
ioctl(cpufd, KVM_ARM_VCPU_INIT, &init);
// Set up CPU registers.
// PC points to the relative offset of guest_main() within the guest code.
vcpu_set_reg(cpufd, KVM_ARM64_REGS_PC, ARM64_ADDR_EXECUTOR_CODE + ((uint64)guest_main - (uint64)&__start_guest));
vcpu_set_reg(cpufd, KVM_ARM64_REGS_SP_EL1, ARM64_ADDR_EL1_STACK_BOTTOM + page_size - 128);
// Pass parameters to guest_main().
vcpu_set_reg(cpufd, KVM_ARM64_REGS_X0, text_size);
return 0;
}
|