aboutsummaryrefslogtreecommitdiffstats
path: root/executor/executor_darwin.h
blob: d555eeb026bbb79ff38180d26c86ee54cbc40f11 (plain)
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
// 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->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;
}