xref: /linux/tools/objtool/klp-checksum.c (revision cca84cb12908f1cfcecaef80a7692017e2d6a945)
1e1076461SJosh Poimboeuf // SPDX-License-Identifier: GPL-2.0-or-later
2e1076461SJosh Poimboeuf #include <string.h>
3e1076461SJosh Poimboeuf #include <subcmd/parse-options.h>
4e1076461SJosh Poimboeuf 
5e1076461SJosh Poimboeuf #include <objtool/arch.h>
6e1076461SJosh Poimboeuf #include <objtool/builtin.h>
7e1076461SJosh Poimboeuf #include <objtool/check.h>
8e1076461SJosh Poimboeuf #include <objtool/elf.h>
9e1076461SJosh Poimboeuf #include <objtool/klp.h>
10e1076461SJosh Poimboeuf #include <objtool/objtool.h>
11e1076461SJosh Poimboeuf #include <objtool/warn.h>
12e1076461SJosh Poimboeuf #include <objtool/checksum.h>
13e1076461SJosh Poimboeuf 
14e1076461SJosh Poimboeuf static int checksum_debug_init(struct objtool_file *file)
15e1076461SJosh Poimboeuf {
16e1076461SJosh Poimboeuf 	char *dup, *s;
17e1076461SJosh Poimboeuf 
18e1076461SJosh Poimboeuf 	if (!opts.debug_checksum)
19e1076461SJosh Poimboeuf 		return 0;
20e1076461SJosh Poimboeuf 
21e1076461SJosh Poimboeuf 	dup = strdup(opts.debug_checksum);
22e1076461SJosh Poimboeuf 	if (!dup) {
23e1076461SJosh Poimboeuf 		ERROR_GLIBC("strdup");
24e1076461SJosh Poimboeuf 		return -1;
25e1076461SJosh Poimboeuf 	}
26e1076461SJosh Poimboeuf 
27e1076461SJosh Poimboeuf 	s = dup;
28e1076461SJosh Poimboeuf 	while (*s) {
29e1076461SJosh Poimboeuf 		bool found = false;
30e1076461SJosh Poimboeuf 		struct symbol *sym;
31e1076461SJosh Poimboeuf 		char *comma;
32e1076461SJosh Poimboeuf 
33e1076461SJosh Poimboeuf 		comma = strchr(s, ',');
34e1076461SJosh Poimboeuf 		if (comma)
35e1076461SJosh Poimboeuf 			*comma = '\0';
36e1076461SJosh Poimboeuf 
37e1076461SJosh Poimboeuf 		for_each_sym_by_name(file->elf, s, sym) {
38873a2208SJosh Poimboeuf 			if (!is_func_sym(sym) && !is_object_sym(sym))
39e1076461SJosh Poimboeuf 				continue;
40e1076461SJosh Poimboeuf 			sym->debug_checksum = 1;
41e1076461SJosh Poimboeuf 			found = true;
42e1076461SJosh Poimboeuf 		}
43e1076461SJosh Poimboeuf 
44e1076461SJosh Poimboeuf 		if (!found)
45e1076461SJosh Poimboeuf 			WARN("--debug-checksum: can't find '%s'", s);
46e1076461SJosh Poimboeuf 
47e1076461SJosh Poimboeuf 		if (!comma)
48e1076461SJosh Poimboeuf 			break;
49e1076461SJosh Poimboeuf 
50e1076461SJosh Poimboeuf 		s = comma + 1;
51e1076461SJosh Poimboeuf 	}
52e1076461SJosh Poimboeuf 
53e1076461SJosh Poimboeuf 	free(dup);
54e1076461SJosh Poimboeuf 	return 0;
55e1076461SJosh Poimboeuf }
56e1076461SJosh Poimboeuf 
57e1076461SJosh Poimboeuf static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
58e1076461SJosh Poimboeuf 				 struct instruction *insn)
59e1076461SJosh Poimboeuf {
60e1076461SJosh Poimboeuf 	struct reloc *reloc = insn_reloc(file, insn);
61e1076461SJosh Poimboeuf 	struct alternative *alt;
62e1076461SJosh Poimboeuf 	unsigned long offset;
63e1076461SJosh Poimboeuf 	struct symbol *sym;
64e1076461SJosh Poimboeuf 	static bool in_alt;
65e1076461SJosh Poimboeuf 
66e1076461SJosh Poimboeuf 	if (insn->fake)
67e1076461SJosh Poimboeuf 		return;
68e1076461SJosh Poimboeuf 
69e1076461SJosh Poimboeuf 	if (!reloc) {
70e1076461SJosh Poimboeuf 		struct symbol *call_dest = insn_call_dest(insn);
71*cca84cb1SJosh Poimboeuf 		struct instruction *jump_dest = insn->jump_dest;
72e1076461SJosh Poimboeuf 
73*cca84cb1SJosh Poimboeuf 		/*
74*cca84cb1SJosh Poimboeuf 		 * For a jump/call non-relocated dest offset embedded in the
75*cca84cb1SJosh Poimboeuf 		 * instruction, the offset may vary due to changes in
76*cca84cb1SJosh Poimboeuf 		 * surrounding code.  Just hash the opcode and a
77*cca84cb1SJosh Poimboeuf 		 * position-independent representation of the destination.
78*cca84cb1SJosh Poimboeuf 		 */
79*cca84cb1SJosh Poimboeuf 
80*cca84cb1SJosh Poimboeuf 		if (call_dest || jump_dest) {
81*cca84cb1SJosh Poimboeuf 			unsigned char buf[16];
82*cca84cb1SJosh Poimboeuf 			size_t len;
83*cca84cb1SJosh Poimboeuf 
84*cca84cb1SJosh Poimboeuf 			len = arch_jump_opcode_bytes(file, insn, buf);
85*cca84cb1SJosh Poimboeuf 			__checksum_update_insn(func, insn, buf, len);
86*cca84cb1SJosh Poimboeuf 
87*cca84cb1SJosh Poimboeuf 			if (call_dest) {
88873a2208SJosh Poimboeuf 				__checksum_update_insn(func, insn, call_dest->demangled_name,
89e1076461SJosh Poimboeuf 						       strlen(call_dest->demangled_name));
90*cca84cb1SJosh Poimboeuf 
91*cca84cb1SJosh Poimboeuf 			} else if (jump_dest) {
92*cca84cb1SJosh Poimboeuf 				struct symbol *dest_sym;
93*cca84cb1SJosh Poimboeuf 				unsigned long offset;
94*cca84cb1SJosh Poimboeuf 
95*cca84cb1SJosh Poimboeuf 				/*
96*cca84cb1SJosh Poimboeuf 				 * use insn->_sym instead of insn_sym() here.
97*cca84cb1SJosh Poimboeuf 				 * For alternative replacements, the latter
98*cca84cb1SJosh Poimboeuf 				 * would give the function of the code being
99*cca84cb1SJosh Poimboeuf 				 * replaced.
100*cca84cb1SJosh Poimboeuf 				 */
101*cca84cb1SJosh Poimboeuf 				dest_sym = jump_dest->_sym;
102*cca84cb1SJosh Poimboeuf 				if (!dest_sym)
103*cca84cb1SJosh Poimboeuf 					goto alts;
104*cca84cb1SJosh Poimboeuf 
105*cca84cb1SJosh Poimboeuf 				__checksum_update_insn(func, insn, dest_sym->demangled_name,
106*cca84cb1SJosh Poimboeuf 						       strlen(dest_sym->demangled_name));
107*cca84cb1SJosh Poimboeuf 
108*cca84cb1SJosh Poimboeuf 				offset = jump_dest->offset - dest_sym->offset;
109*cca84cb1SJosh Poimboeuf 				__checksum_update_insn(func, insn, &offset, sizeof(offset));
110*cca84cb1SJosh Poimboeuf 			}
111*cca84cb1SJosh Poimboeuf 
112e1076461SJosh Poimboeuf 			goto alts;
113e1076461SJosh Poimboeuf 		}
114*cca84cb1SJosh Poimboeuf 	}
115*cca84cb1SJosh Poimboeuf 
116*cca84cb1SJosh Poimboeuf 	__checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len);
117*cca84cb1SJosh Poimboeuf 
118*cca84cb1SJosh Poimboeuf 	if (!reloc)
119*cca84cb1SJosh Poimboeuf 		goto alts;
120e1076461SJosh Poimboeuf 
121e1076461SJosh Poimboeuf 	sym = reloc->sym;
122e1076461SJosh Poimboeuf 	offset = arch_insn_adjusted_addend(insn, reloc);
123e1076461SJosh Poimboeuf 
124e1076461SJosh Poimboeuf 	if (is_string_sec(sym->sec)) {
125e1076461SJosh Poimboeuf 		char *str;
126e1076461SJosh Poimboeuf 
127e1076461SJosh Poimboeuf 		str = sym->sec->data->d_buf + sym->offset + offset;
128873a2208SJosh Poimboeuf 		__checksum_update_insn(func, insn, str, strlen(str));
129e1076461SJosh Poimboeuf 		goto alts;
130e1076461SJosh Poimboeuf 	}
131e1076461SJosh Poimboeuf 
132e1076461SJosh Poimboeuf 	if (is_sec_sym(sym)) {
133e1076461SJosh Poimboeuf 		sym = find_symbol_containing(reloc->sym->sec, offset);
134e1076461SJosh Poimboeuf 		if (!sym)
135e1076461SJosh Poimboeuf 			goto alts;
136e1076461SJosh Poimboeuf 
137e1076461SJosh Poimboeuf 		offset -= sym->offset;
138e1076461SJosh Poimboeuf 	}
139e1076461SJosh Poimboeuf 
140873a2208SJosh Poimboeuf 	__checksum_update_insn(func, insn, sym->demangled_name,
141873a2208SJosh Poimboeuf 			       strlen(sym->demangled_name));
142873a2208SJosh Poimboeuf 	__checksum_update_insn(func, insn, &offset, sizeof(offset));
143e1076461SJosh Poimboeuf 
144e1076461SJosh Poimboeuf alts:
145e1076461SJosh Poimboeuf 	for (alt = insn->alts; alt; alt = alt->next) {
146e1076461SJosh Poimboeuf 		struct alt_group *alt_group = alt->insn->alt_group;
147e1076461SJosh Poimboeuf 
148e1076461SJosh Poimboeuf 		/* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */
149e1076461SJosh Poimboeuf 		if (in_alt)
150e1076461SJosh Poimboeuf 			break;
151e1076461SJosh Poimboeuf 		in_alt = true;
152e1076461SJosh Poimboeuf 
153873a2208SJosh Poimboeuf 		__checksum_update_insn(func, insn, &alt->type,
154873a2208SJosh Poimboeuf 				       sizeof(alt->type));
155e1076461SJosh Poimboeuf 
156e1076461SJosh Poimboeuf 		if (alt_group && alt_group->orig_group) {
157e1076461SJosh Poimboeuf 			struct instruction *alt_insn;
158e1076461SJosh Poimboeuf 
159873a2208SJosh Poimboeuf 			__checksum_update_insn(func, insn, &alt_group->feature,sizeof(alt_group->feature));
160e1076461SJosh Poimboeuf 
161e1076461SJosh Poimboeuf 			for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) {
162e1076461SJosh Poimboeuf 				checksum_update_insn(file, func, alt_insn);
163e1076461SJosh Poimboeuf 				if (!alt_group->last_insn || alt_insn == alt_group->last_insn)
164e1076461SJosh Poimboeuf 					break;
165e1076461SJosh Poimboeuf 			}
166e1076461SJosh Poimboeuf 		} else {
167e1076461SJosh Poimboeuf 			checksum_update_insn(file, func, alt->insn);
168e1076461SJosh Poimboeuf 		}
169e1076461SJosh Poimboeuf 
170e1076461SJosh Poimboeuf 		in_alt = false;
171e1076461SJosh Poimboeuf 	}
172e1076461SJosh Poimboeuf }
173e1076461SJosh Poimboeuf 
174873a2208SJosh Poimboeuf static void checksum_update_object(struct objtool_file *file, struct symbol *sym)
175873a2208SJosh Poimboeuf {
176873a2208SJosh Poimboeuf 	struct reloc *reloc;
177873a2208SJosh Poimboeuf 
178873a2208SJosh Poimboeuf 	__checksum_update_object(sym, 0, "len", &sym->len, sizeof(sym->len));
179873a2208SJosh Poimboeuf 
180873a2208SJosh Poimboeuf 	if (sym->sec->data->d_buf)
181873a2208SJosh Poimboeuf 		__checksum_update_object(sym, 0, "data",
182873a2208SJosh Poimboeuf 					 sym->sec->data->d_buf + sym->offset,
183873a2208SJosh Poimboeuf 					 sym->len);
184873a2208SJosh Poimboeuf 
185873a2208SJosh Poimboeuf 	sym_for_each_reloc(file->elf, sym, reloc) {
186873a2208SJosh Poimboeuf 		unsigned long sym_offset = reloc_offset(reloc) - sym->offset;
187873a2208SJosh Poimboeuf 		struct symbol *target = reloc->sym;
188873a2208SJosh Poimboeuf 		s64 offset;
189873a2208SJosh Poimboeuf 
190873a2208SJosh Poimboeuf 		offset = reloc_addend(reloc);
191873a2208SJosh Poimboeuf 
192873a2208SJosh Poimboeuf 		if (is_string_sec(target->sec)) {
193873a2208SJosh Poimboeuf 			char *str;
194873a2208SJosh Poimboeuf 
195873a2208SJosh Poimboeuf 			str = target->sec->data->d_buf + target->offset + offset;
196873a2208SJosh Poimboeuf 			__checksum_update_object(sym, sym_offset,
197873a2208SJosh Poimboeuf 						 "reloc string", str, strlen(str));
198873a2208SJosh Poimboeuf 			continue;
199873a2208SJosh Poimboeuf 		}
200873a2208SJosh Poimboeuf 
201873a2208SJosh Poimboeuf 		if (is_sec_sym(target)) {
202873a2208SJosh Poimboeuf 			target = find_symbol_containing(reloc->sym->sec, offset);
203873a2208SJosh Poimboeuf 			if (!target)
204873a2208SJosh Poimboeuf 				continue;
205873a2208SJosh Poimboeuf 
206873a2208SJosh Poimboeuf 			offset -= target->offset;
207873a2208SJosh Poimboeuf 		}
208873a2208SJosh Poimboeuf 
209873a2208SJosh Poimboeuf 		__checksum_update_object(sym, sym_offset, "reloc name",
210873a2208SJosh Poimboeuf 					 target->demangled_name,
211873a2208SJosh Poimboeuf 					 strlen(target->demangled_name));
212873a2208SJosh Poimboeuf 		__checksum_update_object(sym, sym_offset, "reloc addend",
213873a2208SJosh Poimboeuf 					 &offset, sizeof(offset));
214873a2208SJosh Poimboeuf 	}
215873a2208SJosh Poimboeuf }
216873a2208SJosh Poimboeuf 
217e1076461SJosh Poimboeuf int calculate_checksums(struct objtool_file *file)
218e1076461SJosh Poimboeuf {
219e1076461SJosh Poimboeuf 	struct instruction *insn;
220873a2208SJosh Poimboeuf 	struct symbol *sym;
221e1076461SJosh Poimboeuf 
222e1076461SJosh Poimboeuf 	if (checksum_debug_init(file))
223e1076461SJosh Poimboeuf 		return -1;
224e1076461SJosh Poimboeuf 
225873a2208SJosh Poimboeuf 	for_each_sym(file->elf, sym) {
226873a2208SJosh Poimboeuf 
227e1076461SJosh Poimboeuf 		/*
228e1076461SJosh Poimboeuf 		 * Skip cold subfunctions and aliases: they share the
229e1076461SJosh Poimboeuf 		 * parent's checksum via func_for_each_insn() which
230e1076461SJosh Poimboeuf 		 * follows func->cfunc into the cold subfunction.
231e1076461SJosh Poimboeuf 		 */
232873a2208SJosh Poimboeuf 		if (is_cold_func(sym) || is_alias_sym(sym) || !sym->len ||
233873a2208SJosh Poimboeuf 		    !sym->sec || !sym->sec->data)
234e1076461SJosh Poimboeuf 			continue;
235e1076461SJosh Poimboeuf 
236873a2208SJosh Poimboeuf 		if (is_func_sym(sym)) {
237873a2208SJosh Poimboeuf 			checksum_init(sym);
238873a2208SJosh Poimboeuf 			func_for_each_insn(file, sym, insn)
239873a2208SJosh Poimboeuf 				checksum_update_insn(file, sym, insn);
240873a2208SJosh Poimboeuf 			checksum_finish(sym);
241e1076461SJosh Poimboeuf 
242873a2208SJosh Poimboeuf 		} else if (is_object_sym(sym)) {
243873a2208SJosh Poimboeuf 			checksum_init(sym);
244873a2208SJosh Poimboeuf 			checksum_update_object(file, sym);
245873a2208SJosh Poimboeuf 			checksum_finish(sym);
246e1076461SJosh Poimboeuf 		}
247873a2208SJosh Poimboeuf 
248873a2208SJosh Poimboeuf 	}
249873a2208SJosh Poimboeuf 
250e1076461SJosh Poimboeuf 	return 0;
251e1076461SJosh Poimboeuf }
252e1076461SJosh Poimboeuf 
253e1076461SJosh Poimboeuf int create_sym_checksum_section(struct objtool_file *file)
254e1076461SJosh Poimboeuf {
255e1076461SJosh Poimboeuf 	struct section *sec;
256e1076461SJosh Poimboeuf 	struct symbol *sym;
257e1076461SJosh Poimboeuf 	unsigned int idx = 0;
258e1076461SJosh Poimboeuf 	struct sym_checksum *checksum;
259e1076461SJosh Poimboeuf 	size_t entsize = sizeof(struct sym_checksum);
260e1076461SJosh Poimboeuf 
261e1076461SJosh Poimboeuf 	sec = find_section_by_name(file->elf, ".discard.sym_checksum");
262e1076461SJosh Poimboeuf 	if (sec) {
263e1076461SJosh Poimboeuf 		if (!opts.dryrun)
264e1076461SJosh Poimboeuf 			WARN("file already has .discard.sym_checksum section, skipping");
265e1076461SJosh Poimboeuf 
266e1076461SJosh Poimboeuf 		return 0;
267e1076461SJosh Poimboeuf 	}
268e1076461SJosh Poimboeuf 
269e1076461SJosh Poimboeuf 	for_each_sym(file->elf, sym)
270e1076461SJosh Poimboeuf 		if (sym->csum.checksum)
271e1076461SJosh Poimboeuf 			idx++;
272e1076461SJosh Poimboeuf 
273e1076461SJosh Poimboeuf 	sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize,
274e1076461SJosh Poimboeuf 				      idx, idx);
275e1076461SJosh Poimboeuf 	if (!sec)
276e1076461SJosh Poimboeuf 		return -1;
277e1076461SJosh Poimboeuf 
278e1076461SJosh Poimboeuf 	idx = 0;
279e1076461SJosh Poimboeuf 	for_each_sym(file->elf, sym) {
280e1076461SJosh Poimboeuf 		if (!sym->csum.checksum)
281e1076461SJosh Poimboeuf 			continue;
282e1076461SJosh Poimboeuf 
283e1076461SJosh Poimboeuf 		if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize,
284e1076461SJosh Poimboeuf 				    sym, 0, R_TEXT64))
285e1076461SJosh Poimboeuf 			return -1;
286e1076461SJosh Poimboeuf 
287e1076461SJosh Poimboeuf 		checksum = (struct sym_checksum *)sec->data->d_buf + idx;
288e1076461SJosh Poimboeuf 		checksum->addr = 0; /* reloc */
289e1076461SJosh Poimboeuf 		checksum->checksum = sym->csum.checksum;
290e1076461SJosh Poimboeuf 
291e1076461SJosh Poimboeuf 		mark_sec_changed(file->elf, sec, true);
292e1076461SJosh Poimboeuf 
293e1076461SJosh Poimboeuf 		idx++;
294e1076461SJosh Poimboeuf 	}
295e1076461SJosh Poimboeuf 
296e1076461SJosh Poimboeuf 	return 0;
297e1076461SJosh Poimboeuf }
298e1076461SJosh Poimboeuf 
299e1076461SJosh Poimboeuf static const char * const klp_checksum_usage[] = {
300e1076461SJosh Poimboeuf 	"objtool klp checksum [<options>] file.o",
301e1076461SJosh Poimboeuf 	NULL,
302e1076461SJosh Poimboeuf };
303e1076461SJosh Poimboeuf 
304e1076461SJosh Poimboeuf int cmd_klp_checksum(int argc, const char **argv)
305e1076461SJosh Poimboeuf {
306e1076461SJosh Poimboeuf 	struct objtool_file *file;
307e1076461SJosh Poimboeuf 	int ret;
308e1076461SJosh Poimboeuf 
309e1076461SJosh Poimboeuf 	const struct option options[] = {
310873a2208SJosh Poimboeuf 		OPT_STRING(0,	"debug-checksum", &opts.debug_checksum,	"syms", "enable checksum debug output"),
311e1076461SJosh Poimboeuf 		OPT_BOOLEAN(0,	"dry-run", &opts.dryrun, "don't write modifications"),
312e1076461SJosh Poimboeuf 		OPT_END(),
313e1076461SJosh Poimboeuf 	};
314e1076461SJosh Poimboeuf 
315e1076461SJosh Poimboeuf 	argc = parse_options(argc, argv, options, klp_checksum_usage, 0);
316e1076461SJosh Poimboeuf 	if (argc != 1)
317e1076461SJosh Poimboeuf 		usage_with_options(klp_checksum_usage, options);
318e1076461SJosh Poimboeuf 
319e1076461SJosh Poimboeuf 	opts.checksum = true;
320e1076461SJosh Poimboeuf 
321e1076461SJosh Poimboeuf 	objname = argv[0];
322e1076461SJosh Poimboeuf 
323e1076461SJosh Poimboeuf 	file = objtool_open_read(objname);
324e1076461SJosh Poimboeuf 	if (!file)
325e1076461SJosh Poimboeuf 		return 1;
326e1076461SJosh Poimboeuf 
327e1076461SJosh Poimboeuf 	ret = decode_file(file);
328e1076461SJosh Poimboeuf 	if (ret)
329e1076461SJosh Poimboeuf 		goto out;
330e1076461SJosh Poimboeuf 
331e1076461SJosh Poimboeuf 	ret = calculate_checksums(file);
332e1076461SJosh Poimboeuf 	if (ret)
333e1076461SJosh Poimboeuf 		goto out;
334e1076461SJosh Poimboeuf 
335e1076461SJosh Poimboeuf 	ret = create_sym_checksum_section(file);
336e1076461SJosh Poimboeuf 
337e1076461SJosh Poimboeuf out:
338e1076461SJosh Poimboeuf 	free_insns(file);
339e1076461SJosh Poimboeuf 
340e1076461SJosh Poimboeuf 	if (ret)
341e1076461SJosh Poimboeuf 		return ret;
342e1076461SJosh Poimboeuf 
343e1076461SJosh Poimboeuf 	if (!opts.dryrun && file->elf->changed && elf_write(file->elf))
344e1076461SJosh Poimboeuf 		return 1;
345e1076461SJosh Poimboeuf 
346e1076461SJosh Poimboeuf 	return elf_close(file->elf);
347e1076461SJosh Poimboeuf }
348