12013 fix GCC4 as primary compiler
1 /* 2 * Copyright (C) 2019 ARM. 3 * 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. 8 * 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. 13 * 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 16 */ 17 18 #include "smatch.h" 19 #include "smatch_extra.h" 20 #include "smatch_function_hashtable.h" 21 22 static bool expr_has_memory_addr(struct expression *expr); 23 24 static DEFINE_HASHTABLE_SEARCH(search_symbol, char, char); 25 static DEFINE_HASHTABLE_INSERT(insert_symbol, char, char); 26 static struct hashtable *symbols; 27 28 static void match_assign(struct expression *expr) 29 { 30 char *left_name; 31 struct symbol *left_sym; 32 33 left_name = expr_to_var_sym(expr->left, &left_sym); 34 if (!left_name || !left_sym) 35 return; 36 37 /* 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. 42 */ 43 if (expr_has_memory_addr(expr->right)) 44 insert_symbol(symbols, left_name, left_name); 45 } 46 47 static void match_endfunc(struct symbol *sym) 48 { 49 destroy_function_hashtable(symbols); 50 symbols = create_function_hashtable(4000); 51 } 52 53 static bool expr_has_untagged_symbol(struct expression *expr) 54 { 55 char *name; 56 struct symbol *sym; 57 58 if (expr->type != EXPR_SYMBOL) 59 return false; 60 61 name = expr_to_var_sym(expr, &sym); 62 if (!name || !sym) 63 return false; 64 65 /* See if this is something we already know is of interest */ 66 if (search_symbol(symbols, name)) 67 return true; 68 69 return false; 70 } 71 72 static bool expr_has_untagged_member(struct expression *expr) 73 { 74 if (expr->type != EXPR_DEREF) 75 return false; 76 77 if (!strcmp(expr->member->name, "vm_start") || 78 !strcmp(expr->member->name, "vm_end") || 79 !strcmp(expr->member->name, "addr_limit")) 80 return true; 81 82 return false; 83 } 84 85 static bool expr_has_macro_with_name(struct expression *expr, const char *macro_name) 86 { 87 char *name; 88 89 name = get_macro_name(expr->pos); 90 return (name && !strcmp(name, macro_name)); 91 } 92 93 static bool expr_has_untagged_macro(struct expression *expr) 94 { 95 if (expr_has_macro_with_name(expr, "PAGE_SIZE") || 96 expr_has_macro_with_name(expr, "PAGE_MASK") || 97 expr_has_macro_with_name(expr, "TASK_SIZE")) 98 return true; 99 100 /** 101 * We can't detect a marco (such as PAGE_MASK) inside another macro 102 * such as offset_in_page, therefore we have to detect the outer macro 103 * instead. 104 */ 105 if (expr_has_macro_with_name(expr, "offset_in_page")) 106 return true; 107 108 return false; 109 } 110 111 /* 112 * Identify expressions that contain memory addresses, in the future 113 * we may use annotations on symbols or function parameters. 114 */ 115 static bool expr_has_memory_addr(struct expression *expr) 116 { 117 if (expr->type == EXPR_PREOP || expr->type == EXPR_POSTOP) 118 expr = strip_expr(expr->unop); 119 120 if (expr_has_untagged_member(expr)) 121 return true; 122 123 if (expr_has_untagged_macro(expr)) 124 return true; 125 126 if (expr_has_untagged_symbol(expr)) 127 return true; 128 129 return false; 130 } 131 132 int rl_is_larger_or_equal(struct range_list *rl, sval_t sval) 133 { 134 struct data_range *tmp; 135 136 FOR_EACH_PTR(rl, tmp) { 137 if (sval_cmp(tmp->max, sval) >= 0) 138 return 1; 139 } END_FOR_EACH_PTR(tmp); 140 return 0; 141 } 142 143 int rl_range_has_min_value(struct range_list *rl, sval_t sval) 144 { 145 struct data_range *tmp; 146 147 FOR_EACH_PTR(rl, tmp) { 148 if (!sval_cmp(tmp->min, sval)) { 149 return 1; 150 } 151 } END_FOR_EACH_PTR(tmp); 152 return 0; 153 } 154 155 static bool rl_is_tagged(struct range_list *rl) 156 { 157 sval_t invalid; 158 sval_t invalid_kernel; 159 160 invalid.type = &ullong_ctype; 161 invalid.value = 1ULL << 56; 162 invalid_kernel.type = &ullong_ctype; 163 invalid_kernel.value = 0xff8ULL << 52; 164 165 /* 166 * We only care for tagged addresses, thus ignore anything where the 167 * ranges of potential values cannot possibly have any of the top byte 168 * bits set. 169 */ 170 if (!rl_is_larger_or_equal(rl, invalid)) 171 return false; 172 173 /* 174 * Tagged addresses are untagged in the kernel by using sign_extend64 in 175 * the untagged_addr macro. For userspace addresses bit 55 will always 176 * be 0 and thus this has the effect of clearing the top byte. However 177 * for kernel addresses this is not true and the top bits end up set to 178 * all 1s. The untagged_addr macro results in leaving a gap in the range 179 * of possible values which can exist, thus let's look for a tell-tale 180 * range which starts from (0xff8ULL << 52). 181 */ 182 if (rl_range_has_min_value(rl, invalid_kernel)) 183 return false; 184 185 return true; 186 } 187 188 static void match_condition(struct expression *expr) 189 { 190 struct range_list *rl = NULL; 191 struct expression *val = NULL; 192 struct symbol *type; 193 char *var_name; 194 195 /* 196 * Match instances where something is compared against something 197 * else - we include binary operators as these are commonly used 198 * to make a comparison, e.g. if (start & ~PAGE_MASK). 199 */ 200 if (expr->type != EXPR_COMPARE && 201 expr->type != EXPR_BINOP) 202 return; 203 204 /* 205 * Look on both sides of the comparison for something that shouldn't 206 * be compared with a tagged address, e.g. macros such as PAGE_MASK 207 * or struct members named .vm_start. 208 */ 209 if (expr_has_memory_addr(expr->left)) 210 val = expr->right; 211 212 /* 213 * The macro 'offset_in_page' has the PAGE_MASK macro inside it, this 214 * results in 'expr_has_memory_addr' returning true for both sides. To 215 * work around this we assume PAGE_MASK (or similar) is on the right 216 * side, thus we do the following test last. 217 */ 218 if (expr_has_memory_addr(expr->right)) 219 val = expr->left; 220 221 if (!val) 222 return; 223 224 /* We only care about memory addresses which are 64 bits */ 225 type = get_type(val); 226 if (!type || type_bits(type) != 64) 227 return; 228 229 /* We only care for comparison against user originated data */ 230 if (!get_user_rl(val, &rl)) 231 return; 232 233 /* We only care for tagged addresses */ 234 if (!rl_is_tagged(rl)) 235 return; 236 237 /* Finally, we believe we may have spotted a risky comparison */ 238 var_name = expr_to_var(val); 239 if (var_name) 240 sm_warning("comparison of a potentially tagged address (%s, %d, %s)", get_function(), get_param_num(val), var_name); 241 } 242 243 void check_arm64_tagged(int id) 244 { 245 char *arch; 246 247 if (option_project != PROJ_KERNEL) 248 return; 249 250 /* Limit to aarch64 */ 251 arch = getenv("ARCH"); 252 if (!arch || strcmp(arch, "arm64")) 253 return; 254 255 symbols = create_function_hashtable(4000); 256 257 add_hook(&match_assign, ASSIGNMENT_HOOK); 258 add_hook(&match_condition, CONDITION_HOOK); 259 add_hook(&match_endfunc, END_FUNC_HOOK); 260 } --- EOF ---