From e5d3015f47ca8f88dfedb85243e8e77957bd51cd Mon Sep 17 00:00:00 2001 From: Patrick Meyer Date: Thu, 6 May 2021 23:08:51 +0200 Subject: executor: initial darwin support --- executor/common.h | 17 +++++-- executor/common_bsd.h | 26 +++++----- executor/executor.cc | 23 +++++++-- executor/executor_darwin.h | 123 +++++++++++++++++++++++++++++++++++++++++++++ executor/gen.go | 2 +- pkg/csource/generated.go | 37 ++++++++------ sys/targets/targets.go | 17 ++++--- 7 files changed, 202 insertions(+), 43 deletions(-) create mode 100644 executor/executor_darwin.h diff --git a/executor/common.h b/executor/common.h index b5a1ba593..c8bfccb9b 100644 --- a/executor/common.h +++ b/executor/common.h @@ -17,6 +17,13 @@ #if GOOS_freebsd || GOOS_test && HOSTGOOS_freebsd #include // for htobe*. +#elif GOOS_darwin +#include +#define htobe16(x) OSSwapHostToBigInt16(x) +#define htobe32(x) OSSwapHostToBigInt32(x) +#define htobe64(x) OSSwapHostToBigInt64(x) +#define le16toh(x) OSSwapLittleToHostInt16(x) +#define htole16(x) OSSwapHostToLittleInt16(x) #elif GOOS_windows #define htobe16 _byteswap_ushort #define htobe32 _byteswap_ulong @@ -51,7 +58,7 @@ NORETURN void doexit(int status) #if SYZ_EXECUTOR || SYZ_MULTI_PROC || SYZ_REPEAT && SYZ_CGROUPS || \ SYZ_NET_DEVICES || __NR_syz_mount_image || __NR_syz_read_part_table || \ __NR_syz_usb_connect || __NR_syz_usb_connect_ath9k || \ - (GOOS_freebsd || GOOS_openbsd || GOOS_netbsd) && SYZ_NET_INJECTION + (GOOS_freebsd || GOOS_darwin || GOOS_openbsd || GOOS_netbsd) && SYZ_NET_INJECTION static unsigned long long procid; #endif @@ -208,7 +215,7 @@ static void use_temporary_dir(void) #endif #endif -#if GOOS_akaros || GOOS_netbsd || GOOS_freebsd || GOOS_openbsd || GOOS_test +#if GOOS_akaros || GOOS_netbsd || GOOS_freebsd || GOOS_darwin || GOOS_openbsd || GOOS_test #if (SYZ_EXECUTOR || SYZ_REPEAT) && SYZ_EXECUTOR_USES_FORK_SERVER && (SYZ_EXECUTOR || SYZ_USE_TMP_DIR) #include #include @@ -311,7 +318,7 @@ static void thread_start(void* (*fn)(void*), void* arg) #endif #endif -#if GOOS_freebsd || GOOS_netbsd || GOOS_openbsd || GOOS_akaros || GOOS_test +#if GOOS_freebsd || GOOS_darwin || GOOS_netbsd || GOOS_openbsd || GOOS_akaros || GOOS_test #if SYZ_EXECUTOR || SYZ_THREADED #include @@ -428,7 +435,7 @@ static uint16 csum_inet_digest(struct csum_inet* csum) #if GOOS_akaros #include "common_akaros.h" -#elif GOOS_freebsd || GOOS_netbsd || GOOS_openbsd +#elif GOOS_freebsd || GOOS_darwin || GOOS_netbsd || GOOS_openbsd #include "common_bsd.h" #elif GOOS_fuchsia #include "common_fuchsia.h" @@ -442,7 +449,7 @@ static uint16 csum_inet_digest(struct csum_inet* csum) #error "unknown OS" #endif -#if SYZ_EXECUTOR || __NR_syz_execute_func +#if !GOOS_darwin && SYZ_EXECUTOR || __NR_syz_execute_func // syz_execute_func(text ptr[in, text[taget]]) static long syz_execute_func(volatile long text) { diff --git a/executor/common_bsd.h b/executor/common_bsd.h index 1bed0cf3c..398d402fb 100644 --- a/executor/common_bsd.h +++ b/executor/common_bsd.h @@ -89,12 +89,11 @@ static int fault_injected(int fd) #endif -#if GOOS_openbsd - +#if GOOS_openbsd || GOOS_darwin #define __syscall syscall +#endif // GOOS_openbsd || GOOS_darwin -#if SYZ_EXECUTOR || __NR_syz_open_pts - +#if GOOS_openbsd && (SYZ_EXECUTOR || __NR_syz_open_pts) #include #include @@ -110,15 +109,18 @@ static uintptr_t syz_open_pts(void) close(master); return slave; } - -#endif // SYZ_EXECUTOR || __NR_syz_open_pts - -#endif // GOOS_openbsd +#endif // GOOS_openbsd && (SYZ_EXECUTOR || __NR_syz_open_pts) #if SYZ_EXECUTOR || SYZ_NET_INJECTION #include +// FIXME(HerrSpace): XNU has xnu/bsd/netinet/if_tun.h, but it's not shipped in +// the installed header files (probably internal). It's also likely not very +// interesting, as XNU only ships a tun driver called utun, not tap which is +// currently required for SYZ_NET_INJECTION anyhow. +#if !GOOS_darwin #include +#endif // !GOOS_darwin #include static int tunfd = -1; @@ -269,7 +271,7 @@ static void initialize_tun(int tun_id) #endif // SYZ_EXECUTOR || SYZ_NET_INJECTION -#if SYZ_EXECUTOR || __NR_syz_emit_ethernet && SYZ_NET_INJECTION +#if !GOOS_darwin && SYZ_EXECUTOR || __NR_syz_emit_ethernet && SYZ_NET_INJECTION #include #include @@ -287,7 +289,7 @@ static long syz_emit_ethernet(volatile long a0, volatile long a1) } #endif -#if SYZ_EXECUTOR || SYZ_NET_INJECTION && (__NR_syz_extract_tcp_res || SYZ_REPEAT) +#if !GOOS_darwin && SYZ_EXECUTOR || SYZ_NET_INJECTION && (__NR_syz_extract_tcp_res || SYZ_REPEAT) #include static int read_tun(char* data, int size) @@ -305,14 +307,14 @@ static int read_tun(char* data, int size) } #endif -#if SYZ_EXECUTOR || __NR_syz_extract_tcp_res && SYZ_NET_INJECTION +#if !GOOS_darwin && SYZ_EXECUTOR || __NR_syz_extract_tcp_res && SYZ_NET_INJECTION struct tcp_resources { uint32 seq; uint32 ack; }; -#if GOOS_freebsd +#if GOOS_freebsd || GOOS_darwin #include #else #include diff --git a/executor/executor.cc b/executor/executor.cc index 161616be4..254a5d74a 100644 --- a/executor/executor.cc +++ b/executor/executor.cc @@ -213,6 +213,19 @@ struct cover_t { uint32 size; char* data; char* data_end; + // Note: On everything but darwin the first value in data is the count of + // recorded PCs, followed by the PCs. We therefore set data_offset to the + // size of one PC. + // On darwin data points to an instance of the ksancov_trace struct. Here we + // set data_offset to the offset between data and the structs 'pcs' member, + // which contains the PCs. + intptr_t data_offset; + // Note: On everything but darwin this is 0, as the PCs contained in data + // are already correct. XNUs KSANCOV API, however, chose to always squeeze + // PCs into 32 bit. To make the recorded PC fit, KSANCOV substracts a fixed + // offset (VM_MIN_KERNEL_ADDRESS for AMD64) and then truncates the result to + // uint32_t. We get this from the 'offset' member in ksancov_trace. + intptr_t pc_offset; }; struct thread_t { @@ -356,6 +369,8 @@ static void setup_features(char** enable, int n); #include "executor_akaros.h" #elif GOOS_freebsd || GOOS_netbsd || GOOS_openbsd #include "executor_bsd.h" +#elif GOOS_darwin +#include "executor_darwin.h" #elif GOOS_windows #include "executor_windows.h" #elif GOOS_test @@ -895,12 +910,12 @@ void write_coverage_signal(cover_t* cov, uint32* signal_count_pos, uint32* cover { // Write out feedback signals. // Currently it is code edges computed as xor of two subsequent basic block PCs. - cover_data_t* cover_data = ((cover_data_t*)cov->data) + 1; + cover_data_t* cover_data = (cover_data_t*)(cov->data + cov->data_offset); uint32 nsig = 0; cover_data_t prev_pc = 0; bool prev_filter = true; for (uint32 i = 0; i < cov->size; i++) { - cover_data_t pc = cover_data[i]; + cover_data_t pc = cover_data[i] + cov->pc_offset; uint32 sig = pc; if (use_cover_edges(pc)) sig ^= hash(prev_pc); @@ -932,7 +947,7 @@ void write_coverage_signal(cover_t* cov, uint32* signal_count_pos, uint32* cover // Truncate PCs to uint32 assuming that they fit into 32-bits. // True for x86_64 and arm64 without KASLR. for (uint32 i = 0; i < cover_size; i++) - write_output(cover_data[i]); + write_output(cover_data[i] + cov->pc_offset); *cover_count_pos = cover_size; } #endif @@ -1098,6 +1113,8 @@ void thread_create(thread_t* th, int id) th->created = true; th->id = id; th->executing = false; + th->cov.data_offset = is_kernel_64_bit ? sizeof(uint64_t) : sizeof(uint32_t); + th->cov.pc_offset = 0; event_init(&th->ready); event_init(&th->done); event_set(&th->done); diff --git a/executor/executor_darwin.h b/executor/executor_darwin.h new file mode 100644 index 000000000..c16691663 --- /dev/null +++ b/executor/executor_darwin.h @@ -0,0 +1,123 @@ +// Copyright 2021 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. + +#include +#include +#include + +// FIXME(HerrSpace): As executor is written in C++, we need to make this patch: +// -struct ksancov_trace *trace = (void *)mc.ptr; into +// +struct ksancov_trace *trace = (ksancov_trace *)mc.ptr; +// twice to make this header compile. This used to be C++ friendly in Catalina, +// but was broken in xnu source drop 7195.50.7.100.1. +#include + +static void os_init(int argc, char** argv, void* data, size_t data_size) +{ + // Note: We use is_kernel_64_bit in executor.cc to decide which PC pointer + // size to expect. However in KSANCOV we always get back 32bit pointers, + // which then get reconstructed to 64bit pointers by adding a fixed offset. + is_kernel_64_bit = false; + + int prot = PROT_READ | PROT_WRITE | PROT_EXEC; + int flags = MAP_ANON | MAP_PRIVATE | MAP_FIXED; + + if (mmap(data, data_size, prot, flags, -1, 0) != data) + fail("mmap of data segment failed"); + + // Makes sure the file descriptor limit is sufficient to map control pipes. + struct rlimit rlim; + rlim.rlim_cur = rlim.rlim_max = kMaxFd; + setrlimit(RLIMIT_NOFILE, &rlim); +} + +static intptr_t execute_syscall(const call_t* c, intptr_t a[kMaxArgs]) +{ + if (c->call) + return c->call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]); + + return __syscall(c->sys_nr, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]); +} + +static void cover_open(cover_t* cov, bool extra) +{ + int fd = ksancov_open(); + if (fd == -1) + fail("open of /dev/ksancov failed"); + if (dup2(fd, cov->fd) < 0) + failmsg("failed to dup cover fd", "from=%d, to=%d", fd, cov->fd); + close(fd); + + // Note: In the other KCOV implementations we pass the shared memory size + // to the initial ioctl, before mmaping. KSANCOV reversed this logic. + // Here we instead pass the maximum number of traced PCs to the initial + // KSANCOV_IOC_TRACE ioctl. We then pass a size_t pointer to the second + // KSANCOV_IOC_MAP ioctl, hence the kernel is instead telling us the final + // size. We have a sanity check in executor.cc checking that cov.size isn't + // larger or equal to kCoverSize. To make sure that assumption holds, we're + // calculating the max_entries accordingly. + size_t max_entries = floor( + (kCoverSize - sizeof(struct ksancov_trace)) / sizeof(uint32_t)); + + // Note: XNUs KSANCOV API forces us to choose the mode after opening the + // device and before mmaping the coverage buffer. As the function we are + // in, cover_open(), expects us to mmap here, we are forced to commit to a + // mode here as well. For other OSes we commit to a mode in cover_enable(), + // based on collect_comps. This is not really a problem though, as TRACE_PC + // is the only relevant mode for us for now. XNU doesn't support TRACE_CMP + // and we don't care about the counters/nedges modes in XNU. + if (ksancov_mode_trace(cov->fd, max_entries)) + fail("ioctl init trace write failed"); + + uintptr_t mmap_ptr = 0; + size_t mmap_alloc_size = 0; + if (ksancov_map(cov->fd, &mmap_ptr, &mmap_alloc_size)) + fail("cover mmap failed"); + + // Sanity check to make sure our assumptions in the max_entries calculation + // hold up. + if (mmap_alloc_size > kCoverSize) + fail("mmap allocation size larger than anticipated"); + + cov->data = (char*)mmap_ptr; + cov->data_end = cov->data + mmap_alloc_size; +} + +static void cover_protect(cover_t* cov) +{ +} + +static void cover_unprotect(cover_t* cov) +{ +} + +static void cover_enable(cover_t* cov, bool collect_comps, bool extra) +{ + if (collect_comps) + fail("TRACE_CMP not implemented on darwin"); + if (extra) + fail("Extra coverage collection not implemented on darwin"); + // Note: we are already comitted to TRACE_PC here, hence we don't make use + // of collect_comps. For more details see the comment in cover_open(). + if (ksancov_thread_self(cov->fd)) + exitf("cover enable write trace failed"); +} + +static void cover_reset(cover_t* cov) +{ + ksancov_reset((struct ksancov_header*)cov->data); + ksancov_start((struct ksancov_header*)cov->data); +} + +static void cover_collect(cover_t* cov) +{ + struct ksancov_trace* trace = (struct ksancov_trace*)cov->data; + cov->size = ksancov_trace_head(trace); + cov->data_offset = ((int64_t) & (trace->pcs)) - ((int64_t)(cov->data)); + cov->pc_offset = trace->offset; +} + +static bool use_cover_edges(uint64 pc) +{ + return true; +} diff --git a/executor/gen.go b/executor/gen.go index e00528816..a4932913f 100644 --- a/executor/gen.go +++ b/executor/gen.go @@ -1,7 +1,7 @@ // 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. -// +build amd64,!freebsd,!openbsd,!netbsd +// +build amd64,!freebsd,!darwin,!openbsd,!netbsd //go:generate bash -c "gcc kvm_gen.cc kvm.S -o kvm_gen && ./kvm_gen > kvm.S.h && rm ./kvm_gen" diff --git a/pkg/csource/generated.go b/pkg/csource/generated.go index d387901d8..b2c055f5e 100644 --- a/pkg/csource/generated.go +++ b/pkg/csource/generated.go @@ -11,6 +11,13 @@ var commonHeader = ` #if GOOS_freebsd || GOOS_test && HOSTGOOS_freebsd #include +#elif GOOS_darwin +#include +#define htobe16(x) OSSwapHostToBigInt16(x) +#define htobe32(x) OSSwapHostToBigInt32(x) +#define htobe64(x) OSSwapHostToBigInt64(x) +#define le16toh(x) OSSwapLittleToHostInt16(x) +#define htole16(x) OSSwapHostToLittleInt16(x) #elif GOOS_windows #define htobe16 _byteswap_ushort #define htobe32 _byteswap_ulong @@ -45,7 +52,7 @@ NORETURN void doexit(int status) #if SYZ_EXECUTOR || SYZ_MULTI_PROC || SYZ_REPEAT && SYZ_CGROUPS || \ SYZ_NET_DEVICES || __NR_syz_mount_image || __NR_syz_read_part_table || \ __NR_syz_usb_connect || __NR_syz_usb_connect_ath9k || \ - (GOOS_freebsd || GOOS_openbsd || GOOS_netbsd) && SYZ_NET_INJECTION + (GOOS_freebsd || GOOS_darwin || GOOS_openbsd || GOOS_netbsd) && SYZ_NET_INJECTION static unsigned long long procid; #endif @@ -188,7 +195,7 @@ static void use_temporary_dir(void) #endif #endif -#if GOOS_akaros || GOOS_netbsd || GOOS_freebsd || GOOS_openbsd || GOOS_test +#if GOOS_akaros || GOOS_netbsd || GOOS_freebsd || GOOS_darwin || GOOS_openbsd || GOOS_test #if (SYZ_EXECUTOR || SYZ_REPEAT) && SYZ_EXECUTOR_USES_FORK_SERVER && (SYZ_EXECUTOR || SYZ_USE_TMP_DIR) #include #include @@ -275,7 +282,7 @@ static void thread_start(void* (*fn)(void*), void* arg) #endif #endif -#if GOOS_freebsd || GOOS_netbsd || GOOS_openbsd || GOOS_akaros || GOOS_test +#if GOOS_freebsd || GOOS_darwin || GOOS_netbsd || GOOS_openbsd || GOOS_akaros || GOOS_test #if SYZ_EXECUTOR || SYZ_THREADED #include @@ -423,7 +430,7 @@ void child() } #endif -#elif GOOS_freebsd || GOOS_netbsd || GOOS_openbsd +#elif GOOS_freebsd || GOOS_darwin || GOOS_netbsd || GOOS_openbsd #include @@ -1622,12 +1629,11 @@ static int fault_injected(int fd) #endif -#if GOOS_openbsd - +#if GOOS_openbsd || GOOS_darwin #define __syscall syscall +#endif -#if SYZ_EXECUTOR || __NR_syz_open_pts - +#if GOOS_openbsd && (SYZ_EXECUTOR || __NR_syz_open_pts) #include #include @@ -1641,15 +1647,14 @@ static uintptr_t syz_open_pts(void) close(master); return slave; } - -#endif - #endif #if SYZ_EXECUTOR || SYZ_NET_INJECTION #include +#if !GOOS_darwin #include +#endif #include static int tunfd = -1; @@ -1779,7 +1784,7 @@ static void initialize_tun(int tun_id) #endif -#if SYZ_EXECUTOR || __NR_syz_emit_ethernet && SYZ_NET_INJECTION +#if !GOOS_darwin && SYZ_EXECUTOR || __NR_syz_emit_ethernet && SYZ_NET_INJECTION #include #include @@ -1796,7 +1801,7 @@ static long syz_emit_ethernet(volatile long a0, volatile long a1) } #endif -#if SYZ_EXECUTOR || SYZ_NET_INJECTION && (__NR_syz_extract_tcp_res || SYZ_REPEAT) +#if !GOOS_darwin && SYZ_EXECUTOR || SYZ_NET_INJECTION && (__NR_syz_extract_tcp_res || SYZ_REPEAT) #include static int read_tun(char* data, int size) @@ -1814,14 +1819,14 @@ static int read_tun(char* data, int size) } #endif -#if SYZ_EXECUTOR || __NR_syz_extract_tcp_res && SYZ_NET_INJECTION +#if !GOOS_darwin && SYZ_EXECUTOR || __NR_syz_extract_tcp_res && SYZ_NET_INJECTION struct tcp_resources { uint32 seq; uint32 ack; }; -#if GOOS_freebsd +#if GOOS_freebsd || GOOS_darwin #include #else #include @@ -10114,7 +10119,7 @@ static void use_temporary_dir(void) #error "unknown OS" #endif -#if SYZ_EXECUTOR || __NR_syz_execute_func +#if !GOOS_darwin && SYZ_EXECUTOR || __NR_syz_execute_func static long syz_execute_func(volatile long text) { #if defined(__GNUC__) diff --git a/sys/targets/targets.go b/sys/targets/targets.go index 475f9ed6b..6f399be92 100644 --- a/sys/targets/targets.go +++ b/sys/targets/targets.go @@ -334,12 +334,17 @@ var List = map[string]map[string]*Target{ }, Darwin: { AMD64: { - PtrSize: 8, - PageSize: 4 << 10, - DataOffset: 512 << 24, - LittleEndian: true, - CCompiler: "clang", - CFlags: []string{"-m64"}, + PtrSize: 8, + PageSize: 4 << 10, + DataOffset: 512 << 24, + LittleEndian: true, + CCompiler: "clang", + CFlags: []string{ + "-m64", + "-I", sourceDirVar + "/san", + // FIXME(HerrSpace): syscall was marked as deprecated on macos + "-Wno-deprecated-declarations", + }, NeedSyscallDefine: dontNeedSyscallDefine, }, }, -- cgit mrf-deployment