use std::collections::{BTreeMap, BTreeSet};
use std::ops::{Deref, RangeInclusive};
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
use minidump::system_info::PointerWidth;
use minidump::*;
use minidump_unwind::{
walk_stack, CallStack, CallStackInfo, FrameTrust, StackFrame, SymbolProvider, SystemInfo,
};
use crate::op_analysis::MemoryAddressInfo;
use crate::process_state::{LinuxStandardBase, ProcessState};
use crate::{
arg_recovery, evil, AdjustedAddress, CrashInconsistency, LinuxProcLimits, LinuxProcStatus,
};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ProcessorOptions<'a> {
pub evil_json: Option<&'a Path>,
pub recover_function_args: bool,
pub stat_reporter: Option<&'a PendingProcessorStats>,
}
#[derive(Debug)]
pub struct PendingProcessorStats {
subscriptions: PendingProcessorStatSubscriptions,
stats: Arc<Mutex<PendingProcessorStatsInner>>,
}
#[derive(Default, Debug, Clone)]
struct PendingProcessorStatsInner {
num_threads_processed: u64,
total_threads: u64,
num_frames_processed: u64,
new_walked_frames: Vec<WalkedFrame>,
unwalked_result: Option<ProcessState>,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct PendingProcessorStatSubscriptions {
pub thread_count: bool,
pub frame_count: bool,
pub unwalked_result: bool,
pub live_frames: bool,
}
#[derive(Debug, Clone)]
pub struct WalkedFrame {
pub thread_idx: usize,
pub frame_idx: usize,
pub frame: StackFrame,
}
impl PendingProcessorStats {
pub fn new(subscriptions: PendingProcessorStatSubscriptions) -> Self {
Self {
subscriptions,
stats: Default::default(),
}
}
pub fn get_thread_count(&self) -> (u64, u64) {
assert!(
self.subscriptions.thread_count,
"tried to get thread count stats, but wasn't subscribed!"
);
let stats = self.stats.lock().unwrap();
(stats.num_threads_processed, stats.total_threads)
}
pub fn get_frame_count(&self) -> u64 {
assert!(
self.subscriptions.frame_count,
"tried to get frame count stats, but wasn't subscribed!"
);
let stats = self.stats.lock().unwrap();
stats.num_frames_processed
}
pub fn drain_new_frames(&self, mut callback: impl FnMut(WalkedFrame)) {
assert!(
self.subscriptions.live_frames,
"tried to get new frames, but wasn't subscribed!"
);
let mut stats = self.stats.lock().unwrap();
for frame in stats.new_walked_frames.drain(..) {
callback(frame);
}
}
pub fn take_unwalked_result(&self) -> Option<ProcessState> {
assert!(
self.subscriptions.unwalked_result,
"tried to get unwalked result, but wasn't subscribed!"
);
let mut stats = self.stats.lock().unwrap();
stats.unwalked_result.take()
}
pub(crate) fn set_total_threads(&self, total_threads: u64) {
if self.subscriptions.thread_count {
let mut stats = self.stats.lock().unwrap();
stats.total_threads = total_threads;
}
}
pub(crate) fn inc_processed_threads(&self) {
if self.subscriptions.thread_count {
let mut stats = self.stats.lock().unwrap();
stats.num_threads_processed += 1;
}
}
pub(crate) fn add_walked_frame(&self, thread_idx: usize, frame_idx: usize, frame: &StackFrame) {
if self.subscriptions.live_frames || self.subscriptions.frame_count {
let mut stats = self.stats.lock().unwrap();
stats.num_frames_processed += 1;
if self.subscriptions.live_frames {
stats.new_walked_frames.push(WalkedFrame {
thread_idx,
frame_idx,
frame: frame.clone(),
});
}
}
}
pub(crate) fn add_unwalked_result(&self, state: &ProcessState) {
if self.subscriptions.unwalked_result {
let mut stats = self.stats.lock().unwrap();
stats.unwalked_result = Some(state.clone());
}
}
}
impl ProcessorOptions<'_> {
pub fn stable_basic() -> Self {
ProcessorOptions {
evil_json: None,
recover_function_args: false,
stat_reporter: None,
}
}
pub fn stable_all() -> Self {
ProcessorOptions {
evil_json: None,
recover_function_args: false,
stat_reporter: None,
}
}
pub fn unstable_all() -> Self {
ProcessorOptions {
evil_json: None,
recover_function_args: true,
stat_reporter: None,
}
}
fn check_deprecated_and_disabled(&self) {
}
}
impl Default for ProcessorOptions<'_> {
fn default() -> Self {
Self::stable_basic()
}
}
#[derive(Clone, Debug, thiserror::Error)]
pub enum ProcessError {
#[error("Failed to read minidump")]
MinidumpReadError(#[from] minidump::Error),
#[error("An unknown error occurred")]
UnknownError,
#[error("The system information stream was not found")]
MissingSystemInfo,
#[error("The thread list stream was not found")]
MissingThreadList,
}
impl ProcessError {
pub fn name(&self) -> &'static str {
match self {
ProcessError::MinidumpReadError(_) => "MinidumpReadError",
ProcessError::UnknownError => "UnknownError",
ProcessError::MissingSystemInfo => "MissingSystemInfo",
ProcessError::MissingThreadList => "MissingThreadList",
}
}
}
pub async fn process_minidump<'a, T, P>(
dump: &Minidump<'a, T>,
symbol_provider: &P,
) -> Result<ProcessState, ProcessError>
where
T: Deref<Target = [u8]> + 'a,
P: SymbolProvider + Sync,
{
process_minidump_with_options(dump, symbol_provider, ProcessorOptions::default()).await
}
fn get_microcode_version(linux_cpu_info: &MinidumpLinuxCpuInfo, evil: &evil::Evil) -> Option<u64> {
linux_cpu_info
.iter()
.find_map(|(key, val)| {
if key.as_bytes() == b"microcode" {
val.to_str().ok()
} else {
None
}
})
.or(evil.cpu_microcode_version.as_deref())
.and_then(|val| val.strip_prefix("0x"))
.and_then(|val| u64::from_str_radix(val, 16).ok())
}
pub async fn process_minidump_with_options<'a, T, P>(
dump: &Minidump<'a, T>,
symbol_provider: &P,
options: ProcessorOptions<'_>,
) -> Result<ProcessState, ProcessError>
where
T: Deref<Target = [u8]> + 'a,
P: SymbolProvider + Sync,
{
let info = MinidumpInfo::new(dump, options)?;
let mut exception_details = info.get_exception_details();
if let Some(details) = &mut exception_details {
info.check_for_bitflips(details);
info.check_for_guard_pages(details);
info.check_for_crash_inconsistencies(details);
}
info.into_process_state(dump, symbol_provider, exception_details)
.await
}
struct MinidumpInfo<'a> {
options: ProcessorOptions<'a>,
evil: crate::evil::Evil,
thread_list: MinidumpThreadList<'a>,
thread_names: MinidumpThreadNames,
dump_system_info: MinidumpSystemInfo,
linux_standard_base: Option<LinuxStandardBase>,
linux_proc_status: Option<LinuxProcStatus>,
linux_proc_limits: Option<LinuxProcLimits>,
system_info: SystemInfo,
mac_crash_info: Option<Vec<RawMacCrashInfo>>,
mac_boot_args: Option<MinidumpMacBootargs>,
misc_info: Option<MinidumpMiscInfo>,
dump_thread_id: Option<u32>,
requesting_thread_id: Option<u32>,
modules: MinidumpModuleList,
unloaded_modules: MinidumpUnloadedModuleList,
memory_list: UnifiedMemoryList<'a>,
linux_memory_map_count: Option<usize>,
memory_info: UnifiedMemoryInfoList<'a>,
handle_data_stream: Option<MinidumpHandleDataStream>,
exception: Option<MinidumpException<'a>>,
soft_errors: Option<serde_json::Value>,
}
impl<'a> MinidumpInfo<'a> {
pub fn new<T: Deref<Target = [u8]> + 'a>(
dump: &'a Minidump<'a, T>,
options: ProcessorOptions<'a>,
) -> Result<Self, ProcessError> {
options.check_deprecated_and_disabled();
let evil = options
.evil_json
.and_then(evil::handle_evil)
.unwrap_or_default();
let thread_list = dump
.get_stream::<MinidumpThreadList>()
.or(Err(ProcessError::MissingThreadList))?;
let num_threads = thread_list.threads.len() as u64;
if let Some(reporter) = options.stat_reporter {
reporter.set_total_threads(num_threads);
}
let thread_names = dump
.get_stream::<MinidumpThreadNames>()
.unwrap_or_else(|_| MinidumpThreadNames::default());
let dump_system_info = dump
.get_stream::<MinidumpSystemInfo>()
.or(Err(ProcessError::MissingSystemInfo))?;
let (os_version, os_build) = dump_system_info.os_parts();
let linux_standard_base = dump.get_stream::<MinidumpLinuxLsbRelease>().ok();
let linux_cpu_info = dump
.get_stream::<MinidumpLinuxCpuInfo>()
.unwrap_or_default();
let _linux_environ = dump.get_stream::<MinidumpLinuxEnviron>().ok();
let linux_proc_status = dump.get_stream::<MinidumpLinuxProcStatus>().ok();
let linux_proc_limits = dump.get_stream::<MinidumpLinuxProcLimits>().ok();
let soft_errors = dump.get_stream::<MinidumpSoftErrors>().ok();
let cpu_microcode_version = get_microcode_version(&linux_cpu_info, &evil);
let linux_standard_base = linux_standard_base.map(LinuxStandardBase::from);
let linux_proc_status = linux_proc_status.map(LinuxProcStatus::from);
let linux_proc_limits = linux_proc_limits.map(LinuxProcLimits::from);
let soft_errors =
soft_errors.and_then(|se| serde_json::from_str::<serde_json::Value>(se.as_ref()).ok());
let cpu_info = dump_system_info
.cpu_info()
.map(|string| string.into_owned());
let system_info = SystemInfo {
os: dump_system_info.os,
os_version: Some(os_version),
os_build,
cpu: dump_system_info.cpu,
cpu_info,
cpu_microcode_version,
cpu_count: dump_system_info.raw.number_of_processors as usize,
};
let mac_crash_info = dump
.get_stream::<MinidumpMacCrashInfo>()
.ok()
.map(|info| info.raw);
let mac_boot_args = dump.get_stream::<MinidumpMacBootargs>().ok();
let misc_info = dump.get_stream::<MinidumpMiscInfo>().ok();
let breakpad_info = dump.get_stream::<MinidumpBreakpadInfo>();
let (dump_thread_id, requesting_thread_id) = if let Ok(info) = breakpad_info {
(info.dump_thread_id, info.requesting_thread_id)
} else {
(None, None)
};
let modules = match dump.get_stream::<MinidumpModuleList>() {
Ok(module_list) => module_list,
Err(_) => MinidumpModuleList::new(),
};
let unloaded_modules = match dump.get_stream::<MinidumpUnloadedModuleList>() {
Ok(module_list) => module_list,
Err(_) => MinidumpUnloadedModuleList::new(),
};
let memory_list = dump.get_memory().unwrap_or_default();
let memory_info_list = dump.get_stream::<MinidumpMemoryInfoList>().ok();
let linux_maps = dump.get_stream::<MinidumpLinuxMaps>().ok();
let linux_memory_map_count = linux_maps.clone().map(|maps| maps.memory_map_count());
let memory_info =
UnifiedMemoryInfoList::new(memory_info_list, linux_maps).unwrap_or_default();
let handle_data_stream = dump.get_stream::<MinidumpHandleDataStream>().ok();
let exception = dump.get_stream::<MinidumpException>().ok();
Ok(MinidumpInfo {
options,
evil,
thread_list,
thread_names,
dump_system_info,
linux_standard_base,
linux_proc_status,
linux_proc_limits,
system_info,
mac_crash_info,
mac_boot_args,
misc_info,
dump_thread_id,
requesting_thread_id,
modules,
unloaded_modules,
memory_list,
memory_info,
linux_memory_map_count,
handle_data_stream,
exception,
soft_errors,
})
}
pub fn get_exception_details(&self) -> Option<ExceptionDetails<'a>> {
use crate::op_analysis::InstructionPointerUpdate;
let exception = self.exception.as_ref()?;
let reason = exception.get_crash_reason(self.system_info.os, self.system_info.cpu);
let address = exception.get_crash_address(self.system_info.os, self.system_info.cpu);
let stack_memory_ref = self
.thread_list
.get_thread(exception.get_crashing_thread_id())
.and_then(|thread| thread.stack_memory(&self.memory_list));
let context = exception.context(&self.dump_system_info, self.misc_info.as_ref());
let mut exception_info: Option<crate::ExceptionInfo> = None;
let mut instruction_registers: BTreeSet<&'static str> = Default::default();
if let Some(context) = context.as_ref() {
match crate::op_analysis::analyze_thread_context(
context,
&self.memory_list,
stack_memory_ref,
) {
Ok(op_analysis) => {
let access_addresses =
op_analysis.memory_access_list.as_ref().map(|access_list| {
access_list
.iter()
.map(|access| access.address_info)
.collect::<Vec<MemoryAddressInfo>>()
});
let addresses = access_addresses.map(|mut accesses| {
match op_analysis.instruction_pointer_update {
Some(InstructionPointerUpdate::Update { address_info }) => {
accesses.push(address_info);
accesses
}
_ => accesses,
}
});
let addresses = addresses.as_deref();
let adjusted_address = try_detect_null_pointer_in_disguise(addresses)
.map(|offset| AdjustedAddress::NullPointerWithOffset(offset.into()))
.or_else(|| {
try_get_non_canonical_crash_address(
&self.system_info,
addresses,
reason,
address,
)
.map(|addr| AdjustedAddress::NonCanonical(addr.into()))
});
instruction_registers.clone_from(&op_analysis.registers);
exception_info = Some(crate::ExceptionInfo::with_op_analysis(
reason,
address.into(),
adjusted_address,
op_analysis,
));
}
Err(e) => {
tracing::warn!("failed to analyze the thread context: {e}");
}
}
}
let info =
exception_info.unwrap_or_else(|| crate::ExceptionInfo::new(reason, address.into()));
Some(ExceptionDetails {
info,
context,
instruction_registers,
})
}
pub fn check_for_bitflips(&self, exception_details: &mut ExceptionDetails<'a>) {
if self.system_info.cpu.pointer_width() != PointerWidth::Bits64 {
return;
}
if self.system_info.cpu == system_info::Cpu::Arm64 {
return;
}
let info = &mut exception_details.info;
use bitflip::BitRange;
use memory_operation::MemoryOperation;
let bit_flip_address = match &info.adjusted_address {
Some(AdjustedAddress::NonCanonical(v)) => Some((v.0, BitRange::Amd64NonCanonical)),
Some(AdjustedAddress::NullPointerWithOffset(_)) => None,
None => Some((
info.address.0,
if self.system_info.cpu != system_info::Cpu::X86_64 {
BitRange::All
} else {
BitRange::Amd64Canononical
},
)),
};
if let Some((address, bit_range)) = bit_flip_address {
let memory_op = MemoryOperation::from_crash_reason(&info.reason);
info.possible_bit_flips = bitflip::try_bit_flips(
address,
None,
bit_range,
exception_details.context.as_deref(),
&self.memory_info,
memory_op,
);
if let Some(context) = exception_details.context.as_deref() {
for reg in &exception_details.instruction_registers {
if let Some(address) = context.get_register(reg) {
info.possible_bit_flips.extend(bitflip::try_bit_flips(
address,
Some(reg),
bit_range,
Some(context),
&self.memory_info,
memory_op,
));
}
}
}
}
}
pub fn check_for_guard_pages(&self, exception_details: &mut ExceptionDetails<'a>) {
const GUARD_MEMORY_MAX_SIZE: u64 = 2 << 14;
if let Some(access_list) = &mut exception_details.info.memory_access_list {
for access in &mut access_list.accesses {
let Some(info) = self
.memory_info
.memory_info_at_address(access.address_info.address)
else {
continue;
};
let Some(range) = info.memory_range() else {
continue;
};
fn is_accessible(range: &UnifiedMemoryInfo) -> bool {
range.is_readable() || range.is_writable() || range.is_executable()
}
let is_adjacent_to_accessible_memory = || {
for region in self.memory_info.by_addr() {
let Some(other_range) = region.memory_range() else {
continue;
};
if other_range.end + 1 == range.start && is_accessible(®ion) {
return true;
}
if range.end + 1 == other_range.start {
return is_accessible(®ion);
}
}
false
};
if !is_accessible(&info)
&& range.end - range.start < GUARD_MEMORY_MAX_SIZE
&& is_adjacent_to_accessible_memory()
{
access.address_info.is_likely_guard_page = true;
}
}
}
}
pub fn check_for_crash_inconsistencies(&self, exception_details: &mut ExceptionDetails) {
use minidump_common::errors::{
ExceptionCodeLinuxSigfpeKind as LinuxSigfpe,
ExceptionCodeMacArithmeticPpcType as MacArithPpc,
ExceptionCodeMacArithmeticX86Type as MacArithX86, ExceptionCodeWindows,
ExceptionCodeWindowsAccessType as WinAccess, NtStatusWindows,
};
use CrashInconsistency as Inconsistency;
let mut inconsistencies = Vec::new();
match exception_details.info.reason {
CrashReason::MacArithmeticPpc(MacArithPpc::EXC_PPC_ZERO_DIVIDE)
| CrashReason::MacArithmeticX86(MacArithX86::EXC_I386_DIV)
| CrashReason::LinuxSigfpe(LinuxSigfpe::FPE_INTDIV)
| CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_INT_DIVIDE_BY_ZERO) => {
if exception_details
.info
.instruction_properties
.as_ref()
.is_some_and(|p| !p.is_division)
{
inconsistencies.push(Inconsistency::IntDivByZeroNotPossible);
}
}
CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_PRIV_INSTRUCTION)
| CrashReason::WindowsNtStatus(NtStatusWindows::STATUS_PRIVILEGED_INSTRUCTION) => {
if exception_details
.info
.instruction_properties
.as_ref()
.is_some_and(|p| !p.is_privileged)
{
inconsistencies.push(Inconsistency::PrivInstructionCrashWithoutPrivInstruction);
}
}
CrashReason::WindowsAccessViolation(WinAccess::READ) => {
let is_gpf = represents_general_protection_fault(
self.system_info.os,
exception_details.info.reason,
exception_details.info.address.0,
) && self.crashing_access_is_not_among_accesses(exception_details);
let gpf_implies_non_canonical = exception_details
.info
.instruction_properties
.as_ref()
.is_some_and(|p| p.is_only_gpf_when_non_canonical);
if is_gpf {
if gpf_implies_non_canonical
&& self.non_canonical_address_is_not_among_accesses(exception_details)
{
inconsistencies.push(Inconsistency::NonCanonicalAddressFalselyReported);
}
} else {
if self.crashing_access_is_not_among_accesses(exception_details) {
inconsistencies.push(Inconsistency::CrashingAccessNotFoundInMemoryAccesses);
}
if self.access_is_not_violation(exception_details) {
inconsistencies.push(Inconsistency::AccessViolationWhenAccessAllowed);
}
}
}
CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_STACK_OVERFLOW)
| CrashReason::WindowsAccessViolation(WinAccess::WRITE)
| CrashReason::WindowsAccessViolation(WinAccess::EXEC) => {
if self.crashing_access_is_not_among_accesses(exception_details) {
inconsistencies.push(Inconsistency::CrashingAccessNotFoundInMemoryAccesses);
}
if self.access_is_not_violation(exception_details) {
inconsistencies.push(Inconsistency::AccessViolationWhenAccessAllowed);
}
}
_ => (),
}
exception_details.info.inconsistencies = inconsistencies;
}
fn non_canonical_address_is_not_among_accesses(
&self,
exception_details: &ExceptionDetails,
) -> bool {
use crate::op_analysis::InstructionPointerUpdate;
const NON_CANONICAL_RANGE: RangeInclusive<u64> =
0x0000_8000_0000_0000..=0xffff_7fff_ffff_ffff;
let info = &exception_details.info;
info.instruction_properties
.as_ref()
.is_some_and(|properties| properties.is_access_derivable)
&& info.memory_access_list.as_ref().is_some_and(|access_list| {
!access_list
.iter()
.any(|access| NON_CANONICAL_RANGE.contains(&access.address_info.address))
})
&& info
.instruction_pointer_update
.as_ref()
.is_some_and(|update| match update {
InstructionPointerUpdate::Update { address_info } => {
!NON_CANONICAL_RANGE.contains(&address_info.address)
}
InstructionPointerUpdate::NoUpdate => true,
})
}
fn crashing_access_is_not_among_accesses(&self, exception_details: &ExceptionDetails) -> bool {
use crate::op_analysis::MemoryAccessType;
use minidump_common::errors::{
ExceptionCodeWindows, ExceptionCodeWindowsAccessType as WinAccess,
};
let info = &exception_details.info;
let crash_address = info.address.0;
let has_access_derivable_instruction = info
.instruction_properties
.as_ref()
.is_some_and(|p| p.is_access_derivable);
if let Some(access_list) = &info.memory_access_list {
match info.reason {
CrashReason::WindowsAccessViolation(WinAccess::READ) => {
has_access_derivable_instruction
&& !access_list.contains_access(crash_address, MemoryAccessType::Read)
&& !access_list.contains_access(crash_address, MemoryAccessType::ReadWrite)
}
CrashReason::WindowsAccessViolation(WinAccess::WRITE) => {
has_access_derivable_instruction
&& !access_list.contains_access(crash_address, MemoryAccessType::Write)
&& !access_list.contains_access(crash_address, MemoryAccessType::ReadWrite)
}
CrashReason::WindowsAccessViolation(WinAccess::EXEC) => exception_details
.context
.as_ref()
.is_some_and(|context| context.get_instruction_pointer() != crash_address),
CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_STACK_OVERFLOW) => {
has_access_derivable_instruction && access_list.is_empty()
}
_ => false,
}
} else {
false
}
}
fn access_is_not_violation(&self, exception_details: &ExceptionDetails) -> bool {
{
use memory_operation::MemoryOperation;
use minidump_common::errors::ExceptionCodeWindowsAccessType as WinAccess;
let info = &exception_details.info;
let crash_address = info.address.0;
if let Some(mi) = self.memory_info.memory_info_at_address(crash_address) {
matches!(
info.reason,
CrashReason::WindowsAccessViolation(WinAccess::READ)
| CrashReason::WindowsAccessViolation(WinAccess::WRITE)
| CrashReason::WindowsAccessViolation(WinAccess::EXEC)
) && MemoryOperation::from_crash_reason(&info.reason).is_allowed_for(&mi)
} else {
false
}
}
}
pub async fn into_process_state<P, T>(
self,
dump: &Minidump<'a, T>,
symbol_provider: &P,
exception_details: Option<ExceptionDetails<'a>>,
) -> Result<ProcessState, ProcessError>
where
T: Deref<Target = [u8]> + 'a,
P: SymbolProvider + Sync,
{
let crashing_thread_id = self.exception.as_ref().map(|e| e.get_crashing_thread_id());
let (exception_info, exception_context) = match exception_details {
Some(details) => (Some(details.info), details.context),
None => (None, None),
};
let mut requesting_thread = None;
let threads = self
.thread_list
.threads
.iter()
.enumerate()
.map(|(i, thread)| {
let id = thread.raw.thread_id;
if self.dump_thread_id == Some(id) {
return CallStack::with_info(id, CallStackInfo::DumpThreadSkipped);
}
let thread_context =
thread.context(&self.dump_system_info, self.misc_info.as_ref());
let context = if crashing_thread_id.or(self.requesting_thread_id) == Some(id) {
requesting_thread = Some(i);
exception_context.as_deref().or(thread_context.as_deref())
} else {
thread_context.as_deref()
};
let name = self
.thread_names
.get_name(thread.raw.thread_id)
.map(|cow| cow.into_owned());
let (info, frames) = if let Some(context) = context {
let ctx = context.clone();
(
CallStackInfo::Ok,
vec![StackFrame::from_context(ctx, FrameTrust::Context)],
)
} else {
(CallStackInfo::MissingContext, vec![])
};
CallStack {
frames,
info,
thread_id: id,
thread_name: name,
last_error_value: thread.last_error(self.system_info.cpu, &self.memory_list),
}
})
.collect();
let unknown_streams = dump.unknown_streams().collect();
let unimplemented_streams = dump.unimplemented_streams().collect();
let symbol_stats = symbol_provider.stats();
let process_id = if let Some(misc_info) = self.misc_info.as_ref() {
misc_info.raw.process_id().cloned()
} else {
self.linux_proc_status
.map(|linux_proc_status| linux_proc_status.pid)
};
let process_create_time = if let Some(misc_info) = self.misc_info.as_ref() {
misc_info.process_create_time()
} else {
None
};
let mut state = ProcessState {
process_id,
time: SystemTime::UNIX_EPOCH + Duration::from_secs(dump.header.time_date_stamp as u64),
process_create_time,
cert_info: self.evil.certs,
exception_info,
assertion: None,
requesting_thread,
system_info: self.system_info,
linux_standard_base: self.linux_standard_base,
linux_proc_limits: self.linux_proc_limits,
mac_crash_info: self.mac_crash_info,
mac_boot_args: self.mac_boot_args,
threads,
modules: self.modules,
unloaded_modules: self.unloaded_modules,
handles: self.handle_data_stream,
unknown_streams,
unimplemented_streams,
symbol_stats,
linux_memory_map_count: self.linux_memory_map_count,
soft_errors: self.soft_errors,
};
if let Some(reporter) = self.options.stat_reporter {
reporter.add_unwalked_result(&state);
}
{
let memory_list = &self.memory_list;
let modules = &state.modules;
let system_info = &state.system_info;
let unloaded_modules = &state.unloaded_modules;
let options = &self.options;
futures_util::future::join_all(
state
.threads
.iter_mut()
.zip(self.thread_list.threads.iter())
.enumerate()
.map(|(i, (stack, thread))| async move {
let mut stack_memory = thread.stack_memory(memory_list);
let stack_ptr = stack
.frames
.first()
.map(|ctx_frame| ctx_frame.context.get_stack_pointer());
if let Some(stack_ptr) = stack_ptr {
let contains_stack_ptr = stack_memory
.as_ref()
.and_then(|memory| memory.get_memory_at_address::<u64>(stack_ptr))
.is_some();
if !contains_stack_ptr {
stack_memory =
memory_list.memory_at_address(stack_ptr).or(stack_memory);
}
}
walk_stack(
i,
|frame_idx: usize, frame: &StackFrame| {
if let Some(reporter) = options.stat_reporter {
reporter.add_walked_frame(i, frame_idx, frame);
}
},
stack,
stack_memory,
modules,
system_info,
symbol_provider,
)
.await;
for frame in &mut stack.frames {
if frame.module.is_none() {
let mut offsets = BTreeMap::new();
for unloaded in
unloaded_modules.modules_at_address(frame.instruction)
{
let offset = frame.instruction - unloaded.raw.base_of_image;
offsets
.entry(unloaded.name.clone())
.or_insert_with(BTreeSet::new)
.insert(offset);
}
frame.unloaded_modules = offsets;
}
}
if options.recover_function_args {
arg_recovery::fill_arguments(stack, stack_memory);
}
if let Some(reporter) = options.stat_reporter {
reporter.inc_processed_threads();
}
stack
}),
)
.await
};
let symbol_stats = symbol_provider.stats();
state.symbol_stats = symbol_stats;
Ok(state)
}
}
impl crate::ExceptionInfo {
fn new(reason: CrashReason, address: crate::Address) -> Self {
Self {
reason,
address,
adjusted_address: None,
instruction_str: None,
instruction_properties: None,
memory_access_list: None,
instruction_pointer_update: None,
possible_bit_flips: Default::default(),
inconsistencies: Default::default(),
}
}
fn with_op_analysis(
reason: CrashReason,
address: crate::Address,
adjusted_address: Option<AdjustedAddress>,
op_analysis: crate::op_analysis::OpAnalysis,
) -> Self {
Self {
reason,
address,
adjusted_address,
instruction_str: Some(op_analysis.instruction_str),
instruction_properties: Some(op_analysis.instruction_properties),
memory_access_list: op_analysis.memory_access_list,
instruction_pointer_update: op_analysis.instruction_pointer_update,
possible_bit_flips: Default::default(),
inconsistencies: Default::default(),
}
}
}
struct ExceptionDetails<'a> {
info: crate::ExceptionInfo,
context: Option<std::borrow::Cow<'a, MinidumpContext>>,
instruction_registers: BTreeSet<&'static str>,
}
fn try_get_non_canonical_crash_address(
system_info: &SystemInfo,
memory_addresses: Option<&[MemoryAddressInfo]>,
reason: CrashReason,
address: u64,
) -> Option<u64> {
use system_info::Cpu;
const NON_CANONICAL_RANGE: RangeInclusive<u64> = 0x0000_8000_0000_0000..=0xffff_7fff_ffff_ffff;
if system_info.cpu != Cpu::X86_64 {
return None;
}
if !represents_general_protection_fault(system_info.os, reason, address) {
return None;
}
if memory_addresses.is_none() {
tracing::warn!(
"lack of instruction analysis prevented determination of non-canonical address"
);
return None;
}
for access in memory_addresses.unwrap().iter() {
if NON_CANONICAL_RANGE.contains(&access.address) {
return Some(access.address);
}
}
tracing::warn!(
r#"somehow got a general protection fault in an instruction
that doesn't appear to access a non-canonical address"#
);
None
}
fn represents_general_protection_fault(
os: system_info::Os,
reason: CrashReason,
address: u64,
) -> bool {
use minidump_common::errors as minidump_errors;
use system_info::Os;
const SI_KERNEL_U32: u32 = minidump_errors::ExceptionCodeLinuxSicode::SI_KERNEL as u32;
match (os, reason, address) {
(
Os::Windows,
CrashReason::WindowsAccessViolation(
minidump_errors::ExceptionCodeWindowsAccessType::READ,
),
u64::MAX,
) => true,
(Os::Windows, _, _) => false,
(
Os::MacOs,
CrashReason::MacBadAccessX86(
minidump_errors::ExceptionCodeMacBadAccessX86Type::EXC_I386_GPFLT,
),
0,
) => true,
(Os::MacOs, _, _) => false,
(
Os::Linux,
CrashReason::LinuxGeneral(minidump_errors::ExceptionCodeLinux::SIGSEGV, SI_KERNEL_U32),
0,
) => true,
(
Os::Linux,
CrashReason::LinuxGeneral(minidump_errors::ExceptionCodeLinux::SIGBUS, SI_KERNEL_U32),
0,
) => true,
(Os::Linux, _, _) => false,
(_, _, _) => {
tracing::warn!("we don't currently support non-canonical analysis for your OS");
false
}
}
}
fn try_detect_null_pointer_in_disguise(
memory_addresses: Option<&[MemoryAddressInfo]>,
) -> Option<u64> {
if let Some(memory_addresses) = memory_addresses {
for access in memory_addresses.iter() {
if access.is_likely_null_pointer_dereference {
return Some(access.address);
}
}
}
None
}
pub mod memory_operation {
use super::*;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum MemoryOperation {
#[default]
Undetermined,
Read,
Write,
Execute,
}
impl std::fmt::Display for MemoryOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Undetermined => "Undetermined",
Self::Read => "Read",
Self::Write => "Write",
Self::Execute => "Execute",
})
}
}
impl MemoryOperation {
pub fn from_crash_reason(reason: &CrashReason) -> Self {
use minidump_common::errors::ExceptionCodeWindowsAccessType as WinAccess;
match reason {
CrashReason::WindowsAccessViolation(WinAccess::READ) => Self::Read,
CrashReason::WindowsAccessViolation(WinAccess::WRITE) => Self::Write,
CrashReason::WindowsAccessViolation(WinAccess::EXEC) => Self::Execute,
_ => Self::default(),
}
}
pub fn is_possibly_allowed_for(&self, memory_info: &UnifiedMemoryInfo) -> bool {
match self {
Self::Undetermined => true,
Self::Read => memory_info.is_readable(),
Self::Write => memory_info.is_writable(),
Self::Execute => memory_info.is_executable(),
}
}
pub fn is_allowed_for(&self, memory_info: &UnifiedMemoryInfo) -> bool {
match self {
Self::Undetermined => false,
Self::Read => memory_info.is_readable(),
Self::Write => memory_info.is_writable(),
Self::Execute => memory_info.is_executable(),
}
}
}
}
mod bitflip {
use super::*;
use crate::memory_operation::MemoryOperation;
use crate::PossibleBitFlip;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitRange {
Amd64Canononical,
Amd64NonCanonical,
All,
}
impl BitRange {
pub fn range(&self) -> std::ops::Range<u32> {
match self {
Self::All => 0..u64::BITS,
Self::Amd64Canononical => 0..48,
Self::Amd64NonCanonical => 48..u64::BITS,
}
}
}
pub fn try_bit_flips(
address: u64,
source_register: Option<&'static str>,
bit_range: BitRange,
exception_context: Option<&MinidumpContext>,
memory_info: &UnifiedMemoryInfoList,
memory_operation: MemoryOperation,
) -> Vec<PossibleBitFlip> {
let mut addresses = Vec::new();
if let Some(mi) = memory_info.memory_info_at_address(address) {
if memory_operation.is_possibly_allowed_for(&mi) {
return addresses;
}
}
let create_possible_address = |new_address: u64| {
let mut ret = PossibleBitFlip::new(new_address, source_register);
ret.calculate_heuristics(
address,
bit_range == BitRange::Amd64NonCanonical,
exception_context,
);
ret
};
for i in bit_range.range() {
let possible_address = address ^ (1 << i);
if possible_address == 0 {
addresses.push(create_possible_address(possible_address));
}
if let Some(mi) = memory_info.memory_info_at_address(possible_address) {
if memory_operation.is_possibly_allowed_for(&mi) {
addresses.push(create_possible_address(possible_address))
}
}
}
addresses
}
}