// 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; void* got = mmap(data, data_size, prot, flags, -1, 0); if (data != got) failmsg("mmap of data segment failed", "want %p, got %p", data, got); // 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"); } static void cover_mmap(cover_t* cov) { if (cov->mmap_alloc_ptr != NULL) fail("cover_mmap invoked on an already mmapped cover_t object"); uintptr_t mmap_ptr = 0; if (ksancov_map(cov->fd, &mmap_ptr, &cov->mmap_alloc_size)) fail("cover mmap failed"); // Sanity check to make sure our assumptions in the max_entries calculation // hold up. if (cov->mmap_alloc_size > kCoverSize) fail("mmap allocation size larger than anticipated"); cov->mmap_alloc_ptr = (char*)mmap_ptr; cov->data = (char*)mmap_ptr; cov->data_end = cov->data + cov->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; }