-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathcmp_hints.c
More file actions
183 lines (156 loc) · 5.47 KB
/
cmp_hints.c
File metadata and controls
183 lines (156 loc) · 5.47 KB
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
* KCOV comparison operand collection and hint pool management.
*
* Parses KCOV_TRACE_CMP trace buffers to extract constants that the
* kernel compared syscall-derived values against. These constants
* are stored in per-syscall hint pools and used during argument
* generation to produce values more likely to pass kernel validation.
*
* Buffer format (each record is 4 x u64):
* [0] type - KCOV_CMP_CONST | KCOV_CMP_SIZE(n)
* [1] arg1 - first comparison operand
* [2] arg2 - second comparison operand
* [3] ip - instruction pointer (unused here)
*/
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include "cmp_hints.h"
#include "kcov.h"
#include "random.h"
#include "syscall.h"
#include "trinity.h"
#include "utils.h"
/* From uapi/linux/kcov.h */
#define KCOV_CMP_CONST (1U << 0)
/* Words per comparison record in the trace buffer. */
#define WORDS_PER_CMP 4
struct cmp_hints_shared *cmp_hints_shm = NULL;
void cmp_hints_init(void)
{
if (kcov_shm == NULL)
return;
/*
* Stays alloc_shared() rather than alloc_shared_global().
* Children are the producers for the per-syscall pools — every
* syscall calls cmp_hints_collect() (via kcov_collect_cmp) in child
* context, which acquires pool->lock and mutates pool->values[] /
* pool->count via pool_add_locked. An mprotect PROT_READ on this
* region would EFAULT
* the lock-acquire write itself (the lock byte lives inside the
* region) and disable the CMP-guided arg generation entirely.
*
* Wild-write risk this leaves open: a child syscall whose user-buffer
* arg aliases into a pool could let the kernel scribble into
* pool->values[] (worst case: a duplicate slips past the linear-scan
* dedup, or a stale value is handed back as a hint — not a crash) or
* into the lock byte (a stuck lock would deadlock subsequent
* cmp_hints_collect callers in that one syscall slot).
* Diagnostic-grade only.
*/
cmp_hints_shm = alloc_shared(sizeof(struct cmp_hints_shared));
memset(cmp_hints_shm, 0, sizeof(struct cmp_hints_shared));
output(0, "KCOV: CMP hint pool allocated (%lu KB)\n",
(unsigned long) sizeof(struct cmp_hints_shared) / 1024);
}
static void pool_lock(struct cmp_hint_pool *pool)
{
lock(&pool->lock);
}
static void pool_unlock(struct cmp_hint_pool *pool)
{
unlock(&pool->lock);
}
/*
* Insert val into the unordered values[] array. Dedups via linear scan.
* When the pool is full, overwrites a random slot in place. Caller must
* hold pool->lock.
*/
static void pool_add_locked(struct cmp_hint_pool *pool, unsigned long val)
{
unsigned int i, count = pool->count;
for (i = 0; i < count; i++)
if (pool->values[i] == val)
return;
if (count < CMP_HINTS_PER_SYSCALL) {
pool->values[count] = val;
/*
* RELEASE-store count so a lockless reader in cmp_hints_try_get
* that observes the new count is guaranteed to also see the
* values[] store above.
*/
__atomic_store_n(&pool->count, count + 1, __ATOMIC_RELEASE);
} else {
pool->values[rand() % CMP_HINTS_PER_SYSCALL] = val;
}
}
void cmp_hints_collect(unsigned long *trace_buf, unsigned int nr)
{
unsigned long count;
unsigned long i;
struct cmp_hint_pool *pool;
if (cmp_hints_shm == NULL || trace_buf == NULL)
return;
if (nr >= MAX_NR_SYSCALL)
return;
pool = &cmp_hints_shm->pools[nr];
count = __atomic_load_n(&trace_buf[0], __ATOMIC_RELAXED);
/* Buffer is the per-child KCOV_TRACE_CMP mmap, sized off
* KCOV_CMP_BUFFER_SIZE u64 entries. Truncation accounting lives
* in kcov_collect_cmp(); here we just clamp to be defensive. */
if (count > KCOV_CMP_RECORDS_MAX)
count = KCOV_CMP_RECORDS_MAX;
if (count == 0)
return;
pool_lock(pool);
for (i = 0; i < count; i++) {
unsigned long *rec = &trace_buf[1 + i * WORDS_PER_CMP];
unsigned long type = rec[0];
unsigned long arg1 = rec[1];
unsigned long arg2 = rec[2];
/* We only care about comparisons where one side is a
* compile-time constant — those reveal what the kernel
* actually checks for. */
if (!(type & KCOV_CMP_CONST))
continue;
/*
* Filter out uninteresting comparison operands inline so the
* compiler can fold the per-record check to a couple of
* branches: skip 0/1/2/3 (caught by the ~3UL mask going to 0)
* and the all-ones sentinel.
*/
if (((arg1 & ~3UL) != 0) && (arg1 != (unsigned long) -1))
pool_add_locked(pool, arg1);
if (((arg2 & ~3UL) != 0) && (arg2 != (unsigned long) -1))
pool_add_locked(pool, arg2);
}
pool_unlock(pool);
}
bool cmp_hints_try_get(unsigned int nr, unsigned long *out)
{
struct cmp_hint_pool *pool;
unsigned int count;
if (cmp_hints_shm == NULL || nr >= MAX_NR_SYSCALL)
return false;
pool = &cmp_hints_shm->pools[nr];
/*
* Lockless read. Multiple children fuzzing the same syscall would
* otherwise serialize on pool->lock just to grab one hint.
*
* Tolerated race: a stale count snapshot still indexes a populated
* slot — count is monotonic up to the CMP_HINTS_PER_SYSCALL cap, and
* once full it stops moving (full-pool eviction overwrites in place).
* Each slot is a naturally-aligned unsigned long, so a concurrent
* eviction yields either the pre- or post-overwrite value at the
* hardware level; both are valid hints that lived in the pool.
*
* For fuzzer hints this is benign — values[] entries are direct
* unsigned longs substituted as syscall args, never dereferenced.
*/
count = __atomic_load_n(&pool->count, __ATOMIC_ACQUIRE);
if (count == 0)
return false;
*out = pool->values[rand() % count];
return true;
}