From cc7ff54d12d8f66c9ccda5cc2b73d974e20ee008 Mon Sep 17 00:00:00 2001 From: oddcoder Date: Thu, 5 Mar 2020 18:54:26 +0200 Subject: [PATCH 1/3] Catch circular dependency when resolving loading shared libraries This patch implements tree-based data-structure for catching circular dependency where libA.so will depen on arbitrarily long chain (including zero length) of libNs.so one of which will depend on libA.so again. The main intention of this patch was merely capturing the dependency tree to prioterize which Elf's .init_array and which .fini_array should run first, but as a side effect it can capture circular dependencies as well. --- src/ld_so/linker.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 818a5feb48..b80d5d8b9f 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -1,6 +1,6 @@ use alloc::{ boxed::Box, - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, string::{String, ToString}, vec::Vec, }; @@ -59,6 +59,8 @@ pub struct Linker { mmaps: BTreeMap, verbose: bool, tls_index_offset: usize, + // used to detect circular dependencies in the Linker::load function + cir_dep: BTreeSet, } impl Linker { @@ -71,6 +73,7 @@ impl Linker { mmaps: BTreeMap::new(), verbose, tls_index_offset: 0, + cir_dep: BTreeSet::new(), } } @@ -78,6 +81,14 @@ impl Linker { if self.verbose { println!("load {}: {}", name, path); } + if self.cir_dep.contains(name) { + return Err(Error::Malformed(format!( + "Circular dependency: {} is a dependency of itself", + name + ))); + } + + self.cir_dep.insert(name.to_string()); let mut data = Vec::new(); let path_c = CString::new(path) @@ -91,19 +102,17 @@ impl Linker { file.read_to_end(&mut data) .map_err(|err| Error::Malformed(format!("failed to read '{}': {}", path, err)))?; } - - self.load_data(name, data.into_boxed_slice()) + let result = self.load_data(name, data.into_boxed_slice()); + self.cir_dep.remove(name); + result } pub fn load_data(&mut self, name: &str, data: Box<[u8]>) -> Result<()> { - //TODO: Prevent failures due to recursion - { - let elf = Elf::parse(&data)?; - //println!("{:#?}", elf); + let elf = Elf::parse(&data)?; + //println!("{:#?}", elf); - for library in elf.libraries.iter() { - self.load_library(library)?; - } + for library in elf.libraries.iter() { + self.load_library(library)?; } self.objects.insert(name.to_string(), data); From 6aeb2d6fa24190d15525a9856e9fc580dd2322c6 Mon Sep 17 00:00:00 2001 From: oddcoder Date: Sun, 19 Apr 2020 13:54:50 +0200 Subject: [PATCH 2/3] Implement code that use .init_array and .fini_array This patch implements ld.so code that makes use of both .init_array and .fini_array. .init_array is fully utilized and is used in the correct manner. However .fini_array is not used yet although the function that runs .fini_array exists --- src/header/dlfcn/mod.rs | 24 +++---- src/ld_so/linker.rs | 136 +++++++++++++++++++++++++++++++++++----- src/ld_so/src/lib.rs | 1 + src/ld_so/start.rs | 6 +- 4 files changed, 138 insertions(+), 29 deletions(-) diff --git a/src/header/dlfcn/mod.rs b/src/header/dlfcn/mod.rs index f9c3bc8149..732068301a 100644 --- a/src/header/dlfcn/mod.rs +++ b/src/header/dlfcn/mod.rs @@ -57,22 +57,16 @@ pub unsafe extern "C" fn dlopen(filename: *const c_char, flags: c_int) -> *mut c eprintln!("dlopen: linker_ptr: {:p}", tcb.linker_ptr); let mut linker = (&*tcb.linker_ptr).lock(); - match linker.load_library(filename) { - Ok(()) => (), - Err(err) => { - eprintln!("dlopen: failed to load {}", filename); - ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); - return ptr::null_mut(); - } + if let Err(err) = linker.load_library(filename) { + eprintln!("dlopen: failed to load {}", filename); + ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); + return ptr::null_mut(); } - match linker.link(None, None) { - Ok(ok) => (), - Err(err) => { - eprintln!("dlopen: failed to link '{}': {}", filename, err); - ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); - return ptr::null_mut(); - } + if let Err(err) = linker.link(None, None) { + eprintln!("dlopen: failed to link '{}': {}", filename, err); + ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst); + return ptr::null_mut(); }; // TODO @@ -125,6 +119,8 @@ pub unsafe extern "C" fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *m #[no_mangle] pub extern "C" fn dlclose(handle: *mut c_void) -> c_int { + // TODO: Loader::fini() should be called about here + 0 } diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index b80d5d8b9f..fbd7dd0abd 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -42,7 +42,20 @@ pub struct DSO { pub base_addr: usize, pub entry_point: usize, } +#[derive(Default, Debug)] +pub struct DepTree { + pub name: String, + pub deps: Vec, +} +impl DepTree { + fn new(name: String) -> DepTree { + DepTree { + name, + deps: Vec::new(), + } + } +} pub struct Linker { // Used by load /// Library path to search when loading library by name @@ -59,8 +72,10 @@ pub struct Linker { mmaps: BTreeMap, verbose: bool, tls_index_offset: usize, - // used to detect circular dependencies in the Linker::load function + /// A set used to detect circular dependencies in the Linker::load function cir_dep: BTreeSet, + /// Each object will have its children callec once with no repetition. + dep_tree: DepTree, } impl Linker { @@ -74,10 +89,19 @@ impl Linker { verbose, tls_index_offset: 0, cir_dep: BTreeSet::new(), + dep_tree: Default::default(), } } pub fn load(&mut self, name: &str, path: &str) -> Result<()> { + self.dep_tree = self.load_recursive(name, path)?; + if self.verbose { + println!("Dep tree: {:#?}", self.dep_tree); + } + return Ok(()); + } + + fn load_recursive(&mut self, name: &str, path: &str) -> Result { if self.verbose { println!("load {}: {}", name, path); } @@ -88,9 +112,9 @@ impl Linker { ))); } - self.cir_dep.insert(name.to_string()); + let mut deps = DepTree::new(name.to_string()); let mut data = Vec::new(); - + self.cir_dep.insert(name.to_string()); let path_c = CString::new(path) .map_err(|err| Error::Malformed(format!("invalid path '{}': {}", path, err)))?; @@ -102,29 +126,32 @@ impl Linker { file.read_to_end(&mut data) .map_err(|err| Error::Malformed(format!("failed to read '{}': {}", path, err)))?; } - let result = self.load_data(name, data.into_boxed_slice()); + deps.deps = self.load_data(name, data.into_boxed_slice())?; self.cir_dep.remove(name); - result + Ok(deps) } - pub fn load_data(&mut self, name: &str, data: Box<[u8]>) -> Result<()> { + pub fn load_data(&mut self, name: &str, data: Box<[u8]>) -> Result> { let elf = Elf::parse(&data)?; //println!("{:#?}", elf); - + let mut deps = Vec::new(); for library in elf.libraries.iter() { - self.load_library(library)?; + if let Some(dep) = self.load_library(library)? { + deps.push(dep); + } } self.objects.insert(name.to_string(), data); - Ok(()) + return Ok(deps); } - pub fn load_library(&mut self, name: &str) -> Result<()> { + pub fn load_library(&mut self, name: &str) -> Result> { if self.objects.contains_key(name) { - Ok(()) + // It should be previously resolved so we don't need to worry about it + Ok(None) } else if name.contains('/') { - self.load(name, name) + Ok(Some(self.load_recursive(name, name)?)) } else { let library_path = self.library_path.clone(); for part in library_path.split(PATH_SEP) { @@ -146,8 +173,7 @@ impl Linker { }; if access { - self.load(name, &path)?; - return Ok(()); + return Ok(Some(self.load_recursive(name, &path)?)); } } @@ -212,6 +238,73 @@ impl Linker { None } } + pub fn run_init(&self) -> Result<()> { + self.run_init_tree(&self.dep_tree) + } + + fn run_init_tree(&self, root: &DepTree) -> Result<()> { + for node in root.deps.iter() { + self.run_init_tree(node)?; + } + if self.verbose { + println!("init {}", &root.name); + } + let mmap = match self.mmaps.get(&root.name) { + Some(some) => some, + None => return Ok(()), + }; + let elf = Elf::parse(self.objects.get(&root.name).unwrap())?; + for section in elf.section_headers { + let name = match elf.shdr_strtab.get(section.sh_name) { + Some(x) => match x { + Ok(y) => y, + _ => continue, + }, + _ => continue, + }; + if name == ".init_array" { + let addr = mmap.as_ptr() as usize + section.vm_range().start; + for i in (0..section.sh_size).step_by(8) { + unsafe { call_inits_finis(addr + i as usize) }; + } + } + } + return Ok(()); + } + + pub fn run_fini(&self) -> Result<()> { + self.run_fini_tree(&self.dep_tree) + } + + fn run_fini_tree(&self, root: &DepTree) -> Result<()> { + if self.verbose { + println!("init {}", &root.name); + } + let mmap = match self.mmaps.get(&root.name) { + Some(some) => some, + None => return Ok(()), + }; + let elf = Elf::parse(self.objects.get(&root.name).unwrap())?; + for section in elf.section_headers { + let name = match elf.shdr_strtab.get(section.sh_name) { + Some(x) => match x { + Ok(y) => y, + _ => continue, + }, + _ => continue, + }; + if name == ".fini_array" { + let addr = mmap.as_ptr() as usize + section.vm_range().start; + for i in (0..section.sh_size).step_by(8) { + unsafe { call_inits_finis(addr + i as usize) }; + } + } + } + for node in root.deps.iter() { + self.run_fini_tree(node)?; + } + return Ok(()); + } pub fn link(&mut self, primary_opt: Option<&str>, dso: Option) -> Result> { unsafe { _r_debug.state = RTLDState::RT_ADD }; @@ -726,3 +819,18 @@ impl Linker { Ok(entry_opt) } } + +unsafe extern "C" fn call_inits_finis(addr: usize) { + #[cfg(target_arch = "x86_64")] + asm!(" + cmp qword ptr [rdi], 0 + je end + call [rdi] +end: nop + " + : + : + : + : "intel", "volatile" + ); +} diff --git a/src/ld_so/src/lib.rs b/src/ld_so/src/lib.rs index f26bc0af64..e3d17c6565 100644 --- a/src/ld_so/src/lib.rs +++ b/src/ld_so/src/lib.rs @@ -33,6 +33,7 @@ next: pop rsi xor r11, r11 fninit jmp rax + # TODO: Loader::fini() should be called about here " : : diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index 1d60b714fd..7b433faa4a 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -203,7 +203,11 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> loop {} } }; - + if let Err(e) = linker.run_init() { + eprintln!("ld.so: failed to run .init_array"); + unistd::_exit(1); + loop {} + } if let Some(tcb) = unsafe { Tcb::current() } { tcb.linker_ptr = Box::into_raw(Box::new(Mutex::new(linker))); } From b717f6cf5ab82583afaa103d73a7d0bf5f57df47 Mon Sep 17 00:00:00 2001 From: oddcoder Date: Sun, 19 Apr 2020 12:07:41 +0200 Subject: [PATCH 3/3] Move IO Initialization to .init_array section Most shared object in linux have their initialization in a separate .init_array section. but glibc does not have the same behavour. Instead the initialization is part of crt0. which (as it seams to me) run after .init_array section. As such, this patch move IO initialization to separate function that is marked as .init_array member and then the crt0 call this function only if init_array was never invoked (as in statically linked binaries). --- src/start.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/start.rs b/src/start.rs index ade155b965..3dd856f59f 100644 --- a/src/start.rs +++ b/src/start.rs @@ -60,6 +60,24 @@ pub unsafe fn relibc_verify_host() { intrinsics::abort(); } } +#[link_section = ".init_array"] +#[used] +static INIT_ARRAY: [extern "C" fn(); 1] = [init_array]; + +static mut init_complete: bool = false; + +extern "C" fn init_array() { + io_init(); + unsafe { init_complete = true }; +} +fn io_init() { + unsafe { + // Initialize stdin/stdout/stderr, see https://github.com/rust-lang/rust/issues/51718 + stdio::stdin = stdio::default_stdin.get(); + stdio::stdout = stdio::default_stdout.get(); + stdio::stderr = stdio::default_stderr.get(); + } +} #[inline(never)] #[no_mangle] @@ -95,11 +113,9 @@ pub unsafe extern "C" fn relibc_start(sp: &'static Stack) -> ! { platform::inner_environ = copy_string_array(envp, len); platform::environ = platform::inner_environ.as_mut_ptr(); - // Initialize stdin/stdout/stderr, see https://github.com/rust-lang/rust/issues/51718 - stdio::stdin = stdio::default_stdin.get(); - stdio::stdout = stdio::default_stdout.get(); - stdio::stderr = stdio::default_stderr.get(); - + if !init_complete { + init_array(); + } pthread_init(); // Run preinit array