From d2359114dfb4204cfac9052624f84d32e9e65b5d Mon Sep 17 00:00:00 2001 From: Robert Bartlensky Date: Wed, 27 Jul 2022 01:53:44 +0100 Subject: [PATCH] Handle COPY relocations. We now have a `.copyrel` section where at runtime the loader will copy dynamic "objects" into. --- src/elf.rs | 76 ++++++++++++++++++++++++++++------------- src/elf/chunk.rs | 20 +++++++++-- src/elf/copy_rel.rs | 56 ++++++++++++++++++++++++++++++ src/elf/section.rs | 56 ++++++++++++++---------------- src/elf/symbol_table.rs | 24 ++++++++++--- src/symbol.rs | 30 ++++++++++------ 6 files changed, 190 insertions(+), 72 deletions(-) create mode 100644 src/elf/copy_rel.rs diff --git a/src/elf.rs b/src/elf.rs index d1e915b..0f4416c 100644 --- a/src/elf.rs +++ b/src/elf.rs @@ -28,6 +28,9 @@ use std::{ sync::Arc, }; +mod copy_rel; +pub use copy_rel::CopyRel; + mod options; pub use options::*; @@ -162,6 +165,8 @@ pub struct Writer<'d> { plt_rel_section: SectionPtr<'d>, hash_section: Option>, gnu_hash_section: Option>, + // holds the space required by COPY relocations + copy_rel_section: SectionPtr<'d>, } impl<'d> Writer<'d> { @@ -299,6 +304,11 @@ impl<'d> Writer<'d> { ); } + let copy_rel_section = + Section::builder(section_with_name(section_names.get_or_create(".copyrel").offset)) + .synthetic(CopyRel::default()) + .build(); + let null_section = Section::new(Default::default()); let shstr_section = Section::builder(Default::default()).synthetic(section_names).build(); let got_section = Section::new(got_sh); @@ -325,6 +335,7 @@ impl<'d> Writer<'d> { Arc::clone(&dynamic_section), Arc::clone(&dyn_rel_section), Arc::clone(&plt_rel_section), + Arc::clone(©_rel_section), ]; if let Some(s) = hash_section.clone() { @@ -361,6 +372,7 @@ impl<'d> Writer<'d> { plt_rel_section, hash_section, gnu_hash_section, + copy_rel_section, }) } @@ -467,7 +479,6 @@ impl<'d> Writer<'d> { reference: &'d Path, ) -> Result, ErrorType> { elf_sym.st_shndx = SHN_UNDEF as u16; - elf_sym.st_size = 0; elf_sym.st_value = 0; let r = Self::add_symbol_inner( elf_sym, @@ -693,6 +704,11 @@ impl<'d> Writer<'d> { } } } + + // handle dynamic object symbols + let copy_rel = self.copy_rel_section.read(); + as_mut_sym_tab(&self.dyn_sym_section) + .update_object_symbols(copy_rel.sh_addr, copy_rel.index()); } fn compute_tables(&mut self) { @@ -711,6 +727,7 @@ impl<'d> Writer<'d> { // populate .rela.{dyn,plt} sections let got_addr = self.got_address(); let got_plt_addr = self.got_plt_address(); + let copy_rel_addr = self.copy_rel_section.read().sh_addr; for (section, rels) in [ (&self.dyn_rel_section, &mut self.dyn_rels), (&self.plt_rel_section, &mut self.plt_rels), @@ -721,6 +738,7 @@ impl<'d> Writer<'d> { match r_type(rel.r_info) { R_X86_64_GLOB_DAT => rel.r_offset += got_addr, R_X86_64_JUMP_SLOT => rel.r_offset += got_plt_addr, + R_X86_64_COPY => rel.r_offset += copy_rel_addr, _ => {} } rel.serialize(&mut data); @@ -811,39 +829,47 @@ impl<'d> Writer<'d> { } { + // mappings from dyn_sym to their index in the dyn sym section + let dyn_sym_indices = sorted + .iter() + .enumerate() + .map(|(i, dyn_sym)| (dyn_sym.st_name, i)) + .collect::>(); + drop(dyn_syms); + let mut symbols = as_mut_sym_tab(&self.sym_section); + let mut dyn_syms = as_mut_sym_tab(&self.dyn_sym_section); let section_names = as_str_tab(&self.shstr_section); + for (sym, dyn_sym) in symbols.named_mut().values_mut().filter_map(|s| s.dynamic().map(|d| (s, d))) { - let mut calculated_index = None; - let mut index = || { - if let Some(index) = calculated_index { - index - } else { - let index = sorted - .iter() - .enumerate() - .find(|(_, s)| s.st_name == dyn_sym.st_name()) - .map(|(i, _)| i) - .unwrap(); - calculated_index = Some(index); - index - } - }; + let index = dyn_sym_indices[&dyn_sym.st_name()]; if let Some(offset) = sym.got_offset() { - self.dyn_rels.push(rela(offset, index(), R_X86_64_GLOB_DAT)); + self.dyn_rels.push(rela(offset, index, R_X86_64_GLOB_DAT)); + } + let dyn_symbol = &mut dyn_syms.get_mut(dyn_sym).unwrap(); + if dyn_symbol.is_object() { + let size = dyn_symbol.st_size; + let offset = self + .copy_rel_section + .write() + .inner_mut::() + .unwrap() + .insert(dyn_sym, size); + self.dyn_rels.push(rela(offset as usize, index, R_X86_64_COPY)); + dyn_symbol.st_value = offset; } if let Some(offset) = sym.got_plt_offset() { sym.set_relocation_offset(self.plt_rels.len()); - self.plt_rels.push(rela(offset, index(), R_X86_64_JUMP_SLOT)); + self.plt_rels.push(rela(offset, index, R_X86_64_JUMP_SLOT)); } } self.dyn_rel_section.write().sh_size = (self.dyn_rels.len() * size_of::()) as u64; - self.plt_rel_section.write().sh_size = (self.plt_rels.len() * size_of::()) as u64; drop(symbols); + drop(dyn_syms); // calculate how many dyntags we have, so that we know how big our dynamic section is // + 1 for the null entry let extra_dyn_entries = @@ -851,7 +877,6 @@ impl<'d> Writer<'d> { self.dynamic_section.write().sh_size = ((self.dyn_entries.len() + extra_dyn_entries) * 2 * size_of::()) as u64; } - drop(dyn_syms); self.sort_sections(); @@ -865,19 +890,22 @@ impl<'d> Writer<'d> { fn handle_relocations(&mut self) { let got_address = self.got_address(); let plt_address = self.plt_address(); - let sh_name = self.sym_section.read().sh_name; + let sh_names = [self.sym_section.read().sh_name, self.dyn_sym_section.read().sh_name]; // first patch symbol values - for section in &mut self.sections.iter_mut().skip(1).filter(|s| s.read().sh_name != sh_name) + for section in + &mut self.sections.iter_mut().skip(1).filter(|s| !sh_names.contains(&s.read().sh_name)) { let mut symbols = as_mut_sym_tab(&self.sym_section); section.write().patch_symbol_values(&mut symbols); } let symbols = as_sym_tab(&self.sym_section); - for section in &mut self.sections.iter_mut().skip(1).filter(|s| s.read().sh_name != sh_name) + let dyn_symbols = as_sym_tab(&self.dyn_sym_section); + for section in + &mut self.sections.iter_mut().skip(1).filter(|s| !sh_names.contains(&s.read().sh_name)) { for chunk in section.write().chunks_mut() { - chunk.apply_relocations(got_address, plt_address, &symbols); + chunk.apply_relocations(got_address, plt_address, &symbols, &dyn_symbols); } } } diff --git a/src/elf/chunk.rs b/src/elf/chunk.rs index 674e23c..c0ab8c0 100644 --- a/src/elf/chunk.rs +++ b/src/elf/chunk.rs @@ -40,13 +40,20 @@ impl Chunk { &mut self.relocations } - pub fn apply_relocations(&mut self, got_address: u64, plt_address: u64, table: &SymbolTable) { + pub fn apply_relocations( + &mut self, + got_address: u64, + plt_address: u64, + table: &SymbolTable, + dyn_table: &SymbolTable, + ) { for (rela, symbol_ref) in &self.relocations { apply_relocation( *self.address.as_ref().unwrap(), &mut self.data[..], *rela, table.get(*symbol_ref).unwrap(), + dyn_table, got_address, plt_address, ); @@ -87,10 +94,19 @@ fn apply_relocation( data: &mut [u8], rel: Rela, symbol: &crate::elf::Symbol<'_>, + dyn_table: &SymbolTable, got_address: u64, plt_address: u64, ) { - let s: i64 = symbol.st_value.try_into().unwrap(); + // e.g. `stderr` will be undefined, but we might reference it. Since + // `stderr` is an object, we will know its address at runtime because + // we will issue a COPY relocation to make a copy of `stderr` for our binary. + let s: i64 = match symbol.dynamic() { + Some(dyn_sym) if symbol.is_undefined() => { + dyn_table.get(dyn_sym).unwrap().st_value.try_into().unwrap() + } + _ => symbol.st_value.try_into().unwrap(), + }; let a = rel.r_addend; let p: i64 = (chunk_address + rel.r_offset).try_into().unwrap(); let _z = symbol.st_size; diff --git a/src/elf/copy_rel.rs b/src/elf/copy_rel.rs new file mode 100644 index 0000000..273839d --- /dev/null +++ b/src/elf/copy_rel.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; + +use goblin::elf64::section_header::{SectionHeader, SHF_ALLOC, SHF_WRITE, SHT_NOBITS}; + +use super::{ + section::{Synthesized, SynthesizedKind}, + Section, SymbolRef, +}; + +#[derive(Default)] +pub struct CopyRel { + // the total size of the section + size: u64, + // a mappings from a symbol to a "slot" in the section + slots: HashMap, +} + +impl CopyRel { + pub fn insert(&mut self, sym: SymbolRef, size: u64) -> u64 { + let offset = self.size; + self.slots.insert(sym, offset); + self.size += size; + offset + } +} + +impl<'p> Synthesized<'p> for CopyRel { + fn fill_header(&self, sh: &mut SectionHeader) { + *sh = SectionHeader { + sh_type: SHT_NOBITS, + sh_flags: (SHF_ALLOC | SHF_WRITE) as u64, + sh_addralign: 64, + ..*sh + } + } + + fn expand(&self, sh: &mut Section) { + sh.sh_size = self.size as u64; + } + + fn as_ref<'k>(kind: &'k SynthesizedKind<'p>) -> Option<&'k Self> { + if let SynthesizedKind::CopyRel(s) = kind { + Some(s) + } else { + None + } + } + + fn as_ref_mut<'k>(kind: &'k mut SynthesizedKind<'p>) -> Option<&'k mut Self> { + if let SynthesizedKind::CopyRel(s) = kind { + Some(s) + } else { + None + } + } +} diff --git a/src/elf/section.rs b/src/elf/section.rs index a3e364b..87e0655 100644 --- a/src/elf/section.rs +++ b/src/elf/section.rs @@ -10,7 +10,7 @@ use goblin::elf64::section_header::{SectionHeader, SHT_NOBITS}; use parking_lot::RwLock; use std::sync::Arc; -use super::SymbolTable; +use super::{CopyRel, SymbolTable}; pub type SectionPtr<'p> = Arc>>; @@ -65,34 +65,27 @@ impl<'p> SectionBuilder<'p> { pub enum SynthesizedKind<'p> { Plt(Plt), StringTable(StringTable), - Hash(HashTable), - GnuHash(GnuHashTable), + HashTable(HashTable), + GnuHashTable(GnuHashTable), SymbolTable(SymbolTable<'p>), + CopyRel(CopyRel), } -impl From for SynthesizedKind<'_> { - fn from(p: Plt) -> Self { - Self::Plt(p) - } -} - -impl From for SynthesizedKind<'_> { - fn from(p: StringTable) -> Self { - Self::StringTable(p) - } -} - -impl From for SynthesizedKind<'_> { - fn from(p: HashTable) -> Self { - Self::Hash(p) - } +macro_rules! impl_from { + ($kind: tt) => { + impl From<$kind> for SynthesizedKind<'_> { + fn from(p: $kind) -> Self { + Self::$kind(p) + } + } + }; } -impl From for SynthesizedKind<'_> { - fn from(p: GnuHashTable) -> Self { - Self::GnuHash(p) - } -} +impl_from!(Plt); +impl_from!(StringTable); +impl_from!(HashTable); +impl_from!(GnuHashTable); +impl_from!(CopyRel); impl<'p> From> for SynthesizedKind<'p> { fn from(p: SymbolTable<'p>) -> Self { @@ -107,9 +100,10 @@ impl<'p> Synthesized<'p> for SynthesizedKind<'p> { match self { Plt(p) => p.fill_header(sh), StringTable(s) => s.fill_header(sh), - Hash(h) => h.fill_header(sh), - GnuHash(h) => h.fill_header(sh), + HashTable(h) => h.fill_header(sh), + GnuHashTable(h) => h.fill_header(sh), SymbolTable(s) => s.fill_header(sh), + CopyRel(s) => s.fill_header(sh), } } @@ -119,9 +113,10 @@ impl<'p> Synthesized<'p> for SynthesizedKind<'p> { match self { Plt(p) => p.expand(sh), StringTable(s) => s.expand(sh), - Hash(h) => h.expand(sh), - GnuHash(h) => h.expand(sh), + HashTable(h) => h.expand(sh), + GnuHashTable(h) => h.expand(sh), SymbolTable(s) => s.expand(sh), + CopyRel(s) => s.expand(sh), } } @@ -131,9 +126,10 @@ impl<'p> Synthesized<'p> for SynthesizedKind<'p> { match self { Plt(p) => p.finalize(sh), StringTable(s) => s.finalize(sh), - Hash(h) => h.finalize(sh), - GnuHash(h) => h.finalize(sh), + HashTable(h) => h.finalize(sh), + GnuHashTable(h) => h.finalize(sh), SymbolTable(s) => s.finalize(sh), + CopyRel(s) => s.expand(sh), } } diff --git a/src/elf/symbol_table.rs b/src/elf/symbol_table.rs index d5ac3e8..9cee311 100644 --- a/src/elf/symbol_table.rs +++ b/src/elf/symbol_table.rs @@ -159,7 +159,7 @@ impl<'p> SymbolTable<'p> { } /// Useful for those cases where we need quick access to a symbol with a given - ///st_name, and we want to know its index in the final symbol table. + /// st_name, and we want to know its index in the final symbol table. pub fn sorted_with_indexes(&mut self) -> HashMap)> { let mut syms: Vec<&mut Symbol<'p>> = self.symbols.values_mut().collect(); syms.sort_by(|s1, s2| sort_symbols_func(s1, s2)); @@ -189,6 +189,20 @@ impl<'p> SymbolTable<'p> { pub fn named_mut(&mut self) -> &mut HashMap> { &mut self.symbols } + + /// Updates all objects symbols to point to `.copyrel`. + /// Note: This assumes that `st_value`s already have the correct offsets + /// into `.copyrel`. + pub fn update_object_symbols(&mut self, copy_rel_addr: u64, index: usize) { + for sym in + self.symbols + .iter_mut() + .filter_map(|(_, sym)| if sym.is_object() { Some(sym) } else { None }) + { + sym.st_value += copy_rel_addr; + sym.st_shndx = index as u16; + } + } } impl<'p> Synthesized<'p> for SymbolTable<'p> { @@ -317,7 +331,7 @@ impl<'p> Synthesized<'p> for HashTable { } fn as_ref<'k>(kind: &'k SynthesizedKind<'p>) -> Option<&'k Self> { - if let SynthesizedKind::Hash(h) = kind { + if let SynthesizedKind::HashTable(h) = kind { Some(h) } else { None @@ -325,7 +339,7 @@ impl<'p> Synthesized<'p> for HashTable { } fn as_ref_mut<'k>(kind: &'k mut SynthesizedKind<'p>) -> Option<&'k mut Self> { - if let SynthesizedKind::Hash(h) = kind { + if let SynthesizedKind::HashTable(h) = kind { Some(h) } else { None @@ -431,7 +445,7 @@ impl<'p> Synthesized<'p> for GnuHashTable { } fn as_ref<'k>(kind: &'k SynthesizedKind<'p>) -> Option<&'k Self> { - if let SynthesizedKind::GnuHash(h) = kind { + if let SynthesizedKind::GnuHashTable(h) = kind { Some(h) } else { None @@ -439,7 +453,7 @@ impl<'p> Synthesized<'p> for GnuHashTable { } fn as_ref_mut<'k>(kind: &'k mut SynthesizedKind<'p>) -> Option<&'k mut Self> { - if let SynthesizedKind::GnuHash(h) = kind { + if let SynthesizedKind::GnuHashTable(h) = kind { Some(h) } else { None diff --git a/src/symbol.rs b/src/symbol.rs index c360211..fcabbf6 100644 --- a/src/symbol.rs +++ b/src/symbol.rs @@ -5,9 +5,9 @@ use std::{ path::Path, }; -use goblin::{ - elf32::sym::STT_TLS, - elf64::sym::{Sym, STB_GLOBAL, STB_LOCAL, STB_WEAK}, +use goblin::elf64::{ + section_header::SHN_UNDEF, + sym::{Sym, STB_GLOBAL, STB_LOCAL, STB_WEAK, STT_OBJECT, STT_TLS}, }; #[derive(Debug, Clone, Copy)] @@ -58,23 +58,31 @@ impl<'r> Symbol<'r> { self.st_type() == STT_TLS } + pub const fn is_object(&self) -> bool { + self.st_type() == STT_OBJECT + } + pub const fn is_local(&self) -> bool { self.st_bind() == STB_LOCAL } + pub const fn is_undefined(&self) -> bool { + self.sym.st_shndx == SHN_UNDEF as u16 + } + pub const fn reference(&self) -> &Path { self.reference } - pub fn got_offset(&self) -> Option { + pub const fn got_offset(&self) -> Option { self.in_got } - pub fn got_plt_offset(&self) -> Option { + pub const fn got_plt_offset(&self) -> Option { self.in_got_plt } - pub fn plt_index(&self) -> Option { + pub const fn plt_index(&self) -> Option { self.in_plt } @@ -90,7 +98,7 @@ impl<'r> Symbol<'r> { self.in_plt = Some(index); } - pub fn higher_bind_than(&self, sym: Symbol<'_>) -> bool { + pub const fn higher_bind_than(&self, sym: Symbol<'_>) -> bool { let s1 = self.st_bind(); let s2 = sym.st_bind(); !matches!( @@ -99,15 +107,15 @@ impl<'r> Symbol<'r> { ) } - pub fn hash(&self) -> u32 { + pub const fn hash(&self) -> u32 { self.hash } - pub fn gnu_hash(&self) -> u32 { + pub const fn gnu_hash(&self) -> u32 { self.gnu_hash } - pub fn dynamic(&self) -> Option { + pub const fn dynamic(&self) -> Option { self.dynamic } @@ -115,7 +123,7 @@ impl<'r> Symbol<'r> { self.dynamic = Some(dynamic); } - pub fn relocation_offset(&self) -> Option { + pub const fn relocation_offset(&self) -> Option { self.relocation_offset }