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
|
// 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 <math.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
// 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 <ksancov.h>
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->data != 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->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;
}
static bool is_kernel_data(uint64 addr)
{
return false;
}
static bool use_cover_edges(uint64 pc)
{
return true;
}
|