Files
node/test/parallel/test-dlopen-binary.js
Philipp Dunkel ee36353428 vfs: load native addons from a mounted file system
A native addon inside a virtual file system could not be require()d:
dlopen() and LoadLibrary() open a shared object by path, and a VFS path
has no inode for them to open.

Read the addon's bytes from the VFS and load them from a private,
self-cleaning image instead, using the smallest on-disk footprint each
platform allows: on Linux an anonymous memfd loaded through
/proc/self/fd, so the bytes never reach the file system; on other POSIX
platforms a file in a 0700 mkdtemp() directory, unlinked right after
loading, with the mapping keeping it alive; on Windows a temp file
opened FILE_FLAG_DELETE_ON_CLOSE whose handle is held until exit.

This is internal. process.dlopen() keeps its documented
(module, filename[, flags]) signature and ignores anything further; the
bytes are passed to a dlopenBinary() reachable only through the
process_methods binding, which the VFS hook calls for VFS paths. Addons
on the real file system are untouched and load directly.

Signed-off-by: Philipp Dunkel <pip@pipobscure.com>
PR-URL: https://github.com/nodejs/node/pull/65680
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-09-04 16:48:33 +00:00

52 lines
2.1 KiB
JavaScript

// Flags: --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { internalBinding } = require('internal/test/binding');
// dlopenBinary() loads a native addon from bytes already in memory, for an
// addon that the dynamic loader cannot open by path (e.g. one served by a
// virtual file system). It is internal: process.dlopen() keeps its documented
// (module, filename[, flags]) signature and never takes the bytes.
const { dlopenBinary } = internalBinding('process_methods');
assert.strictEqual(typeof dlopenBinary, 'function');
assert.strictEqual(process.dlopen.length, 0);
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');
const bytes = fs.readFileSync(addonPath);
const flags = (os.constants.dlopen?.RTLD_LAZY) || 0;
// A path that does not exist on disk, as a VFS-resident addon would be.
const fakePath = path.join(os.tmpdir(), 'nonexistent-vfs-dir', 'binding.node');
assert.ok(!fs.existsSync(fakePath));
const m = { exports: {} };
dlopenBinary(m, fakePath, flags, bytes);
assert.strictEqual(typeof m.exports.hello, 'function');
assert.strictEqual(m.exports.hello(), 'world');
// The flags argument may be left undefined when passing bytes.
const m2 = { exports: {} };
dlopenBinary(m2, fakePath, undefined, bytes);
assert.strictEqual(m2.exports.hello(), 'world');
// Without the bytes, that nonexistent path cannot be loaded.
assert.throws(() => dlopenBinary({ exports: {} }, fakePath, flags),
{ code: 'ERR_DLOPEN_FAILED' });
// A non-buffer binary is rejected.
assert.throws(
() => dlopenBinary({ exports: {} }, fakePath, flags, 'nope'),
{ code: 'ERR_INVALID_ARG_TYPE' });
// process.dlopen() ignores a fourth argument rather than loading from it: the
// public signature is unchanged, so this still fails on the missing path.
assert.throws(
() => process.dlopen({ exports: {} }, fakePath, flags, bytes),
{ code: 'ERR_DLOPEN_FAILED' });