Skip to content

diagnostics_channel: fix dangling binding pointer - #65860

Open
TrevorBurnham wants to merge 1 commit into
nodejs:mainfrom
TrevorBurnham:dc-clear-channel-binding-data
Open

diagnostics_channel: fix dangling binding pointer#65860
TrevorBurnham wants to merge 1 commit into
nodejs:mainfrom
TrevorBurnham:dc-clear-channel-binding-data

Conversation

@TrevorBurnham

@TrevorBurnham TrevorBurnham commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes: #65858

diagnostics_channel's Channel reads its subscriber count through a raw BindingData* that's never cleared, so any native holder that outlives environment cleanup reads a destroyed object. That impacts node:sqlite's DatabaseSync, which holds a strong BaseObjectPtr<Channel> for its lifetime, leading to a potential segfault.

This PR clears binding_data_ on every Channel the binding owns whenever it gives up that ownership, so the existing null check in HasSubscribers() can do its job.

Two changes go to node:sqlite as well: DatabaseSync::trace_channel_ becomes a BaseObjectWeakPtr, matching the convention permission documents and follows, where BindingData is the sole owner of channels. And TraceCallback now tests AreTraceEventsSuppressed() before the channel, so a suppressed callback doesn't dereference it at all.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Sep 6, 2026
@TrevorBurnham
TrevorBurnham force-pushed the dc-clear-channel-binding-data branch 3 times, most recently from ff5512d to 7db0a64 Compare September 6, 2026 18:14
`Channel` reads its subscriber count through a raw `BindingData*` that
was never cleared, so any native holder that outlives environment
cleanup reads a destroyed object. The null check in `HasSubscribers()`
could not fire, because the pointer was only ever assigned in the
constructor.

`node:sqlite` holds a strong `BaseObjectPtr<Channel>` for the lifetime
of a `DatabaseSync`, which made this reachable from ordinary
JavaScript. A statement left mid-step at exit is finalized by the
destructor chain after `Environment::RunCleanup()` has destroyed the
binding, and `sqlite3_finalize()` invokes the profile callback for such
a statement. The result was a segfault at normal process exit; inside a
worker it took down the whole process.

Clear `binding_data_` on every `Channel` the binding owns whenever it
gives up that ownership, both in the destructor and in
`PrepareForSerialization()`, so that the existing null check in
`HasSubscribers()` does its job. The second check in `Publish()` is now
unreachable and is dropped. This protects any holder that is itself a
`BaseObject`, and so is destroyed later in the same cleanup. A holder
that is not a `BaseObject` still needs a cleanup hook or a weak
reference, because `Realm::~Realm()` checks that no `BaseObject`s
remain.

On the `node:sqlite` side, switch `DatabaseSync::trace_channel_` to a
`BaseObjectWeakPtr`, so that it follows the same convention `permission`
documents, where `BindingData` is the sole owner of channels.
`TraceCallback` already null-checks, so this needs no other change
there.

Also check `AreTraceEventsSuppressed()` before the channel in
`TraceCallback()`, so that a suppressed callback does not dereference it
at all. `StatementSync::Finalize()` already suppresses trace events, so
the reported path was meant to be a no-op; only the order of the `||`
operands took it through the channel first.

Fixes: nodejs#65858
Assisted-by: Claude Opus 5
Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
@TrevorBurnham
TrevorBurnham force-pushed the dc-clear-channel-binding-data branch from 7db0a64 to 4d9852c Compare September 6, 2026 21:49
@TrevorBurnham
TrevorBurnham marked this pull request as ready for review September 6, 2026 22:39
@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.19%. Comparing base (7147a3e) to head (4d9852c).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
src/node_diagnostics_channel.cc 87.50% 0 Missing and 1 partial ⚠️
src/node_sqlite.cc 50.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65860      +/-   ##
==========================================
+ Coverage   90.18%   90.19%   +0.01%     
==========================================
  Files         771      771              
  Lines      264619   264626       +7     
  Branches    50224    50231       +7     
==========================================
+ Hits       238643   238682      +39     
+ Misses      16959    16949      -10     
+ Partials     9017     8995      -22     
Files with missing lines Coverage Δ
src/node_diagnostics_channel.h 71.42% <ø> (+14.28%) ⬆️
src/node_sqlite.h 86.40% <ø> (ø)
src/node_diagnostics_channel.cc 84.18% <87.50%> (+0.53%) ⬆️
src/node_sqlite.cc 82.20% <50.00%> (ø)

... and 23 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

diagnostics_channel: Channel::binding_data_ dangles after environment cleanup, crashing node:sqlite at exit

2 participants