aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Potapenko <glider@google.com>2024-07-23 14:17:26 +0200
committerAlexander Potapenko <glider@google.com>2024-07-29 15:29:47 +0000
commit2fb4dcc9c10e100beedbbc223c2a9762bc45403e (patch)
tree7cae62f7ec97510df2f58be325ce630de2206c56
parenta22b1135716d02277936c6f48acb1086b3f9a362 (diff)
executor: arm64: sys/linux: introduce syzos API
Allow guest payload to call syzos API functions. The available calls are enumerated by SYZOS_API_* constants, and have a form of: struct api_call { uint64 call; uint64 struct_size; /* arbitrary call-related data here */ }; Complex instruction sequences are too easy to break, so most of the time fuzzer won't be able to efficiently mutate them. We replace kvm_text_arm64 with a sequence of `struct api_call`, making it possible to intermix assembly instructions (SYZOS_API_CODE) with higher-level constructs. Right now the supported calls are: - SYZOS_API_UEXIT - abort from KVM_RUN (1 argument: exit code, uint64) - SYZOS_API_CODE - execute an ARM64 assembly blob (1 argument: inline array of int32's)
-rw-r--r--executor/common_kvm_arm64.h18
-rw-r--r--executor/common_kvm_arm64_syzos.h66
-rw-r--r--executor/kvm.h4
-rw-r--r--sys/linux/dev_kvm.txt24
4 files changed, 98 insertions, 14 deletions
diff --git a/executor/common_kvm_arm64.h b/executor/common_kvm_arm64.h
index ba02e244b..e6bb2b665 100644
--- a/executor/common_kvm_arm64.h
+++ b/executor/common_kvm_arm64.h
@@ -9,6 +9,7 @@
#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
@@ -60,13 +61,10 @@ static struct addr_size alloc_guest_mem(struct addr_size* free, size_t size)
return ret;
}
-static void fill_with_ret(void* addr, int size)
-{
- uint32* insn = (uint32*)addr;
-
- for (int i = 0; i < size / 4; i++)
- insn[i] = 0xd65f03c0; // 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)
@@ -108,6 +106,7 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat
// 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)
@@ -119,12 +118,11 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat
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);
- // Fill the guest code page with RET instructions to be on the safe side.
- fill_with_ret(next.addr, next.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);
@@ -143,6 +141,8 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat
// 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;
}
diff --git a/executor/common_kvm_arm64_syzos.h b/executor/common_kvm_arm64_syzos.h
index b9edf0069..022378b34 100644
--- a/executor/common_kvm_arm64_syzos.h
+++ b/executor/common_kvm_arm64_syzos.h
@@ -11,10 +11,70 @@
// Start/end of the guest section.
extern char *__start_guest, *__stop_guest;
+typedef enum {
+ SYZOS_API_UEXIT,
+ SYZOS_API_CODE,
+ SYZOS_API_STOP, // Must be the last one
+} syzos_api_id;
+
+struct api_call_header {
+ uint64 call;
+ uint64 size;
+};
+
+struct api_call_uexit {
+ struct api_call_header header;
+ uint64 exit_code;
+};
+
+struct api_call_code {
+ struct api_call_header header;
+ uint32 insns[];
+};
+
+void guest_uexit(uint64 exit_code);
+void guest_execute_code(uint32* insns, uint64 size);
+
// Main guest function that performs necessary setup and passes the control to the user-provided
// payload.
-GUEST_CODE static void guest_main()
+GUEST_CODE static void guest_main(uint64 size)
+{
+ uint64 addr = ARM64_ADDR_USER_CODE;
+
+ while (size >= sizeof(struct api_call_header)) {
+ struct api_call_header* cmd = (struct api_call_header*)addr;
+ if (cmd->call >= SYZOS_API_STOP)
+ return;
+ if (cmd->size > size)
+ return;
+ switch (cmd->call) {
+ case SYZOS_API_UEXIT: {
+ struct api_call_uexit* ucmd = (struct api_call_uexit*)cmd;
+ guest_uexit(ucmd->exit_code);
+ break;
+ }
+ case SYZOS_API_CODE: {
+ struct api_call_code* ccmd = (struct api_call_code*)cmd;
+ guest_execute_code(ccmd->insns, cmd->size - sizeof(struct api_call_header));
+ break;
+ }
+ }
+ addr += cmd->size;
+ size -= cmd->size;
+ };
+}
+
+GUEST_CODE void guest_execute_code(uint32* insns, uint64 size)
+{
+ volatile void (*fn)() = (volatile void (*)())insns;
+ fn();
+}
+
+// Perform a userspace exit that can be handled by the host.
+// The host returns from ioctl(KVM_RUN) with kvm_run.exit_reason=KVM_EXIT_MMIO,
+// and can handle the call depending on the data passed as exit code.
+GUEST_CODE void guest_uexit(uint64 exit_code)
{
- void (*guest_payload)() = (void (*)())ARM64_ADDR_USER_CODE;
- guest_payload();
+ volatile uint64* ptr = (volatile uint64*)ARM64_ADDR_UEXIT;
+ *ptr = exit_code;
}
diff --git a/executor/kvm.h b/executor/kvm.h
index 49a493818..1afbcd40e 100644
--- a/executor/kvm.h
+++ b/executor/kvm.h
@@ -76,6 +76,10 @@
#define NEXT_INSN $0xbadc0de
#define PREFIX_SIZE 0xba1d
+// Write to this page to trigger a page fault and stop KVM_RUN.
+#define ARM64_ADDR_EXIT 0xdddd0000
+// Dedicated address within the exit page for the uexit command.
+#define ARM64_ADDR_UEXIT (ARM64_ADDR_EXIT + 256)
#define ARM64_ADDR_USER_CODE 0xeeee0000
#define ARM64_ADDR_EXECUTOR_CODE 0xeeee8000
#define ARM64_ADDR_EL1_STACK_BOTTOM 0xffff1000
diff --git a/sys/linux/dev_kvm.txt b/sys/linux/dev_kvm.txt
index bcc4edec8..98554e303 100644
--- a/sys/linux/dev_kvm.txt
+++ b/sys/linux/dev_kvm.txt
@@ -232,12 +232,32 @@ kvm_text_x86_64 {
size len[text, intptr]
}
+# Unlike on other architectures, ARM64 text is a sequence of commands, each starting with
+# the call number and the command length.
kvm_text_arm64 {
typ const[0, intptr]
- text ptr[in, text[arm64]]
- size len[text, intptr]
+ text ptr[in, array[syzos_api_call, 1:32]]
+ size bytesize[text, int64]
+}
+
+syzos_api_uexit {
+ call const[0, int64]
+ size bytesize[parent, int64]
+ exit_code intptr
}
+syzos_api_code {
+ call const[1, int64]
+ size bytesize[parent, int64]
+ insns text[arm64]
+ ret const[0xd65f03c0, int32]
+} [packed]
+
+syzos_api_call [
+ uexit syzos_api_uexit
+ code syzos_api_code
+] [varlen]
+
kvm_text_ppc64 {
typ const[0, intptr]
text ptr[in, text[ppc64]]