Skip to content

Commit f3179f7

Browse files
bmecktargos
authored andcommitted
policy: ensure workers do not read fs for policy
This prevents a main thread from rewriting the policy file and loading a worker that has a different policy from the main thread. PR-URL: #25710 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent ebcdbeb commit f3179f7

File tree

5 files changed

+138
-16
lines changed

5 files changed

+138
-16
lines changed

lib/internal/bootstrap/node.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ function startup() {
173173

174174
// TODO(joyeecheung): move this down further to get better snapshotting
175175
const experimentalPolicy = getOptionValue('--experimental-policy');
176-
if (experimentalPolicy) {
176+
if (isMainThread && experimentalPolicy) {
177177
process.emitWarning('Policies are experimental.',
178178
'ExperimentalWarning');
179179
const { pathToFileURL, URL } = NativeModule.require('url');
@@ -364,8 +364,6 @@ function startup() {
364364
}
365365

366366
function startWorkerThreadExecution() {
367-
prepareUserCodeExecution();
368-
369367
// If we are in a worker thread, execute the script sent through the
370368
// message port.
371369
const {
@@ -382,7 +380,9 @@ function startWorkerThreadExecution() {
382380
debug(`[${threadId}] is setting up worker child environment`);
383381

384382
const port = getEnvMessagePort();
385-
port.on('message', createMessageHandler(port));
383+
port.on('message', createMessageHandler(
384+
port,
385+
prepareUserCodeExecution));
386386
port.start();
387387

388388
// Overwrite fatalException
@@ -476,7 +476,7 @@ function prepareUserCodeExecution() {
476476

477477
// For user code, we preload modules if `-r` is passed
478478
const preloadModules = getOptionValue('--require');
479-
if (preloadModules) {
479+
if (preloadModules.length) {
480480
const {
481481
_preloadModules
482482
} = NativeModule.require('internal/modules/cjs/loader');

lib/internal/process/policy.js

+18
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@ const {
55
} = require('internal/errors').codes;
66
const { Manifest } = require('internal/policy/manifest');
77
let manifest;
8+
let manifestSrc;
9+
let manifestURL;
810

911
module.exports = Object.freeze({
1012
__proto__: null,
1113
setup(src, url) {
14+
manifestSrc = src;
15+
manifestURL = url;
1216
if (src === null) {
1317
manifest = null;
1418
return;
@@ -31,6 +35,20 @@ module.exports = Object.freeze({
3135
return manifest;
3236
},
3337

38+
get src() {
39+
if (typeof manifestSrc === 'undefined') {
40+
throw new ERR_MANIFEST_TDZ();
41+
}
42+
return manifestSrc;
43+
},
44+
45+
get url() {
46+
if (typeof manifestURL === 'undefined') {
47+
throw new ERR_MANIFEST_TDZ();
48+
}
49+
return manifestURL;
50+
},
51+
3452
assertIntegrity(moduleURL, content) {
3553
this.manifest.matchesIntegrity(moduleURL, content);
3654
}

lib/internal/process/worker_thread_only.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,24 @@ function initializeWorkerStdio() {
4343
};
4444
}
4545

46-
function createMessageHandler(port) {
46+
function createMessageHandler(port, prepareUserCodeExecution) {
4747
const publicWorker = require('worker_threads');
4848

4949
return function(message) {
5050
if (message.type === messageTypes.LOAD_SCRIPT) {
51-
const { filename, doEval, workerData, publicPort, hasStdin } = message;
51+
const {
52+
filename,
53+
doEval,
54+
workerData,
55+
publicPort,
56+
manifestSrc,
57+
manifestURL,
58+
hasStdin
59+
} = message;
60+
if (manifestSrc) {
61+
require('internal/process/policy').setup(manifestSrc, manifestURL);
62+
}
63+
prepareUserCodeExecution();
5264
publicWorker.parentPort = publicPort;
5365
publicWorker.workerData = workerData;
5466

lib/internal/worker.js

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const {
1212
ERR_INVALID_ARG_TYPE,
1313
} = require('internal/errors').codes;
1414
const { validateString } = require('internal/validators');
15+
const { getOptionValue } = require('internal/options');
1516

1617
const {
1718
drainMessagePort,
@@ -112,6 +113,9 @@ class Worker extends EventEmitter {
112113
doEval: !!options.eval,
113114
workerData: options.workerData,
114115
publicPort: port2,
116+
manifestSrc: getOptionValue('--experimental-policy') ?
117+
require('internal/process/policy').src :
118+
null,
115119
hasStdin: !!options.stdin
116120
}, [port2]);
117121
// Actually start the new thread now that everything is in place.

test/parallel/test-policy-integrity.js

+97-9
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ const parentFilepath = path.join(tmpdir.path, 'parent.js');
3333
const parentURL = pathToFileURL(parentFilepath);
3434
const parentBody = 'require(\'./dep.js\')';
3535

36+
const workerSpawningFilepath = path.join(tmpdir.path, 'worker_spawner.js');
37+
const workerSpawningURL = pathToFileURL(workerSpawningFilepath);
38+
const workerSpawningBody = `
39+
const { Worker } = require('worker_threads');
40+
// make sure this is gone to ensure we don't do another fs read of it
41+
// will error out if we do
42+
require('fs').unlinkSync(${JSON.stringify(policyFilepath)});
43+
const w = new Worker(${JSON.stringify(parentFilepath)});
44+
w.on('exit', process.exit);
45+
`;
46+
3647
const depFilepath = path.join(tmpdir.path, 'dep.js');
3748
const depURL = pathToFileURL(depFilepath);
3849
const depBody = '';
@@ -49,8 +60,9 @@ if (!tmpdirURL.pathname.endsWith('/')) {
4960
}
5061
function test({
5162
shouldFail = false,
63+
preload = [],
5264
entry,
53-
onerror,
65+
onerror = undefined,
5466
resources = {}
5567
}) {
5668
const manifest = {
@@ -65,7 +77,9 @@ function test({
6577
}
6678
fs.writeFileSync(policyFilepath, JSON.stringify(manifest, null, 2));
6779
const { status } = spawnSync(process.execPath, [
68-
'--experimental-policy', policyFilepath, entry
80+
'--experimental-policy', policyFilepath,
81+
...preload.map((m) => ['-r', m]).flat(),
82+
entry
6983
]);
7084
if (shouldFail) {
7185
assert.notStrictEqual(status, 0);
@@ -74,13 +88,25 @@ function test({
7488
}
7589
}
7690

77-
const { status } = spawnSync(process.execPath, [
78-
'--experimental-policy', policyFilepath,
79-
'--experimental-policy', policyFilepath
80-
], {
81-
stdio: 'pipe'
82-
});
83-
assert.notStrictEqual(status, 0, 'Should not allow multiple policies');
91+
{
92+
const { status } = spawnSync(process.execPath, [
93+
'--experimental-policy', policyFilepath,
94+
'--experimental-policy', policyFilepath
95+
], {
96+
stdio: 'pipe'
97+
});
98+
assert.notStrictEqual(status, 0, 'Should not allow multiple policies');
99+
}
100+
{
101+
const enoentFilepath = path.join(tmpdir.path, 'enoent');
102+
try { fs.unlinkSync(enoentFilepath); } catch {}
103+
const { status } = spawnSync(process.execPath, [
104+
'--experimental-policy', enoentFilepath, '-e', ''
105+
], {
106+
stdio: 'pipe'
107+
});
108+
assert.notStrictEqual(status, 0, 'Should not allow missing policies');
109+
}
84110

85111
test({
86112
shouldFail: true,
@@ -195,6 +221,21 @@ test({
195221
}
196222
}
197223
});
224+
test({
225+
shouldFail: false,
226+
preload: [depFilepath],
227+
entry: parentFilepath,
228+
resources: {
229+
[parentURL]: {
230+
body: parentBody,
231+
match: true,
232+
},
233+
[depURL]: {
234+
body: depBody,
235+
match: true,
236+
}
237+
}
238+
});
198239
test({
199240
shouldFail: true,
200241
entry: parentFilepath,
@@ -295,3 +336,50 @@ test({
295336
}
296337
}
297338
});
339+
test({
340+
shouldFail: true,
341+
entry: workerSpawningFilepath,
342+
resources: {
343+
[workerSpawningURL]: {
344+
body: workerSpawningBody,
345+
match: true,
346+
},
347+
}
348+
});
349+
test({
350+
shouldFail: false,
351+
entry: workerSpawningFilepath,
352+
resources: {
353+
[workerSpawningURL]: {
354+
body: workerSpawningBody,
355+
match: true,
356+
},
357+
[parentURL]: {
358+
body: parentBody,
359+
match: true,
360+
},
361+
[depURL]: {
362+
body: depBody,
363+
match: true,
364+
}
365+
}
366+
});
367+
test({
368+
shouldFail: false,
369+
entry: workerSpawningFilepath,
370+
preload: [parentFilepath],
371+
resources: {
372+
[workerSpawningURL]: {
373+
body: workerSpawningBody,
374+
match: true,
375+
},
376+
[parentURL]: {
377+
body: parentBody,
378+
match: true,
379+
},
380+
[depURL]: {
381+
body: depBody,
382+
match: true,
383+
}
384+
}
385+
});

0 commit comments

Comments
 (0)