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