Skip to content

[Profiler] Fix UB in DebugInfoStore::Get(): re-acquire iterator after map insertion#8283

Closed
korniltsev-grafanista-yolo-vibecoder239 wants to merge 1 commit into
DataDog:masterfrom
korniltsev-grafanista-yolo-vibecoder239:fix/debuginfostore-stale-iterator
Closed

[Profiler] Fix UB in DebugInfoStore::Get(): re-acquire iterator after map insertion#8283
korniltsev-grafanista-yolo-vibecoder239 wants to merge 1 commit into
DataDog:masterfrom
korniltsev-grafanista-yolo-vibecoder239:fix/debuginfostore-stale-iterator

Conversation

@korniltsev-grafanista-yolo-vibecoder239

@korniltsev-grafanista-yolo-vibecoder239 korniltsev-grafanista-yolo-vibecoder239 commented Mar 10, 2026

Copy link
Copy Markdown

Summary

In DebugInfoStore::Get(), ParseModuleDebugInfo(moduleId) inserts into the _modulesInfo unordered_map via _modulesInfo[moduleId], which may trigger a rehash that invalidates all iterators. The old code reused a stale iterator obtained before the insertion — undefined behavior per the C++ standard. Fix: re-acquire the iterator via find() after insertion, and simplify the now-unnecessary ternary.

Proof of UB

Per cppreference unordered_map::insert:

If rehashing occurs (due to the insertion), all iterators are invalidated. Otherwise iterators are not invalidated.

And unordered_map::operator[] performs an insert if the key doesn't exist, with the same invalidation rules.

In the original code:

auto it = _modulesInfo.find(moduleId);           // iterator obtained
if (it == _modulesInfo.cend())
{
    ParseModuleDebugInfo(moduleId);               // inserts via _modulesInfo[moduleId]
}
ModuleDebugInfo& info = (it == _modulesInfo.cend()) ? _modulesInfo[moduleId] : it->second;
                         // ^^^ stale iterator compared after insertion — UB if rehash occurred

Severity: Low. In practice this bug does not cause any observable problem. libstdc++ and libc++ use a node-based unordered_map implementation where rehash re-links nodes into new buckets but never frees or moves the nodes themselves. Stale iterators still point to valid memory and the end() sentinel comparison happens to produce correct results. The fix is still warranted to eliminate the UB — a different stdlib implementation or future change could break this.

Changes

  • profiler/src/ProfilerEngine/Datadog.Profiler.Native/DebugInfoStore.cpp: After ParseModuleDebugInfo() inserts into the map, re-acquire the iterator with it = _modulesInfo.find(moduleId). Remove the ternary that compared the stale iterator.

🤖 Generated with Claude Code

… map insertion

ParseModuleDebugInfo() inserts into _modulesInfo, which may rehash the
unordered_map and invalidate all iterators. The old code reused a stale
iterator obtained before the insertion — undefined behavior per the C++
standard. Re-acquire via find() after insertion and simplify the ternary.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

@gleocadie gleocadie left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@gleocadie gleocadie added the area:profiler Issues related to the continous-profiler label Mar 12, 2026
}

ModuleDebugInfo& info = (it == _modulesInfo.cend()) ? _modulesInfo[moduleId] : it->second;
ModuleDebugInfo& info = it->second;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should still check if it is cend or not. What if the ParseModuleDebugInfo failed ? (ex: file does not exist, wrong pdb....)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:profiler Issues related to the continous-profiler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants