// Flags: --expose-internals
'use strict';
// Loading an addon from bytes materializes them into an image in the temporary
// directory, so dlopenBinary() requires file-system write access on top of the
// addon permission every dlopen already needs. Granting only one of the two is
// not enough.
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const addonPath = path.join(
__dirname, '..', 'addons', 'hello-world', 'build', 'Release', 'binding.node');
if (!fs.existsSync(addonPath)) common.skip('the hello-world addon is not built');
// Loads the addon from bytes at a path that does not exist on disk, as an
// addon served by a virtual file system would be, and reports what happened.
const child = `
const { internalBinding } = require('internal/test/binding');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { dlopenBinary } = internalBinding('process_methods');
const bytes = fs.readFileSync(${JSON.stringify(addonPath)});
const target = path.join(os.tmpdir(), 'nonexistent-vfs-dir', 'binding.node');
const m = { exports: {} };
try {
dlopenBinary(m, target, undefined, bytes);
console.log('LOADED', m.exports.hello());
} catch (err) {
console.log('DENIED', err.code, err.permission ?? '');
}
`;
function run(...permissions) {
return spawnSync(
process.execPath,
['--expose-internals', '--permission', '--allow-fs-read=*',
...permissions, '-e', child],
{ encoding: 'utf8' });
}
// Both permissions granted: the addon loads.
{
const res = run('--allow-addons', '--allow-fs-write=*');
assert.strictEqual(res.status, 0, res.stderr);
assert.match(res.stdout, /LOADED world/);
}
// Addons allowed but no write access: refused by the file-system check, so the
// bytes are never materialized.
{
const res = run('--allow-addons');
assert.strictEqual(res.status, 0, res.stderr);
assert.match(res.stdout, /DENIED ERR_ACCESS_DENIED FileSystemWrite/);
}
// Write access but addons disallowed: refused before the bytes are looked at,
// the same as any other dlopen under the permission model.
{
const res = run('--allow-fs-write=*');
assert.strictEqual(res.status, 0, res.stderr);
assert.match(res.stdout, /DENIED ERR_DLOPEN_DISABLED/);
}
// Without the permission model neither check applies.
{
const res = spawnSync(process.execPath, ['--expose-internals', '-e', child],
{ encoding: 'utf8' });
assert.strictEqual(res.status, 0, res.stderr);
assert.match(res.stdout, /LOADED world/);
}
// The same, reached the way a program actually gets here: an addon inside a
// mounted VFS. Mounting one under the permission model needs --allow-fs-vfs.
{
const mounted = `
const fs = require('fs');
const vfs = require('node:vfs');
const myVfs = vfs.create();
myVfs.writeFileSync('/binding.node',
fs.readFileSync(${JSON.stringify(addonPath)}));
const mountPoint = myVfs.mount();
try {
console.log('LOADED', require(mountPoint + '/binding.node').hello());
} catch (err) {
console.log('DENIED', err.code, err.permission ?? '');
}
`;
const vfsRun = (...permissions) => spawnSync(
process.execPath,
['--permission', '--allow-fs-read=*',
...permissions, '-e', mounted],
{ encoding: 'utf8' });
const ok = vfsRun('--allow-fs-vfs', '--allow-addons', '--allow-fs-write=*');
assert.strictEqual(ok.status, 0, ok.stderr);
assert.match(ok.stdout, /LOADED world/);
// Mounting itself is refused without --allow-fs-vfs.
const noVfs = vfsRun('--allow-addons', '--allow-fs-write=*');
assert.notStrictEqual(noVfs.status, 0);
assert.match(noVfs.stderr, /ERR_INVALID_STATE/);
assert.match(noVfs.stderr, /--allow-fs-vfs/);
// Mounted, addons allowed, but no write access: the addon is refused.
const noWrite = vfsRun('--allow-fs-vfs', '--allow-addons');
assert.strictEqual(noWrite.status, 0, noWrite.stderr);
assert.match(noWrite.stdout, /DENIED ERR_ACCESS_DENIED FileSystemWrite/);
}