projects/linux·blob master · refs

421 lines · 9.3 KiB

1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Compute "sympos", the position used by livepatch to disambiguate
4 * duplicate symbol names in the patched object.
5 */
6#include <stdlib.h>
7#include <string.h>
8#include <fcntl.h>
9
10#include <objtool/objtool.h>
11#include <objtool/warn.h>
12#include <objtool/endianness.h>
13#include <objtool/klp.h>
14
15#include <linux/string.h>
16
17struct vmlinux_sym {
18 struct hlist_node hash;
19 const char *name;
20 u64 addr;
21};
22
23struct vmlinux_symid {
24 struct hlist_node hash;
25 u64 id;
26 u64 addr;
27};
28
29struct vmlinux_o_symid {
30 struct hlist_node hash;
31 u64 id;
32 unsigned int sym_idx;
33};
34
35static DEFINE_HASHTABLE(vmlinux_o_symids, 16);
36
37/*
38 * The original linked kernel, found next to the orig vmlinux.o. Read with raw
39 * libelf rather than elf_open_read(): only the symbol table and the resolved
40 * .klp.symid table are needed, not the (huge) instruction/reloc machinery.
41 *
42 * Both tables are built once by read_orig_vmlinux(). The Elf handle stays
43 * open because the hashed names point into its mmapped string table.
44 */
45static struct {
46 Elf *elf;
47 DECLARE_HASHTABLE(syms, 16); /* name -> address */
48 DECLARE_HASHTABLE(symids, 16); /* .klp.symid id -> address */
49} vmlinux;
50
51/*
52 * Would the symbol be visible to the runtime's kallsyms-based symbol lookup?
53 */
54static bool vmlinux_sym_in_kallsyms(Elf *elf, GElf_Sym *sym)
55{
56 unsigned int type = GELF_ST_TYPE(sym->st_info);
57 GElf_Shdr shdr;
58 Elf_Scn *scn;
59
60 if (sym->st_shndx == SHN_UNDEF || sym->st_shndx >= SHN_LORESERVE)
61 return false;
62
63 if (type == STT_SECTION || type == STT_FILE)
64 return false;
65
66 scn = elf_getscn(elf, sym->st_shndx);
67 if (!scn || !gelf_getshdr(scn, &shdr))
68 return false;
69
70 return shdr.sh_flags & SHF_ALLOC;
71}
72
73static int read_orig_vmlinux(const char *filename)
74{
75 size_t shstrndx, nr_syms = 0, nr_symids = 0, strtab_idx = 0;
76 Elf_Data *symtab_data = NULL, *symid_data = NULL;
77 struct klp_symid *symids;
78 Elf_Scn *scn = NULL;
79 GElf_Ehdr ehdr;
80 int fd;
81
82 fd = open(filename, O_RDONLY);
83 if (fd == -1) {
84 ERROR_GLIBC("can't open '%s'", filename);
85 return -1;
86 }
87
88 if (elf_version(EV_CURRENT) == EV_NONE) {
89 ERROR_ELF("elf_version");
90 return -1;
91 }
92
93 vmlinux.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL);
94 if (!vmlinux.elf) {
95 ERROR_ELF("elf_begin");
96 return -1;
97 }
98
99 if (!gelf_getehdr(vmlinux.elf, &ehdr)) {
100 ERROR_ELF("gelf_getehdr");
101 return -1;
102 }
103
104 if (elf_getshdrstrndx(vmlinux.elf, &shstrndx)) {
105 ERROR_ELF("elf_getshdrstrndx");
106 return -1;
107 }
108
109 while ((scn = elf_nextscn(vmlinux.elf, scn))) {
110 const char *name;
111 GElf_Shdr shdr;
112
113 if (!gelf_getshdr(scn, &shdr)) {
114 ERROR_ELF("gelf_getshdr");
115 return -1;
116 }
117
118 if (shdr.sh_type == SHT_SYMTAB) {
119 symtab_data = elf_getdata(scn, NULL);
120 if (!symtab_data) {
121 ERROR_ELF("elf_getdata");
122 return -1;
123 }
124 nr_syms = shdr.sh_size / shdr.sh_entsize;
125 strtab_idx = shdr.sh_link;
126 continue;
127 }
128
129 name = elf_strptr(vmlinux.elf, shstrndx, shdr.sh_name);
130 if (name && !strcmp(name, KLP_SYMID_SEC)) {
131 if (shdr.sh_size % sizeof(struct klp_symid)) {
132 ERROR("%s: %s: struct klp_symid size mismatch",
133 filename, KLP_SYMID_SEC);
134 return -1;
135 }
136 symid_data = elf_getdata(scn, NULL);
137 if (!symid_data) {
138 ERROR_ELF("elf_getdata");
139 return -1;
140 }
141 nr_symids = shdr.sh_size / sizeof(struct klp_symid);
142 }
143 }
144
145 if (!symtab_data) {
146 ERROR("%s: missing symbol table", filename);
147 return -1;
148 }
149
150 if (!symid_data) {
151 ERROR("%s: missing %s section, kernel not built with CONFIG_KLP_BUILD?",
152 filename, KLP_SYMID_SEC);
153 return -1;
154 }
155
156 for (size_t i = 0; i < nr_syms; i++) {
157 struct vmlinux_sym *vsym;
158 const char *name;
159 GElf_Sym s;
160
161 if (!gelf_getsym(symtab_data, i, &s)) {
162 ERROR_ELF("gelf_getsym");
163 return -1;
164 }
165
166 if (!vmlinux_sym_in_kallsyms(vmlinux.elf, &s))
167 continue;
168
169 name = elf_strptr(vmlinux.elf, strtab_idx, s.st_name);
170 if (!name)
171 continue;
172
173 vsym = calloc(1, sizeof(*vsym));
174 if (!vsym) {
175 ERROR_GLIBC("calloc");
176 return -1;
177 }
178
179 vsym->name = name;
180 vsym->addr = s.st_value;
181 hash_add(vmlinux.syms, &vsym->hash, str_hash(name));
182 }
183
184 symids = symid_data->d_buf;
185
186 for (size_t i = 0; i < nr_symids; i++) {
187 struct vmlinux_symid *vsymid;
188
189 vsymid = calloc(1, sizeof(*vsymid));
190 if (!vsymid) {
191 ERROR_GLIBC("calloc");
192 return -1;
193 }
194
195 vsymid->id = __bswap_if_needed(&ehdr, symids[i].id);
196 vsymid->addr = __bswap_if_needed(&ehdr, symids[i].addr);
197 hash_add(vmlinux.symids, &vsymid->hash, vsymid->id);
198 }
199
200 /* the fd and Elf handle stay open, the hashed names live in the mmap */
201 return 0;
202}
203
204/*
205 * Read the orig vmlinux.o's .klp.symid table, an array of entries whose 'addr'
206 * fields have relocs to the symbols they describe.
207 */
208static int read_vmlinux_o_symids(struct elf *vmlinux_o)
209{
210 struct section *sec;
211
212 for_each_sec(vmlinux_o, sec) {
213 unsigned long nr;
214
215 if (strcmp(sec->name, KLP_SYMID_SEC))
216 continue;
217
218 if (sec_size(sec) % sizeof(struct klp_symid)) {
219 ERROR("%s: %s: struct klp_symid size mismatch",
220 vmlinux_o->name, KLP_SYMID_SEC);
221 return -1;
222 }
223
224 nr = sec_size(sec) / sizeof(struct klp_symid);
225
226 for (unsigned long i = 0; i < nr; i++) {
227 unsigned long offset = i * sizeof(struct klp_symid);
228 struct vmlinux_o_symid *entry;
229 struct klp_symid *symid;
230 struct reloc *reloc;
231
232 entry = calloc(1, sizeof(*entry));
233 if (!entry) {
234 ERROR_GLIBC("calloc");
235 return -1;
236 }
237
238 symid = sec->data->d_buf + offset;
239 entry->id = bswap_if_needed(vmlinux_o, symid->id);
240
241 reloc = find_reloc_by_dest(vmlinux_o, sec,
242 offset + offsetof(struct klp_symid, addr));
243 if (!reloc) {
244 ERROR("%s: missing reloc for %s entry",
245 vmlinux_o->name, KLP_SYMID_SEC);
246 return -1;
247 }
248 entry->sym_idx = reloc->sym->idx;
249
250 hash_add(vmlinux_o_symids, &entry->hash, entry->sym_idx);
251 }
252 }
253
254 return 0;
255}
256
257int klp_sympos_init(struct elf *orig)
258{
259 char *filename;
260 int ret;
261
262 if (!str_ends_with(objname, "vmlinux.o"))
263 return 0;
264
265 if (read_vmlinux_o_symids(orig))
266 return -1;
267
268 filename = strndup(objname, strlen(objname) - 2);
269 if (!filename) {
270 ERROR_GLIBC("strndup");
271 return -1;
272 }
273
274 ret = read_orig_vmlinux(filename);
275 free(filename);
276
277 return ret;
278}
279
280/* Find the symbol's id in the orig vmlinux.o's .klp.symid table */
281static int find_vmlinux_o_symid(struct symbol *sym, u64 *id)
282{
283 struct vmlinux_o_symid *entry;
284
285 hash_for_each_possible(vmlinux_o_symids, entry, hash, sym->idx) {
286 if (entry->sym_idx == sym->idx) {
287 *id = entry->id;
288 return 0;
289 }
290 }
291
292 ERROR("no %s entry for symbol %s in orig vmlinux.o", KLP_SYMID_SEC,
293 sym->name);
294 return -1;
295}
296
297/* Find the symbol's final address in the orig vmlinux's .klp.symid table */
298static int find_vmlinux_symid_addr(u64 id, u64 *addr)
299{
300 struct vmlinux_symid *symid;
301
302 hash_for_each_possible(vmlinux.symids, symid, hash, id) {
303 if (symid->id == id) {
304 *addr = symid->addr;
305 return 0;
306 }
307 }
308
309 return -1;
310}
311
312/*
313 * Find the sympos of a vmlinux-local symbol by ranking its final address
314 * among the duplicately named symbols in the linked orig vmlinux, replicating
315 * the order in which kallsyms_on_each_match_symbol() counts them.
316 */
317static unsigned long find_vmlinux_sympos(struct symbol *sym)
318{
319 unsigned long nr_matches = 0, sympos = 1;
320 u32 key = str_hash(sym->name);
321 struct vmlinux_sym *vsym;
322 bool found = false;
323 u64 id, addr;
324
325 hash_for_each_possible(vmlinux.syms, vsym, hash, key)
326 if (!strcmp(vsym->name, sym->name))
327 nr_matches++;
328
329 if (!nr_matches) {
330 ERROR("can't find symbol %s in orig vmlinux", sym->name);
331 return ULONG_MAX;
332 }
333
334 /*
335 * Unique symbols don't need disambiguating. They also have no
336 * .klp.symid entry, which is only emitted for names duplicated in
337 * vmlinux.o, so the lookups below would fail.
338 */
339 if (nr_matches == 1)
340 return 0;
341
342 if (find_vmlinux_o_symid(sym, &id))
343 return ULONG_MAX;
344
345 if (find_vmlinux_symid_addr(id, &addr)) {
346 ERROR("no %s entry for symbol %s in orig vmlinux", KLP_SYMID_SEC,
347 sym->name);
348 return ULONG_MAX;
349 }
350
351 hash_for_each_possible(vmlinux.syms, vsym, hash, key) {
352 if (strcmp(vsym->name, sym->name))
353 continue;
354
355 if (vsym->addr < addr)
356 sympos++;
357 else if (vsym->addr == addr)
358 found = true;
359 }
360
361 if (!found) {
362 ERROR("%s address mismatch for symbol %s, stale orig vmlinux?",
363 KLP_SYMID_SEC, sym->name);
364 return ULONG_MAX;
365 }
366
367 return sympos;
368}
369
370static bool is_init_sym(struct symbol *sym)
371{
372 return strstarts(sym->sec->name, ".init");
373}
374
375/*
376 * "sympos" is used by livepatch to disambiguate duplicate symbol names.
377 */
378unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym)
379{
380 unsigned long sympos = 0, nr_matches = 0;
381 bool has_dup = false;
382 struct symbol *s;
383
384 if (is_init_sym(sym)) {
385 ERROR("%s: can't patch or reference init code/data", sym->name);
386 return ULONG_MAX;
387 }
388
389 if (sym->bind != STB_LOCAL)
390 return 0;
391
392 /*
393 * vmlinux: the final link reorders symbols relative to vmlinux.o,
394 * so the position needs to be derived from the linked orig vmlinux via
395 * the .klp.symid table.
396 */
397 if (vmlinux.elf)
398 return find_vmlinux_sympos(sym);
399
400 /*
401 * modules: the final .ko preserves symbol table order, so a
402 * symtab-order count here matches the runtime count done by
403 * module_kallsyms_on_each_symbol().
404 */
405 for_each_sym(elf, s) {
406 if (!strcmp(s->name, sym->name)) {
407 nr_matches++;
408 if (s == sym)
409 sympos = nr_matches;
410 else
411 has_dup = true;
412 }
413 }
414
415 if (!sympos) {
416 ERROR("can't find sympos for %s", sym->name);
417 return ULONG_MAX;
418 }
419
420 return has_dup ? sympos : 0;
421}