Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4976,6 +4976,7 @@ dependencies = [
"minidump-unwind",
"moka",
"regex",
"scroll 0.12.0",
"sentry",
"serde",
"serde_json",
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ rayon = "1.10.0"
regex = "1.5.5"
reqwest = "0.12.15"
rustls = { version = "0.23.31", features = ["ring"] }
scroll = "0.12.0"
sentry = { version = "0.42.0", default-features = false, features = [
# default features, except `release-health` is disabled
"backtrace",
Expand Down
1 change: 1 addition & 0 deletions crates/symbolicator-native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ minidump-processor = { workspace = true }
minidump-unwind = { workspace = true }
moka = { workspace = true }
regex = { workspace = true }
scroll = { workspace = true }
sentry = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
3 changes: 3 additions & 0 deletions crates/symbolicator-native/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use symbolicator_service::utils::hex::HexValue;
use symbolicator_sources::SourceConfig;
use thiserror::Error;

use crate::memory::MemoryAccess;
pub use crate::metrics::StacktraceOrigin;

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -68,6 +69,8 @@ pub struct SymbolicateStacktraces {
pub frame_order: FrameOrder,
/// Whether we extract variables.
pub extract_variables: bool,
/// The program memory, if it is available.
pub memory: Option<Arc<dyn MemoryAccess>>,
}

/// Location of an attachment file, such as a minidump.
Expand Down
1 change: 1 addition & 0 deletions crates/symbolicator-native/src/lib.rs
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;

Expand Down
49 changes: 49 additions & 0 deletions crates/symbolicator-native/src/memory.rs
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.
Comment thread
Dav1dde marked this conversation as resolved.
Outdated
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 {}
1 change: 1 addition & 0 deletions crates/symbolicator-native/src/symbolication/apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl SymbolicationActor {
rewrite_first_module: Default::default(),
frame_order: FrameOrder::CalleeFirst,
extract_variables,
memory: None,
};

let mut system_info = SystemInfo {
Expand Down
83 changes: 62 additions & 21 deletions crates/symbolicator-native/src/symbolication/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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![];
Expand Down Expand Up @@ -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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 format!("{s:?}") materializes an uncapped, substantially larger String that is retained in the response, potentially exhausting worker memory.

Evidence
  • /minidump and /symbolicate-any accept options.extract_variables; minidump processing passes Some(Arc::new(minidump)) as the memory source to native symbolication (crates/symbolicator/src/endpoints/minidump.rs:47-50, crates/symbolicator-native/src/symbolication/process_minidump.rs:672-676).
  • resolve_variable_value obtains TypeSize::Bytes(size) from SymCache primitive/pointer types and uses that value for FrameOffset memory access (crates/symbolicator-native/src/symbolication/native.rs:212-215, 231-257); no application-level maximum is applied to size.
  • get_memory_at_address safely rejects ranges beyond the mapped memory region, but a valid range may still span the dump's large memory region (crates/symbolicator-native/src/memory.rs:15-27). The fallback then Debug-formats every byte into an owned String with no output cap (crates/symbolicator-native/src/symbolication/native.rs:255-257), and the String is retained in the variables map at lines 185-195.
  • The local minidump body limit is 250 MiB (crates/symbolicator-service/src/config.rs:663-665), which limits input bytes but not the formatted output or serialization work; the remote attachment path additionally streams the storage response without applying that limit (crates/symbolicator-native/src/symbolication/attachments.rs:36-52).
Also found at 1 additional location
  • crates/symbolicator-native/src/symbolication/native.rs:256-258

Identified by Warden · wrdn-dos-review · 88N-CRQ

}

rv.push(SymbolicatedFrame {
Expand Down Expand Up @@ -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();

Expand All @@ -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(
Expand All @@ -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,
};
Comment thread
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(|&reg| registers.get(reg))
.map(|v| v.to_string())
}
.get(id as usize)
.and_then(|&reg| 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,8 @@ impl SymbolicationActor {
metric!(distribution("minidump.upload.size") = len as f64);

let bv = ByteView::map_file(minidump_file)?;
// let minidump = SelfCell::try_new(bv, |bv| Minidump::read(unsafe { &*bv }))?;

Comment thread
Dav1dde marked this conversation as resolved.
Outdated
let minidump = Minidump::read(bv)?;

let StackWalkMinidumpResult {
Expand Down Expand Up @@ -672,6 +674,7 @@ impl SymbolicationActor {
rewrite_first_module,
frame_order: FrameOrder::CalleeFirst,
extract_variables: request.extract_variables,
memory: Some(Arc::new(minidump)),
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
Dav1dde marked this conversation as resolved.
};

Ok((request, minidump_state))
Expand Down
12 changes: 7 additions & 5 deletions crates/symbolicator-native/src/symbolication/symbolicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::interface::{
FrameTrust, RawFrame, RawStacktrace, Registers, Signal, SymbolicateStacktraces,
SymbolicatedFrame,
};
use crate::memory::MemoryAccess;
use crate::metrics::{StacktraceMetrics, record_symbolication_metrics};

use super::demangle::DemangleCache;
Expand Down Expand Up @@ -110,6 +111,7 @@ impl SymbolicationActor {
rewrite_first_module,
frame_order,
extract_variables,
memory,
} = request;

if frame_order == FrameOrder::CallerFirst {
Expand Down Expand Up @@ -142,7 +144,7 @@ impl SymbolicationActor {
&module_lookup,
&mut metrics,
signal,
extract_variables,
memory.as_deref().filter(|_| extract_variables),
)
})
.collect();
Expand Down Expand Up @@ -178,7 +180,7 @@ fn symbolicate_stacktrace(
caches: &ModuleLookup,
metrics: &mut StacktraceMetrics,
signal: Option<Signal>,
extract_variables: bool,
memory: Option<&dyn MemoryAccess>,
) -> CompleteStacktrace {
let default_adjustment = AdjustInstructionAddr::default_for_thread(&thread);
let mut symbolicated_frames = vec![];
Expand All @@ -194,7 +196,7 @@ fn symbolicate_stacktrace(
&mut frame,
index,
adjustment,
extract_variables,
memory,
) {
Ok(frames) => {
if matches!(frame.trust, FrameTrust::Scan) {
Expand Down Expand Up @@ -319,7 +321,7 @@ fn symbolicate_frame(
frame: &mut RawFrame,
index: usize,
adjustment: AdjustInstructionAddr,
extract_variables: bool,
memory: Option<&dyn MemoryAccess>,
) -> Result<Vec<SymbolicatedFrame>, FrameStatus> {
let lookup_result = caches
.lookup_cache(frame.instruction_addr.0, frame.addr_mode)
Expand Down Expand Up @@ -347,7 +349,7 @@ fn symbolicate_frame(
relative_addr,
frame,
index,
extract_variables,
memory,
)
}
Ok(CacheFileEntry::PortablePdbCache(ppdb_cache)) => {
Expand Down
1 change: 1 addition & 0 deletions crates/symbolicator-native/tests/integration/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub fn make_symbolication_request(
rewrite_first_module: Default::default(),
frame_order: FrameOrder::CalleeFirst,
extract_variables: false,
memory: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/symbolicator-stress/src/workloads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ pub fn prepare_payload(
rewrite_first_module: Default::default(),
frame_order: FrameOrder::CallerFirst,
extract_variables: true,
memory: None,
})
}
Payload::Js { source, event } => {
Expand Down
1 change: 1 addition & 0 deletions crates/symbolicator/src/endpoints/symbolicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub async fn symbolicate_frames(
rewrite_first_module: Default::default(),
frame_order: body.options.frame_order,
extract_variables: body.options.extract_variables,
memory: None,
},
body.options,
)?;
Expand Down
2 changes: 2 additions & 0 deletions crates/symbolicator/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@ mod tests {
rewrite_first_module: Default::default(),
frame_order: FrameOrder::CalleeFirst,
extract_variables: false,
memory: None,
};

let request_id = service
Expand Down Expand Up @@ -681,6 +682,7 @@ mod tests {
rewrite_first_module: Default::default(),
frame_order: FrameOrder::CalleeFirst,
extract_variables: false,
memory: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/symbolicli/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ pub fn create_native_symbolication_request(
// "callee first"
frame_order: FrameOrder::CalleeFirst,
extract_variables,
memory: None,
})
}

Expand Down
Loading