DwarfFrameParser's global cache holds non-owning Dwarf/Elf handles and is never evicted
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
Research direction
Start in dwarf/src/dwarfFrameParser.C and trace frameParsers, DwarfFrameParser::create() and setupCFIData; then inspect DwarfHandle destruction and the reader-lifetime paths described in the issue. Compare cache eviction with shared ownership, accounting for both dangling handles and pointer-address reuse. Done means closing a reader cannot leave a usable stale cache entry; add regression coverage for that lifetime case and the reported unwind tests.
Written by the indexing model from the issue text.
Description
DwarfFrameParser keeps a process-global cache that is keyed on raw pointers, holds them
without owning them, and is never evicted from (dwarf/src/dwarfFrameParser.C):
struct frameParser_key {
Dwarf *dbg;
Elf *eh_frame;
Architecture arch;
...
};
std::map<frameParser_key, DwarfFrameParser::Ptr> frameParsers;
create() only ever inserts; grep frameParsers finds no erase, clear or invalidate anywhere.
The Dwarf * and Elf * are owned by DwarfHandle, whose destructor calls dwarf_end().
Destroying the owner therefore leaves live cache entries pointing at freed handles.
1. Use-after-free
Any code that opens a symbol reader for a library nothing else holds, and then closes it,
destroys that library's Symtab, ends its handles, and leaves the cached parser using them.
Observed on ppc64le (RHEL 8, POWER9), stack from gdb:
#0 __gelf_getehdr_rdlock (elf=0x1188dc80)
#2 dwarf_getcfi_elf (elf=0x1188dc80) dwarf_getcfi_elf.c:325
#6 DwarfFrameParser::setupCFIData dwarfFrameParser.C:441
#7 DwarfFrameParser::getRegsForFunction
#12 DwarfWalker::getFrameBase dwarfWalker.C:1683
#13 DwarfWalker::parseSubprogram
#18 DwarfWalker::parse._omp_fn.0
Reproduction rates with a closeSymbolReader() on that path, per-test isolated, 10 runs each:
| test | with the close | without it | master |
|---|---|---|---|
| test1_27 | 10/10 | 3/10 | 3/10 |
| test1_17 | 6/10 | 0/10 | 0/10 |
| test1_3 | 6/10 | 0/10 | 0/10 |
| snip_ref_shlib_var | 2/10 | 0/10 | 0/10 |
(The residual 3/10 on test1_27 is the unrelated parallel-DWARF race in #2407.)
This is reachable from ordinary API use: SymtabReaderFactory reference counting is sound
(findOpenSymtab() does take a reference), so a caller that opens and closes symmetrically —
exactly what the refcount invites — can free handles the cache still holds.
2. The quieter problem: wrong parser on address reuse
Because the key is raw pointer values and entries are never removed, an address reused by a
later allocation matches a stale entry, and create() returns the parser for a different
file. That is incorrect unwind data rather than a crash, with nothing to report it. I have not
constructed a case for this; it follows from the code.
Why it has not been hit more often
Every current caller avoids closing. common/src/addrtranslate-sysv.C has its
closeSymbolReader() commented out, and int_thread_db.C leaves its readers open — #2409 had
to keep doing so, with a comment, specifically because of this. So the tree currently works by
never releasing readers, which is a leak standing in for a lifetime fix.
Possible directions
- Evict on destruction: have
DwarfHandle's destructor (orElf_Xteardown) purge entries
referencing the handles being ended. There is already a mutex for it. - Or give the parser shared ownership of what it points at, so the cache cannot outlive it.
- Keying on something stable (file identity) rather than pointer values would remove the
address-reuse hazard independently.
Found while working on #2409; not fixed there, deliberately.
- Dominant language
- C
- Stars
- 836
- Forks
- 177
- Avg merge
- 3d 20h
- Merged PRs (30d)
- 61
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dyninst/dyninst
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
SPIDevice selects an active-high device as soon as it is createdPossibly taken @mikeysklar claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
adafruit/circuitpython#11869 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 78/100
gnif/LookingGlass#1352 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100