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