1 // SPDX-License-Identifier: GPL-2.0-or-later 2 #include <string.h> 3 #include <subcmd/parse-options.h> 4 5 #include <objtool/arch.h> 6 #include <objtool/builtin.h> 7 #include <objtool/check.h> 8 #include <objtool/elf.h> 9 #include <objtool/klp.h> 10 #include <objtool/objtool.h> 11 #include <objtool/warn.h> 12 #include <objtool/checksum.h> 13 14 static int checksum_debug_init(struct objtool_file *file) 15 { 16 char *dup, *s; 17 18 if (!opts.debug_checksum) 19 return 0; 20 21 dup = strdup(opts.debug_checksum); 22 if (!dup) { 23 ERROR_GLIBC("strdup"); 24 return -1; 25 } 26 27 s = dup; 28 while (*s) { 29 bool found = false; 30 struct symbol *sym; 31 char *comma; 32 33 comma = strchr(s, ','); 34 if (comma) 35 *comma = '\0'; 36 37 for_each_sym_by_name(file->elf, s, sym) { 38 if (!is_func_sym(sym) && !is_object_sym(sym)) 39 continue; 40 sym->debug_checksum = 1; 41 found = true; 42 } 43 44 if (!found) 45 WARN("--debug-checksum: can't find '%s'", s); 46 47 if (!comma) 48 break; 49 50 s = comma + 1; 51 } 52 53 free(dup); 54 return 0; 55 } 56 57 /* 58 * Detect a reference to anonymous constant pool data which the compiler places 59 * in .rodata.cst<num> and which either has an .LC<num> symbol associated with 60 * it or (with Clang) no symbol at all. These are typically initializers for 61 * local function stack data, so they're considered part of the function rather 62 * than data per se. 63 */ 64 static bool is_anonymous_const_data(struct symbol *sym) 65 { 66 return strstarts(sym->sec->name, ".rodata.cst") && 67 (is_sec_sym(sym) || strstarts(sym->name, ".LC")); 68 } 69 70 static void checksum_update_insn(struct objtool_file *file, struct symbol *func, 71 struct instruction *insn) 72 { 73 struct reloc *reloc = insn_reloc(file, insn); 74 struct alternative *alt; 75 unsigned long offset; 76 struct symbol *sym; 77 static bool in_alt; 78 79 if (insn->fake) 80 return; 81 82 if (!reloc) { 83 struct symbol *call_dest = insn_call_dest(insn); 84 struct instruction *jump_dest = insn->jump_dest; 85 86 /* 87 * For a jump/call non-relocated dest offset embedded in the 88 * instruction, the offset may vary due to changes in 89 * surrounding code. Just hash the opcode and a 90 * position-independent representation of the destination. 91 */ 92 93 if (call_dest || jump_dest) { 94 unsigned char buf[16]; 95 size_t len; 96 97 len = arch_jump_opcode_bytes(file, insn, buf); 98 __checksum_update_insn(func, insn, buf, len); 99 100 if (call_dest) { 101 __checksum_update_insn(func, insn, call_dest->demangled_name, 102 strlen(call_dest->demangled_name)); 103 104 } else if (jump_dest) { 105 struct symbol *dest_sym; 106 unsigned long offset; 107 108 /* 109 * use insn->_sym instead of insn_sym() here. 110 * For alternative replacements, the latter 111 * would give the function of the code being 112 * replaced. 113 */ 114 dest_sym = jump_dest->_sym; 115 if (!dest_sym) 116 goto alts; 117 118 __checksum_update_insn(func, insn, dest_sym->demangled_name, 119 strlen(dest_sym->demangled_name)); 120 121 offset = jump_dest->offset - dest_sym->offset; 122 __checksum_update_insn(func, insn, &offset, sizeof(offset)); 123 } 124 125 goto alts; 126 } 127 } 128 129 __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); 130 131 if (!reloc) 132 goto alts; 133 134 sym = reloc->sym; 135 offset = arch_insn_adjusted_addend(insn, reloc); 136 137 if (is_string_sec(sym->sec)) { 138 char *str; 139 140 str = sym->sec->data->d_buf + sym->offset + offset; 141 __checksum_update_insn(func, insn, str, strlen(str)); 142 goto alts; 143 } 144 145 if (is_anonymous_const_data(sym)) { 146 void *cst; 147 148 cst = sym->sec->data->d_buf + sym->offset + offset; 149 __checksum_update_insn(func, insn, cst, sym->sec->sh.sh_entsize); 150 goto alts; 151 } 152 153 if (is_sec_sym(sym)) { 154 sym = find_symbol_containing(reloc->sym->sec, offset); 155 if (!sym) 156 goto alts; 157 158 offset -= sym->offset; 159 } 160 161 __checksum_update_insn(func, insn, sym->demangled_name, 162 strlen(sym->demangled_name)); 163 __checksum_update_insn(func, insn, &offset, sizeof(offset)); 164 165 alts: 166 for (alt = insn->alts; alt; alt = alt->next) { 167 struct alt_group *alt_group = alt->insn->alt_group; 168 169 /* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */ 170 if (in_alt) 171 break; 172 in_alt = true; 173 174 __checksum_update_insn(func, insn, &alt->type, 175 sizeof(alt->type)); 176 177 if (alt_group && alt_group->orig_group) { 178 struct instruction *alt_insn; 179 180 __checksum_update_insn(func, insn, &alt_group->feature,sizeof(alt_group->feature)); 181 182 for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) { 183 checksum_update_insn(file, func, alt_insn); 184 if (!alt_group->last_insn || alt_insn == alt_group->last_insn) 185 break; 186 } 187 } else { 188 checksum_update_insn(file, func, alt->insn); 189 } 190 191 in_alt = false; 192 } 193 } 194 195 static void checksum_update_object(struct objtool_file *file, struct symbol *sym) 196 { 197 struct reloc *reloc; 198 199 __checksum_update_object(sym, 0, "len", &sym->len, sizeof(sym->len)); 200 201 if (sym->sec->data->d_buf) 202 __checksum_update_object(sym, 0, "data", 203 sym->sec->data->d_buf + sym->offset, 204 sym->len); 205 206 sym_for_each_reloc(file->elf, sym, reloc) { 207 unsigned long sym_offset = reloc_offset(reloc) - sym->offset; 208 struct symbol *target = reloc->sym; 209 s64 offset; 210 211 offset = reloc_addend(reloc); 212 213 if (is_string_sec(target->sec)) { 214 char *str; 215 216 str = target->sec->data->d_buf + target->offset + offset; 217 __checksum_update_object(sym, sym_offset, 218 "reloc string", str, strlen(str)); 219 continue; 220 } 221 222 if (is_sec_sym(target)) { 223 target = find_symbol_containing(reloc->sym->sec, offset); 224 if (!target) 225 continue; 226 227 offset -= target->offset; 228 } 229 230 __checksum_update_object(sym, sym_offset, "reloc name", 231 target->demangled_name, 232 strlen(target->demangled_name)); 233 __checksum_update_object(sym, sym_offset, "reloc addend", 234 &offset, sizeof(offset)); 235 } 236 } 237 238 int calculate_checksums(struct objtool_file *file) 239 { 240 struct instruction *insn; 241 struct symbol *sym; 242 243 if (checksum_debug_init(file)) 244 return -1; 245 246 for_each_sym(file->elf, sym) { 247 248 /* 249 * Skip cold subfunctions and aliases: they share the 250 * parent's checksum via func_for_each_insn() which 251 * follows func->cfunc into the cold subfunction. 252 */ 253 if (is_cold_func(sym) || is_alias_sym(sym) || !sym->len || 254 !sym->sec || !sym->sec->data) 255 continue; 256 257 if (is_func_sym(sym)) { 258 checksum_init(sym); 259 func_for_each_insn(file, sym, insn) 260 checksum_update_insn(file, sym, insn); 261 checksum_finish(sym); 262 263 } else if (is_object_sym(sym)) { 264 checksum_init(sym); 265 checksum_update_object(file, sym); 266 checksum_finish(sym); 267 } 268 269 } 270 271 return 0; 272 } 273 274 int create_sym_checksum_section(struct objtool_file *file) 275 { 276 struct section *sec; 277 struct symbol *sym; 278 unsigned int idx = 0; 279 struct sym_checksum *checksum; 280 size_t entsize = sizeof(struct sym_checksum); 281 282 sec = find_section_by_name(file->elf, ".discard.sym_checksum"); 283 if (sec) { 284 if (!opts.dryrun) 285 WARN("file already has .discard.sym_checksum section, skipping"); 286 287 return 0; 288 } 289 290 for_each_sym(file->elf, sym) 291 if (sym->csum.checksum) 292 idx++; 293 294 sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize, 295 idx, idx); 296 if (!sec) 297 return -1; 298 299 idx = 0; 300 for_each_sym(file->elf, sym) { 301 if (!sym->csum.checksum) 302 continue; 303 304 if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize, 305 sym, 0, R_TEXT64)) 306 return -1; 307 308 checksum = (struct sym_checksum *)sec->data->d_buf + idx; 309 checksum->addr = 0; /* reloc */ 310 checksum->checksum = sym->csum.checksum; 311 312 mark_sec_changed(file->elf, sec, true); 313 314 idx++; 315 } 316 317 return 0; 318 } 319 320 static const char * const klp_checksum_usage[] = { 321 "objtool klp checksum [<options>] file.o", 322 NULL, 323 }; 324 325 int cmd_klp_checksum(int argc, const char **argv) 326 { 327 struct objtool_file *file; 328 int ret; 329 330 const struct option options[] = { 331 OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "syms", "enable checksum debug output"), 332 OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), 333 OPT_END(), 334 }; 335 336 argc = parse_options(argc, argv, options, klp_checksum_usage, 0); 337 if (argc != 1) 338 usage_with_options(klp_checksum_usage, options); 339 340 opts.checksum = true; 341 342 objname = argv[0]; 343 344 file = objtool_open_read(objname); 345 if (!file) 346 return 1; 347 348 ret = decode_file(file); 349 if (ret) 350 goto out; 351 352 ret = calculate_checksums(file); 353 if (ret) 354 goto out; 355 356 ret = create_sym_checksum_section(file); 357 358 out: 359 free_insns(file); 360 361 if (ret) 362 return ret; 363 364 if (!opts.dryrun && file->elf->changed && elf_write(file->elf)) 365 return 1; 366 367 return elf_close(file->elf); 368 } 369