261 lines · c
1/*2 * parse_vdso.c: Linux reference vDSO parser3 * 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 file7 * is licensed under the Creative Commons Zero License, version 1.0,8 * available at http://creativecommons.org/publicdomain/zero/1.0/legalcode9 *10 * The vDSO is a regular ELF DSO that the kernel maps into user space when11 * it starts a program. It works equally well in statically and dynamically12 * linked binaries.13 *14 * This code is tested on x86. In principle it should work on any15 * 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 <elf.h>23 24#include "parse_vdso.h"25 26/* And here's the code. */27#ifndef ELF_BITS28# if ULONG_MAX > 0xffffffffUL29# define ELF_BITS 6430# else31# define ELF_BITS 3232# endif33#endif34 35#define ELF_BITS_XFORM2(bits, x) Elf##bits##_##x36#define ELF_BITS_XFORM(bits, x) ELF_BITS_XFORM2(bits, x)37#define ELF(x) ELF_BITS_XFORM(ELF_BITS, x)38 39#ifdef __s390x__40#define ELF_HASH_ENTRY ELF(Xword)41#else42#define ELF_HASH_ENTRY ELF(Word)43#endif44 45static struct vdso_info46{47 bool valid;48 49 /* Load information */50 uintptr_t load_addr;51 uintptr_t load_offset; /* load_addr - recorded vaddr */52 53 /* Symbol table */54 ELF(Sym) *symtab;55 const char *symstrings;56 ELF_HASH_ENTRY *bucket, *chain;57 ELF_HASH_ENTRY nbucket, nchain;58 59 /* Version table */60 ELF(Versym) *versym;61 ELF(Verdef) *verdef;62} vdso_info;63 64/*65 * Straight from the ELF specification...and then tweaked slightly, in order to66 * avoid a few clang warnings.67 */68static unsigned long elf_hash(const char *name)69{70 unsigned long h = 0, g;71 const unsigned char *uch_name = (const unsigned char *)name;72 73 while (*uch_name)74 {75 h = (h << 4) + *uch_name++;76 g = h & 0xf0000000;77 if (g)78 h ^= g >> 24;79 h &= ~g;80 }81 return h;82}83 84void vdso_init_from_sysinfo_ehdr(uintptr_t base)85{86 size_t i;87 bool found_vaddr = false;88 89 vdso_info.valid = false;90 91 vdso_info.load_addr = base;92 93 ELF(Ehdr) *hdr = (ELF(Ehdr)*)base;94 if (hdr->e_ident[EI_CLASS] !=95 (ELF_BITS == 32 ? ELFCLASS32 : ELFCLASS64)) {96 return; /* Wrong ELF class -- check ELF_BITS */97 }98 99 ELF(Phdr) *pt = (ELF(Phdr)*)(vdso_info.load_addr + hdr->e_phoff);100 ELF(Dyn) *dyn = 0;101 102 /*103 * We need two things from the segment table: the load offset104 * and the dynamic table.105 */106 for (i = 0; i < hdr->e_phnum; i++)107 {108 if (pt[i].p_type == PT_LOAD && !found_vaddr) {109 found_vaddr = true;110 vdso_info.load_offset = base111 + (uintptr_t)pt[i].p_offset112 - (uintptr_t)pt[i].p_vaddr;113 } else if (pt[i].p_type == PT_DYNAMIC) {114 dyn = (ELF(Dyn)*)(base + pt[i].p_offset);115 }116 }117 118 if (!found_vaddr || !dyn)119 return; /* Failed */120 121 /*122 * Fish out the useful bits of the dynamic table.123 */124 ELF_HASH_ENTRY *hash = 0;125 vdso_info.symstrings = 0;126 vdso_info.symtab = 0;127 vdso_info.versym = 0;128 vdso_info.verdef = 0;129 for (i = 0; dyn[i].d_tag != DT_NULL; i++) {130 switch (dyn[i].d_tag) {131 case DT_STRTAB:132 vdso_info.symstrings = (const char *)133 ((uintptr_t)dyn[i].d_un.d_ptr134 + vdso_info.load_offset);135 break;136 case DT_SYMTAB:137 vdso_info.symtab = (ELF(Sym) *)138 ((uintptr_t)dyn[i].d_un.d_ptr139 + vdso_info.load_offset);140 break;141 case DT_HASH:142 hash = (ELF_HASH_ENTRY *)143 ((uintptr_t)dyn[i].d_un.d_ptr144 + vdso_info.load_offset);145 break;146 case DT_VERSYM:147 vdso_info.versym = (ELF(Versym) *)148 ((uintptr_t)dyn[i].d_un.d_ptr149 + vdso_info.load_offset);150 break;151 case DT_VERDEF:152 vdso_info.verdef = (ELF(Verdef) *)153 ((uintptr_t)dyn[i].d_un.d_ptr154 + vdso_info.load_offset);155 break;156 }157 }158 if (!vdso_info.symstrings || !vdso_info.symtab || !hash)159 return; /* Failed */160 161 if (!vdso_info.verdef)162 vdso_info.versym = 0;163 164 /* Parse the hash table header. */165 vdso_info.nbucket = hash[0];166 vdso_info.nchain = hash[1];167 vdso_info.bucket = &hash[2];168 vdso_info.chain = &hash[vdso_info.nbucket + 2];169 170 /* That's all we need. */171 vdso_info.valid = true;172}173 174static bool vdso_match_version(ELF(Versym) ver,175 const char *name, ELF(Word) hash)176{177 /*178 * This is a helper function to check if the version indexed by179 * ver matches name (which hashes to hash).180 *181 * The version definition table is a mess, and I don't know how182 * to do this in better than linear time without allocating memory183 * to build an index. I also don't know why the table has184 * variable size entries in the first place.185 *186 * For added fun, I can't find a comprehensible specification of how187 * to parse all the weird flags in the table.188 *189 * So I just parse the whole table every time.190 */191 192 /* First step: find the version definition */193 ver &= 0x7fff; /* Apparently bit 15 means "hidden" */194 ELF(Verdef) *def = vdso_info.verdef;195 while(true) {196 if ((def->vd_flags & VER_FLG_BASE) == 0197 && (def->vd_ndx & 0x7fff) == ver)198 break;199 200 if (def->vd_next == 0)201 return false; /* No definition. */202 203 def = (ELF(Verdef) *)((char *)def + def->vd_next);204 }205 206 /* Now figure out whether it matches. */207 ELF(Verdaux) *aux = (ELF(Verdaux)*)((char *)def + def->vd_aux);208 return def->vd_hash == hash209 && !strcmp(name, vdso_info.symstrings + aux->vda_name);210}211 212void *vdso_sym(const char *version, const char *name)213{214 unsigned long ver_hash;215 if (!vdso_info.valid)216 return 0;217 218 ver_hash = elf_hash(version);219 ELF(Word) chain = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket];220 221 for (; chain != STN_UNDEF; chain = vdso_info.chain[chain]) {222 ELF(Sym) *sym = &vdso_info.symtab[chain];223 224 /* Check for a defined global or weak function w/ right name. */225 if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC &&226 ELF64_ST_TYPE(sym->st_info) != STT_NOTYPE)227 continue;228 if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL &&229 ELF64_ST_BIND(sym->st_info) != STB_WEAK)230 continue;231 if (sym->st_shndx == SHN_UNDEF)232 continue;233 if (strcmp(name, vdso_info.symstrings + sym->st_name))234 continue;235 236 /* Check symbol version. */237 if (vdso_info.versym238 && !vdso_match_version(vdso_info.versym[chain],239 version, ver_hash))240 continue;241 242 return (void *)(vdso_info.load_offset + sym->st_value);243 }244 245 return 0;246}247 248void vdso_init_from_auxv(void *auxv)249{250 ELF(auxv_t) *elf_auxv = auxv;251 for (int i = 0; elf_auxv[i].a_type != AT_NULL; i++)252 {253 if (elf_auxv[i].a_type == AT_SYSINFO_EHDR) {254 vdso_init_from_sysinfo_ehdr(elf_auxv[i].a_un.a_val);255 return;256 }257 }258 259 vdso_info.valid = false;260}261