Description
This code causes node to segfault on exit.
function run() {
let vm = require('vm');
let ctx = vm.createContext();
let scr = new vm.Script('({})');
require('weak-napi')(ctx, () => console.log('freed'));
scr.runInContext(ctx);
}
run();
ketchup% npm i weak-napi && node alpha.js
zsh: segmentation fault (core dumped) node alpha.js
Sometimes(:tm:) you can fix this by adding global.gc()
before the script exits, but that's defeatable, e.g. with this, which segfaults again:
let vm = require('vm');
let ctx = vm.createContext();
let scr = new vm.Script('({"bar":function(require){require("https").get = () => {};}})');
scr.runInContext(ctx).bar(require);
require('weak-napi')(ctx, () => console.log('freed'))
vm = null; ctx = null; scr = null;
global.gc();
The backtrace is meaningless to me; it's trying to clean up the environment and it's trying a double-free. Ooh, I wonder if ASAN will catch this? Note that the backtrace is way worse on older nodes.
ketchup% gdb --args ~/clone/node/out/Debug/node alpha.js
...
Thread 1 "node" received signal SIGSEGV, Segmentation fault.
0x000055837b4de4b2 in v8impl::(anonymous namespace)::RefBase::Delete (reference=0x558381065540) at ../src/js_native_api_v8.cc:248
248 delete reference;
(gdb) bt
#0 0x000055837b4de4b2 in v8impl::(anonymous namespace)::RefBase::Delete (reference=0x558381065540) at ../src/js_native_api_v8.cc:248
#1 v8impl::(anonymous namespace)::RefBase::Finalize (this=0x558381065540, is_env_teardown=<optimised out>)
at ../src/js_native_api_v8.cc:281
#2 0x000055837b4feb4c in v8impl::RefTracker::FinalizeAll (list=0x558381109d08) at ../src/js_native_api_v8.h:43
#3 napi_env__::~napi_env__ (this=0x558381109cd0, __in_chrg=<optimised out>) at ../src/js_native_api_v8.h:66
#4 node_napi_env__::~node_napi_env__ (this=0x558381109cd0, __in_chrg=<optimised out>) at ../src/node_api.cc:17
#5 node_napi_env__::~node_napi_env__ (this=0x558381109cd0, __in_chrg=<optimised out>) at ../src/node_api.cc:17
#6 0x000055837b4fa0c2 in napi_env__::Unref (this=<optimised out>) at ../src/js_native_api_v8.h:77
#7 operator() (arg=<optimised out>, __closure=0x0) at ../src/node_api.cc:103
#8 _FUN () at ../src/node_api.cc:104
#9 0x000055837b4d18ec in node::Environment::RunCleanup (this=this@entry=0x558380ff2830) at ../src/env.cc:661
#10 0x000055837b4797ef in node::FreeEnvironment (env=0x558380ff2830) at ../src/api/environment.cc:371
#11 0x000055837b57deb1 in node::FunctionDeleter<node::Environment, &node::FreeEnvironment>::operator() (pointer=<optimised out>,
this=0x7ffed4b26e30) at ../src/util.h:636
#12 std::unique_ptr<node::Environment, node::FunctionDeleter<node::Environment, &node::FreeEnvironment> >::~unique_ptr (
this=0x7ffed4b26e30, __in_chrg=<optimised out>) at /usr/include/c++/10/bits/unique_ptr.h:361
#13 node::NodeMainInstance::Run (this=this@entry=0x7ffed4b26f80, env_info=env_info@entry=0x55837fa57780 <node::env_info>)
at ../src/node_main_instance.cc:135
#14 0x000055837b4f7908 in node::Start (argc=<optimised out>, argv=<optimised out>) at ../src/node.cc:1078
#15 0x000055837cb7cb63 in main (argc=2, argv=0x7ffed4b271b8) at ../src/node_main.cc:127
ketchup% node --version
v12.20.0
Debug node built is from HEAD today (d90fa196c5540109bf9c5063f8c51673340ad9e3). Ubuntu 20.10, amd64.
I found this trying to diagnose jestjs/jest#10289 ; this createContext
/ runInContext
dance is how Jest works. However, I assume Jest works for most people most of the time, so it can't always segfault. As far as I can see, Jest always use Script
to load user code, with most core modules require
'd like in the second bit of code.