rust-lang / rust-lang/backtrace-rs

`resolve` passes symbols which can be invalidated

Open
#753 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Rust
Stars
628
Forks
291
PR merge metrics
No merged PRs in 30d

Description

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

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/symbolize/gimli.rs, especially resolve and Cache::with_global, then review the smoke test clear_symbol_cache_during_resolve_callback described in the issue. Run the cargo test command and Valgrind reproduction first. Done means callbacks can clear or re-enter the symbol cache without invalid reads, while symbol resolution continues safely.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.