diff --git a/libafl_qemu/src/modules/usermode/asan.rs b/libafl_qemu/src/modules/usermode/asan.rs index b1e62c3065..040b9be16d 100644 --- a/libafl_qemu/src/modules/usermode/asan.rs +++ b/libafl_qemu/src/modules/usermode/asan.rs @@ -3,11 +3,9 @@ use core::{fmt, slice}; use std::{ - borrow::Cow, env, - fmt::{Debug, Display, Write}, + fmt::{Debug, Display}, fs, - path::PathBuf, pin::Pin, sync::Mutex, }; @@ -21,19 +19,13 @@ use libc::{ }; use meminterval::{Interval, IntervalTree}; use num_enum::{IntoPrimitive, TryFromPrimitive}; -use object::Object; -use rangemap::RangeMap; use crate::{ emu::EmulatorModules, modules::{ calls::FullBacktraceCollector, snapshot::SnapshotModule, - utils::{ - addr2line_legacy, - filters::{HasAddressFilter, StdAddressFilter}, - load_file_section, - }, + utils::filters::{HasAddressFilter, StdAddressFilter}, AddressFilter, EmulatorModule, EmulatorModuleTuple, }, qemu::{Hook, MemAccessInfo, QemuHooks, SyscallHookResult}, @@ -1348,126 +1340,8 @@ where /// # Safety /// Will access the global [`FullBacktraceCollector`]. /// Calling this function concurrently might be racey. -#[expect(clippy::too_many_lines, clippy::unnecessary_cast)] pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &AsanError) { - let mut regions = HashMap::new(); - for region in qemu.mappings() { - if let Some(path) = region.path() { - let start = region.start(); - let end = region.end(); - let entry = regions.entry(path.to_owned()).or_insert(start..end); - if start < entry.start { - *entry = start..entry.end; - } - if end > entry.end { - *entry = entry.start..end; - } - } - } - - let mut resolvers = vec![]; - let mut images = vec![]; - let mut ranges = RangeMap::new(); - - for (path, rng) in regions { - let data = fs::read(&path); - if data.is_err() { - continue; - } - let data = data.unwrap(); - let idx = images.len(); - images.push((path, data)); - ranges.insert(rng, idx); - } - - let arena_data = typed_arena::Arena::new(); - - for img in &images { - if let Ok(obj) = object::read::File::parse(&*img.1) { - let endian = if obj.is_little_endian() { - addr2line::gimli::RunTimeEndian::Little - } else { - addr2line::gimli::RunTimeEndian::Big - }; - - let mut load_section = |id: addr2line::gimli::SectionId| -> Result<_, _> { - load_file_section(id, &obj, endian, &arena_data) - }; - - let dwarf = addr2line::gimli::Dwarf::load(&mut load_section).unwrap(); - let ctx = addr2line::Context::from_dwarf(dwarf) - .expect("Failed to create an addr2line context"); - - //let ctx = addr2line::Context::new(&obj).expect("Failed to create an addr2line context"); - resolvers.push(Some((obj, ctx))); - } else { - resolvers.push(None); - } - } - - let resolve_addr = |addr: GuestAddr| -> String { - let mut info = String::new(); - if let Some((rng, idx)) = ranges.get_key_value(&addr) { - let raddr = (addr - rng.start) as u64; - if let Some((obj, ctx)) = resolvers[*idx].as_ref() { - let symbols = obj.symbol_map(); - let mut func = symbols.get(raddr).map(|x| x.name().to_string()); - - if func.is_none() { - let pathname = PathBuf::from(images[*idx].0.clone()); - let mut split_dwarf_loader = addr2line_legacy::SplitDwarfLoader::new( - |data, endian| { - addr2line::gimli::EndianSlice::new( - arena_data.alloc(Cow::Owned(data.into_owned())), - endian, - ) - }, - Some(pathname), - ); - - let frames = ctx.find_frames(raddr); - if let Ok(mut frames) = split_dwarf_loader.run(frames) { - if let Some(frame) = frames.next().unwrap_or(None) { - if let Some(function) = frame.function { - if let Ok(name) = function.raw_name() { - let demangled = - addr2line::demangle_auto(name, function.language); - func = Some(demangled.to_string()); - } - } - } - } - } - - if let Some(name) = func { - info += " in "; - info += &name; - } - - if let Some(loc) = ctx.find_location(raddr).unwrap_or(None) { - if info.is_empty() { - info += " in"; - } - info += " "; - if let Some(file) = loc.file { - info += file; - } - if let Some(line) = loc.line { - info += ":"; - info += &line.to_string(); - } - } else { - let _ = write!(&mut info, " ({}+{raddr:#x})", images[*idx].0); - } - } - if info.is_empty() { - let _ = write!(&mut info, " ({}+{raddr:#x})", images[*idx].0); - } - } - info - }; - - // TODO, make a class Resolver for resolving the addresses?? + let resolver = crate::modules::utils::addr2line::AddressResolver::new(&qemu); eprintln!("================================================================="); let backtrace = FullBacktraceCollector::backtrace() .map(|r| { @@ -1478,7 +1352,7 @@ pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &Asa .unwrap_or(vec![pc]); eprintln!("AddressSanitizer Error: {err}"); for (i, addr) in backtrace.iter().rev().enumerate() { - eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr)); + eprintln!("\t#{i} {addr:#x}{}", resolver.resolve(*addr)); } let addr = match err { AsanError::Read(addr, _) | AsanError::Write(addr, _) | AsanError::BadFree(addr, _) => { @@ -1493,13 +1367,13 @@ pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &Asa } else { eprintln!("Freed at:"); for (i, addr) in item.free_backtrace.iter().rev().enumerate() { - eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr)); + eprintln!("\t#{i} {addr:#x}{}", resolver.resolve(*addr)); } eprintln!("And previously allocated at:"); } for (i, addr) in item.backtrace.iter().rev().enumerate() { - eprintln!("\t#{i} {addr:#x}{}", resolve_addr(*addr)); + eprintln!("\t#{i} {addr:#x}{}", resolver.resolve(*addr)); } }; diff --git a/libafl_qemu/src/modules/utils/addr2line.rs b/libafl_qemu/src/modules/utils/addr2line.rs new file mode 100644 index 0000000000..36514e606b --- /dev/null +++ b/libafl_qemu/src/modules/utils/addr2line.rs @@ -0,0 +1,166 @@ +//! Utils for addr2line + +use std::{borrow::Cow, fmt::Write, fs}; + +use addr2line::{fallible_iterator::FallibleIterator, Loader}; +use goblin::elf::dynamic::{DF_1_PIE, DT_FLAGS_1}; +use hashbrown::HashMap; +use libafl_qemu_sys::GuestAddr; +use rangemap::RangeMap; + +use crate::Qemu; +// (almost) Copy paste from addr2line/src/bin/addr2line.rs +fn print_function(name: Option<&str>, language: Option) -> String { + let ret = if let Some(name) = name { + addr2line::demangle_auto(Cow::from(name), language).to_string() + } else { + "??".to_string() + }; + // println!("{ret:?}"); + ret +} + +/// check if this binary is pie (for 64bit binary only) +#[must_use] +pub fn is_pie(file: object::File<'_>) -> bool { + let is_pie = match file { + object::File::Elf64(elf) => { + let mut is_pie = false; + let table = elf.elf_section_table(); + let dyn_sec = table.dynamic(elf.endian(), elf.data()); + if let Ok(Some(d)) = dyn_sec { + let arr = d.0; + for v in arr { + if v.d_tag.get(elf.endian()) == DT_FLAGS_1 + && v.d_val.get(elf.endian()) & DF_1_PIE == DF_1_PIE + { + is_pie = true; + } + } + } + is_pie + } + _ => false, + }; + + is_pie +} + +pub struct AddressResolver { + ranges: RangeMap, + images: Vec<(String, Vec)>, + resolvers: Vec>, +} + +impl AddressResolver { + #[must_use] + pub fn new(qemu: &Qemu) -> Self { + let mut regions = HashMap::new(); + for region in qemu.mappings() { + if let Some(path) = region.path() { + let start = region.start(); + let end = region.end(); + let entry = regions.entry(path.to_owned()).or_insert(start..end); + if start < entry.start { + *entry = start..entry.end; + } + if end > entry.end { + *entry = entry.start..end; + } + } + } + + let mut resolvers = vec![]; + let mut images = vec![]; + let mut ranges: RangeMap = RangeMap::new(); + + for (path, rng) in regions { + let data = fs::read(&path); + if data.is_err() { + continue; + } + let data = data.unwrap(); + let idx = images.len(); + images.push((path, data)); + ranges.insert(rng, idx); + } + + for img in &images { + if let Ok(obj) = object::read::File::parse(&*img.1) { + let is_pie = is_pie(obj); + + let ctx = Loader::new(img.0.clone()).unwrap(); + resolvers.push(Some((ctx, is_pie))); + } else { + resolvers.push(None); + } + } + Self { + ranges, + images, + resolvers, + } + } + + #[must_use] + pub fn resolve(&self, pc: GuestAddr) -> String { + let resolve_addr = |addr: GuestAddr| -> String { + let mut info = String::new(); + if let Some((range, idx)) = self.ranges.get_key_value(&addr) { + if let Some((ctx, is_pie)) = self.resolvers[*idx].as_ref() { + let raddr = if *is_pie { addr - range.start } else { addr }; + let mut frames = ctx.find_frames(raddr.into()).unwrap().peekable(); + let mut fname = None; + while let Some(frame) = frames.next().unwrap() { + // Only use the symbol table if this isn't an inlined function. + let symbol = if matches!(frames.peek(), Ok(None)) { + ctx.find_symbol(raddr.into()) + } else { + None + }; + if symbol.is_some() { + // Prefer the symbol table over the DWARF name because: + // - the symbol can include a clone suffix + // - llvm may omit the linkage name in the DWARF with -g1 + fname = Some(print_function(symbol, None)); + } else if let Some(func) = frame.function { + fname = Some(print_function( + func.raw_name().ok().as_deref(), + func.language, + )); + } else { + fname = Some(print_function(None, None)); + } + } + + if let Some(name) = fname { + info += " in "; + info += &name; + } + + if let Some(loc) = ctx.find_location(raddr.into()).unwrap_or(None) { + if info.is_empty() { + info += " in"; + } + info += " "; + if let Some(file) = loc.file { + info += file; + } + if let Some(line) = loc.line { + info += ":"; + info += &line.to_string(); + } + } else { + let _ = write!(&mut info, " ({}+{addr:#x})", self.images[*idx].0); + } + } + if info.is_empty() { + let _ = write!(&mut info, " ({}+{addr:#x})", self.images[*idx].0); + } + } + info + }; + + resolve_addr(pc) + } +} diff --git a/libafl_qemu/src/modules/utils/addr2lines.rs b/libafl_qemu/src/modules/utils/addr2lines.rs deleted file mode 100644 index 0719eb2f87..0000000000 --- a/libafl_qemu/src/modules/utils/addr2lines.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::borrow::Cow; - -use object::{Object, ObjectSection}; - -pub fn load_file_section<'input, 'arena, Endian: addr2line::gimli::Endianity>( - id: addr2line::gimli::SectionId, - file: &object::File<'input>, - endian: Endian, - arena_data: &'arena typed_arena::Arena>, -) -> Result, object::Error> { - // TODO: Unify with dwarfdump.rs in gimli. - let name = id.name(); - match file.section_by_name(name) { - Some(section) => match section.uncompressed_data()? { - Cow::Borrowed(b) => Ok(addr2line::gimli::EndianSlice::new(b, endian)), - Cow::Owned(b) => Ok(addr2line::gimli::EndianSlice::new( - arena_data.alloc(b.into()), - endian, - )), - }, - None => Ok(addr2line::gimli::EndianSlice::new(&[][..], endian)), - } -} - -/// Taken from `addr2line` [v0.22](https://github.com/gimli-rs/addr2line/blob/5c3c83f74f992220b2d9a17b3ac498a89214bf92/src/builtin_split_dwarf_loader.rs) -/// has been removed in version v0.23 for some reason. -/// TODO: find another cleaner solution. -pub mod addr2line_legacy { - use std::{borrow::Cow, env, ffi::OsString, fs::File, path::PathBuf, sync::Arc}; - - use addr2line::{gimli, LookupContinuation, LookupResult}; - use object::Object; - - #[cfg(unix)] - fn convert_path>( - r: &R, - ) -> Result { - use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; - let bytes = r.to_slice()?; - let s = OsStr::from_bytes(&bytes); - Ok(PathBuf::from(s)) - } - - #[cfg(not(unix))] - fn convert_path>( - r: &R, - ) -> Result { - let bytes = r.to_slice()?; - let s = str::from_utf8(&bytes).map_err(|_| gimli::Error::BadUtf8)?; - Ok(PathBuf::from(s)) - } - - fn load_section<'data, O, R, F>( - id: gimli::SectionId, - file: &O, - endian: R::Endian, - loader: &mut F, - ) -> R - where - O: Object<'data>, - R: gimli::Reader, - F: FnMut(Cow<'data, [u8]>, R::Endian) -> R, - { - use object::ObjectSection; - - let data = id - .dwo_name() - .and_then(|dwo_name| { - file.section_by_name(dwo_name) - .and_then(|section| section.uncompressed_data().ok()) - }) - .unwrap_or(Cow::Borrowed(&[])); - loader(data, endian) - } - - /// A simple builtin split DWARF loader. - pub struct SplitDwarfLoader - where - R: gimli::Reader, - F: FnMut(Cow<'_, [u8]>, R::Endian) -> R, - { - loader: F, - dwarf_package: Option>, - } - - impl SplitDwarfLoader - where - R: gimli::Reader, - F: FnMut(Cow<'_, [u8]>, R::Endian) -> R, - { - fn load_dwarf_package( - loader: &mut F, - path: Option, - ) -> Option> { - let mut path = path.map_or_else(env::current_exe, Ok).ok()?; - let dwp_extension = path.extension().map_or_else( - || OsString::from("dwp"), - |previous_extension| { - let mut previous_extension = previous_extension.to_os_string(); - previous_extension.push(".dwp"); - previous_extension - }, - ); - path.set_extension(dwp_extension); - let file = File::open(&path).ok()?; - let map = unsafe { memmap2::Mmap::map(&file).ok()? }; - let dwp = object::File::parse(&*map).ok()?; - - let endian = if dwp.is_little_endian() { - gimli::RunTimeEndian::Little - } else { - gimli::RunTimeEndian::Big - }; - - let empty = loader(Cow::Borrowed(&[]), endian); - gimli::DwarfPackage::load::<_, gimli::Error>( - |section_id| Ok(load_section(section_id, &dwp, endian, loader)), - empty, - ) - .ok() - } - - /// Create a new split DWARF loader. - pub fn new(mut loader: F, path: Option) -> SplitDwarfLoader { - let dwarf_package = SplitDwarfLoader::load_dwarf_package(&mut loader, path); - SplitDwarfLoader { - loader, - dwarf_package, - } - } - - /// Run the provided `LookupResult` to completion, loading any necessary - /// split DWARF along the way. - pub fn run(&mut self, mut l: LookupResult) -> L::Output - where - L: LookupContinuation, - { - loop { - let (load, continuation) = match l { - LookupResult::Output(output) => break output, - LookupResult::Load { load, continuation } => (load, continuation), - }; - - let mut r: Option>> = None; - if let Some(dwp) = self.dwarf_package.as_ref() { - if let Ok(Some(cu)) = dwp.find_cu(load.dwo_id, &load.parent) { - r = Some(Arc::new(cu)); - } - } - - if r.is_none() { - let mut path = PathBuf::new(); - if let Some(p) = load.comp_dir.as_ref() { - if let Ok(p) = convert_path(p) { - path.push(p); - } - } - - if let Some(p) = load.path.as_ref() { - if let Ok(p) = convert_path(p) { - path.push(p); - } - } - - if let Ok(file) = File::open(&path) { - if let Ok(map) = unsafe { memmap2::Mmap::map(&file) } { - if let Ok(file) = object::File::parse(&*map) { - let endian = if file.is_little_endian() { - gimli::RunTimeEndian::Little - } else { - gimli::RunTimeEndian::Big - }; - - r = gimli::Dwarf::load::<_, gimli::Error>(|id| { - Ok(load_section(id, &file, endian, &mut self.loader)) - }) - .ok() - .map(|mut dwo_dwarf| { - dwo_dwarf.make_dwo(&load.parent); - Arc::new(dwo_dwarf) - }); - } - } - } - } - - l = continuation.resume(r); - } - } - } -} diff --git a/libafl_qemu/src/modules/utils/mod.rs b/libafl_qemu/src/modules/utils/mod.rs index 54dc6737e9..6ad6c17029 100644 --- a/libafl_qemu/src/modules/utils/mod.rs +++ b/libafl_qemu/src/modules/utils/mod.rs @@ -1,3 +1,6 @@ -pub mod addr2lines; pub mod filters; -pub use addr2lines::*; + +#[cfg(feature = "usermode")] +pub use addr2line::*; +#[cfg(feature = "usermode")] +pub mod addr2line;