Skip to content

resolve passes symbols which can be invalidated #753

Description

@Zoxc

resolve in src/symbolize/gimli.rs passes cb a Symbol built from name/location references into the current cached Mapping, and then continues iterating the same DWARF state after the callback returns. Because Cache::with_global is reentrant on the same thread, a callback can call clear_symbol_cache() or recursively call resolve() and clear/evict that Mapping while the outer call is still active, leaving both the in-callback &Symbol and the outer frames iterator with dangling references to freed debug data (use-after-free UB).

This can be reproduced by:

fn clear_symbol_cache_during_resolve_callback() {
    #[inline(never)]
    fn target() -> *mut c_void {
        let mut frames = Vec::new();
        backtrace::trace(|frame| {
            frames.push(frame.ip());
            frames.len() < 32
        });

        frames
            .into_iter()
            .find(|ip| {
                let mut found = false;
                backtrace::resolve(*ip, |_| found = true);
                found
            })
            .expect("expected to capture a resolvable frame")
    }

    let ip = target();
    let mut saw_symbol = false;
    backtrace::resolve(ip, |symbol| {
        saw_symbol = true;

        // This reproduces the gimli cache lifetime bug by evicting the mapping
        // that backs the borrowed name/location data passed to the callback,
        // then touching the borrowed symbol again.
        backtrace::clear_symbol_cache();

        let _ = symbol.filename().map(|path| path.as_os_str().len());
        let _ = symbol.name().map(|name| name.to_string());
    });

    assert!(saw_symbol, "expected to resolve at least one symbol");
}
cargo test --test smoke clear_symbol_cache_during_resolve_callback --no-run
valgrind --tool=memcheck target/debug/deps/smoke-b6b34c31904af23c clear_symbol_cache_during_resolve_callback
==26174== Invalid read of size 1
==26174==    at 0x41E172C: run_utf8_validation (validations.rs:0)
==26174==    by 0x41E172C: core::str::converts::from_utf8 (converts.rs:91)
==26174==    by 0x4135AC1: <backtrace::symbolize::SymbolName>::new (mod.rs:310)
==26174==    by 0x411BBCD: <backtrace::symbolize::gimli::Symbol>::name (gimli.rs:521)
==26174==    by 0x4135B95: <backtrace::symbolize::Symbol>::name (mod.rs:208)
==26174==    by 0x407EDBA: smoke::clear_symbol_cache_during_resolve_callback::{closure#0} (smoke.rs:272)
==26174==    by 0x41540DA: backtrace::symbolize::gimli::resolve::{closure#0} (gimli.rs:449)
==26174==    by 0x4154464: backtrace::symbolize::gimli::resolve::{closure#1} (gimli.rs:473)
==26174==    by 0x4153AEF: <backtrace::symbolize::gimli::Cache>::with_global::<backtrace::symbolize::gimli::resolve::{closure#1}> (gimli.rs:379)
==26174==    by 0x411CD31: backtrace::symbolize::gimli::resolve (gimli.rs:453)
==26174==    by 0x408203C: backtrace::symbolize::resolve_unsynchronized::<smoke::clear_symbol_cache_during_resolve_callback::{closure#0}> (mod.rs:162)
==26174==    by 0x40823A2: backtrace::symbolize::resolve::<smoke::clear_symbol_cache_during_resolve_callback::{closure#0}> (mod.rs:63)
==26174==    by 0x407BE3F: smoke::clear_symbol_cache_during_resolve_callback (smoke.rs:263)
==26174==  Address 0x5a35ee6 is not stack'd, malloc'd or (recently) free'd

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions