Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: add option to disable loading native addons #39977

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/api/cli.md
Expand Up @@ -613,6 +613,14 @@ added: v9.0.0
Disables runtime checks for `async_hooks`. These will still be enabled
dynamically when `async_hooks` is enabled.

### `--no-addons`
d3lm marked this conversation as resolved.
Show resolved Hide resolved
<!-- YAML
added: REPLACEME
-->

Enable a `no-addons` resolution condition as well as disable loading native addons. When `--no-addons` is specified,
calling `process.dlopen` or requiring a native C++ addon will fail and throw an exception.
d3lm marked this conversation as resolved.
Show resolved Hide resolved

### `--no-warnings`
<!-- YAML
added: v6.0.0
Expand Down Expand Up @@ -1424,6 +1432,7 @@ Node.js options that are allowed are:
* `--no-deprecation`
* `--no-experimental-repl-await`
* `--no-force-async-hooks-checks`
* `--no-addons`
d3lm marked this conversation as resolved.
Show resolved Hide resolved
* `--no-warnings`
* `--node-memory-debug`
* `--openssl-config`
Expand Down Expand Up @@ -1805,3 +1814,4 @@ $ node --max-old-space-size=1536 index.js
[security warning]: #warning-binding-inspector-to-a-public-ipport-combination-is-insecure
[timezone IDs]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
[ways that `TZ` is handled in other environments]: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
[`dlopen`]: https://nodejs.org/api/process.html#process_process_dlopen_module_filename_flags
4 changes: 4 additions & 0 deletions doc/node.1
Expand Up @@ -277,6 +277,10 @@ Silence deprecation warnings.
Disable runtime checks for `async_hooks`.
These will still be enabled dynamically when `async_hooks` is enabled.
.
.It Fl -no-addons
Enable a `no-addons` resolution condition as well as disable loading native addons. When `--no-addons` is specified,
calling `process.dlopen` or requiring a native C++ addon will fail and throw an exception.
.
.It Fl -no-warnings
Silence all process warnings (including deprecations).
.
Expand Down
4 changes: 4 additions & 0 deletions lib/internal/modules/cjs/helpers.js
Expand Up @@ -33,6 +33,10 @@ let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
// TODO: Use this set when resolving pkg#exports conditions in loader.js.
const cjsConditions = new SafeSet(['require', 'node', ...userConditions]);

if (getOptionValue('--no-addons')) {
cjsConditions.add('no-addons');
}

function loadNativeModule(filename, request) {
const mod = NativeModule.map.get(filename);
if (mod?.canBeRequiredByUsers) {
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/modules/esm/resolve.js
Expand Up @@ -58,7 +58,8 @@ const { Module: CJSModule } = require('internal/modules/cjs/loader');

const packageJsonReader = require('internal/modules/package_json_reader');
const userConditions = getOptionValue('--conditions');
const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import', ...userConditions]);
const noAddons = getOptionValue('--no-addons');
const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import', ...(noAddons ? ['no-addons'] : []), ...userConditions]);
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);

/**
Expand Down
5 changes: 5 additions & 0 deletions src/node_binding.cc
Expand Up @@ -415,6 +415,11 @@ inline napi_addon_register_func GetNapiInitializerCallback(DLib* dlib) {
// cache that's a plain C list or hash table that's shared across contexts?
void DLOpen(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

if (!env->options()->allow_native_addons) {
d3lm marked this conversation as resolved.
Show resolved Hide resolved
return THROW_ERR_DLOPEN_FAILED(env, "Cannot load native addon because loading addons is disabled.");
}

auto context = env->context();

CHECK_NULL(thread_local_modpending);
Expand Down
5 changes: 5 additions & 0 deletions src/node_options.cc
Expand Up @@ -402,6 +402,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
&EnvironmentOptions::force_async_hooks_checks,
kAllowedInEnvironment,
true);
AddOption("--addons",
bmeck marked this conversation as resolved.
Show resolved Hide resolved
"disable loading native addons",
&EnvironmentOptions::allow_native_addons,
kAllowedInEnvironment,
true);
AddOption("--warnings",
"silence all process warnings",
&EnvironmentOptions::warnings,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Expand Up @@ -121,6 +121,7 @@ class EnvironmentOptions : public Options {
uint64_t max_http_header_size = 16 * 1024;
bool deprecation = true;
bool force_async_hooks_checks = true;
bool allow_native_addons = true;
bool warnings = true;
bool force_context_aware = false;
bool pending_deprecation = false;
Expand Down
9 changes: 9 additions & 0 deletions test/addons/no-addons/binding.gyp
@@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ '../hello-world/binding.cc' ],
'includes': ['../common.gypi'],
}
]
}
15 changes: 15 additions & 0 deletions test/addons/no-addons/test-worker.js
@@ -0,0 +1,15 @@
'use strict';

const common = require('../../common');
const assert = require('assert');
const path = require('path');
const { Worker } = require('worker_threads');

const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`);

const worker = new Worker(`require(${JSON.stringify(binding)})`, { eval: true });

worker.on('error', common.mustCall((error) => {
assert.strictEqual(error.code, 'ERR_DLOPEN_FAILED');
assert.strictEqual(error.message, 'Cannot load native addon because loading addons is disabled.');
}));
19 changes: 19 additions & 0 deletions test/addons/no-addons/test.js
@@ -0,0 +1,19 @@
'use strict';

const common = require('../../common');
const assert = require('assert');

const bindingPath = require.resolve(`./build/${common.buildType}/binding`);

let threw = false;

try {
require(bindingPath);
} catch (error) {
threw = true;
assert(error instanceof Error);
assert.strictEqual(error.code, 'ERR_DLOPEN_FAILED');
assert.strictEqual(error.message, 'Cannot load native addon because loading addons is disabled.');
}

assert(threw);
3 changes: 3 additions & 0 deletions test/fixtures/node_modules/pkgexports/addons-entry.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/fixtures/node_modules/pkgexports/no-addons-entry.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions test/fixtures/node_modules/pkgexports/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions test/parallel/test-no-addons-resolution-condition.js
@@ -0,0 +1,26 @@
'use strict';

const { Worker, isMainThread, parentPort } = require('worker_threads');
const common = require('../common');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const { createRequire } = require('module');

const loadFixture = createRequire(fixtures.path('node_modules'));

if (isMainThread) {
[[], ['--no-addons']].map((execArgv) => {
d3lm marked this conversation as resolved.
Show resolved Hide resolved
const worker = new Worker(__filename, { execArgv });

worker.on('message', common.mustCall((message) => {
if (execArgv.length === 0) {
assert.strictEqual(message, 'using native addons');
} else {
assert.strictEqual(message, 'not using native addons');
}
}));
});
} else {
const message = loadFixture('pkgexports/no-addons');
parentPort.postMessage(message);
}