2 * Copyright (C) 2019 ARM.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt
19 #include "smatch_extra.h"
20 #include "smatch_function_hashtable.h"
22 static bool expr_has_memory_addr(struct expression
*expr
);
24 static DEFINE_HASHTABLE_SEARCH(search_symbol
, char, char);
25 static DEFINE_HASHTABLE_INSERT(insert_symbol
, char, char);
26 static struct hashtable
*symbols
;
28 static void match_assign(struct expression
*expr
)
31 struct symbol
*left_sym
;
33 left_name
= expr_to_var_sym(expr
->left
, &left_sym
);
34 if (!left_name
|| !left_sym
)
38 * Once we have spotted a symbol of interest (one that may hold
39 * an untagged memory address), we keep track of any assignments
40 * made, such that we can also treat the assigned symbol as something
41 * of interest. This tracking is limited in scope to the function.
43 if (expr_has_memory_addr(expr
->right
))
44 insert_symbol(symbols
, left_name
, left_name
);
47 static void match_endfunc(struct symbol
*sym
)
49 destroy_function_hashtable(symbols
);
50 symbols
= create_function_hashtable(4000);
53 static bool expr_has_untagged_symbol(struct expression
*expr
)
58 if (expr
->type
!= EXPR_SYMBOL
)
61 name
= expr_to_var_sym(expr
, &sym
);
65 /* See if this is something we already know is of interest */
66 if (search_symbol(symbols
, name
))
72 static bool expr_has_untagged_member(struct expression
*expr
)
74 if (expr
->type
!= EXPR_DEREF
)
77 if (!strcmp(expr
->member
->name
, "vm_start") ||
78 !strcmp(expr
->member
->name
, "vm_end") ||
79 !strcmp(expr
->member
->name
, "addr_limit"))
85 static bool expr_has_macro_with_name(struct expression
*expr
, const char *macro_name
)
89 if (expr
->type
== EXPR_PREOP
&& expr
->op
== '~')
90 expr
= strip_expr(expr
->unop
);
92 name
= get_macro_name(expr
->pos
);
93 return (name
&& !strcmp(name
, macro_name
));
96 static bool expr_has_untagged_macro(struct expression
*expr
)
98 if (expr_has_macro_with_name(expr
, "PAGE_SIZE") ||
99 expr_has_macro_with_name(expr
, "PAGE_MASK") ||
100 expr_has_macro_with_name(expr
, "TASK_SIZE"))
104 * We can't detect a marco (such as PAGE_MASK) inside another macro
105 * such as offset_in_page, therefore we have to detect the outer macro
108 if (expr_has_macro_with_name(expr
, "offset_in_page"))
115 * Identify expressions that contain memory addresses, in the future
116 * we may use annotations on symbols or function parameters.
118 static bool expr_has_memory_addr(struct expression
*expr
)
120 if (expr
->type
== EXPR_PREOP
|| expr
->type
== EXPR_POSTOP
)
121 expr
= strip_expr(expr
->unop
);
123 if (expr_has_untagged_member(expr
))
126 if (expr_has_untagged_macro(expr
))
129 if (expr_has_untagged_symbol(expr
))
135 int rl_is_larger_or_equal(struct range_list
*rl
, sval_t sval
)
137 struct data_range
*tmp
;
139 FOR_EACH_PTR(rl
, tmp
) {
140 if (sval_cmp(tmp
->max
, sval
) >= 0)
142 } END_FOR_EACH_PTR(tmp
);
146 int rl_range_has_min_value(struct range_list
*rl
, sval_t sval
)
148 struct data_range
*tmp
;
150 FOR_EACH_PTR(rl
, tmp
) {
151 if (!sval_cmp(tmp
->min
, sval
)) {
154 } END_FOR_EACH_PTR(tmp
);
158 static bool rl_is_tagged(struct range_list
*rl
)
160 sval_t invalid
= { .type
= &ullong_ctype
, .value
= (1ULL << 56) };
161 sval_t invalid_kernel
= { .type
= &ullong_ctype
, .value
= (0xff8ULL
<< 52) };
164 * We only care for tagged addresses, thus ignore anything where the
165 * ranges of potential values cannot possibly have any of the top byte
168 if (!rl_is_larger_or_equal(rl
, invalid
))
172 * Tagged addresses are untagged in the kernel by using sign_extend64 in
173 * the untagged_addr macro. For userspace addresses bit 55 will always
174 * be 0 and thus this has the effect of clearing the top byte. However
175 * for kernel addresses this is not true and the top bits end up set to
176 * all 1s. The untagged_addr macro results in leaving a gap in the range
177 * of possible values which can exist, thus let's look for a tell-tale
178 * range which starts from (0xff8ULL << 52).
180 if (rl_range_has_min_value(rl
, invalid_kernel
))
186 static void match_condition(struct expression
*expr
)
188 struct range_list
*rl
= NULL
;
189 struct expression
*val
= NULL
;
194 * Match instances where something is compared against something
195 * else - we include binary operators as these are commonly used
196 * to make a comparison, e.g. if (start & ~PAGE_MASK).
198 if (expr
->type
!= EXPR_COMPARE
&&
199 expr
->type
!= EXPR_BINOP
)
203 * Look on both sides of the comparison for something that shouldn't
204 * be compared with a tagged address, e.g. macros such as PAGE_MASK
205 * or struct members named .vm_start.
207 if (expr_has_memory_addr(expr
->left
))
211 * The macro 'offset_in_page' has the PAGE_MASK macro inside it, this
212 * results in 'expr_has_memory_addr' returning true for both sides. To
213 * work around this we assume PAGE_MASK (or similar) is on the right
214 * side, thus we do the following test last.
216 if (expr_has_memory_addr(expr
->right
))
222 /* We only care about memory addresses which are 64 bits */
223 type
= get_type(val
);
224 if (!type
|| type_bits(type
) != 64)
227 /* We only care for comparison against user originated data */
228 if (!get_user_rl(val
, &rl
))
231 /* We only care for tagged addresses */
232 if (!rl_is_tagged(rl
))
235 /* Finally, we believe we may have spotted a risky comparison */
236 var_name
= expr_to_var(val
);
238 sm_warning("comparison of a potentially tagged address (%s, %d, %s)", get_function(), get_param_num(val
), var_name
);
241 void check_arm64_tagged(int id
)
245 if (option_project
!= PROJ_KERNEL
)
248 /* Limit to aarch64 */
249 arch
= getenv("ARCH");
250 if (!arch
|| strcmp(arch
, "arm64"))
253 symbols
= create_function_hashtable(4000);
255 add_hook(&match_assign
, ASSIGNMENT_HOOK
);
256 add_hook(&match_condition
, CONDITION_HOOK
);
257 add_hook(&match_endfunc
, END_FUNC_HOOK
);