Skip to content

Commit b3cfaed

Browse files
committedAug 19, 2018
worker: add feature access control API
Similarly to the previous commit, this API allows prohibiting usage of certain features inside a worker thread. Refs: https://github.com/nodejs/node/issues/22107
1 parent 8537311 commit b3cfaed

File tree

5 files changed

+95
-0
lines changed

5 files changed

+95
-0
lines changed
 

‎doc/api/worker_threads.md

+13
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,21 @@ if (isMainThread) {
305305
```
306306

307307
### new Worker(filename[, options])
308+
<!-- YAML
309+
changes:
310+
- version: REPLACEME
311+
pr-url: https://github.com/nodejs/node/pull/???
312+
description: The `accessControl` option was added.
313+
-->
308314

309315
* `filename` {string} The path to the Worker’s main script. Must be
310316
either an absolute path or a relative path (i.e. relative to the
311317
current working directory) starting with `./` or `../`.
312318
If `options.eval` is true, this is a string containing JavaScript code rather
313319
than a path.
314320
* `options` {Object}
321+
* `accessControl` {Object} A set of access restrictions that will be applied
322+
to the worker thread. See [`process.accessControl.apply()`][] for details.
315323
* `eval` {boolean} If true, interpret the first argument to the constructor
316324
as a script that is executed once the worker is online.
317325
* `workerData` {any} Any JavaScript value that will be cloned and made
@@ -327,6 +335,10 @@ if (isMainThread) {
327335
* stderr {boolean} If this is set to `true`, then `worker.stderr` will
328336
not automatically be piped through to `process.stderr` in the parent.
329337

338+
Note that if `accessControl.fsRead` is set to `false`, the worker thread will
339+
not be able to read its main script from the file system, so a source script
340+
should be passed in instead and the `eval` option should be set.
341+
330342
### Event: 'error'
331343
<!-- YAML
332344
added: v10.5.0
@@ -473,6 +485,7 @@ active handle in the event system. If the worker is already `unref()`ed calling
473485
[`port.on('message')`]: #worker_threads_event_message
474486
[`process.exit()`]: process.html#process_process_exit_code
475487
[`process.abort()`]: process.html#process_process_abort
488+
[`process.accessControl.apply()`]: process.html#process_process_accesscontrol_apply_restrictions
476489
[`process.chdir()`]: process.html#process_process_chdir_directory
477490
[`process.env`]: process.html#process_process_env
478491
[`process.stdin`]: process.html#process_process_stdin

‎lib/internal/worker.js

+6
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,12 @@ class Worker extends EventEmitter {
274274
publicPort: port2,
275275
hasStdin: !!options.stdin
276276
}, [port2]);
277+
278+
if (typeof options.accessControl === 'object' &&
279+
options.accessControl !== null) {
280+
this[kHandle].applyAccessControl(options.accessControl);
281+
}
282+
277283
// Actually start the new thread now that everything is in place.
278284
this[kHandle].startThread();
279285
}

‎src/node_worker.cc

+17
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,22 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) {
353353
new Worker(env, args.This());
354354
}
355355

356+
void Worker::ApplyAccessControl(const FunctionCallbackInfo<Value>& args) {
357+
Worker* w;
358+
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
359+
CHECK(w->stopped_);
360+
361+
AccessControl access_control;
362+
Local<Object> obj;
363+
if (!args[0]->ToObject(w->env()->context()).ToLocal(&obj) ||
364+
!AccessControl::FromObject(w->env()->context(), obj)
365+
.To(&access_control)) {
366+
return;
367+
}
368+
369+
w->env_->access_control()->apply(access_control);
370+
}
371+
356372
void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {
357373
Worker* w;
358374
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
@@ -434,6 +450,7 @@ void InitWorker(Local<Object> target,
434450
env->SetProtoMethod(w, "stopThread", Worker::StopThread);
435451
env->SetProtoMethod(w, "ref", Worker::Ref);
436452
env->SetProtoMethod(w, "unref", Worker::Unref);
453+
env->SetProtoMethod(w, "applyAccessControl", Worker::ApplyAccessControl);
437454

438455
Local<String> workerString =
439456
FIXED_ONE_BYTE_STRING(env->isolate(), "Worker");

‎src/node_worker.h

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class Worker : public AsyncWrap {
3939
bool is_stopped() const;
4040

4141
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
42+
static void ApplyAccessControl(
43+
const v8::FunctionCallbackInfo<v8::Value>& args);
4244
static void StartThread(const v8::FunctionCallbackInfo<v8::Value>& args);
4345
static void StopThread(const v8::FunctionCallbackInfo<v8::Value>& args);
4446
static void GetMessagePort(const v8::FunctionCallbackInfo<v8::Value>& args);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Flags: --experimental-worker
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const fs = require('fs');
6+
const child_process = require('child_process');
7+
const { Worker } = require('worker_threads');
8+
9+
// Do not use isMainThread so that this test itself can be run inside a Worker.
10+
if (!process.env.HAS_STARTED_WORKER) {
11+
process.env.HAS_STARTED_WORKER = 1;
12+
const w = new Worker(__filename, {
13+
accessControl: {
14+
fsWrite: false,
15+
childProcesses: false,
16+
loadAddons: false,
17+
createWorkers: false
18+
}
19+
});
20+
21+
w.on('exit', common.mustCall((code) => {
22+
assert.strictEqual(code, 0);
23+
}));
24+
} else {
25+
// We needed fs read access to load modules before.
26+
process.accessControl.apply({ fsRead: false });
27+
28+
common.expectsError(() => { fs.readFileSync(__filename); }, {
29+
type: Error,
30+
message: 'Access to this API has been restricted',
31+
code: 'ERR_ACCESS_DENIED'
32+
});
33+
34+
common.expectsError(() => { fs.writeFileSync(__filename, ''); }, {
35+
type: Error,
36+
message: 'Access to this API has been restricted',
37+
code: 'ERR_ACCESS_DENIED'
38+
});
39+
40+
common.expectsError(() => { child_process.spawnSync('node'); }, {
41+
type: Error,
42+
message: 'Access to this API has been restricted',
43+
code: 'ERR_ACCESS_DENIED'
44+
});
45+
46+
common.expectsError(() => { child_process.spawn('node'); }, {
47+
type: Error,
48+
message: 'Access to this API has been restricted',
49+
code: 'ERR_ACCESS_DENIED'
50+
});
51+
52+
common.expectsError(() => { new Worker(__filename); }, {
53+
type: Error,
54+
message: 'Access to this API has been restricted',
55+
code: 'ERR_ACCESS_DENIED'
56+
});
57+
}

0 commit comments

Comments
 (0)