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 static void checksum_update_insn(struct objtool_file *file, struct symbol *func, 58 struct instruction *insn) 59 { 60 struct reloc *reloc = insn_reloc(file, insn); 61 struct alternative *alt; 62 unsigned long offset; 63 struct symbol *sym; 64 static bool in_alt; 65 66 if (insn->fake) 67 return; 68 69 if (!reloc) { 70 struct symbol *call_dest = insn_call_dest(insn); 71 struct instruction *jump_dest = insn->jump_dest; 72 73 /* 74 * For a jump/call non-relocated dest offset embedded in the 75 * instruction, the offset may vary due to changes in 76 * surrounding code. Just hash the opcode and a 77 * position-independent representation of the destination. 78 */ 79 80 if (call_dest || jump_dest) { 81 unsigned char buf[16]; 82 size_t len; 83 84 len = arch_jump_opcode_bytes(file, insn, buf); 85 __checksum_update_insn(func, insn, buf, len); 86 87 if (call_dest) { 88 __checksum_update_insn(func, insn, call_dest->demangled_name, 89 strlen(call_dest->demangled_name)); 90 91 } else if (jump_dest) { 92 struct symbol *dest_sym; 93 unsigned long offset; 94 95 /* 96 * use insn->_sym instead of insn_sym() here. 97 * For alternative replacements, the latter 98 * would give the function of the code being 99 * replaced. 100 */ 101 dest_sym = jump_dest->_sym; 102 if (!dest_sym) 103 goto alts; 104 105 __checksum_update_insn(func, insn, dest_sym->demangled_name, 106 strlen(dest_sym->demangled_name)); 107 108 offset = jump_dest->offset - dest_sym->offset; 109 __checksum_update_insn(func, insn, &offset, sizeof(offset)); 110 } 111 112 goto alts; 113 } 114 } 115 116 __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); 117 118 if (!reloc) 119 goto alts; 120 121 sym = reloc->sym; 122 offset = arch_insn_adjusted_addend(insn, reloc); 123 124 if (is_string_sec(sym->sec)) { 125 char *str; 126 127 str = sym->sec->data->d_buf + sym->offset + offset; 128 __checksum_update_insn(func, insn, str, strlen(str)); 129 goto alts; 130 } 131 132 if (is_sec_sym(sym)) { 133 sym = find_symbol_containing(reloc->sym->sec, offset); 134 if (!sym) 135 goto alts; 136 137 offset -= sym->offset; 138 } 139 140 __checksum_update_insn(func, insn, sym->demangled_name, 141 strlen(sym->demangled_name)); 142 __checksum_update_insn(func, insn, &offset, sizeof(offset)); 143 144 alts: 145 for (alt = insn->alts; alt; alt = alt->next) { 146 struct alt_group *alt_group = alt->insn->alt_group; 147 148 /* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */ 149 if (in_alt) 150 break; 151 in_alt = true; 152 153 __checksum_update_insn(func, insn, &alt->type, 154 sizeof(alt->type)); 155 156 if (alt_group && alt_group->orig_group) { 157 struct instruction *alt_insn; 158 159 __checksum_update_insn(func, insn, &alt_group->feature,sizeof(alt_group->feature)); 160 161 for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) { 162 checksum_update_insn(file, func, alt_insn); 163 if (!alt_group->last_insn || alt_insn == alt_group->last_insn) 164 break; 165 } 166 } else { 167 checksum_update_insn(file, func, alt->insn); 168 } 169 170 in_alt = false; 171 } 172 } 173 174 static void checksum_update_object(struct objtool_file *file, struct symbol *sym) 175 { 176 struct reloc *reloc; 177 178 __checksum_update_object(sym, 0, "len", &sym->len, sizeof(sym->len)); 179 180 if (sym->sec->data->d_buf) 181 __checksum_update_object(sym, 0, "data", 182 sym->sec->data->d_buf + sym->offset, 183 sym->len); 184 185 sym_for_each_reloc(file->elf, sym, reloc) { 186 unsigned long sym_offset = reloc_offset(reloc) - sym->offset; 187 struct symbol *target = reloc->sym; 188 s64 offset; 189 190 offset = reloc_addend(reloc); 191 192 if (is_string_sec(target->sec)) { 193 char *str; 194 195 str = target->sec->data->d_buf + target->offset + offset; 196 __checksum_update_object(sym, sym_offset, 197 "reloc string", str, strlen(str)); 198 continue; 199 } 200 201 if (is_sec_sym(target)) { 202 target = find_symbol_containing(reloc->sym->sec, offset); 203 if (!target) 204 continue; 205 206 offset -= target->offset; 207 } 208 209 __checksum_update_object(sym, sym_offset, "reloc name", 210 target->demangled_name, 211 strlen(target->demangled_name)); 212 __checksum_update_object(sym, sym_offset, "reloc addend", 213 &offset, sizeof(offset)); 214 } 215 } 216 217 int calculate_checksums(struct objtool_file *file) 218 { 219 struct instruction *insn; 220 struct symbol *sym; 221 222 if (checksum_debug_init(file)) 223 return -1; 224 225 for_each_sym(file->elf, sym) { 226 227 /* 228 * Skip cold subfunctions and aliases: they share the 229 * parent's checksum via func_for_each_insn() which 230 * follows func->cfunc into the cold subfunction. 231 */ 232 if (is_cold_func(sym) || is_alias_sym(sym) || !sym->len || 233 !sym->sec || !sym->sec->data) 234 continue; 235 236 if (is_func_sym(sym)) { 237 checksum_init(sym); 238 func_for_each_insn(file, sym, insn) 239 checksum_update_insn(file, sym, insn); 240 checksum_finish(sym); 241 242 } else if (is_object_sym(sym)) { 243 checksum_init(sym); 244 checksum_update_object(file, sym); 245 checksum_finish(sym); 246 } 247 248 } 249 250 return 0; 251 } 252 253 int create_sym_checksum_section(struct objtool_file *file) 254 { 255 struct section *sec; 256 struct symbol *sym; 257 unsigned int idx = 0; 258 struct sym_checksum *checksum; 259 size_t entsize = sizeof(struct sym_checksum); 260 261 sec = find_section_by_name(file->elf, ".discard.sym_checksum"); 262 if (sec) { 263 if (!opts.dryrun) 264 WARN("file already has .discard.sym_checksum section, skipping"); 265 266 return 0; 267 } 268 269 for_each_sym(file->elf, sym) 270 if (sym->csum.checksum) 271 idx++; 272 273 sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize, 274 idx, idx); 275 if (!sec) 276 return -1; 277 278 idx = 0; 279 for_each_sym(file->elf, sym) { 280 if (!sym->csum.checksum) 281 continue; 282 283 if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize, 284 sym, 0, R_TEXT64)) 285 return -1; 286 287 checksum = (struct sym_checksum *)sec->data->d_buf + idx; 288 checksum->addr = 0; /* reloc */ 289 checksum->checksum = sym->csum.checksum; 290 291 mark_sec_changed(file->elf, sec, true); 292 293 idx++; 294 } 295 296 return 0; 297 } 298 299 static const char * const klp_checksum_usage[] = { 300 "objtool klp checksum [<options>] file.o", 301 NULL, 302 }; 303 304 int cmd_klp_checksum(int argc, const char **argv) 305 { 306 struct objtool_file *file; 307 int ret; 308 309 const struct option options[] = { 310 OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "syms", "enable checksum debug output"), 311 OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), 312 OPT_END(), 313 }; 314 315 argc = parse_options(argc, argv, options, klp_checksum_usage, 0); 316 if (argc != 1) 317 usage_with_options(klp_checksum_usage, options); 318 319 opts.checksum = true; 320 321 objname = argv[0]; 322 323 file = objtool_open_read(objname); 324 if (!file) 325 return 1; 326 327 ret = decode_file(file); 328 if (ret) 329 goto out; 330 331 ret = calculate_checksums(file); 332 if (ret) 333 goto out; 334 335 ret = create_sym_checksum_section(file); 336 337 out: 338 free_insns(file); 339 340 if (ret) 341 return ret; 342 343 if (!opts.dryrun && file->elf->changed && elf_write(file->elf)) 344 return 1; 345 346 return elf_close(file->elf); 347 } 348