Skip to content

src: fix use-after-free in CleanupHookThunkRun - #65196

Open
sreehariannam wants to merge 1 commit into
nodejs:mainfrom
sreehariannam:fix-cleanup-hook-thunk-uaf
Open

src: fix use-after-free in CleanupHookThunkRun#65196
sreehariannam wants to merge 1 commit into
nodejs:mainfrom
sreehariannam:fix-cleanup-hook-thunk-uaf

Conversation

@sreehariannam

@sreehariannam sreehariannam commented Aug 10, 2026

Copy link
Copy Markdown

Summary

CleanupHookThunkRun() read thunk->isolate / thunk->fun / thunk->arg
from the CleanupHookThunk after invoking thunk->fun(). For every
node::ObjectWrap still alive at teardown, thunk->fun is
ObjectWrap::CleanupHook, which deletes the wrap. Since #63642,
~ObjectWrap() calls RemoveEnvironmentCleanupHook() itself, which erases
the CleanupHookThunk from cleanup_hook_registry and frees the node it
lives in. The subsequent read to make the (now redundant) second
RemoveEnvironmentCleanupHook() call was therefore a use-after-free — this
is now the ordinary teardown path for every ObjectWrap-based addon, not an
edge case.

The fix caches isolate/fun/arg before invoking the hook, so nothing is
read from thunk once it may have already been freed.

Root cause and fix as diagnosed in the issue.

Fixes: #65195

Test plan

  • test/addons/worker-addon-exit (built with node-gyp, run under
    valgrind) is the existing repro described in the issue.

CleanupHookThunkRun() read thunk->isolate/fun/arg from the
CleanupHookThunk after invoking thunk->fun(). For every
node::ObjectWrap alive at teardown, thunk->fun is
ObjectWrap::CleanupHook, which deletes the wrap; ~ObjectWrap() calls
RemoveEnvironmentCleanupHook() itself, erasing the CleanupHookThunk
from the registry and freeing the node it lives in. The subsequent
read of thunk->isolate/fun/arg to make the (now redundant) second
RemoveEnvironmentCleanupHook() call was therefore a use-after-free.

Cache the fields before running the hook so nothing is read from
`thunk` once it may have been freed.

Fixes: nodejs#65195
@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 Aug 10, 2026
@sreehariannam

Copy link
Copy Markdown
Author

cc @addaleax (you wrote both #63642 and #63985, whose interaction causes this) and @legendecas (reviewed #63642) — would appreciate a look when you have a chance.

@sreehariannam

Copy link
Copy Markdown
Author

cc @nsavoire — thanks for the thorough root-cause writeup and repro in the issue, it made this a straightforward fix to apply. If you get a chance to re-run your valgrind repro (test/addons/worker-addon-exit) against this branch, that would help confirm it before a maintainer looks at it.

@aduh95

aduh95 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

#65195 claims the issue is 100% deterministic, shouldn't we be adding a test? Also, there's a commit linter failure to fix here

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.30%. Comparing base (404b0cf) to head (893ed58).
⚠️ Report is 48 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65196      +/-   ##
==========================================
- Coverage   90.32%   90.30%   -0.03%     
==========================================
  Files         760      760              
  Lines      248525   248528       +3     
  Branches    46894    46895       +1     
==========================================
- Hits       224488   224430      -58     
- Misses      15469    15522      +53     
- Partials     8568     8576       +8     
Files with missing lines Coverage Δ
src/api/hooks.cc 89.44% <100.00%> (+0.20%) ⬆️

... and 32 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.

tienne added a commit to tienne/gestalt that referenced this pull request Aug 15, 2026
8월 15일부터 CI가 7연속 빨간불이었다. Node 22는 늘 통과했고 24만 죽었다.
8월 12일 마지막 성공 런의 러너는 24.18.0이었고 첫 실패부터 24.19.0이다.
그 사이 우리 커밋은 룰북과 스킬 문서뿐이라 네이티브 쪽을 건드리지 않았다.

24.19.0이 node::ObjectWrap에 정리 훅을 붙이면서(nodejs/node#63642)
better-sqlite3 11.x의 Database 소멸자가 RemoveEnvironmentCleanupHook의
CHECK_NOT_NULL(env)에서 abort한다. 스택이 정확히 그 경로다.

Node 쪽 수정(nodejs/node#65196)은 아직 미머지고 24.19.0 뒤로 24.x 릴리즈도
없어서 기다릴 수가 없다. better-sqlite3 13.x가 N-API로 옮겨가며 이 경로를
없앴지만 darwin-arm64에서 Node 20/22가 깨지는 회귀(#1514)가 열려 있어
검증 없이는 못 올린다. 우선 러너를 묶어 불을 끄고 업그레이드는 따로 본다.

커버리지 업로드 조건도 함께 고친다 — 24로 비교하던 자리라 핀을 넣으면
영영 안 걸린다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

Use-after-free in CleanupHookThunkRun for every node::ObjectWrap alive at teardown

3 participants