xref: /linux/tools/objtool/klp-checksum.c (revision cca84cb12908f1cfcecaef80a7692017e2d6a945)
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