-
Notifications
You must be signed in to change notification settings - Fork 242
Expand file tree
/
Copy pathcmp_hints.c
More file actions
180 lines (149 loc) · 4.32 KB
/
cmp_hints.c
File metadata and controls
180 lines (149 loc) · 4.32 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
/*
* 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;
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);
}
/*
* Filter out uninteresting comparison values.
* Skip 0, 1, -1, and very small values that are likely to be
* boolean/flag checks rather than meaningful constants.
*/
static bool interesting_value(unsigned long val)
{
if (val == 0 || val == 1)
return false;
if (val == (unsigned long) -1)
return false;
if (val < 4)
return false;
return true;
}
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 sorted values[] array. Dedups via binary search.
* When the pool is full, evicts a random slot. Caller must hold pool->lock.
*/
static void pool_add_locked(struct cmp_hint_pool *pool, unsigned long val)
{
unsigned int lo = 0, hi = pool->count, mid;
while (lo < hi) {
mid = (lo + hi) / 2;
if (pool->values[mid] == val)
return;
if (pool->values[mid] < val)
lo = mid + 1;
else
hi = mid;
}
if (pool->count < CMP_HINTS_PER_SYSCALL) {
memmove(&pool->values[lo + 1], &pool->values[lo],
(pool->count - lo) * sizeof(unsigned long));
pool->values[lo] = val;
pool->count++;
} else {
unsigned int victim = rand() % CMP_HINTS_PER_SYSCALL;
unsigned int pos = (victim < lo) ? lo - 1 : lo;
if (victim < pos) {
memmove(&pool->values[victim], &pool->values[victim + 1],
(pos - victim) * sizeof(unsigned long));
} else if (victim > pos) {
memmove(&pool->values[pos + 1], &pool->values[pos],
(victim - pos) * sizeof(unsigned long));
}
pool->values[pos] = 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);
if (count > (KCOV_TRACE_SIZE - 1) / WORDS_PER_CMP)
count = (KCOV_TRACE_SIZE - 1) / WORDS_PER_CMP;
if (count == 0)
return;
pool_lock(pool);
for (i = 0; i < count; i++) {
unsigned long type = trace_buf[1 + i * WORDS_PER_CMP];
unsigned long arg1 = trace_buf[1 + i * WORDS_PER_CMP + 1];
unsigned long arg2 = trace_buf[1 + i * WORDS_PER_CMP + 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;
if (interesting_value(arg1))
pool_add_locked(pool, arg1);
if (interesting_value(arg2))
pool_add_locked(pool, arg2);
}
pool_unlock(pool);
}
unsigned long cmp_hints_get(unsigned int nr)
{
struct cmp_hint_pool *pool;
unsigned long val = 0;
unsigned int count;
if (cmp_hints_shm == NULL || nr >= MAX_NR_SYSCALL)
return 0;
pool = &cmp_hints_shm->pools[nr];
pool_lock(pool);
count = pool->count;
if (count > 0)
val = pool->values[rand() % count];
pool_unlock(pool);
return val;
}
bool cmp_hints_available(unsigned int nr)
{
if (cmp_hints_shm == NULL || nr >= MAX_NR_SYSCALL)
return false;
return __atomic_load_n(&cmp_hints_shm->pools[nr].count,
__ATOMIC_RELAXED) > 0;
}