Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions benchmark/crypto/argon2.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ if (!hasOpenSSL(3, 2)) {
const bench = common.createBenchmark(main, {
mode: ['sync', 'async'],
algorithm: ['argon2d', 'argon2i', 'argon2id'],
passes: [1, 3],
parallelism: [2, 4, 8],
memory: [2 ** 11, 2 ** 16, 2 ** 21],
n: [50],
passes: [3],
parallelism: [1],
// Argon2 memory cost dominates runtime. Keep the default suite small enough
// to finish while still covering a low-cost and a memory-heavy derivation.
memory: [2 ** 11, 2 ** 16],
n: [5],
});

function measureSync(n, algorithm, message, nonce, options) {
Expand Down
6 changes: 5 additions & 1 deletion doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1039,9 +1039,13 @@
added:
- v23.6.0
- v22.20.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64221

Check warning on line 1044 in doc/api/cli.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: This is enabled by default.
-->

> Stability: 1.0 - Early development
> Stability: 1.2 - Release candidate

Enable experimental import support for `.node` addons.

Expand Down
6 changes: 6 additions & 0 deletions lib/_http_outgoing.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,12 @@ OutgoingMessage.prototype.uncork = function uncork() {

this[kChunkedBuffer].length = 0;
this[kChunkedLength] = 0;

// If we had a pending drain and flushed all data, emit the drain event.
if (this[kNeedDrain] && this.writableLength === 0) {
this[kNeedDrain] = false;
this.emit('drain');
}
};

OutgoingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
Expand Down
18 changes: 17 additions & 1 deletion lib/child_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,10 +547,26 @@ function getPermissionModelFlagsToCopy() {
return permissionModelFlagsToCopy;
}

function hasPermissionFlagInEnv(nodeOptions) {
// Parse NODE_OPTIONS into individual tokens and check if any token
// is an actual --permission or --permission-audit flag. We use exact
// token matching rather than substring matching to avoid false positives
// when unrelated option values contain '--permission' (e.g.,
// --title=--permission).
if (!nodeOptions) return false;
const tokens = nodeOptions.split(/\s+/);
return tokens.some((token) =>
token === '--permission' ||
token.startsWith('--permission=') ||
token === '--permission-audit' ||
token.startsWith('--permission-audit='),
);
}

function copyPermissionModelFlagsToEnv(env, key, args) {
// Do not override if permission was already passed to file
if (args.includes('--permission') || args.includes('--permission-audit') ||
(env[key] && env[key].indexOf('--permission') !== -1)) {
hasPermissionFlagInEnv(env[key])) {
return;
}

Expand Down
38 changes: 34 additions & 4 deletions lib/internal/vfs/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
} = primordials;

const { Buffer } = require('buffer');
const { isArrayBufferView } = require('internal/util/types');
const { resolve, sep } = require('path');
const { fileURLToPath, URL } = require('internal/url');
const { kEmptyObject } = require('internal/util');
Expand Down Expand Up @@ -46,6 +47,31 @@ function noopFd(fd) {
return undefined;
}

function toWriteBuffer(data, options) {
if (Buffer.isBuffer(data)) return data;
if (isArrayBufferView(data)) {
return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
}
const encoding = typeof options === 'string' ? options : options?.encoding;
return Buffer.from(data, encoding || 'utf8');
}

function writeFileSyncFd(fd, data, options) {
const vfd = getVirtualFd(fd);
if (vfd === undefined) return undefined;

const buffer = toWriteBuffer(data, options);
let offset = 0;
let length = buffer.byteLength;
while (length > 0) {
const written = vfd.entry.writeSync(buffer, offset, length, null);
offset += written;
length -= written;
}

return true;
}

// Registry of active VFS instances.
const activeVFSList = [];

Expand Down Expand Up @@ -288,10 +314,14 @@ function createVfsHandlers() {

// ==================== Sync path-based write ops ====================

writeFileSync: (path, data, options) =>
vfsOpVoid(path, (vfs, n) => vfs.writeFileSync(n, data, options)),
appendFileSync: (path, data, options) =>
vfsOpVoid(path, (vfs, n) => vfs.appendFileSync(n, data, options)),
writeFileSync(path, data, options) {
if (typeof path === 'number') return writeFileSyncFd(path, data, options);
return vfsOpVoid(path, (vfs, n) => vfs.writeFileSync(n, data, options));
},
appendFileSync(path, data, options) {
if (typeof path === 'number') return writeFileSyncFd(path, data, options);
return vfsOpVoid(path, (vfs, n) => vfs.appendFileSync(n, data, options));
},
mkdirSync: (path, options) =>
vfsOp(path, (vfs, n) => ({ result: vfs.mkdirSync(n, options) })),
rmdirSync: (path) => vfsOpVoid(path, (vfs, n) => vfs.rmdirSync(n)),
Expand Down
3 changes: 3 additions & 0 deletions src/node_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_url_pattern.h"
#include "permission/permission.h"
#include "util.h"

#include <string>
Expand Down Expand Up @@ -450,6 +451,8 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
return THROW_ERR_DLOPEN_DISABLED(
env, "Cannot load native addon because loading addons is disabled.");
}
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kAddon, "");

auto context = env->context();

Expand Down
2 changes: 1 addition & 1 deletion src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class EnvironmentOptions : public Options {
bool require_module = true;
std::string dns_result_order;
bool enable_source_maps = false;
bool experimental_addon_modules = EXPERIMENTALS_DEFAULT_VALUE;
bool experimental_addon_modules = true;
bool experimental_eventsource = EXPERIMENTALS_DEFAULT_VALUE;
bool experimental_ffi = EXPERIMENTALS_DEFAULT_VALUE;
bool experimental_websocket = true;
Expand Down
64 changes: 64 additions & 0 deletions test/parallel/test-http-response-drain-cork.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict';
const common = require('../common');
const http = require('http');
const assert = require('assert');

// Test that drain event is emitted correctly when using cork/uncork
// with ServerResponse and the write buffer is full

const server = http.createServer(common.mustCall(async (req, res) => {
res.cork();

// Write small amount - won't need drain
assert.strictEqual(res.write('1'.repeat(10)), true);

// Write enough to exceed highWaterMark (set in 'connection' listener)
assert.strictEqual(res.write('2'.repeat(1000)), false);

// Verify writableNeedDrain is set
assert.strictEqual(res.writableNeedDrain, true);

// Wait for drain event after uncorking
const drainPromise = new Promise((resolve) => {
res.once('drain', common.mustCall(() => {
// After drain, writableNeedDrain should be false
assert.strictEqual(res.writableNeedDrain, false);
resolve();
}));
});

// Uncork should trigger drain
res.uncork();
await drainPromise;

res.end();
}));

server.on('connection', common.mustCall((socket) => {
// Set high water mark large enough to cover HTTP overhead + first
// small content batch, but not enough to cover second batch.
socket._writableState.highWaterMark = 1000;
}));

server.listen(0, common.localhostIPv4, common.mustCall(() => {
http.get({
host: common.localhostIPv4,
port: server.address().port,
}, common.mustCall((res) => {
let data = '';
res.setEncoding('utf8');

res.on('data', (chunk) => {
data += chunk;
});

res.on('end', common.mustCall(() => {
// Verify we got all the data
assert.strictEqual(data.length, 10 + 1000);
assert.strictEqual(data.substring(0, 10), '1'.repeat(10));
assert.strictEqual(data.substring(10), '2'.repeat(1000));

server.close(common.mustCall());
}));
}));
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Flags: --permission --allow-child-process --allow-fs-read=* --allow-worker
// Tests that NODE_OPTIONS values containing '--permission' as a substring
// in unrelated option values (e.g., --title=--permission) do NOT suppress
// Permission Model flag propagation to child processes.
'use strict';

const common = require('../common');
const { isMainThread } = require('worker_threads');

if (!isMainThread) {
common.skip('This test only works on a main thread');
}
if (process.config.variables.node_without_node_options) {
common.skip('missing NODE_OPTIONS support');
}

const assert = require('assert');
const childProcess = require('child_process');

// Verify that the parent has Permission Model enabled
assert.ok(process.permission.has('child'));
assert.strictEqual(process.env.NODE_OPTIONS, undefined);

// Test cases: NODE_OPTIONS values that contain '--permission' as a substring
// but are NOT actual permission flags. These should NOT suppress propagation.
const testCases = [
{ name: 'title', nodeOptions: '--title=--permission' },
{ name: 'conditions', nodeOptions: '--conditions=--permission' },
{ name: 'trace-event-categories', nodeOptions: '--trace-event-categories=--permission' },
{ name: 'title-audit', nodeOptions: '--title=--permission-audit' },
];

for (const { name, nodeOptions } of testCases) {
// Spawn a child with the problematic NODE_OPTIONS value.
// Without the fix, the substring check causes propagation to be skipped,
// and the child will not have process.permission.
const { status, stdout } = childProcess.spawnSync(
process.execPath,
[
'-e',
`
console.log(typeof process.permission);
console.log(process.permission && process.permission.has("child"));
console.log(process.env.NODE_OPTIONS);
`,
],
{
env: {
...process.env,
'NODE_OPTIONS': nodeOptions,
}
}
);

assert.strictEqual(status, 0, `child process for ${name} exited with status ${status}`);

const [permType, hasChild] = stdout.toString().split('\n');

// Verify the child has Permission Model enabled (the bug caused it to be absent)
assert.strictEqual(permType, 'object', `child ${name} should have process.permission object`);

// Verify the child inherited child permission
assert.strictEqual(hasChild, 'true', `child ${name} should have child permission`);
}

// Also verify that a child with a real --permission flag in NODE_OPTIONS
// still gets its own flags honored (regression test for existing behavior).
{
const { status, stdout } = childProcess.spawnSync(
process.execPath,
[
'-e',
`
console.log(process.permission.has("fs.write"));
console.log(process.permission.has("fs.read"));
console.log(process.permission.has("child"));
`,
],
{
env: {
...process.env,
'NODE_OPTIONS': '--permission --allow-fs-write=*',
}
}
);

assert.strictEqual(status, 0);
const [fsWrite, fsRead, child] = stdout.toString().split('\n');
assert.strictEqual(fsWrite, 'true');
assert.strictEqual(fsRead, 'false');
assert.strictEqual(child, 'false');
}

{
assert.strictEqual(process.env.NODE_OPTIONS, undefined);
}
38 changes: 38 additions & 0 deletions test/parallel/test-permission-drop-addons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Flags: --permission --allow-addons --allow-fs-read=*
'use strict';

const common = require('../common');
const { isMainThread } = require('worker_threads');

if (!isMainThread) {
common.skip('This test only works on a main thread');
}

const assert = require('assert');

let bindingPath;
try {
bindingPath = require.resolve(
`../addons/hello-world/build/${common.buildType}/binding`);
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
throw err;
}
common.skip('addon not found');
}

function openAddon() {
process.dlopen({ exports: {} }, bindingPath);
}

assert.ok(process.permission.has('addon'));
openAddon();

process.permission.drop('addon');
assert.ok(!process.permission.has('addon'));
assert.throws(() => {
openAddon();
}, common.expectsError({
code: 'ERR_ACCESS_DENIED',
permission: 'Addon',
}));
Loading
Loading