src: fix use-after-free in CleanupHookThunkRun - #65196
Conversation
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
|
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. |
|
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. |
|
#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 Report✅ All modified and coverable lines are covered by tests. 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
🚀 New features to boost your workflow:
|
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>
Summary
CleanupHookThunkRun()readthunk->isolate/thunk->fun/thunk->argfrom the
CleanupHookThunkafter invokingthunk->fun(). For everynode::ObjectWrapstill alive at teardown,thunk->funisObjectWrap::CleanupHook, which deletes the wrap. Since #63642,~ObjectWrap()callsRemoveEnvironmentCleanupHook()itself, which erasesthe
CleanupHookThunkfromcleanup_hook_registryand frees the node itlives in. The subsequent read to make the (now redundant) second
RemoveEnvironmentCleanupHook()call was therefore a use-after-free — thisis now the ordinary teardown path for every
ObjectWrap-based addon, not anedge case.
The fix caches
isolate/fun/argbefore invoking the hook, so nothing isread from
thunkonce 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 withnode-gyp, run undervalgrind) is the existing repro described in the issue.