Uh oh!
There was an error while loading. Please reload this page.
fix(profiler): fix SIGSEGV in Dictionary::clear under concurrent lookup - #522
fix(profiler): fix SIGSEGV in Dictionary::clear under concurrent lookup#522jbachorik wants to merge 6 commits into
Conversation
The vtable-stub branch in HotspotSupport::walkVM called classMap()->lookup() directly in an async-signal-handler context without holding _class_map_lock. A concurrent Profiler::clearCallTraces() call that acquired the exclusive lock and freed the dictionary's key storage caused a UAF SIGSEGV. Fix: route the signal-handler path through a new Profiler::lookupClassSignalSafe() which acquires a shared lock via tryLockShared() and uses Dictionary::lookup_readonly() (for_insert=false, sentinel=0) — never calling malloc, fully async-signal-safe. The existing Profiler::lookupClass() retains the inserting overload for JVMTI/JNI callers (objectSampler, livenessTracker, javaApi) that rely on class auto-registration and guard only id == -1 as the error sentinel. Tests added: - vtable_graceful_skip_ut.cpp: verifies the class_id > 0 guard in the vtable-stub branch - stress_dictionary.cpp: concurrent locked-lookup vs clear, extended duration, and lookup_readonly non-insertion invariant - hotspotSupport_vtable_lookup_ut.cpp: static check + functional tests for Dictionary::lookup_readonly semantics Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CI Test ResultsRun:#25734411251 | Commit:
Status Overview
Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled Failed Testsmusl-amd64/debug / 21-librcaJob:View logs No detailed failure information available. Check the job logs. musl-amd64/debug / 11-librcaJob:View logs No detailed failure information available. Check the job logs. musl-amd64/debug / 25-librcaJob:View logs No detailed failure information available. Check the job logs. musl-amd64/debug / 17-librcaJob:View logs No detailed failure information available. Check the job logs. musl-aarch64/debug / 25-librcaJob:View logs No detailed failure information available. Check the job logs. musl-aarch64/debug / 17-librcaJob:View logs No detailed failure information available. Check the job logs. Summary: Total: 32 | Passed: 24 | Failed: 6 | Cancelled: 2 Updated: 2026-05-12 15:47:15 UTC |
jbachorik
commented
May 11, 2026
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:31653edaaf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Pull request overview
Fixes a SIGSEGV/UAF race between HotspotSupport::walkVM()’s vtable-stub signal-handler lookup path and Dictionary::clear() by introducing a lock-protected, malloc-free class lookup for signal-handler contexts.
Changes:
- Added
Dictionary::lookup_readonly()(non-inserting lookup returning0on miss) andProfiler::lookupClassSignalSafe()(shared try-lock + readonly lookup,-1when lock unavailable). - Updated
hotspotSupport.cppvtable-stub handling to uselookupClassSignalSafe()and skip frame emission whenclass_id <= 0. - Added new C++ GTests covering static call-site expectations, stress scenarios, and the
class_id > 0guard behavior.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| ddprof-lib/src/main/cpp/dictionary.h | Exposes new lookup_readonly() API with “0 on miss” semantics. |
| ddprof-lib/src/main/cpp/dictionary.cpp | Implements lookup_readonly() via the existing non-inserting lookup path. |
| ddprof-lib/src/main/cpp/profiler.h | Declares Profiler::lookupClassSignalSafe(). |
| ddprof-lib/src/main/cpp/profiler.cpp | Implements lookupClassSignalSafe() (shared try-lock + readonly lookup) and clarifies lookupClass() flow. |
| ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp | Switches vtable-stub class lookup to the signal-safe API and guards frame emission on class_id > 0. |
| ddprof-lib/src/test/cpp/stress_dictionary.cpp | Adds stress tests for lookup/clear concurrency and readonly semantics. |
| ddprof-lib/src/test/cpp/hotspotSupport_vtable_lookup_ut.cpp | Adds static source scanning checks + functional tests for lookup_readonly(). |
| ddprof-lib/src/test/cpp/vtable_graceful_skip_ut.cpp | Adds an isolated unit test modeling the class_id > 0 frame emission guard. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- stress_dictionary.cpp:334 -- move EXPECT_EQ out of worker threads via atomic violations counter - vtable_graceful_skip_ut.cpp:24 -- fix BCI_ALLOC value from -3 to -12 (matches production vmEntry.h) - hotspotSupport_vtable_lookup_ut.cpp:25 -- __FILE__-based path resolution + CI-aware FAIL/SKIP Co-Authored-By: muse <muse@noreply>
- stress_dictionary.cpp:207,272 -- use lookup_readonly() instead of lookup() to mirror lookupClassSignalSafe() production path Co-Authored-By: muse <muse@noreply>
jbachorik
commented
May 12, 2026
@codex review |
jbachorik
commented
May 12, 2026
@copilot review |
Codex Review: Didn't find any major issues. 🚀 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Resolved conflict in hotspotSupport.cpp: keep lookupClassSignalSafe() call site (required by existing tests). Fix the implementation to use tryLockSharedBounded() instead of tryLockShared() — the unbounded variant can spin indefinitely under concurrent reader contention in a signal handler.
jbachorik
commented
May 12, 2026
Superseded by #524. The lock-free DoubleBufferedDictionary eliminates all locking from dictionary read/write paths, making the shared-lock approach in this PR unnecessary. |
Summary
hotspotSupport.cpp:533calledprofiler->classMap()->lookup()directly inwalkVM's signal-handler path without holding_class_map_lock. ConcurrentDictionary::clear()(called fromProfiler::dumpand chunk rotation under the exclusive lock) freedrow->keys[]androw->nextchains mid-traversal, causing a SIGSEGV. The fix routes all class lookups through a newlookupClassSignalSafe()method that acquires_class_map_lockshared before calling a malloc-free read-only lookup, eliminating the race.Changes
dictionary.cpp/h: Addlookup_readonly(key, len)—for_insert=false, sentinel=0; never calls malloc; returns 0 on miss (async-signal-safe).profiler.cpp/h: AddlookupClassSignalSafe(key, len)— tryLockShared + lookup_readonly, returns -1 when lock unavailable. KeeplookupClass()with inserting semantics for JVMTI/JNI callers.hotspot/hotspotSupport.cpp:533: Replace directclassMap()->lookup()withlookupClassSignalSafe(); guardfillFrameonclass_id > 0for graceful frame skip.stress_dictionary.cpp: Racelookup_readonlyagainstclear()under lock for 500 ms / 1000 ms, 8 threads — must not SIGSEGV.hotspotSupport_vtable_lookup_ut.cpp: Static text checks (forbidden pattern absent, required call present) + functional tests forlookup_readonlysemantics.vtable_graceful_skip_ut.cpp: Verifiesclass_id > 0guard — frames emitted only for valid IDs, skipped for 0 and -1.Acceptance Criteria
hotspotSupport.cpp:533usesProfiler::lookupClassSignalSafe()instead ofprofiler->classMap()->lookup()directly (signal-safe variant with lock protection)lookupClassSignalSafe()returns -1 (clear in progress), no vtable-target frame is added (depth not incremented) — graceful skip viaclass_id > 0guardstress_dictionary.cppstress-tests concurrent lookup threads vs. clear-under-lock thread for at least 500 ms without SIGSEGVWhat does this PR do?:
Fixes SIGSEGV in
Dictionary::clear()— a use-after-free caused byhotspotSupport.cpp:walkVMcallingclassMap()->lookup()in a signal handler without holding_class_map_lock. Adds a signal-safe lookup path that holds the shared lock for the duration of the read.Motivation:
5 crash events in 2 days with top frame
Dictionary::clear(). Signal handler was bypassing the existing lock protocol.Additional Notes:
lookup_readonlyis async-signal-safe: no malloc, 0 on miss (base_index=1 so 0 is unambiguous)lookupClass()inserting semantics preserved for JVMTI/JNI callers (objectSampler.cpp, etc.)class_id > 0guard handles both miss (0) and lock-unavailable (-1) as graceful frame skipsHow to test the change?:
Three new C++ GTest files:
stress_dictionary.cpp: concurrent clear vs. lookup_readonly for 500 ms/1000 ms across 8 threadshotspotSupport_vtable_lookup_ut.cpp: static call-site checks + lookup_readonly semanticsvtable_graceful_skip_ut.cpp: class_id > 0 guard correctnessFor Datadog employees:
credentials of any kind, I've requested a review from
@DataDog/security-design-and-guidance.Unsure? Have a question? Request a review!
🤖 Generated with Claude Code via muse implement