1 | //! Parse the Linux vDSO. |
2 | //! |
3 | //! The following code is transliterated from |
4 | //! tools/testing/selftests/vDSO/parse_vdso.c in Linux 5.11, which is licensed |
5 | //! with Creative Commons Zero License, version 1.0, |
6 | //! available at <https://creativecommons.org/publicdomain/zero/1.0/legalcode> |
7 | //! |
8 | //! # Safety |
9 | //! |
10 | //! Parsing the vDSO involves a lot of raw pointer manipulation. This |
11 | //! implementation follows Linux's reference implementation, and adds several |
12 | //! additional safety checks. |
13 | #![allow (unsafe_code)] |
14 | |
15 | use super::c; |
16 | use crate::ffi::CStr; |
17 | use crate::utils::check_raw_pointer; |
18 | use core::ffi::c_void; |
19 | use core::mem::size_of; |
20 | use core::ptr::{null, null_mut}; |
21 | use linux_raw_sys::elf::*; |
22 | |
23 | pub(super) struct Vdso { |
24 | // Load information |
25 | load_addr: *const Elf_Ehdr, |
26 | load_end: *const c_void, // the end of the `PT_LOAD` segment |
27 | pv_offset: usize, // recorded paddr - recorded vaddr |
28 | |
29 | // Symbol table |
30 | symtab: *const Elf_Sym, |
31 | symstrings: *const u8, |
32 | bucket: *const u32, |
33 | chain: *const u32, |
34 | nbucket: u32, |
35 | //nchain: u32, |
36 | |
37 | // Version table |
38 | versym: *const u16, |
39 | verdef: *const Elf_Verdef, |
40 | } |
41 | |
42 | // Straight from the ELF specification. |
43 | fn elf_hash(name: &CStr) -> u32 { |
44 | let mut h: u32 = 0; |
45 | for b: &u8 in name.to_bytes() { |
46 | h = (h << 4).wrapping_add(u32::from(*b)); |
47 | let g: u32 = h & 0xf000_0000; |
48 | if g != 0 { |
49 | h ^= g >> 24; |
50 | } |
51 | h &= !g; |
52 | } |
53 | h |
54 | } |
55 | |
56 | /// Create a `Vdso` value by parsing the vDSO at the `sysinfo_ehdr` address. |
57 | fn init_from_sysinfo_ehdr() -> Option<Vdso> { |
58 | // SAFETY: The auxv initialization code does extensive checks to ensure |
59 | // that the value we get really is an `AT_SYSINFO_EHDR` value from the |
60 | // kernel. |
61 | unsafe { |
62 | let hdr = super::param::auxv::sysinfo_ehdr(); |
63 | |
64 | // If the platform doesn't provide a `AT_SYSINFO_EHDR`, we can't locate |
65 | // the vDSO. |
66 | if hdr.is_null() { |
67 | return None; |
68 | } |
69 | |
70 | let mut vdso = Vdso { |
71 | load_addr: hdr, |
72 | load_end: hdr.cast(), |
73 | pv_offset: 0, |
74 | symtab: null(), |
75 | symstrings: null(), |
76 | bucket: null(), |
77 | chain: null(), |
78 | nbucket: 0, |
79 | //nchain: 0, |
80 | versym: null(), |
81 | verdef: null(), |
82 | }; |
83 | |
84 | let hdr = &*hdr; |
85 | let pt = check_raw_pointer::<Elf_Phdr>(vdso.base_plus(hdr.e_phoff)? as *mut _)?.as_ptr(); |
86 | let mut dyn_: *const Elf_Dyn = null(); |
87 | let mut num_dyn = 0; |
88 | |
89 | // We need two things from the segment table: the load offset |
90 | // and the dynamic table. |
91 | let mut found_vaddr = false; |
92 | for i in 0..hdr.e_phnum { |
93 | let phdr = &*pt.add(i as usize); |
94 | if phdr.p_flags & PF_W != 0 { |
95 | // Don't trust any vDSO that claims to be loading writable |
96 | // segments into memory. |
97 | return None; |
98 | } |
99 | if phdr.p_type == PT_LOAD && !found_vaddr { |
100 | // The segment should be readable and executable, because it |
101 | // contains the symbol table and the function bodies. |
102 | if phdr.p_flags & (PF_R | PF_X) != (PF_R | PF_X) { |
103 | return None; |
104 | } |
105 | found_vaddr = true; |
106 | vdso.load_end = vdso.base_plus(phdr.p_offset.checked_add(phdr.p_memsz)?)?; |
107 | vdso.pv_offset = phdr.p_offset.wrapping_sub(phdr.p_vaddr); |
108 | } else if phdr.p_type == PT_DYNAMIC { |
109 | // If `p_offset` is zero, it's more likely that we're looking |
110 | // at memory that has been zeroed than that the kernel has |
111 | // somehow aliased the `Ehdr` and the `Elf_Dyn` array. |
112 | if phdr.p_offset < size_of::<Elf_Ehdr>() { |
113 | return None; |
114 | } |
115 | |
116 | dyn_ = check_raw_pointer::<Elf_Dyn>(vdso.base_plus(phdr.p_offset)? as *mut _)? |
117 | .as_ptr(); |
118 | num_dyn = phdr.p_memsz / size_of::<Elf_Dyn>(); |
119 | } else if phdr.p_type == PT_INTERP || phdr.p_type == PT_GNU_RELRO { |
120 | // Don't trust any ELF image that has an “interpreter” or |
121 | // that uses RELRO, which is likely to be a user ELF image |
122 | // rather and not the kernel vDSO. |
123 | return None; |
124 | } |
125 | } |
126 | |
127 | if !found_vaddr || dyn_.is_null() { |
128 | return None; // Failed |
129 | } |
130 | |
131 | // Fish out the useful bits of the dynamic table. |
132 | let mut hash: *const u32 = null(); |
133 | vdso.symstrings = null(); |
134 | vdso.symtab = null(); |
135 | vdso.versym = null(); |
136 | vdso.verdef = null(); |
137 | let mut i = 0; |
138 | loop { |
139 | if i == num_dyn { |
140 | return None; |
141 | } |
142 | let d = &*dyn_.add(i); |
143 | match d.d_tag { |
144 | DT_STRTAB => { |
145 | vdso.symstrings = |
146 | check_raw_pointer::<u8>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)? |
147 | .as_ptr(); |
148 | } |
149 | DT_SYMTAB => { |
150 | vdso.symtab = |
151 | check_raw_pointer::<Elf_Sym>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)? |
152 | .as_ptr(); |
153 | } |
154 | DT_HASH => { |
155 | hash = check_raw_pointer::<u32>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)? |
156 | .as_ptr(); |
157 | } |
158 | DT_VERSYM => { |
159 | vdso.versym = |
160 | check_raw_pointer::<u16>(vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _)? |
161 | .as_ptr(); |
162 | } |
163 | DT_VERDEF => { |
164 | vdso.verdef = check_raw_pointer::<Elf_Verdef>( |
165 | vdso.addr_from_elf(d.d_un.d_ptr)? as *mut _, |
166 | )? |
167 | .as_ptr(); |
168 | } |
169 | DT_SYMENT => { |
170 | if d.d_un.d_ptr != size_of::<Elf_Sym>() { |
171 | return None; // Failed |
172 | } |
173 | } |
174 | DT_NULL => break, |
175 | _ => {} |
176 | } |
177 | i = i.checked_add(1)?; |
178 | } |
179 | // The upstream code checks `symstrings`, `symtab`, and `hash` for |
180 | // null; here, `check_raw_pointer` has already done that. |
181 | |
182 | if vdso.verdef.is_null() { |
183 | vdso.versym = null(); |
184 | } |
185 | |
186 | // Parse the hash table header. |
187 | vdso.nbucket = *hash.add(0); |
188 | //vdso.nchain = *hash.add(1); |
189 | vdso.bucket = hash.add(2); |
190 | vdso.chain = hash.add(vdso.nbucket as usize + 2); |
191 | |
192 | // That's all we need. |
193 | Some(vdso) |
194 | } |
195 | } |
196 | |
197 | impl Vdso { |
198 | /// Parse the vDSO. |
199 | /// |
200 | /// Returns `None` if the vDSO can't be located or if it doesn't conform to |
201 | /// our expectations. |
202 | #[inline ] |
203 | pub(super) fn new() -> Option<Self> { |
204 | init_from_sysinfo_ehdr() |
205 | } |
206 | |
207 | /// Check the version for a symbol. |
208 | /// |
209 | /// # Safety |
210 | /// |
211 | /// The raw pointers inside `self` must be valid. |
212 | unsafe fn match_version(&self, mut ver: u16, name: &CStr, hash: u32) -> bool { |
213 | // This is a helper function to check if the version indexed by |
214 | // ver matches name (which hashes to hash). |
215 | // |
216 | // The version definition table is a mess, and I don't know how |
217 | // to do this in better than linear time without allocating memory |
218 | // to build an index. I also don't know why the table has |
219 | // variable size entries in the first place. |
220 | // |
221 | // For added fun, I can't find a comprehensible specification of how |
222 | // to parse all the weird flags in the table. |
223 | // |
224 | // So I just parse the whole table every time. |
225 | |
226 | // First step: find the version definition |
227 | ver &= 0x7fff; // Apparently bit 15 means "hidden" |
228 | let mut def = self.verdef; |
229 | loop { |
230 | if (*def).vd_version != VER_DEF_CURRENT { |
231 | return false; // Failed |
232 | } |
233 | |
234 | if ((*def).vd_flags & VER_FLG_BASE) == 0 && ((*def).vd_ndx & 0x7fff) == ver { |
235 | break; |
236 | } |
237 | |
238 | if (*def).vd_next == 0 { |
239 | return false; // No definition. |
240 | } |
241 | |
242 | def = def |
243 | .cast::<u8>() |
244 | .add((*def).vd_next as usize) |
245 | .cast::<Elf_Verdef>(); |
246 | } |
247 | |
248 | // Now figure out whether it matches. |
249 | let aux = &*(def.cast::<u8>()) |
250 | .add((*def).vd_aux as usize) |
251 | .cast::<Elf_Verdaux>(); |
252 | (*def).vd_hash == hash |
253 | && (name == CStr::from_ptr(self.symstrings.add(aux.vda_name as usize).cast())) |
254 | } |
255 | |
256 | /// Look up a symbol in the vDSO. |
257 | pub(super) fn sym(&self, version: &CStr, name: &CStr) -> *mut c::c_void { |
258 | let ver_hash = elf_hash(version); |
259 | let name_hash = elf_hash(name); |
260 | |
261 | // SAFETY: The pointers in `self` must be valid. |
262 | unsafe { |
263 | let mut chain = *self.bucket.add((name_hash % self.nbucket) as usize); |
264 | |
265 | while chain != STN_UNDEF { |
266 | let sym = &*self.symtab.add(chain as usize); |
267 | |
268 | // Check for a defined global or weak function w/ right name. |
269 | // |
270 | // The reference parser in Linux's parse_vdso.c requires |
271 | // symbols to have type `STT_FUNC`, but on powerpc64, the vDSO |
272 | // uses `STT_NOTYPE`, so allow that too. |
273 | if (ELF_ST_TYPE(sym.st_info) != STT_FUNC && |
274 | ELF_ST_TYPE(sym.st_info) != STT_NOTYPE) |
275 | || (ELF_ST_BIND(sym.st_info) != STB_GLOBAL |
276 | && ELF_ST_BIND(sym.st_info) != STB_WEAK) |
277 | || sym.st_shndx == SHN_UNDEF |
278 | || sym.st_shndx == SHN_ABS |
279 | || ELF_ST_VISIBILITY(sym.st_other) != STV_DEFAULT |
280 | || (name != CStr::from_ptr(self.symstrings.add(sym.st_name as usize).cast())) |
281 | // Check symbol version. |
282 | || (!self.versym.is_null() |
283 | && !self.match_version(*self.versym.add(chain as usize), version, ver_hash)) |
284 | { |
285 | chain = *self.chain.add(chain as usize); |
286 | continue; |
287 | } |
288 | |
289 | let sum = self.addr_from_elf(sym.st_value).unwrap(); |
290 | assert!( |
291 | sum as usize >= self.load_addr as usize |
292 | && sum as usize <= self.load_end as usize |
293 | ); |
294 | return sum as *mut c::c_void; |
295 | } |
296 | } |
297 | |
298 | null_mut() |
299 | } |
300 | |
301 | /// Add the given address to the vDSO base address. |
302 | unsafe fn base_plus(&self, offset: usize) -> Option<*const c_void> { |
303 | // Check for overflow. |
304 | let _ = (self.load_addr as usize).checked_add(offset)?; |
305 | // Add the offset to the base. |
306 | Some(self.load_addr.cast::<u8>().add(offset).cast()) |
307 | } |
308 | |
309 | /// Translate an ELF-address-space address into a usable virtual address. |
310 | unsafe fn addr_from_elf(&self, elf_addr: usize) -> Option<*const c_void> { |
311 | self.base_plus(elf_addr.wrapping_add(self.pv_offset)) |
312 | } |
313 | } |
314 | |