-
Notifications
You must be signed in to change notification settings - Fork 14
/
check_arm64_tagged.c
258 lines (210 loc) · 6.75 KB
/
check_arm64_tagged.c
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
* Copyright (C) 2019 ARM.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt
*/
#include "smatch.h"
#include "smatch_extra.h"
#include "smatch_function_hashtable.h"
static bool expr_has_memory_addr(struct expression *expr);
static DEFINE_HASHTABLE_SEARCH(search_symbol, char, char);
static DEFINE_HASHTABLE_INSERT(insert_symbol, char, char);
static struct hashtable *symbols;
static void match_assign(struct expression *expr)
{
char *left_name;
struct symbol *left_sym;
left_name = expr_to_var_sym(expr->left, &left_sym);
if (!left_name || !left_sym)
return;
/*
* Once we have spotted a symbol of interest (one that may hold
* an untagged memory address), we keep track of any assignments
* made, such that we can also treat the assigned symbol as something
* of interest. This tracking is limited in scope to the function.
*/
if (expr_has_memory_addr(expr->right))
insert_symbol(symbols, left_name, left_name);
}
static void match_endfunc(struct symbol *sym)
{
destroy_function_hashtable(symbols);
symbols = create_function_hashtable(4000);
}
static bool expr_has_untagged_symbol(struct expression *expr)
{
char *name;
struct symbol *sym;
if (expr->type != EXPR_SYMBOL)
return false;
name = expr_to_var_sym(expr, &sym);
if (!name || !sym)
return false;
/* See if this is something we already know is of interest */
if (search_symbol(symbols, name))
return true;
return false;
}
static bool expr_has_untagged_member(struct expression *expr)
{
if (expr->type != EXPR_DEREF)
return false;
if (!strcmp(expr->member->name, "vm_start") ||
!strcmp(expr->member->name, "vm_end") ||
!strcmp(expr->member->name, "addr_limit"))
return true;
return false;
}
static bool expr_has_macro_with_name(struct expression *expr, const char *macro_name)
{
char *name;
if (expr->type == EXPR_PREOP && expr->op == '~')
expr = strip_expr(expr->unop);
name = get_macro_name(expr->pos);
return (name && !strcmp(name, macro_name));
}
static bool expr_has_untagged_macro(struct expression *expr)
{
if (expr_has_macro_with_name(expr, "PAGE_SIZE") ||
expr_has_macro_with_name(expr, "PAGE_MASK") ||
expr_has_macro_with_name(expr, "TASK_SIZE"))
return true;
/**
* We can't detect a marco (such as PAGE_MASK) inside another macro
* such as offset_in_page, therefore we have to detect the outer macro
* instead.
*/
if (expr_has_macro_with_name(expr, "offset_in_page"))
return true;
return false;
}
/*
* Identify expressions that contain memory addresses, in the future
* we may use annotations on symbols or function parameters.
*/
static bool expr_has_memory_addr(struct expression *expr)
{
if (expr->type == EXPR_PREOP || expr->type == EXPR_POSTOP)
expr = strip_expr(expr->unop);
if (expr_has_untagged_member(expr))
return true;
if (expr_has_untagged_macro(expr))
return true;
if (expr_has_untagged_symbol(expr))
return true;
return false;
}
int rl_is_larger_or_equal(struct range_list *rl, sval_t sval)
{
struct data_range *tmp;
FOR_EACH_PTR(rl, tmp) {
if (sval_cmp(tmp->max, sval) >= 0)
return 1;
} END_FOR_EACH_PTR(tmp);
return 0;
}
int rl_range_has_min_value(struct range_list *rl, sval_t sval)
{
struct data_range *tmp;
FOR_EACH_PTR(rl, tmp) {
if (!sval_cmp(tmp->min, sval)) {
return 1;
}
} END_FOR_EACH_PTR(tmp);
return 0;
}
static bool rl_is_tagged(struct range_list *rl)
{
sval_t invalid = { .type = &ullong_ctype, .value = (1ULL << 56) };
sval_t invalid_kernel = { .type = &ullong_ctype, .value = (0xff8ULL << 52) };
/*
* We only care for tagged addresses, thus ignore anything where the
* ranges of potential values cannot possibly have any of the top byte
* bits set.
*/
if (!rl_is_larger_or_equal(rl, invalid))
return false;
/*
* Tagged addresses are untagged in the kernel by using sign_extend64 in
* the untagged_addr macro. For userspace addresses bit 55 will always
* be 0 and thus this has the effect of clearing the top byte. However
* for kernel addresses this is not true and the top bits end up set to
* all 1s. The untagged_addr macro results in leaving a gap in the range
* of possible values which can exist, thus let's look for a tell-tale
* range which starts from (0xff8ULL << 52).
*/
if (rl_range_has_min_value(rl, invalid_kernel))
return false;
return true;
}
static void match_condition(struct expression *expr)
{
struct range_list *rl = NULL;
struct expression *val = NULL;
struct symbol *type;
char *var_name;
/*
* Match instances where something is compared against something
* else - we include binary operators as these are commonly used
* to make a comparison, e.g. if (start & ~PAGE_MASK).
*/
if (expr->type != EXPR_COMPARE &&
expr->type != EXPR_BINOP)
return;
/*
* Look on both sides of the comparison for something that shouldn't
* be compared with a tagged address, e.g. macros such as PAGE_MASK
* or struct members named .vm_start.
*/
if (expr_has_memory_addr(expr->left))
val = expr->right;
/*
* The macro 'offset_in_page' has the PAGE_MASK macro inside it, this
* results in 'expr_has_memory_addr' returning true for both sides. To
* work around this we assume PAGE_MASK (or similar) is on the right
* side, thus we do the following test last.
*/
if (expr_has_memory_addr(expr->right))
val = expr->left;
if (!val)
return;
/* We only care about memory addresses which are 64 bits */
type = get_type(val);
if (!type || type_bits(type) != 64)
return;
/* We only care for comparison against user originated data */
if (!get_user_rl(val, &rl))
return;
/* We only care for tagged addresses */
if (!rl_is_tagged(rl))
return;
/* Finally, we believe we may have spotted a risky comparison */
var_name = expr_to_var(val);
if (var_name)
sm_warning("comparison of a potentially tagged address (%s, %d, %s)", get_function(), get_param_num(val), var_name);
}
void check_arm64_tagged(int id)
{
char *arch;
if (option_project != PROJ_KERNEL)
return;
/* Limit to aarch64 */
arch = getenv("ARCH");
if (!arch || strcmp(arch, "arm64"))
return;
symbols = create_function_hashtable(4000);
add_hook(&match_assign, ASSIGNMENT_HOOK);
add_hook(&match_condition, CONDITION_HOOK);
add_hook(&match_endfunc, END_FUNC_HOOK);
}