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
checksum_debug_init(struct objtool_file * file)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 */
is_anonymous_const_data(struct symbol * sym)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
checksum_update_insn(struct objtool_file * file,struct symbol * func,struct instruction * insn)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
checksum_update_object(struct objtool_file * file,struct symbol * sym)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
calculate_checksums(struct objtool_file * file)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
create_sym_checksum_section(struct objtool_file * file)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
cmd_klp_checksum(int argc,const char ** argv)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