Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
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
  • Loading branch information
addaleax committed Aug 19, 2018
commit b3cfaed28ca22962bf347312b681cec2bbbebe28
13 changes: 13 additions & 0 deletions doc/api/worker_threads.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,13 +305,21 @@ if (isMainThread) {
```

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

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

Note that if `accessControl.fsRead` is set to `false`, the worker thread will
not be able to read its main script from the file system, so a source script
should be passed in instead and the `eval` option should be set.

### Event: 'error'
<!-- YAML
added: v10.5.0
Expand Down Expand Up @@ -473,6 +485,7 @@ active handle in the event system. If the worker is already `unref()`ed calling
[`port.on('message')`]: #worker_threads_event_message
[`process.exit()`]: process.html#process_process_exit_code
[`process.abort()`]: process.html#process_process_abort
[`process.accessControl.apply()`]: process.html#process_process_accesscontrol_apply_restrictions
[`process.chdir()`]: process.html#process_process_chdir_directory
[`process.env`]: process.html#process_process_env
[`process.stdin`]: process.html#process_process_stdin
Expand Down
6 changes: 6 additions & 0 deletions lib/internal/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,12 @@ class Worker extends EventEmitter {
publicPort: port2,
hasStdin: !!options.stdin
}, [port2]);

if (typeof options.accessControl === 'object' &&
options.accessControl !== null) {
this[kHandle].applyAccessControl(options.accessControl);
}

// Actually start the new thread now that everything is in place.
this[kHandle].startThread();
}
Expand Down
17 changes: 17 additions & 0 deletions src/node_worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,22 @@ void Worker::New(const FunctionCallbackInfo<Value>& args) {
new Worker(env, args.This());
}

void Worker::ApplyAccessControl(const FunctionCallbackInfo<Value>& args) {
Worker* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
CHECK(w->stopped_);

AccessControl access_control;
Local<Object> obj;
if (!args[0]->ToObject(w->env()->context()).ToLocal(&obj) ||
!AccessControl::FromObject(w->env()->context(), obj)
.To(&access_control)) {
return;
}

w->env_->access_control()->apply(access_control);
}

void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {
Worker* w;
ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
Expand Down Expand Up @@ -434,6 +450,7 @@ void InitWorker(Local<Object> target,
env->SetProtoMethod(w, "stopThread", Worker::StopThread);
env->SetProtoMethod(w, "ref", Worker::Ref);
env->SetProtoMethod(w, "unref", Worker::Unref);
env->SetProtoMethod(w, "applyAccessControl", Worker::ApplyAccessControl);

Local<String> workerString =
FIXED_ONE_BYTE_STRING(env->isolate(), "Worker");
Expand Down
2 changes: 2 additions & 0 deletions src/node_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class Worker : public AsyncWrap {
bool is_stopped() const;

static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ApplyAccessControl(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void StartThread(const v8::FunctionCallbackInfo<v8::Value>& args);
static void StopThread(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMessagePort(const v8::FunctionCallbackInfo<v8::Value>& args);
Expand Down
57 changes: 57 additions & 0 deletions test/parallel/test-worker-access-control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Flags: --experimental-worker
'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const child_process = require('child_process');
const { Worker } = require('worker_threads');

// Do not use isMainThread so that this test itself can be run inside a Worker.
if (!process.env.HAS_STARTED_WORKER) {
process.env.HAS_STARTED_WORKER = 1;
const w = new Worker(__filename, {
accessControl: {
fsWrite: false,
childProcesses: false,
loadAddons: false,
createWorkers: false
}
});

w.on('exit', common.mustCall((code) => {
assert.strictEqual(code, 0);
}));
} else {
// We needed fs read access to load modules before.
process.accessControl.apply({ fsRead: false });

common.expectsError(() => { fs.readFileSync(__filename); }, {
type: Error,
message: 'Access to this API has been restricted',
code: 'ERR_ACCESS_DENIED'
});

common.expectsError(() => { fs.writeFileSync(__filename, ''); }, {
type: Error,
message: 'Access to this API has been restricted',
code: 'ERR_ACCESS_DENIED'
});

common.expectsError(() => { child_process.spawnSync('node'); }, {
type: Error,
message: 'Access to this API has been restricted',
code: 'ERR_ACCESS_DENIED'
});

common.expectsError(() => { child_process.spawn('node'); }, {
type: Error,
message: 'Access to this API has been restricted',
code: 'ERR_ACCESS_DENIED'
});

common.expectsError(() => { new Worker(__filename); }, {
type: Error,
message: 'Access to this API has been restricted',
code: 'ERR_ACCESS_DENIED'
});
}