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 17 struct vmlinux_sym { 18 struct hlist_node hash; 19 const char *name; 20 u64 addr; 21 }; 22 23 struct vmlinux_symid { 24 struct hlist_node hash; 25 u64 id; 26 u64 addr; 27 }; 28 29 struct vmlinux_o_symid { 30 struct hlist_node hash; 31 u64 id; 32 unsigned int sym_idx; 33 }; 34 35 static 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 */ 45 static 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 */ 54 static 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 73 static 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 */ 208 static 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 257 int 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 */ 281 static 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 */ 298 static 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 */ 317 static 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 370 /* 371 * "sympos" is used by livepatch to disambiguate duplicate symbol names. 372 */ 373 unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym) 374 { 375 unsigned long sympos = 0, nr_matches = 0; 376 bool has_dup = false; 377 struct symbol *s; 378 379 if (sym->bind != STB_LOCAL) 380 return 0; 381 382 /* 383 * vmlinux: the final link reorders symbols relative to vmlinux.o, 384 * so the position needs to be derived from the linked orig vmlinux via 385 * the .klp.symid table. 386 */ 387 if (vmlinux.elf) 388 return find_vmlinux_sympos(sym); 389 390 /* 391 * modules: the final .ko preserves symbol table order, so a 392 * symtab-order count here matches the runtime count done by 393 * module_kallsyms_on_each_symbol(). 394 */ 395 for_each_sym(elf, s) { 396 if (!strcmp(s->name, sym->name)) { 397 nr_matches++; 398 if (s == sym) 399 sympos = nr_matches; 400 else 401 has_dup = true; 402 } 403 } 404 405 if (!sympos) { 406 ERROR("can't find sympos for %s", sym->name); 407 return ULONG_MAX; 408 } 409 410 return has_dup ? sympos : 0; 411 } 412