diff --git a/src/ld_so/dso.rs b/src/ld_so/dso.rs index 9c9778ce73..9407972214 100644 --- a/src/ld_so/dso.rs +++ b/src/ld_so/dso.rs @@ -3,7 +3,7 @@ //! * use object::{ - NativeEndian, Object, StringTable, SymbolIndex, elf, + NativeEndian, Object, StringTable, SymbolIndex, U32, elf, read::elf::{ Dyn as _, GnuHashTable, HashTable as SysVHashTable, ProgramHeader as _, Rel as _, Rela as _, Sym as _, Version, VersionTable, @@ -42,25 +42,27 @@ pub type Relr = usize; #[cfg(target_pointer_width = "32")] mod shim { - use object::{NativeEndian, elf::*, read::elf::ElfFile32}; - pub type Dyn = Dyn32; - pub type Rel = Rel32; - pub type Rela = Rela32; - pub type Sym = Sym32; - pub type FileHeader = FileHeader32; - pub type ProgramHeader = ProgramHeader32; + use object::{NativeEndian, read::elf::ElfFile32}; + pub type Dyn = elf::Dyn32; + pub type Rel = elf::Rel32; + pub type Rela = elf::Rela32; + pub type Sym = elf::Sym32; + pub type FileHeader = elf::FileHeader32; + pub type ProgramHeader = elf::ProgramHeader32; + pub type GnuHashHeader = elf::GnuHashHeader; pub type ElfFile<'a> = ElfFile32<'a, NativeEndian>; } #[cfg(target_pointer_width = "64")] mod shim { - use object::{NativeEndian, elf::*, read::elf::ElfFile64}; - pub type Dyn = Dyn64; - pub type Rel = Rel64; - pub type Rela = Rela64; - pub type Sym = Sym64; - pub type FileHeader = FileHeader64; - pub type ProgramHeader = ProgramHeader64; + use object::{NativeEndian, elf, read::elf::ElfFile64}; + pub type Dyn = elf::Dyn64; + pub type Rel = elf::Rel64; + pub type Rela = elf::Rela64; + pub type Sym = elf::Sym64; + pub type FileHeader = elf::FileHeader64; + pub type ProgramHeader = elf::ProgramHeader64; + pub type GnuHashHeader = elf::GnuHashHeader; pub type ElfFile<'a> = ElfFile64<'a, NativeEndian>; } @@ -176,7 +178,7 @@ impl<'data> Dynamic<'data> { unsafe impl Send for Dynamic<'_> {} unsafe impl Sync for Dynamic<'_> {} -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub(super) struct Relocation { pub(super) offset: usize, pub(super) addend: Option, @@ -331,14 +333,35 @@ impl SymbolBinding { } } +pub struct MemoryMapHandle { + ptr: *const u8, + size: usize, +} + +impl MemoryMapHandle { + pub fn as_ptr(&self) -> *const u8 { + self.ptr + } + + pub fn size(&self) -> usize { + self.size + } +} + +impl Drop for MemoryMapHandle { + fn drop(&mut self) { + unsafe { Sys::munmap(self.ptr as *mut c_void, self.size).unwrap() }; + } +} + /// Use to represent a library as well as all the symbols that is loaded withen it. pub struct DSO { pub name: String, pub id: usize, pub dlopened: bool, pub entry_point: usize, - /// Loaded library in-memory data - pub mmap: &'static [u8], + pub base: *const u8, + pub mmap: Option, pub tls_module_id: usize, pub tls_offset: usize, @@ -349,10 +372,56 @@ pub struct DSO { pub pie: bool, /// Whether this DSO *and* its dependencies have been successfully loaded. - is_ready: AtomicBool, + pub is_ready: AtomicBool, + + /// Is this DSO for ld.so? + is_me: bool, } impl DSO { + pub fn from_raw( + base: *const u8, + dyns: &[Dyn], + phdrs: &[ProgramHeader], + id: usize, + tls_module_id: usize, + tls_offset: usize, + ) -> (Self, Master) { + // FIXME: What should `path` be set to? + // FIXME: this function and `parse_dynamic` should be unsafe. + let (dynamic, _debug) = Self::parse_dynamic("", base, true, dyns).unwrap(); + + let tcb_master = phdrs + .iter() + .find(|ph| ph.p_type(NativeEndian) == elf::PT_TLS) + .map(|ph| Master { + ptr: unsafe { base.byte_add(ph.p_vaddr(NativeEndian) as usize) }, + image_size: ph.p_filesz(NativeEndian) as usize, + segment_size: ph.p_memsz(NativeEndian) as usize, + offset: tls_offset + ph.p_memsz(NativeEndian) as usize, + }) + .unwrap(); + + ( + Self { + name: String::from("libc.so.6"), + id, + tls_offset: tcb_master.offset, + tls_module_id, + dlopened: false, + entry_point: 0, + base, + mmap: None, + dynamic, + scope: spin::Once::new(), + pie: true, + is_ready: AtomicBool::new(false), + is_me: true, + }, + tcb_master, + ) + } + pub fn new( path: &str, data: &[u8], @@ -385,7 +454,10 @@ impl DSO { id, dlopened, entry_point, - mmap, + + base: mmap.as_ptr(), + mmap: Some(mmap), + tls_module_id: if tcb_master.is_some() { tls_module_id } else { @@ -397,6 +469,7 @@ impl DSO { dynamic, scope: spin::Once::new(), is_ready: AtomicBool::new(false), + is_me: false, }; Ok((dso, tcb_master, elf.elf_program_headers().to_vec())) @@ -444,11 +517,7 @@ impl DSO { Some(( Symbol { name, - base: if self.pie { - self.mmap.as_ptr() as usize - } else { - 0 - }, + base: if self.pie { self.base as usize } else { 0 }, value: sym.st_value(NativeEndian) as usize, size: sym.st_size(NativeEndian) as usize, sym_type: sym.st_type(), @@ -480,7 +549,7 @@ impl DSO { data: &'a [u8], base_addr: Option, tls_offset: usize, - ) -> Result<(&'static [u8], Option, Dynamic<'static>), String> { + ) -> Result<(MemoryMapHandle, Option, Dynamic<'static>), String> { let endian = elf.endian(); log::trace!("# {}", path); // data for struct LinkMap @@ -640,8 +709,9 @@ impl DSO { let dynamic = dynamic.ok_or_else(|| "Unable to find PT_DYNAMIC section".to_string())?; - let (parsed_dynamic, debug) = Self::parse_dynamic(path, mmap, is_pie_enabled(elf), dynamic) - .map_err(|e| e.to_string())?; + let (parsed_dynamic, debug) = + Self::parse_dynamic(path, mmap.as_ptr(), is_pie_enabled(elf), dynamic.1) + .map_err(|e| e.to_string())?; if let Some(i) = debug { // FIXME: cleanup @@ -664,14 +734,21 @@ impl DSO { } } - Ok((mmap, tcb_master, parsed_dynamic)) + Ok(( + MemoryMapHandle { + ptr: mmap.as_ptr(), + size: mmap.len(), + }, + tcb_master, + parsed_dynamic, + )) } fn parse_dynamic<'a>( path: &str, - mmap: &'a [u8], + mmap: *const u8, is_pie: bool, - (_, entries): (&ProgramHeader, &[Dyn]), + entries: &[Dyn], ) -> object::Result<(Dynamic<'a>, Option)> { let mut runpath = None; let mut got = None; @@ -692,28 +769,66 @@ impl DSO { for (i, entry) in entries.iter().enumerate() { let val = entry.d_val(NativeEndian); - let relative_idx = val as usize - if is_pie { 0 } else { mmap.as_ptr() as usize }; - let ptr = (val as usize + if is_pie { mmap.as_ptr() as usize } else { 0 }) as *const u8; + let relative_idx = val as usize - if is_pie { 0 } else { mmap as usize }; + let ptr = (val as usize + if is_pie { mmap as usize } else { 0 }) as *const u8; let tag = entry.d_tag(NativeEndian) as u32; match tag { elf::DT_DEBUG => debug = Some(i), - // {Gnu,SysV}HashTable::parse() - // - // > The header does not contain a length field, and so all of - // > `data` will be used as the hash table values. It does not - // > matter if this is longer than needed... elf::DT_GNU_HASH => { - let value = GnuHashTable::parse(NativeEndian, &mmap[relative_idx..])?; - hash_table = Some(HashTable::Gnu(value)); + let header = unsafe { ptr.cast::().as_ref() }.unwrap(); + let bloom_count = header.bloom_count.get(NativeEndian) as usize; + let bucket_count = header.bucket_count.get(NativeEndian) as usize; + let symbol_base = header.symbol_base.get(NativeEndian); + + let bloom_size = bloom_count * size_of::(); + let mut ptr = unsafe { ptr.byte_add(size_of::()) }; + + let bloom_filters = unsafe { slice::from_raw_parts(ptr, bloom_size) }; + unsafe { ptr = ptr.byte_add(bloom_size) }; + + let buckets = unsafe { + slice::from_raw_parts(ptr.cast::>(), bucket_count) + }; + unsafe { ptr = ptr.byte_add(bucket_count * size_of::()) }; + + let mut max_symbol = 0; + for bucket in buckets { + let bucket = bucket.get(NativeEndian); + if max_symbol < bucket { + max_symbol = bucket; + } + } + + assert_ne!(max_symbol, 0); + let chains_ptr = ptr.cast::(); + let mut i = max_symbol - symbol_base; + + while unsafe { chains_ptr.add(i as usize).read() & 1 == 0 } { + i += 1; + } + + let values = unsafe { + slice::from_raw_parts(ptr.cast::>(), i as usize + 1) + }; + + let table = GnuHashTable { + symbol_base, + bloom_shift: header.bloom_shift.get(NativeEndian), + bloom_filters, + buckets, + values, + }; + + hash_table = Some(HashTable::Gnu(table)); } // XXX: Both GNU_HASH and HASH may be present, we give priority // to GNU_HASH as it is significantly faster. elf::DT_HASH if hash_table.is_none() => { - let value = SysVHashTable::parse(NativeEndian, &mmap[relative_idx..])?; - hash_table = Some(HashTable::Sysv(value)); + // FIXME: BEFORE MR FIX THIS + // todo!(); } elf::DT_PLTGOT => { @@ -771,14 +886,16 @@ impl DSO { } } - let strtab_offset = strtab_offset.expect("mandatory DT_STRTAB not present"); - let strtab_size = strtab_size.expect("mandatory DT_STRSZ not present"); + let hash_table = hash_table.expect("either DT_GNU_HASH and/or DT_HASH mut be present"); + + let strtab_offset = strtab_offset.expect("DT_STRTAB must be present"); + let strtab_size = strtab_size.expect("DT_STRSZ must be present"); #[expect(clippy::unnecessary_cast, reason = "needed on i586")] let dynstrtab = StringTable::new( - mmap, - strtab_offset as u64, - strtab_offset as u64 + strtab_size as u64, + unsafe { slice::from_raw_parts(mmap.byte_add(strtab_offset), strtab_size as usize) }, + 0, + strtab_size as u64, ); let get_str = |entry: &Dyn| { @@ -812,7 +929,6 @@ impl DSO { let soname = soname.map(get_str).transpose()?; let jmprel = jmprel.unwrap_or_default(); - let hash_table = hash_table.expect("either DT_GNU_HASH and/or DT_HASH mut be present"); let init_array = unsafe { get_array(init_array_ptr, init_array_len) }; let fini_array = unsafe { get_array(fini_array_ptr, fini_array_len) }; @@ -889,8 +1005,6 @@ impl DSO { } fn static_relocate(&self, global_scope: &Scope, reloc: Relocation) -> object::Result<()> { - let b = self.mmap.as_ptr() as usize; - let (sym, my_sym) = if reloc.sym != STN_UNDEF { let name = self.dynamic.symbol_name(reloc.sym).unwrap(); @@ -923,7 +1037,7 @@ impl DSO { .unwrap_or((0, self)); let ptr = if self.pie { - (b + reloc.offset) as *mut u8 + unsafe { self.base.byte_add(reloc.offset).cast_mut() } } else { reloc.offset as *mut u8 }; @@ -956,7 +1070,7 @@ impl DSO { } RelocationKind::GOT => set_usize(s), RelocationKind::OFFSET => set_usize((s + a).wrapping_sub(p)), - RelocationKind::RELATIVE => set_usize(b + a), + RelocationKind::RELATIVE => set_usize(self.base as usize + a), RelocationKind::SYMBOLIC => set_usize(s + a), RelocationKind::TPOFF => { assert!( @@ -974,7 +1088,8 @@ impl DSO { } } RelocationKind::IRELATIVE => unsafe { - let f: unsafe extern "C" fn() -> usize = core::mem::transmute(b + a); + let f: unsafe extern "C" fn() -> usize = + core::mem::transmute(self.base as usize + a); set_usize(f()); }, RelocationKind::COPY => unsafe { @@ -1007,7 +1122,6 @@ impl DSO { return Ok(()); }; - let object_base_addr = self.mmap.as_ptr() as usize; let jmprel = self.dynamic.jmprel; let pltrelsz = self.dynamic.pltrelsz; @@ -1031,14 +1145,14 @@ impl DSO { }; let ptr = if self.pie { - (object_base_addr + reloc.offset) as *mut usize + (self.base as usize + reloc.offset) as *mut usize } else { reloc.offset as *mut usize }; match (reloc.kind, resolve) { (RelocationKind::PLT, Resolve::Lazy) if self.pie => unsafe { - *ptr += object_base_addr; + *ptr += self.base as usize; }, (RelocationKind::PLT, Resolve::Lazy) => { @@ -1082,20 +1196,31 @@ impl DSO { Ok(()) } - pub fn relocate(&self, ph: &[ProgramHeader], resolve: Resolve) -> object::Result<()> { + pub fn relocate(&self, ph: Option<&[ProgramHeader]>, resolve: Resolve) -> object::Result<()> { let global_scope = GLOBAL_SCOPE.read(); - let base = self.mmap.as_ptr(); - unsafe { - apply_relr(base, self.dynamic.relr); + if !self.is_me { + unsafe { + apply_relr(self.base, self.dynamic.relr); + } } - self.dynamic - .static_relocations() - .try_for_each(|reloc| self.static_relocate(&global_scope, reloc))?; + for reloc in self.dynamic.static_relocations() { + if reloc.kind == RelocationKind::RELATIVE && self.is_me { + continue; + } + self.static_relocate(&global_scope, reloc)?; + } + + if self.is_me { + // TODO: assert that ld.so have no lazy relocations. + return Ok(()); + } self.lazy_relocate(&global_scope, resolve)?; + let ph = ph.unwrap(); + // Protect pages for ph in ph .iter() @@ -1120,7 +1245,7 @@ impl DSO { unsafe { let ptr = if self.pie { - self.mmap.as_ptr().add(vaddr) + self.base.add(vaddr) } else { vaddr as *const u8 }; @@ -1135,12 +1260,14 @@ impl DSO { impl Drop for DSO { fn drop(&mut self) { + if self.is_me { + return; + } if self.is_ready.load(Ordering::SeqCst) { // `run_fini` should not be called if we are being prematurely // dropped (e.g. failed to satisfy dependencies). self.run_fini(); } - unsafe { Sys::munmap(self.mmap.as_ptr() as *mut c_void, self.mmap.len()).unwrap() }; } } @@ -1309,3 +1436,7 @@ pub unsafe fn apply_relr(base: *const u8, relr: &[Relr]) { } } } + +// TODO: Add safety comment. +unsafe impl Send for DSO {} +unsafe impl Sync for DSO {} diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 56193ff1ab..05375e5132 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -26,7 +26,7 @@ use crate::{ fcntl, sys_mman, unistd::F_OK, }, - ld_so::dso::SymbolBinding, + ld_so::dso::{Dyn, SymbolBinding}, out::Out, platform::{ Pal, Sys, @@ -417,9 +417,17 @@ impl Config { } } +pub struct Me { + pub base: *const u8, + pub phdrs: &'static [ProgramHeader], + pub dyns: &'static [Dyn], +} + pub struct Linker { config: Config, + me: Me, + next_object_id: usize, next_tls_module_id: usize, tls_size: usize, @@ -431,8 +439,9 @@ pub struct Linker { const ROOT_ID: usize = 1; impl Linker { - pub fn new(config: Config) -> Self { + pub fn new(me: Me, config: Config) -> Self { Self { + me, config, next_object_id: ROOT_ID, next_tls_module_id: 1, @@ -642,7 +651,11 @@ impl Linker { )?; for (i, obj) in new_objects.iter().enumerate() { - obj.relocate(&objects_data[i], resolve).unwrap(); + obj.relocate( + objects_data[i].as_ref().map(|phdrs| phdrs.as_slice()), + resolve, + ) + .unwrap(); } unsafe { @@ -788,7 +801,7 @@ impl Linker { base_addr: Option, dlopened: bool, new_objects: &mut Vec>, - objects_data: &mut Vec>, + objects_data: &mut Vec>>, tcb_masters: &mut Vec, // Scope of the object that caused this object to be loaded. dependent_scope: Option<&mut Scope>, @@ -821,6 +834,49 @@ impl Linker { let debug = self.config.debug_flags.contains(DebugFlags::LOAD); + if name == "libc.so.6" || name == "libc.so" { + if debug { + println!( + "[ld.so]: loading libc.so.6 (aka. ld.so) at {:#?}", + self.me.base + ); + } + + let (me, master) = DSO::from_raw( + self.me.base, + self.me.dyns, + self.me.phdrs, + self.next_object_id, + self.next_tls_module_id, + self.tls_size.next_multiple_of(16), + ); + + self.tls_size = master.offset; + self.next_tls_module_id += 1; + self.next_object_id += 1; + + let obj = Arc::new(me); + + tcb_masters.push(master); + objects_data.push(None); + new_objects.push(obj.clone()); + + if let Some(dependent_scope) = dependent_scope { + match scope_kind { + ScopeKind::Local => dependent_scope.add(&obj), + ScopeKind::Global => GLOBAL_SCOPE.write().add(&obj), + } + } else if let ScopeKind::Global = scope_kind { + GLOBAL_SCOPE.write().add(&obj); + } + + let mut scope = Scope::local(); + scope.set_owner(Arc::downgrade(&obj)); + obj.scope.call_once(|| scope); + + return Ok(obj); + } + let path = self.search_object(name, parent_runpath)?; let file = self.read_file(&path)?; let data = file.data(); @@ -846,8 +902,8 @@ impl Linker { eprintln!( "[ld.so]: loading object: {} at {:#x}:{:#x} (pie: {})", name, - obj.mmap.as_ptr() as usize, - obj.mmap.as_ptr() as usize + obj.mmap.len(), + obj.mmap.as_ref().unwrap().as_ptr() as usize, + obj.mmap.as_ref().unwrap().as_ptr() as usize + obj.mmap.as_ref().unwrap().size(), obj.pie, ); } @@ -896,7 +952,7 @@ impl Linker { )?; } - objects_data.push(elf); + objects_data.push(Some(elf)); new_objects.push(obj.clone()); scope.set_owner(Arc::downgrade(&obj)); @@ -1011,7 +1067,7 @@ impl Linker { #[cfg(target_pointer_width = "64")] extern "C" fn __plt_resolve_inner(obj: *const DSO, relocation_index: c_uint) -> *mut c_void { let obj = unsafe { &*obj }; - let obj_base = obj.mmap.as_ptr() as usize; + let obj_base = obj.base as usize; let jmprel = obj.dynamic.jmprel; let rela = unsafe { &*(jmprel as *const Rela).add(relocation_index as usize) }; diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index ee30ac7410..c2ac4cd86f 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -12,7 +12,7 @@ use alloc::{ use object::{ NativeEndian, elf::{self, PT_DYNAMIC, PT_PHDR}, - read::elf::{Dyn as _, ProgramHeader as _}, + read::elf::{Dyn as _, FileHeader as _, ProgramHeader as _}, }; use crate::{ @@ -23,10 +23,10 @@ use crate::{ }, ld_so::{ dso::{ - DT_RELR, DT_RELRENT, DT_RELRSZ, Dyn, ProgramHeader, Rel, Rela, Relocation, + DT_RELR, DT_RELRENT, DT_RELRSZ, Dyn, FileHeader, ProgramHeader, Rel, Rela, Relocation, RelocationKind, Relr, apply_relr, }, - linker::DebugFlags, + linker::{DebugFlags, Me}, }, platform::{auxv_iter, get_auxvs, types::c_char}, start::Stack, @@ -211,7 +211,7 @@ pub unsafe extern "C" fn relibc_ld_so_start( let is_manual = at_entry == ld_entry; // Whether the dynamic linker was invoked as a command. - let mut i = dynamic; + let mut i = 0; let mut rela_ptr = None; let mut rela_len = None; let mut relr_ptr = None; @@ -219,7 +219,7 @@ pub unsafe extern "C" fn relibc_ld_so_start( let mut rel_ptr = None; let mut rel_len = None; loop { - let entry = unsafe { &*i }; + let entry = unsafe { &*dynamic.add(i) }; let val = entry.d_val(NativeEndian); let ptr = val as *const u8; match entry.d_tag(NativeEndian) as u32 { @@ -241,9 +241,11 @@ pub unsafe extern "C" fn relibc_ld_so_start( } _ => {} } - i = unsafe { i.add(1) }; + i += 1; } + let dyns = unsafe { slice::from_raw_parts(dynamic, i) }; + unsafe fn get_array<'a, T>( ptr: Option<*const T>, len: Option, @@ -251,7 +253,7 @@ pub unsafe extern "C" fn relibc_ld_so_start( ) -> &'a [T] { if let Some(ptr) = ptr { let len = len.expect("dynamic entry was present without it's corresponding size"); - unsafe { core::slice::from_raw_parts(ptr.byte_add(base_addr), len) } + unsafe { slice::from_raw_parts(ptr.byte_add(base_addr), len) } } else { &[] } @@ -298,7 +300,23 @@ pub unsafe extern "C" fn relibc_ld_so_start( } } - stage2(sp, self_base, is_manual, base_addr) + unsafe extern "C" { + safe static __ehdr_start: FileHeader; + } + + let ph_off = __ehdr_start.e_phoff(NativeEndian) as usize; + let ph_num = __ehdr_start.e_phnum(NativeEndian) as usize; + + let my_phdrs = unsafe { + slice::from_raw_parts( + core::ptr::addr_of!(__ehdr_start) + .byte_add(ph_off) + .cast::(), + ph_num, + ) + }; + + stage2(sp, self_base, is_manual, base_addr, dyns, my_phdrs) } fn stage2( @@ -306,6 +324,8 @@ fn stage2( self_base: usize, is_manual: bool, base_addr: Option, + my_dyns: &'static [Dyn], + my_phdrs: &'static [ProgramHeader], ) -> usize { // Setup TCB for ourselves. unsafe { @@ -394,7 +414,9 @@ fn stage2( crate::platform::environ = crate::platform::OUR_ENVIRON.unsafe_mut().as_mut_ptr(); - crate::platform::logger::init(); + if let Err(_) = crate::platform::logger::init() { + log::error!("Logger has already been initialised"); + } } // we might need global lock for this kind of stuff @@ -435,7 +457,14 @@ fn stage2( } } - let mut linker = Linker::new(config); + let mut linker = Linker::new( + Me { + base: self_base as *const u8, + phdrs: my_phdrs, + dyns: my_dyns, + }, + config, + ); let entry = match linker.load_program(&path, base_addr) { Ok(entry) => entry, Err(err) => {