From 089103ea66b357a9e8ce3ed25cff89b20bbe02f7 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Mon, 14 Sep 2026 14:44:54 +0500 Subject: [PATCH 1/3] node-api: do not crash on module version mismatch `node_napi_env__::New()` returns nullptr after throwing when an add-on declares a Node-API version this binary does not support, but `napi_module_register_by_symbol()` dereferenced the result without checking it. Loading such an add-on segfaulted instead of surfacing the error the version check had already produced, so `require()` could not catch it. Reproduced on v20.x, v22.x, v24.x and v26.8.2 with a ten-line add-on whose only distinguishing content is a NAPI_VERSION above NODE_API_SUPPORTED_VERSION_MAX. `main`, `v22.x-staging` and `v24.x-staging` all lack the check. The error path had no test coverage: the message text appears in exactly one file in the repository, `src/node_api.cc`. A test is added next to `test_null_init`, which covers the sibling early return in the same function. Prepared with assistance from a closed-source coding agent, named in the pull request description. The design, review and validation are my own: I verified the fix and the test against a local build, including removing the four added lines and relinking to confirm the test fails without them. Refs: https://github.com/nodejs/node/issues/57233 Signed-off-by: Alexey Karimov --- src/node_api.cc | 4 ++++ .../test_module_version_mismatch/binding.gyp | 12 ++++++++++++ test/node-api/test_module_version_mismatch/test.js | 12 ++++++++++++ .../test_module_version_mismatch.c | 7 +++++++ 4 files changed, 35 insertions(+) create mode 100644 test/node-api/test_module_version_mismatch/binding.gyp create mode 100644 test/node-api/test_module_version_mismatch/test.js create mode 100644 test/node-api/test_module_version_mismatch/test_module_version_mismatch.c diff --git a/src/node_api.cc b/src/node_api.cc index e0e7cca2a4ba..96fdc2e00b72 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -770,6 +770,10 @@ void napi_module_register_by_symbol(v8::Local exports, // Create a new napi_env for this specific module. napi_env env = node_napi_env__::New(context, module_filename, module_api_version); + // `New()` returns nullptr after throwing when the add-on requires a + // Node-API version this binary does not support. Returning here lets that + // error surface; dereferencing `env` instead turns it into a segfault. + if (env == nullptr) return; napi_value _exports = nullptr; env->CallIntoModule([&](napi_env env) { diff --git a/test/node-api/test_module_version_mismatch/binding.gyp b/test/node-api/test_module_version_mismatch/binding.gyp new file mode 100644 index 000000000000..e87600d8fa48 --- /dev/null +++ b/test/node-api/test_module_version_mismatch/binding.gyp @@ -0,0 +1,12 @@ +{ + 'targets': [ + { + 'target_name': 'test_module_version_mismatch', + 'sources': [ 'test_module_version_mismatch.c' ], + # One below NAPI_VERSION_EXPERIMENTAL, so it is always above + # NODE_API_SUPPORTED_VERSION_MAX and never becomes a real version, but is + # not the experimental value the version check deliberately allows. + 'defines': [ 'NAPI_VERSION=2147483646' ] + } + ] +} diff --git a/test/node-api/test_module_version_mismatch/test.js b/test/node-api/test_module_version_mismatch/test.js new file mode 100644 index 000000000000..84d8e9c78195 --- /dev/null +++ b/test/node-api/test_module_version_mismatch/test.js @@ -0,0 +1,12 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); + +// An add-on that requires a newer Node-API version than this binary supports +// must be rejected with an error that `require()` can catch. The version check +// in `node_napi_env__::New()` already produces that error, but its nullptr +// return used to be dereferenced by `napi_module_register_by_symbol()`, so the +// process segfaulted before the error could surface. +assert.throws( + () => require(`./build/${common.buildType}/test_module_version_mismatch`), + /requires Node-API version 2147483646, but this version of Node\.js only supports version \d+ add-ons\./); diff --git a/test/node-api/test_module_version_mismatch/test_module_version_mismatch.c b/test/node-api/test_module_version_mismatch/test_module_version_mismatch.c new file mode 100644 index 000000000000..3af35da40556 --- /dev/null +++ b/test/node-api/test_module_version_mismatch/test_module_version_mismatch.c @@ -0,0 +1,7 @@ +#include + +// This add-on declares a Node-API version that no build supports, so loading it +// must fail with the error `node_napi_env__::New()` throws -- not a crash. +NAPI_MODULE_INIT() { + return exports; +} From a105f1486720e762de952a40532bfd9c1c8c353c Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Tue, 15 Sep 2026 10:35:29 +0500 Subject: [PATCH 2/3] Update test/node-api/test_module_version_mismatch/test_module_version_mismatch.c Co-authored-by: Chengzhong Wu --- .../test_module_version_mismatch/test_module_version_mismatch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/node-api/test_module_version_mismatch/test_module_version_mismatch.c b/test/node-api/test_module_version_mismatch/test_module_version_mismatch.c index 3af35da40556..63027b705b29 100644 --- a/test/node-api/test_module_version_mismatch/test_module_version_mismatch.c +++ b/test/node-api/test_module_version_mismatch/test_module_version_mismatch.c @@ -1,7 +1,7 @@ #include // This add-on declares a Node-API version that no build supports, so loading it -// must fail with the error `node_napi_env__::New()` throws -- not a crash. +// must fail with an error -- not a crash. NAPI_MODULE_INIT() { return exports; } From bab32029e36825eff7aa5c020f0f30f283d60c9a Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Tue, 15 Sep 2026 10:36:28 +0500 Subject: [PATCH 3/3] Update src/node_api.cc Co-authored-by: Chengzhong Wu --- src/node_api.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/node_api.cc b/src/node_api.cc index 96fdc2e00b72..40bdf143a98f 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -770,9 +770,7 @@ void napi_module_register_by_symbol(v8::Local exports, // Create a new napi_env for this specific module. napi_env env = node_napi_env__::New(context, module_filename, module_api_version); - // `New()` returns nullptr after throwing when the add-on requires a - // Node-API version this binary does not support. Returning here lets that - // error surface; dereferencing `env` instead turns it into a segfault. + // `module_api_version` is not supported. if (env == nullptr) return; napi_value _exports = nullptr;