#![allow(unsafe_code)]
use super::c;
use super::elf::*;
use crate::ffi::ZStr;
use crate::io::{self, madvise, Advice};
use core::mem::{align_of, size_of};
use core::ptr::null;
pub(super) struct Vdso {
load_addr: usize,
load_end: usize, load_offset: usize,
symtab: *const Elf_Sym,
symstrings: *const u8,
bucket: *const u32,
chain: *const u32,
nbucket: u32,
versym: *const u16,
verdef: *const Elf_Verdef,
}
fn elf_hash(name: &ZStr) -> u32 {
let mut h: u32 = 0;
for b in name.to_bytes() {
h = (h << 4).wrapping_add(u32::from(*b));
let g = h & 0xf000_0000;
if g != 0 {
h ^= g >> 24;
}
h &= !g;
}
h
}
const fn make_pointer<T>(value: usize) -> Option<*const T> {
if value == 0 || value.checked_add(size_of::<T>()).is_none() || value % align_of::<T>() != 0 {
return None;
}
Some(value as *const T)
}
unsafe fn init_from_sysinfo_ehdr(base: usize) -> Option<Vdso> {
let hdr = check_vdso_base(base)?;
let mut vdso = Vdso {
load_addr: base,
load_end: base,
load_offset: 0,
symtab: null(),
symstrings: null(),
bucket: null(),
chain: null(),
nbucket: 0,
versym: null(),
verdef: null(),
};
let pt = make_pointer::<Elf_Phdr>(base.checked_add(hdr.e_phoff)?)?;
let mut dyn_: *const Elf_Dyn = null();
let mut num_dyn = 0;
let mut found_vaddr = false;
for i in 0..hdr.e_phnum {
let phdr = &*pt.add(i as usize);
if phdr.p_flags & PF_W != 0 {
return None;
}
if phdr.p_type == PT_LOAD && !found_vaddr {
if phdr.p_flags & (PF_R | PF_X) != (PF_R | PF_X) {
return None;
}
found_vaddr = true;
vdso.load_end = base.checked_add(phdr.p_offset.checked_add(phdr.p_memsz)?)?;
vdso.load_offset = base.wrapping_add(phdr.p_offset.wrapping_sub(phdr.p_vaddr));
} else if phdr.p_type == PT_DYNAMIC {
if phdr.p_offset < size_of::<Elf_Ehdr>() {
return None;
}
dyn_ = make_pointer::<Elf_Dyn>(base.checked_add(phdr.p_offset)?)?;
num_dyn = phdr.p_memsz / size_of::<Elf_Dyn>();
} else if phdr.p_type == PT_INTERP || phdr.p_type == PT_GNU_RELRO {
return None;
}
}
if !found_vaddr || dyn_.is_null() {
return None; }
let mut hash: *const u32 = null();
vdso.symstrings = null();
vdso.symtab = null();
vdso.versym = null();
vdso.verdef = null();
let mut i = 0;
loop {
if i == num_dyn {
return None;
}
let d = &*dyn_.add(i);
match d.d_tag {
DT_STRTAB => {
vdso.symstrings = make_pointer::<u8>(d.d_val.checked_add(vdso.load_offset)?)?;
}
DT_SYMTAB => {
vdso.symtab = make_pointer::<Elf_Sym>(d.d_val.checked_add(vdso.load_offset)?)?;
}
DT_HASH => {
hash = make_pointer::<u32>(d.d_val.checked_add(vdso.load_offset)?)?;
}
DT_VERSYM => {
vdso.versym = make_pointer::<u16>(d.d_val.checked_add(vdso.load_offset)?)?;
}
DT_VERDEF => {
vdso.verdef = make_pointer::<Elf_Verdef>(d.d_val.checked_add(vdso.load_offset)?)?;
}
DT_SYMENT => {
if d.d_val != size_of::<Elf_Sym>() {
return None; }
}
DT_NULL => break,
_ => {}
}
i = i.checked_add(1)?;
}
if vdso.symstrings.is_null() || vdso.symtab.is_null() || hash.is_null() {
return None; }
if vdso.verdef.is_null() {
vdso.versym = null();
}
vdso.nbucket = *hash.add(0);
vdso.bucket = hash.add(2);
vdso.chain = hash.add(vdso.nbucket as usize + 2);
Some(vdso)
}
unsafe fn check_vdso_base<'vdso>(base: usize) -> Option<&'vdso Elf_Ehdr> {
madvise(
base as *mut c::c_void,
size_of::<Elf_Ehdr>(),
Advice::Normal,
)
.ok()?;
let hdr = &*make_pointer::<Elf_Ehdr>(base)?;
if hdr.e_ident[..SELFMAG] != ELFMAG {
return None; }
if hdr.e_ident[EI_CLASS] != ELFCLASS {
return None; }
if hdr.e_ident[EI_DATA] != ELFDATA {
return None; }
if !matches!(hdr.e_ident[EI_OSABI], ELFOSABI_SYSV | ELFOSABI_LINUX) {
return None; }
if hdr.e_ident[EI_ABIVERSION] != ELFABIVERSION {
return None; }
if hdr.e_type != ET_DYN {
return None; }
if hdr.e_machine != EM_CURRENT {
return None; }
if hdr.e_ident[EI_VERSION] != EV_CURRENT
|| hdr.e_ehsize as usize != size_of::<Elf_Ehdr>()
|| hdr.e_phentsize as usize != size_of::<Elf_Phdr>()
{
return None;
}
if hdr.e_phnum == PN_XNUM {
return None;
}
if hdr.e_phoff < size_of::<Elf_Ehdr>() {
return None;
}
{
use super::arch::choose::syscall2;
use super::conv::{clockid_t, ret, void_star};
use super::reg::nr;
use crate::time::ClockId;
use linux_raw_sys::general::__NR_clock_getres;
if ret(syscall2(
nr(__NR_clock_getres),
clockid_t(ClockId::Monotonic),
void_star(base as *mut c::c_void),
)) != Err(io::Error::FAULT)
{
#[cfg(feature = "std")]
{
std::process::abort();
}
#[cfg(all(not(feature = "std"), feature = "rustc-dep-of-std"))]
{
core::intrinsics::abort();
}
}
}
Some(hdr)
}
impl Vdso {
#[inline]
pub(super) fn new() -> Option<Self> {
init_from_auxv()
}
unsafe fn match_version(&self, mut ver: u16, name: &ZStr, hash: u32) -> bool {
ver &= 0x7fff; let mut def = self.verdef;
loop {
if (*def).vd_version != VER_DEF_CURRENT {
return false; }
if ((*def).vd_flags & VER_FLG_BASE) == 0 && ((*def).vd_ndx & 0x7fff) == ver {
break;
}
if (*def).vd_next == 0 {
return false; }
def = def
.cast::<u8>()
.add((*def).vd_next as usize)
.cast::<Elf_Verdef>();
}
let aux = &*(def.cast::<u8>())
.add((*def).vd_aux as usize)
.cast::<Elf_Verdaux>();
(*def).vd_hash == hash
&& (name == ZStr::from_ptr(self.symstrings.add(aux.vda_name as usize).cast()))
}
pub(super) fn sym(&self, version: &ZStr, name: &ZStr) -> *const c::c_void {
let ver_hash = elf_hash(version);
let name_hash = elf_hash(name);
unsafe {
let mut chain = *self.bucket.add((name_hash % self.nbucket) as usize);
while chain != STN_UNDEF {
let sym = &*self.symtab.add(chain as usize);
if (ELF_ST_TYPE(sym.st_info) != STT_FUNC &&
ELF_ST_TYPE(sym.st_info) != STT_NOTYPE)
|| (ELF_ST_BIND(sym.st_info) != STB_GLOBAL
&& ELF_ST_BIND(sym.st_info) != STB_WEAK)
|| sym.st_shndx == SHN_UNDEF
|| sym.st_shndx == SHN_ABS
|| ELF_ST_VISIBILITY(sym.st_other) != STV_DEFAULT
|| (name != ZStr::from_ptr(self.symstrings.add(sym.st_name as usize).cast()))
|| (!self.versym.is_null()
&& !self.match_version(*self.versym.add(chain as usize), version, ver_hash))
{
chain = *self.chain.add(chain as usize);
continue;
}
let sum = self.load_offset.checked_add(sym.st_value).unwrap();
assert!(sum >= self.load_addr && sum <= self.load_end);
return sum as *const c::c_void;
}
}
null()
}
}
fn init_from_auxv() -> Option<Vdso> {
unsafe { init_from_sysinfo_ehdr(super::process::sysinfo_ehdr()) }
}