Skip to content

fix(profiler): guard line-number-table copy against stale jmethodID#647

Draft
jbachorik wants to merge 2 commits into
mainfrom
jb/resolve_method
Draft

fix(profiler): guard line-number-table copy against stale jmethodID#647
jbachorik wants to merge 2 commits into
mainfrom
jb/resolve_method

Conversation

@jbachorik

@jbachorik jbachorik commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?:
Guards the JVMTI line-number-table copy in Lookup::fillJavaMethodInfo (flightRecorder.cpp) with SafeAccess::isReadableRange() before memcpy-ing it, instead of dereferencing the pointer unconditionally. Adds a LINE_NUMBER_TABLE_UNREADABLE counter for observability when the guard trips, and a gtest reproducer (lineNumberTableCopy_ut.cpp) that demonstrates the crash pattern and the fix.

Motivation:
PROF-15385

Production crash report: SIGSEGV in __memcpy_evex_unaligned_erms, inside Lookup::resolveMethod (inlined fillJavaMethodInfo), during FlightRecorder::dump. Confirmed on stock HotSpot JDK 11.

fillJavaMethodInfo calls GetLineNumberTable() on a jmethodID captured asynchronously at sample time, which can point at an already-unloaded class by the time the dump thread processes it (the TOCTOU race already documented a few lines above this call, re: GetMethodDeclaringClass). The sibling class_name/method_name/method_sig strings returned by the preceding JVMTI calls are already probed with SafeAccess::isReadableRange for exactly this reason (#537 / ef13aa29f), but the line-number-table pointer was copied unguarded. This PR closes that gap.

Additional Notes:
Reproducer isolates the copy step from JVMTI/JNI (impractical to fake a live JVM in a plain gtest) by exercising the identical copy pattern against a real mmap'd-then-munmap'd page, standing in for "GetLineNumberTable() returned a bad pointer" regardless of the precise reason. Verified independently outside gtest too: the unguarded pattern segfaults (exit 139) and the guarded pattern exits cleanly.

How to test the change?:
./gradlew :ddprof-lib:gtestDebug_lineNumberTableCopy_ut — 3 tests: unguarded pattern crashes as expected (EXPECT_DEATH), guarded pattern skips the copy safely when the source is unmapped, guarded pattern still copies correctly for valid data.

For Datadog employees:

  • If this PR touches code that signs or publishes builds or packages, or handles
    credentials of any kind, I've requested a security review (run the dd:platform-security-review
    skill, or file a request via the PSEC review form).
    bewaire also runs automatically on every PR.
  • This PR doesn't touch any of that.
  • JIRA: PROF-15385

Unsure? Have a question? Request a review!

fillJavaMethodInfo() memcpy'd the JVMTI-returned line-number table
without probing it first, unlike the sibling class/method name+sig
strings hardened in #537. Crash reported in production as SIGSEGV in
Lookup::resolveMethod on stock HotSpot JDK 11.
@jbachorik jbachorik added the AI label Jul 10, 2026
@dd-octo-sts

dd-octo-sts Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

CI Test Results

Run: #29115420127 | Commit: 0a051b8 | Duration: 14m 29s (longest job)

All 32 test jobs passed

Status Overview

JDK glibc-aarch64/debug glibc-amd64/debug musl-aarch64/debug musl-amd64/debug
8 - - -
8-ibm - - -
8-j9 - -
8-librca - -
8-orcl - - -
11 - - -
11-j9 - -
11-librca - -
17 - -
17-graal - -
17-j9 - -
17-librca - -
21 - -
21-graal - -
21-librca - -
25 - -
25-graal - -
25-librca - -

Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled

Summary: Total: 32 | Passed: 32 | Failed: 0


Updated: 2026-07-10 18:59:40 UTC

@dd-octo-sts

dd-octo-sts Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (commit 36f4468)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/124068415 Commit: 36f4468c5b43f9d55e01d3b6645365fc3bfb46f2

⚠️ Significant outliers

  • 🟢 fj-kmeans (JDK 21): runtime -4% (2825→2711 ms)
  • 🟢 future-genetic (JDK 25): runtime -4.7% (2126→2027 ms)
Runtime details (per benchmark × JDK)
Benchmark JDK Latest Dev Δ (dev vs latest) Issues L/D
akka-uct 21 ✅ 10326 ms (21 iters) ✅ 10289 ms (21 iters) ≈ -0.4% (±11.2%) — / —
akka-uct 25 ✅ 8965 ms (24 iters) ✅ 8824 ms (24 iters) ≈ -1.6% (±9.7%) — / —
finagle-chirper 21 ✅ 6017 ms (33 iters) ✅ 6003 ms (33 iters) ≈ -0.2% (±25.4%) ⚠️ W:3 / ⚠️ W:3
finagle-chirper 25 ✅ 5479 ms (36 iters) ✅ 5525 ms (36 iters) ≈ +0.8% (±24.9%) ⚠️ W:3 / ⚠️ W:3
fj-kmeans 21 ✅ 2825 ms (66 iters) ✅ 2711 ms (69 iters) 🟢 -4% — / —
fj-kmeans 25 ✅ 2813 ms (66 iters) ✅ 2820 ms (66 iters) ≈ +0.2% (±2.6%) — / —
future-genetic 25 ✅ 2126 ms (87 iters) ✅ 2027 ms (91 iters) 🟢 -4.7% — / —
naive-bayes 21 ✅ 1264 ms (135 iters) ✅ 1289 ms (133 iters) ≈ +2% (±33.3%) — / —
naive-bayes 25 ✅ 1009 ms (169 iters) ✅ 1018 ms (168 iters) ≈ +0.9% (±32.2%) — / —
reactors 21 ✅ 16193 ms (15 iters) ✅ 16743 ms (15 iters) ≈ +3.4% (±7.3%) — / —
reactors 25 ✅ 18502 ms (15 iters) ✅ 18476 ms (15 iters) ≈ -0.1% (±4.8%) — / —
Internal counter details (ddprof)

ddprof internal counters, latest / dev (✅ = 0, · = unavailable):

Benchmark JDK Dropped rec Dropped jvmti Dropped trace Skipped WC AGCT fail Unwind fail
akka-uct 21 ✅ / ✅ ✅ / ✅ 1 / 1 1952 / 1969 ✅ / ✅ ✅ / ✅
akka-uct 25 ✅ / ✅ ✅ / ✅ 1 / 1 2077 / 2368 ✅ / ✅ ✅ / ✅
finagle-chirper 21 ✅ / ✅ ✅ / ✅ 3 / 4 8413 / 8843 ✅ / ✅ ✅ / ✅
finagle-chirper 25 ✅ / ✅ ✅ / ✅ ✅ / 1 8590 / 8627 ✅ / ✅ ✅ / ✅
fj-kmeans 21 ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅
fj-kmeans 25 ✅ / ✅ ✅ / ✅ 1 / 1 1261 / 1237 ✅ / ✅ ✅ / ✅
future-genetic 21 ✅ / ✅ ✅ / ✅ 1 / 4 2893 / 2959 ✅ / ✅ ✅ / ✅
future-genetic 25 ✅ / ✅ ✅ / ✅ 2 / 1 2868 / 2862 ✅ / ✅ ✅ / ✅
naive-bayes 21 ✅ / ✅ ✅ / ✅ 1 / 2 3473 / 3538 ✅ / ✅ ✅ / ✅
naive-bayes 25 ✅ / ✅ ✅ / ✅ 2 / 5 3507 / 3483 ✅ / ✅ ✅ / ✅
reactors 21 ✅ / ✅ ✅ / ✅ 1 / 2 1661 / 1582 ✅ / ✅ ✅ / ✅
reactors 25 ✅ / ✅ ✅ / ✅ 1 / ✅ 1975 / 1867 ✅ / ✅ ✅ / ✅

@datadog-datadog-us1-prod

datadog-datadog-us1-prod Bot commented Jul 10, 2026

Copy link
Copy Markdown

Pipelines

Fix all issues with BitsAI

⚠️ Warnings

🚦 1 Pipeline job failed

DataDog/java-profiler | reliability-chaos-amd64: [profiler, tcmalloc, 21.0.3-tem]   View in Datadog   GitLab

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: e5ef83e | Docs | Datadog PR Page | Give us feedback!

…hodID-churn stress test

The EXPECT_DEATH reproducer silently passed on -O3 builds because the
optimizer eliminated the dead memcpy/free once owned_table's contents
were never observed; a forced volatile read fixes that. Also adds a
JUnit stress test that races class-unload churn against profiler
dump/resolve and asserts native whitebox counters actually observed a
stale jmethodID, not just that the JVM didn't crash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dd-octo-sts

dd-octo-sts Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (commit e5ef83e)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/124087303 Commit: e5ef83e27614101e69defb0c599585f0e28b1ebd

⚠️ Significant outliers

  • 🔴 future-genetic (JDK 21): runtime +4% (2048→2129 ms)
Runtime details (per benchmark × JDK)
Benchmark JDK Latest Dev Δ (dev vs latest) Issues L/D
akka-uct 21 ✅ 10327 ms (21 iters) ✅ 10396 ms (21 iters) ≈ +0.7% (±10.9%) — / —
akka-uct 25 ✅ 8806 ms (24 iters) ✅ 8888 ms (24 iters) ≈ +0.9% (±11%) — / —
finagle-chirper 21 ✅ 5982 ms (33 iters) ✅ 6066 ms (33 iters) ≈ +1.4% (±25.2%) ⚠️ W:3 / ⚠️ W:3
finagle-chirper 25 ✅ 5471 ms (36 iters) ✅ 5502 ms (36 iters) ≈ +0.6% (±24.4%) ⚠️ W:3 / ⚠️ W:3
fj-kmeans 21 ✅ 2715 ms (68 iters) ✅ 2762 ms (68 iters) ≈ +1.7% (±2.7%) — / —
fj-kmeans 25 ✅ 2849 ms (66 iters) ✅ 2840 ms (66 iters) ≈ -0.3% (±2.6%) — / —
future-genetic 21 ✅ 2048 ms (91 iters) ✅ 2129 ms (87 iters) 🔴 +4% — / —
future-genetic 25 ✅ 2085 ms (89 iters) ✅ 2033 ms (92 iters) ≈ -2.5% (±2.6%) — / —
naive-bayes 21 ✅ 1269 ms (135 iters) ✅ 1265 ms (135 iters) ≈ -0.3% (±32.8%) — / —
naive-bayes 25 ✅ 1004 ms (170 iters) ✅ 1017 ms (168 iters) ≈ +1.3% (±31.7%) — / —
reactors 21 ✅ 16602 ms (15 iters) ✅ 15968 ms (15 iters) ≈ -3.8% (±7.8%) — / —
reactors 25 ✅ 18322 ms (15 iters) ✅ 18907 ms (15 iters) ≈ +3.2% (±4.6%) — / —
Internal counter details (ddprof)

ddprof internal counters, latest / dev (✅ = 0, · = unavailable):

Benchmark JDK Dropped rec Dropped jvmti Dropped trace Skipped WC AGCT fail Unwind fail
akka-uct 21 ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅ ✅ / ✅
akka-uct 25 ✅ / ✅ ✅ / ✅ 4 / 4 2184 / 2291 ✅ / ✅ ✅ / ✅
finagle-chirper 21 ✅ / ✅ ✅ / ✅ 4 / 2 8712 / 8658 ✅ / ✅ ✅ / ✅
finagle-chirper 25 ✅ / ✅ ✅ / ✅ ✅ / ✅ 8439 / 8434 ✅ / ✅ ✅ / ✅
fj-kmeans 21 ✅ / ✅ ✅ / ✅ 1 / 1 1239 / 1267 ✅ / ✅ ✅ / ✅
fj-kmeans 25 ✅ / ✅ ✅ / ✅ 2 / 3 1288 / 1274 ✅ / ✅ ✅ / ✅
future-genetic 21 ✅ / ✅ ✅ / ✅ 1 / 1 2973 / 2888 ✅ / ✅ ✅ / ✅
future-genetic 25 ✅ / ✅ ✅ / ✅ 2 / 1 2926 / 2837 ✅ / ✅ ✅ / ✅
naive-bayes 21 ✅ / ✅ ✅ / ✅ 11 / 3 3532 / 3526 ✅ / ✅ ✅ / ✅
naive-bayes 25 ✅ / ✅ ✅ / ✅ 6 / 3 3456 / 3462 ✅ / ✅ ✅ / ✅
reactors 21 ✅ / ✅ ✅ / ✅ 1 / 2 1715 / 1428 ✅ / ✅ ✅ / ✅
reactors 25 ✅ / ✅ ✅ / ✅ ✅ / 1 1909 / 1913 ✅ / ✅ ✅ / ✅

@jbachorik

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5ef83e276

ℹ️ 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".

}
} else {
TEST_LOG("Failed to allocate %zu bytes for line number table copy", bytes);
Counters::increment(LINE_NUMBER_TABLE_UNREADABLE);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Null unreadable line table before deallocation

When GetLineNumberTable returns a pointer that this new guard classifies as unreadable, this branch only increments the counter; line_number_table is still passed to jvmti->Deallocate(...) immediately afterward. This is the same stale/unmapped-pointer scenario the change is trying to survive, and the string path above explicitly nulls bad JVMTI pointers because deallocation can fault too, so the crash can simply move from memcpy to Deallocate. Skip deallocation or clear the pointer on this unreadable path, accepting the small leak as the existing metadata guard does.

Useful? React with 👍 / 👎.

long unreadableLineTableDelta = after.getOrDefault("line_number_table_unreadable", 0L)
- before.getOrDefault("line_number_table_unreadable", 0L);

assertTrue(skippedDelta > 0 || unreadableLineTableDelta > 0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make the churn assertion deterministic

This assertion makes the test fail unless a best-effort 5-second stress window happens to hit the stale-jmethodID race and increments one of these counters. CHURN_ITERATIONS > 0 and a non-empty dump only prove the workload ran, not that class unloading raced with resolveMethod, so a healthy JVM/host combination can leave both deltas at zero and intermittently fail CI. Treat the zero-counter case as an assumption/diagnostic or construct a deterministic unload condition before asserting the guard fired.

Useful? React with 👍 / 👎.

@dd-octo-sts

dd-octo-sts Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Reliability & Chaos Results

All reliability & chaos checks passed Pipeline: https://gitlab.ddbuild.io/DataDog/java-profiler/-/pipelines/124087297

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant