| use alloc::vec::Vec; |
| |
| use crate::read::{Bytes, ReadError, ReadRef, Result, StringTable}; |
| use crate::{elf, endian}; |
| |
| use super::FileHeader; |
| |
| /// A version index. |
| #[derive(Debug, Default, Clone, Copy)] |
| pub struct VersionIndex(pub u16); |
| |
| impl VersionIndex { |
| /// Return the version index. |
| pub fn index(&self) -> u16 { |
| self.0 & elf::VERSYM_VERSION |
| } |
| |
| /// Return true if it is the local index. |
| pub fn is_local(&self) -> bool { |
| self.index() == elf::VER_NDX_LOCAL |
| } |
| |
| /// Return true if it is the global index. |
| pub fn is_global(&self) -> bool { |
| self.index() == elf::VER_NDX_GLOBAL |
| } |
| |
| /// Return the hidden flag. |
| pub fn is_hidden(&self) -> bool { |
| self.0 & elf::VERSYM_HIDDEN != 0 |
| } |
| } |
| |
| /// A version definition or requirement. |
| /// |
| /// This is derived from entries in the [`elf::SHT_GNU_VERDEF`] and [`elf::SHT_GNU_VERNEED`] sections. |
| #[derive(Debug, Default, Clone, Copy)] |
| pub struct Version<'data> { |
| name: &'data [u8], |
| hash: u32, |
| // Used to keep track of valid indices in `VersionTable`. |
| valid: bool, |
| } |
| |
| impl<'data> Version<'data> { |
| /// Return the version name. |
| pub fn name(&self) -> &'data [u8] { |
| self.name |
| } |
| |
| /// Return hash of the version name. |
| pub fn hash(&self) -> u32 { |
| self.hash |
| } |
| } |
| |
| /// A table of version definitions and requirements. |
| /// |
| /// It allows looking up the version information for a given symbol index. |
| /// |
| /// This is derived from entries in the [`elf::SHT_GNU_VERSYM`], [`elf::SHT_GNU_VERDEF`] |
| /// and [`elf::SHT_GNU_VERNEED`] sections. |
| /// |
| /// Returned by [`SectionTable::versions`](super::SectionTable::versions). |
| #[derive(Debug, Clone)] |
| pub struct VersionTable<'data, Elf: FileHeader> { |
| symbols: &'data [elf::Versym<Elf::Endian>], |
| versions: Vec<Version<'data>>, |
| } |
| |
| impl<'data, Elf: FileHeader> Default for VersionTable<'data, Elf> { |
| fn default() -> Self { |
| VersionTable { |
| symbols: &[], |
| versions: Vec::new(), |
| } |
| } |
| } |
| |
| impl<'data, Elf: FileHeader> VersionTable<'data, Elf> { |
| /// Parse the version sections. |
| pub fn parse<R: ReadRef<'data>>( |
| endian: Elf::Endian, |
| versyms: &'data [elf::Versym<Elf::Endian>], |
| verdefs: Option<VerdefIterator<'data, Elf>>, |
| verneeds: Option<VerneedIterator<'data, Elf>>, |
| strings: StringTable<'data, R>, |
| ) -> Result<Self> { |
| let mut max_index = 0; |
| if let Some(mut verdefs) = verdefs.clone() { |
| while let Some((verdef, _)) = verdefs.next()? { |
| if verdef.vd_flags.get(endian) & elf::VER_FLG_BASE != 0 { |
| continue; |
| } |
| let index = verdef.vd_ndx.get(endian) & elf::VERSYM_VERSION; |
| if max_index < index { |
| max_index = index; |
| } |
| } |
| } |
| if let Some(mut verneeds) = verneeds.clone() { |
| while let Some((_, mut vernauxs)) = verneeds.next()? { |
| while let Some(vernaux) = vernauxs.next()? { |
| let index = vernaux.vna_other.get(endian) & elf::VERSYM_VERSION; |
| if max_index < index { |
| max_index = index; |
| } |
| } |
| } |
| } |
| |
| // Indices should be sequential, but this could be up to |
| // 32k * size_of::<Version>() if max_index is bad. |
| let mut versions = vec![Version::default(); max_index as usize + 1]; |
| |
| if let Some(mut verdefs) = verdefs { |
| while let Some((verdef, mut verdauxs)) = verdefs.next()? { |
| if verdef.vd_flags.get(endian) & elf::VER_FLG_BASE != 0 { |
| continue; |
| } |
| let index = verdef.vd_ndx.get(endian) & elf::VERSYM_VERSION; |
| if index <= elf::VER_NDX_GLOBAL { |
| // TODO: return error? |
| continue; |
| } |
| if let Some(verdaux) = verdauxs.next()? { |
| versions[usize::from(index)] = Version { |
| name: verdaux.name(endian, strings)?, |
| hash: verdef.vd_hash.get(endian), |
| valid: true, |
| }; |
| } |
| } |
| } |
| if let Some(mut verneeds) = verneeds { |
| while let Some((_, mut vernauxs)) = verneeds.next()? { |
| while let Some(vernaux) = vernauxs.next()? { |
| let index = vernaux.vna_other.get(endian) & elf::VERSYM_VERSION; |
| if index <= elf::VER_NDX_GLOBAL { |
| // TODO: return error? |
| continue; |
| } |
| versions[usize::from(index)] = Version { |
| name: vernaux.name(endian, strings)?, |
| hash: vernaux.vna_hash.get(endian), |
| valid: true, |
| }; |
| } |
| } |
| } |
| |
| Ok(VersionTable { |
| symbols: versyms, |
| versions, |
| }) |
| } |
| |
| /// Return true if the version table is empty. |
| pub fn is_empty(&self) -> bool { |
| self.symbols.is_empty() |
| } |
| |
| /// Return version index for a given symbol index. |
| pub fn version_index(&self, endian: Elf::Endian, index: usize) -> VersionIndex { |
| let version_index = match self.symbols.get(index) { |
| Some(x) => x.0.get(endian), |
| // Ideally this would be VER_NDX_LOCAL for undefined symbols, |
| // but currently there are no checks that need this distinction. |
| None => elf::VER_NDX_GLOBAL, |
| }; |
| VersionIndex(version_index) |
| } |
| |
| /// Return version information for a given symbol version index. |
| /// |
| /// Returns `Ok(None)` for local and global versions. |
| /// Returns `Err(_)` if index is invalid. |
| pub fn version(&self, index: VersionIndex) -> Result<Option<&Version<'data>>> { |
| if index.index() <= elf::VER_NDX_GLOBAL { |
| return Ok(None); |
| } |
| self.versions |
| .get(usize::from(index.index())) |
| .filter(|version| version.valid) |
| .read_error("Invalid ELF symbol version index") |
| .map(Some) |
| } |
| |
| /// Return true if the given symbol index satisfies the requirements of `need`. |
| /// |
| /// Returns false for any error. |
| /// |
| /// Note: this function hasn't been fully tested and is likely to be incomplete. |
| pub fn matches(&self, endian: Elf::Endian, index: usize, need: Option<&Version<'_>>) -> bool { |
| let version_index = self.version_index(endian, index); |
| let def = match self.version(version_index) { |
| Ok(def) => def, |
| Err(_) => return false, |
| }; |
| match (def, need) { |
| (Some(def), Some(need)) => need.hash == def.hash && need.name == def.name, |
| (None, Some(_need)) => { |
| // Version must be present if needed. |
| false |
| } |
| (Some(_def), None) => { |
| // For a dlsym call, use the newest version. |
| // TODO: if not a dlsym call, then use the oldest version. |
| !version_index.is_hidden() |
| } |
| (None, None) => true, |
| } |
| } |
| } |
| |
| /// An iterator for the entries in an ELF [`elf::SHT_GNU_VERDEF`] section. |
| #[derive(Debug, Clone)] |
| pub struct VerdefIterator<'data, Elf: FileHeader> { |
| endian: Elf::Endian, |
| data: Bytes<'data>, |
| } |
| |
| impl<'data, Elf: FileHeader> VerdefIterator<'data, Elf> { |
| pub(super) fn new(endian: Elf::Endian, data: &'data [u8]) -> Self { |
| VerdefIterator { |
| endian, |
| data: Bytes(data), |
| } |
| } |
| |
| /// Return the next `Verdef` entry. |
| pub fn next( |
| &mut self, |
| ) -> Result<Option<(&'data elf::Verdef<Elf::Endian>, VerdauxIterator<'data, Elf>)>> { |
| if self.data.is_empty() { |
| return Ok(None); |
| } |
| |
| let verdef = self |
| .data |
| .read_at::<elf::Verdef<_>>(0) |
| .read_error("ELF verdef is too short")?; |
| |
| let mut verdaux_data = self.data; |
| verdaux_data |
| .skip(verdef.vd_aux.get(self.endian) as usize) |
| .read_error("Invalid ELF vd_aux")?; |
| let verdaux = |
| VerdauxIterator::new(self.endian, verdaux_data.0, verdef.vd_cnt.get(self.endian)); |
| |
| let next = verdef.vd_next.get(self.endian); |
| if next != 0 { |
| self.data |
| .skip(next as usize) |
| .read_error("Invalid ELF vd_next")?; |
| } else { |
| self.data = Bytes(&[]); |
| } |
| Ok(Some((verdef, verdaux))) |
| } |
| } |
| |
| /// An iterator for the auxiliary records for an entry in an ELF [`elf::SHT_GNU_VERDEF`] section. |
| #[derive(Debug, Clone)] |
| pub struct VerdauxIterator<'data, Elf: FileHeader> { |
| endian: Elf::Endian, |
| data: Bytes<'data>, |
| count: u16, |
| } |
| |
| impl<'data, Elf: FileHeader> VerdauxIterator<'data, Elf> { |
| pub(super) fn new(endian: Elf::Endian, data: &'data [u8], count: u16) -> Self { |
| VerdauxIterator { |
| endian, |
| data: Bytes(data), |
| count, |
| } |
| } |
| |
| /// Return the next `Verdaux` entry. |
| pub fn next(&mut self) -> Result<Option<&'data elf::Verdaux<Elf::Endian>>> { |
| if self.count == 0 { |
| return Ok(None); |
| } |
| |
| let verdaux = self |
| .data |
| .read_at::<elf::Verdaux<_>>(0) |
| .read_error("ELF verdaux is too short")?; |
| |
| self.data |
| .skip(verdaux.vda_next.get(self.endian) as usize) |
| .read_error("Invalid ELF vda_next")?; |
| self.count -= 1; |
| Ok(Some(verdaux)) |
| } |
| } |
| |
| /// An iterator for the entries in an ELF [`elf::SHT_GNU_VERNEED`] section. |
| #[derive(Debug, Clone)] |
| pub struct VerneedIterator<'data, Elf: FileHeader> { |
| endian: Elf::Endian, |
| data: Bytes<'data>, |
| } |
| |
| impl<'data, Elf: FileHeader> VerneedIterator<'data, Elf> { |
| pub(super) fn new(endian: Elf::Endian, data: &'data [u8]) -> Self { |
| VerneedIterator { |
| endian, |
| data: Bytes(data), |
| } |
| } |
| |
| /// Return the next `Verneed` entry. |
| pub fn next( |
| &mut self, |
| ) -> Result< |
| Option<( |
| &'data elf::Verneed<Elf::Endian>, |
| VernauxIterator<'data, Elf>, |
| )>, |
| > { |
| if self.data.is_empty() { |
| return Ok(None); |
| } |
| |
| let verneed = self |
| .data |
| .read_at::<elf::Verneed<_>>(0) |
| .read_error("ELF verneed is too short")?; |
| |
| let mut vernaux_data = self.data; |
| vernaux_data |
| .skip(verneed.vn_aux.get(self.endian) as usize) |
| .read_error("Invalid ELF vn_aux")?; |
| let vernaux = |
| VernauxIterator::new(self.endian, vernaux_data.0, verneed.vn_cnt.get(self.endian)); |
| |
| let next = verneed.vn_next.get(self.endian); |
| if next != 0 { |
| self.data |
| .skip(next as usize) |
| .read_error("Invalid ELF vn_next")?; |
| } else { |
| self.data = Bytes(&[]); |
| } |
| Ok(Some((verneed, vernaux))) |
| } |
| } |
| |
| /// An iterator for the auxiliary records for an entry in an ELF [`elf::SHT_GNU_VERNEED`] section. |
| #[derive(Debug, Clone)] |
| pub struct VernauxIterator<'data, Elf: FileHeader> { |
| endian: Elf::Endian, |
| data: Bytes<'data>, |
| count: u16, |
| } |
| |
| impl<'data, Elf: FileHeader> VernauxIterator<'data, Elf> { |
| pub(super) fn new(endian: Elf::Endian, data: &'data [u8], count: u16) -> Self { |
| VernauxIterator { |
| endian, |
| data: Bytes(data), |
| count, |
| } |
| } |
| |
| /// Return the next `Vernaux` entry. |
| pub fn next(&mut self) -> Result<Option<&'data elf::Vernaux<Elf::Endian>>> { |
| if self.count == 0 { |
| return Ok(None); |
| } |
| |
| let vernaux = self |
| .data |
| .read_at::<elf::Vernaux<_>>(0) |
| .read_error("ELF vernaux is too short")?; |
| |
| self.data |
| .skip(vernaux.vna_next.get(self.endian) as usize) |
| .read_error("Invalid ELF vna_next")?; |
| self.count -= 1; |
| Ok(Some(vernaux)) |
| } |
| } |
| |
| impl<Endian: endian::Endian> elf::Verdaux<Endian> { |
| /// Parse the version name from the string table. |
| pub fn name<'data, R: ReadRef<'data>>( |
| &self, |
| endian: Endian, |
| strings: StringTable<'data, R>, |
| ) -> Result<&'data [u8]> { |
| strings |
| .get(self.vda_name.get(endian)) |
| .read_error("Invalid ELF vda_name") |
| } |
| } |
| |
| impl<Endian: endian::Endian> elf::Verneed<Endian> { |
| /// Parse the file from the string table. |
| pub fn file<'data, R: ReadRef<'data>>( |
| &self, |
| endian: Endian, |
| strings: StringTable<'data, R>, |
| ) -> Result<&'data [u8]> { |
| strings |
| .get(self.vn_file.get(endian)) |
| .read_error("Invalid ELF vn_file") |
| } |
| } |
| |
| impl<Endian: endian::Endian> elf::Vernaux<Endian> { |
| /// Parse the version name from the string table. |
| pub fn name<'data, R: ReadRef<'data>>( |
| &self, |
| endian: Endian, |
| strings: StringTable<'data, R>, |
| ) -> Result<&'data [u8]> { |
| strings |
| .get(self.vna_name.get(endian)) |
| .read_error("Invalid ELF vna_name") |
| } |
| } |