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