xref: /linux/tools/objtool/klp-checksum.c (revision e10764614ad634071d3bc8cfbf8bce43285d458d)
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))
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(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(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(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(func, insn, sym->demangled_name, strlen(sym->demangled_name));
100 	checksum_update(func, insn, &offset, sizeof(offset));
101 
102 alts:
103 	for (alt = insn->alts; alt; alt = alt->next) {
104 		struct alt_group *alt_group = alt->insn->alt_group;
105 
106 		/* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */
107 		if (in_alt)
108 			break;
109 		in_alt = true;
110 
111 		checksum_update(func, insn, &alt->type, sizeof(alt->type));
112 
113 		if (alt_group && alt_group->orig_group) {
114 			struct instruction *alt_insn;
115 
116 			checksum_update(func, insn, &alt_group->feature, sizeof(alt_group->feature));
117 
118 			for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) {
119 				checksum_update_insn(file, func, alt_insn);
120 				if (!alt_group->last_insn || alt_insn == alt_group->last_insn)
121 					break;
122 			}
123 		} else {
124 			checksum_update_insn(file, func, alt->insn);
125 		}
126 
127 		in_alt = false;
128 	}
129 }
130 
131 int calculate_checksums(struct objtool_file *file)
132 {
133 	struct instruction *insn;
134 	struct symbol *func;
135 
136 	if (checksum_debug_init(file))
137 		return -1;
138 
139 	for_each_sym(file->elf, func) {
140 		/*
141 		 * Skip cold subfunctions and aliases: they share the
142 		 * parent's checksum via func_for_each_insn() which
143 		 * follows func->cfunc into the cold subfunction.
144 		 */
145 		if (!is_func_sym(func) || is_cold_func(func) ||
146 		    is_alias_sym(func) || !func->len)
147 			continue;
148 
149 		checksum_init(func);
150 
151 		func_for_each_insn(file, func, insn)
152 			checksum_update_insn(file, func, insn);
153 
154 		checksum_finish(func);
155 	}
156 	return 0;
157 }
158 
159 int create_sym_checksum_section(struct objtool_file *file)
160 {
161 	struct section *sec;
162 	struct symbol *sym;
163 	unsigned int idx = 0;
164 	struct sym_checksum *checksum;
165 	size_t entsize = sizeof(struct sym_checksum);
166 
167 	sec = find_section_by_name(file->elf, ".discard.sym_checksum");
168 	if (sec) {
169 		if (!opts.dryrun)
170 			WARN("file already has .discard.sym_checksum section, skipping");
171 
172 		return 0;
173 	}
174 
175 	for_each_sym(file->elf, sym)
176 		if (sym->csum.checksum)
177 			idx++;
178 
179 	sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize,
180 				      idx, idx);
181 	if (!sec)
182 		return -1;
183 
184 	idx = 0;
185 	for_each_sym(file->elf, sym) {
186 		if (!sym->csum.checksum)
187 			continue;
188 
189 		if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize,
190 				    sym, 0, R_TEXT64))
191 			return -1;
192 
193 		checksum = (struct sym_checksum *)sec->data->d_buf + idx;
194 		checksum->addr = 0; /* reloc */
195 		checksum->checksum = sym->csum.checksum;
196 
197 		mark_sec_changed(file->elf, sec, true);
198 
199 		idx++;
200 	}
201 
202 	return 0;
203 }
204 
205 static const char * const klp_checksum_usage[] = {
206 	"objtool klp checksum [<options>] file.o",
207 	NULL,
208 };
209 
210 int cmd_klp_checksum(int argc, const char **argv)
211 {
212 	struct objtool_file *file;
213 	int ret;
214 
215 	const struct option options[] = {
216 		OPT_STRING(0,	"debug-checksum", &opts.debug_checksum,	"funcs", "enable checksum debug output"),
217 		OPT_BOOLEAN(0,	"dry-run", &opts.dryrun, "don't write modifications"),
218 		OPT_END(),
219 	};
220 
221 	argc = parse_options(argc, argv, options, klp_checksum_usage, 0);
222 	if (argc != 1)
223 		usage_with_options(klp_checksum_usage, options);
224 
225 	opts.checksum = true;
226 
227 	objname = argv[0];
228 
229 	file = objtool_open_read(objname);
230 	if (!file)
231 		return 1;
232 
233 	ret = decode_file(file);
234 	if (ret)
235 		goto out;
236 
237 	ret = calculate_checksums(file);
238 	if (ret)
239 		goto out;
240 
241 	ret = create_sym_checksum_section(file);
242 
243 out:
244 	free_insns(file);
245 
246 	if (ret)
247 		return ret;
248 
249 	if (!opts.dryrun && file->elf->changed && elf_write(file->elf))
250 		return 1;
251 
252 	return elf_close(file->elf);
253 }
254