Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | /* * parse_vdso.c: Linux reference vDSO parser * Written by Andrew Lutomirski, 2011-2014. * * This code is meant to be linked in to various programs that run on Linux. * As such, it is available with as few restrictions as possible. This file * is licensed under the Creative Commons Zero License, version 1.0, * available at http://creativecommons.org/publicdomain/zero/1.0/legalcode * * The vDSO is a regular ELF DSO that the kernel maps into user space when * it starts a program. It works equally well in statically and dynamically * linked binaries. * * This code is tested on x86. In principle it should work on any * architecture that has a vDSO. */ #include <stdbool.h> #include <stdint.h> #include <string.h> #include <limits.h> #include <elf.h> #include "parse_vdso.h" /* And here's the code. */ #ifndef ELF_BITS # if ULONG_MAX > 0xffffffffUL # define ELF_BITS 64 # else # define ELF_BITS 32 # endif #endif #define ELF_BITS_XFORM2(bits, x) Elf##bits##_##x #define ELF_BITS_XFORM(bits, x) ELF_BITS_XFORM2(bits, x) #define ELF(x) ELF_BITS_XFORM(ELF_BITS, x) static struct vdso_info { bool valid; /* Load information */ uintptr_t load_addr; uintptr_t load_offset; /* load_addr - recorded vaddr */ /* Symbol table */ ELF(Sym) *symtab; const char *symstrings; ELF(Word) *bucket, *chain; ELF(Word) nbucket, nchain; /* Version table */ ELF(Versym) *versym; ELF(Verdef) *verdef; } vdso_info; /* Straight from the ELF specification. */ static unsigned long elf_hash(const unsigned char *name) { unsigned long h = 0, g; while (*name) { h = (h << 4) + *name++; if (g = h & 0xf0000000) h ^= g >> 24; h &= ~g; } return h; } void vdso_init_from_sysinfo_ehdr(uintptr_t base) { size_t i; bool found_vaddr = false; vdso_info.valid = false; vdso_info.load_addr = base; ELF(Ehdr) *hdr = (ELF(Ehdr)*)base; if (hdr->e_ident[EI_CLASS] != (ELF_BITS == 32 ? ELFCLASS32 : ELFCLASS64)) { return; /* Wrong ELF class -- check ELF_BITS */ } ELF(Phdr) *pt = (ELF(Phdr)*)(vdso_info.load_addr + hdr->e_phoff); ELF(Dyn) *dyn = 0; /* * We need two things from the segment table: the load offset * and the dynamic table. */ for (i = 0; i < hdr->e_phnum; i++) { if (pt[i].p_type == PT_LOAD && !found_vaddr) { found_vaddr = true; vdso_info.load_offset = base + (uintptr_t)pt[i].p_offset - (uintptr_t)pt[i].p_vaddr; } else if (pt[i].p_type == PT_DYNAMIC) { dyn = (ELF(Dyn)*)(base + pt[i].p_offset); } } if (!found_vaddr || !dyn) return; /* Failed */ /* * Fish out the useful bits of the dynamic table. */ ELF(Word) *hash = 0; vdso_info.symstrings = 0; vdso_info.symtab = 0; vdso_info.versym = 0; vdso_info.verdef = 0; for (i = 0; dyn[i].d_tag != DT_NULL; i++) { switch (dyn[i].d_tag) { case DT_STRTAB: vdso_info.symstrings = (const char *) ((uintptr_t)dyn[i].d_un.d_ptr + vdso_info.load_offset); break; case DT_SYMTAB: vdso_info.symtab = (ELF(Sym) *) ((uintptr_t)dyn[i].d_un.d_ptr + vdso_info.load_offset); break; case DT_HASH: hash = (ELF(Word) *) ((uintptr_t)dyn[i].d_un.d_ptr + vdso_info.load_offset); break; case DT_VERSYM: vdso_info.versym = (ELF(Versym) *) ((uintptr_t)dyn[i].d_un.d_ptr + vdso_info.load_offset); break; case DT_VERDEF: vdso_info.verdef = (ELF(Verdef) *) ((uintptr_t)dyn[i].d_un.d_ptr + vdso_info.load_offset); break; } } if (!vdso_info.symstrings || !vdso_info.symtab || !hash) return; /* Failed */ if (!vdso_info.verdef) vdso_info.versym = 0; /* Parse the hash table header. */ vdso_info.nbucket = hash[0]; vdso_info.nchain = hash[1]; vdso_info.bucket = &hash[2]; vdso_info.chain = &hash[vdso_info.nbucket + 2]; /* That's all we need. */ vdso_info.valid = true; } static bool vdso_match_version(ELF(Versym) ver, const char *name, ELF(Word) hash) { /* * This is a helper function to check if the version indexed by * ver matches name (which hashes to hash). * * The version definition table is a mess, and I don't know how * to do this in better than linear time without allocating memory * to build an index. I also don't know why the table has * variable size entries in the first place. * * For added fun, I can't find a comprehensible specification of how * to parse all the weird flags in the table. * * So I just parse the whole table every time. */ /* First step: find the version definition */ ver &= 0x7fff; /* Apparently bit 15 means "hidden" */ ELF(Verdef) *def = vdso_info.verdef; while(true) { if ((def->vd_flags & VER_FLG_BASE) == 0 && (def->vd_ndx & 0x7fff) == ver) break; if (def->vd_next == 0) return false; /* No definition. */ def = (ELF(Verdef) *)((char *)def + def->vd_next); } /* Now figure out whether it matches. */ ELF(Verdaux) *aux = (ELF(Verdaux)*)((char *)def + def->vd_aux); return def->vd_hash == hash && !strcmp(name, vdso_info.symstrings + aux->vda_name); } void *vdso_sym(const char *version, const char *name) { unsigned long ver_hash; if (!vdso_info.valid) return 0; ver_hash = elf_hash(version); ELF(Word) chain = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket]; for (; chain != STN_UNDEF; chain = vdso_info.chain[chain]) { ELF(Sym) *sym = &vdso_info.symtab[chain]; /* Check for a defined global or weak function w/ right name. */ if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC) continue; if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL && ELF64_ST_BIND(sym->st_info) != STB_WEAK) continue; if (sym->st_shndx == SHN_UNDEF) continue; if (strcmp(name, vdso_info.symstrings + sym->st_name)) continue; /* Check symbol version. */ if (vdso_info.versym && !vdso_match_version(vdso_info.versym[chain], version, ver_hash)) continue; return (void *)(vdso_info.load_offset + sym->st_value); } return 0; } void vdso_init_from_auxv(void *auxv) { ELF(auxv_t) *elf_auxv = auxv; for (int i = 0; elf_auxv[i].a_type != AT_NULL; i++) { if (elf_auxv[i].a_type == AT_SYSINFO_EHDR) { vdso_init_from_sysinfo_ehdr(elf_auxv[i].a_un.a_val); return; } } vdso_info.valid = false; } |