-
-
Notifications
You must be signed in to change notification settings - Fork 71
feat(variables): Begin reading minidump memory for variables #2035
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| mod caches; | ||
| pub mod interface; | ||
| mod memory; | ||
| mod metrics; | ||
| mod symbolication; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| use minidump::Minidump; | ||
| use scroll::ctx::{SizeWith, TryFromCtx}; | ||
| use symbolic::common::ByteView; | ||
|
|
||
| /// Unified memory access for crash dumps. | ||
| pub trait MemoryAccess: std::fmt::Debug + Send + Sync { | ||
| /// Attempts to lookup a memory range at the specified `addr` with the specified size. | ||
| fn get_memory_at_address(&self, addr: u64, size: usize) -> Option<&'_ [u8]>; | ||
|
|
||
| /// The endianness of the crash. | ||
| fn endian(&self) -> scroll::Endian; | ||
| } | ||
|
|
||
| impl MemoryAccess for Minidump<'static, ByteView<'static>> { | ||
| fn get_memory_at_address(&self, addr: u64, size: usize) -> Option<&'_ [u8]> { | ||
| let memory = self.get_memory()?; | ||
| let memory = memory.memory_at_address(addr)?; | ||
|
|
||
| let start = addr.checked_sub(memory.base_address())? as usize; | ||
| let end = start.checked_add(size)?; | ||
|
|
||
| match memory { | ||
| minidump::UnifiedMemory::Memory(region) => region.bytes.get(start..end), | ||
| minidump::UnifiedMemory::Memory64(region) => region.bytes.get(start..end), | ||
| } | ||
| } | ||
|
|
||
| fn endian(&self) -> scroll::Endian { | ||
| self.endian | ||
| } | ||
| } | ||
|
|
||
| /// Extension trait for [`MemoryAccess`]. | ||
| pub trait MemoryAccessExt: MemoryAccess { | ||
| /// Helper which access the memory of a dump and converts the memory to the specified type. | ||
| fn get_value_at_address<T>(&self, addr: u64) -> Option<T> | ||
| where | ||
| T: SizeWith<scroll::Endian>, | ||
| for<'a> T: TryFromCtx<'a, scroll::Endian>, | ||
| { | ||
| let endian = self.endian(); | ||
| let size = T::size_with(&endian); | ||
| let memory = self.get_memory_at_address(addr, size)?; | ||
|
|
||
| T::try_from_ctx(memory, endian).ok().map(|(value, _)| value) | ||
| } | ||
| } | ||
|
|
||
| impl<T: MemoryAccess + ?Sized> MemoryAccessExt for T {} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,13 +2,16 @@ use std::collections::BTreeMap; | |
|
|
||
| use minidump::CpuContext; | ||
| use symbolic::common::{CpuFamily, InstructionInfo, Language, split_path}; | ||
| use symbolic::symcache::{SourceLocation, SymCache, Type, Variable, VariableLocation}; | ||
| use symbolic::symcache::{ | ||
| SourceLocation, SymCache, Type, TypeSize, VariableLocation, VariableLocationInfo, | ||
| }; | ||
| use symbolicator_service::metric; | ||
| use symbolicator_service::utils::hex::HexValue; | ||
|
|
||
| use crate::interface::{ | ||
| AdjustInstructionAddr, FrameStatus, RawFrame, Registers, Signal, SymbolicatedFrame, | ||
| }; | ||
| use crate::memory::{MemoryAccess, MemoryAccessExt}; | ||
|
|
||
| use super::demangle::DemangleCache; | ||
| use super::module_lookup::CacheLookupResult; | ||
|
|
@@ -20,7 +23,7 @@ pub fn symbolicate_native_frame( | |
| relative_addr: u64, | ||
| frame: &RawFrame, | ||
| index: usize, | ||
| extract_variables: bool, | ||
| memory: Option<&dyn MemoryAccess>, | ||
| ) -> Result<Vec<SymbolicatedFrame>, FrameStatus> { | ||
| tracing::trace!("Symbolicating {:#x}", relative_addr); | ||
| let mut rv = vec![]; | ||
|
|
@@ -53,8 +56,8 @@ pub fn symbolicate_native_frame( | |
| }; | ||
|
|
||
| let mut vars = None; | ||
| if extract_variables { | ||
| vars = do_extract_variables(&source_location, symcache, &frame.registers); | ||
| if let Some(memory) = memory { | ||
| vars = do_extract_variables(&source_location, symcache, &frame.registers, memory); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Variable extraction materializes attacker-sized memory dumps without an output cap When variable extraction is enabled, a SymCache-derived primitive or pointer size controls the minidump range read for a frame-offset variable. Although the memory accessor checks that the range exists, a successful range can be as large as the accepted dump and Evidence
Also found at 1 additional location
Identified by Warden · wrdn-dos-review · 88N-CRQ |
||
| } | ||
|
|
||
| rv.push(SymbolicatedFrame { | ||
|
|
@@ -167,6 +170,7 @@ fn do_extract_variables<'data, 'cache>( | |
| source_location: &SourceLocation<'data, 'cache>, | ||
| cache: &SymCache<'cache>, | ||
| registers: &Registers, | ||
| memory: &dyn MemoryAccess, | ||
| ) -> Option<BTreeMap<String, serde_json::Value>> { | ||
| let mut result = BTreeMap::new(); | ||
|
|
||
|
|
@@ -178,7 +182,9 @@ fn do_extract_variables<'data, 'cache>( | |
| let mut ty = String::new(); | ||
| resolve_type_name(&mut ty, cache, variable.ty(), 0); | ||
|
|
||
| let value = resolve_variable_value(cache, registers, &variable); | ||
| let value = variable | ||
| .locations() | ||
| .find_map(|loc| resolve_variable_value(cache, registers, memory, loc, variable.ty())); | ||
|
|
||
| // This doesn't handle name collisions currently. | ||
| result.insert( | ||
|
|
@@ -199,24 +205,59 @@ fn do_extract_variables<'data, 'cache>( | |
| fn resolve_variable_value( | ||
| cache: &SymCache<'_>, | ||
| registers: &Registers, | ||
| variable: &Variable<'_, '_>, | ||
| ) -> Option<HexValue> { | ||
| variable.locations().find_map(|location| { | ||
| let VariableLocation::Register { id } = location.location else { | ||
| return None; | ||
| }; | ||
| memory: &dyn MemoryAccess, | ||
| location: VariableLocationInfo, | ||
| ty: Option<Type<'_>>, | ||
| ) -> Option<String> { | ||
| let TypeSize::Bytes(size) = match ty? { | ||
| Type::Primitive(ty) => ty.size(), | ||
| Type::Pointer(ty) => ty.size(), | ||
| _ => return None, | ||
| }; | ||
|
Dav1dde marked this conversation as resolved.
|
||
|
|
||
| // Temporary hack, `symbolic` will need an abstraction over registers, which allows | ||
| // mapping register names to the gimli register ids. | ||
| match cache.arch().cpu_family() { | ||
| CpuFamily::Amd64 => minidump::format::CONTEXT_AMD64::REGISTERS, | ||
| CpuFamily::Arm64 => minidump::format::CONTEXT_ARM64::REGISTERS, | ||
| _ => &[], | ||
| match location.location { | ||
| VariableLocation::Register { id } => { | ||
| // Temporary hack, `symbolic` will need an abstraction over registers, which allows | ||
| // mapping register names to the gimli register ids. | ||
| match cache.arch().cpu_family() { | ||
| CpuFamily::Amd64 => minidump::format::CONTEXT_AMD64::REGISTERS, | ||
| CpuFamily::Arm64 => minidump::format::CONTEXT_ARM64::REGISTERS, | ||
| _ => &[], | ||
| } | ||
| .get(id as usize) | ||
| .and_then(|®| registers.get(reg)) | ||
| .map(|v| v.to_string()) | ||
| } | ||
| .get(id as usize) | ||
| .and_then(|®| registers.get(reg)) | ||
| .copied() | ||
| }) | ||
| VariableLocation::FrameOffset { offset } => { | ||
| let &HexValue(frame_base) = match cache.arch().cpu_family() { | ||
| CpuFamily::Amd64 => Some("rbp"), | ||
| CpuFamily::Arm64 => Some("fp"), | ||
| _ => None, | ||
| } | ||
| .and_then(|reg| registers.get(reg))?; | ||
|
|
||
| let addr = u64::try_from(i64::try_from(frame_base).ok()? + offset).ok()?; | ||
|
|
||
| // This obviously will need to be changed to consider the variable type. | ||
| match size { | ||
| 1 => memory | ||
| .get_value_at_address::<u8>(addr) | ||
| .map(|v| HexValue(v.into()).to_string()), | ||
| 2 => memory | ||
| .get_value_at_address::<u16>(addr) | ||
| .map(|v| HexValue(v.into()).to_string()), | ||
| 4 => memory | ||
| .get_value_at_address::<u32>(addr) | ||
| .map(|v| HexValue(v.into()).to_string()), | ||
| 8 => memory | ||
| .get_value_at_address::<u64>(addr) | ||
| .map(|v| HexValue(v).to_string()), | ||
| s => memory | ||
| .get_memory_at_address(addr, s as usize) | ||
| .map(|s| format!("{s:?}")), | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn resolve_type_name( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.