summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGus Caplan <me@gus.host>2018-02-26 14:29:41 -0600
committerGus Caplan <me@gus.host>2018-03-12 14:39:21 -0500
commitcb5f358ee7461f191db8c88e0af26023558f5232 (patch)
treef23e34d68a09fb05d044f00bd795575db63a9370
parenta03c90b661f69200f124718b56b55b0cb3506c71 (diff)
downloadandroid-node-v8-cb5f358ee7461f191db8c88e0af26023558f5232.tar.gz
android-node-v8-cb5f358ee7461f191db8c88e0af26023558f5232.tar.bz2
android-node-v8-cb5f358ee7461f191db8c88e0af26023558f5232.zip
vm: add code generation options
Adds options to a VM Context to disable code generation from strings (such as eval or new Function) and WASM code generation (WebAssembly.compile). PR-URL: https://github.com/nodejs/node/pull/19016 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
-rw-r--r--doc/api/vm.md16
-rw-r--r--lib/vm.js37
-rw-r--r--src/node.cc11
-rw-r--r--src/node_context_data.h5
-rw-r--r--src/node_contextify.cc24
-rw-r--r--src/node_contextify.h3
-rw-r--r--test/parallel/test-vm-codegen.js110
7 files changed, 204 insertions, 2 deletions
diff --git a/doc/api/vm.md b/doc/api/vm.md
index 12e35e9f72..b9ccf93b7b 100644
--- a/doc/api/vm.md
+++ b/doc/api/vm.md
@@ -495,6 +495,14 @@ added: v0.3.1
value of the [`url.origin`][] property of a [`URL`][] object. Most notably,
this string should omit the trailing slash, as that denotes a path.
**Default:** `''`.
+ * `contextCodeGeneration` {Object}
+ * `strings` {boolean} If set to false any calls to `eval` or function
+ constructors (`Function`, `GeneratorFunction`, etc) will throw an
+ `EvalError`.
+ **Default**: `true`.
+ * `wasm` {boolean} If set to false any attempt to compile a WebAssembly
+ module will throw a `WebAssembly.CompileError`.
+ **Default**: `true`.
First contextifies the given `sandbox`, runs the compiled code contained by
the `vm.Script` object within the created sandbox, and returns the result.
@@ -578,6 +586,14 @@ added: v0.3.1
the [`url.origin`][] property of a [`URL`][] object. Most notably, this
string should omit the trailing slash, as that denotes a path.
**Default:** `''`.
+ * `codeGeneration` {Object}
+ * `strings` {boolean} If set to false any calls to `eval` or function
+ constructors (`Function`, `GeneratorFunction`, etc) will throw an
+ `EvalError`.
+ **Default**: `true`.
+ * `wasm` {boolean} If set to false any attempt to compile a WebAssembly
+ module will throw a `WebAssembly.CompileError`.
+ **Default**: `true`.
If given a `sandbox` object, the `vm.createContext()` method will [prepare
that sandbox][contextified] so that it can be used in calls to
diff --git a/lib/vm.js b/lib/vm.js
index 554aff8bfc..5266dbd29f 100644
--- a/lib/vm.js
+++ b/lib/vm.js
@@ -84,14 +84,36 @@ function validateString(prop, propName) {
throw new ERR_INVALID_ARG_TYPE(propName, 'string', prop);
}
+function validateBool(prop, propName) {
+ if (prop !== undefined && typeof prop !== 'boolean')
+ throw new ERR_INVALID_ARG_TYPE(propName, 'boolean', prop);
+}
+
+function validateObject(prop, propName) {
+ if (prop !== undefined && (typeof prop !== 'object' || prop === null))
+ throw new ERR_INVALID_ARG_TYPE(propName, 'Object', prop);
+}
+
function getContextOptions(options) {
if (options) {
+ validateObject(options.contextCodeGeneration,
+ 'options.contextCodeGeneration');
const contextOptions = {
name: options.contextName,
- origin: options.contextOrigin
+ origin: options.contextOrigin,
+ codeGeneration: typeof options.contextCodeGeneration === 'object' ? {
+ strings: options.contextCodeGeneration.strings,
+ wasm: options.contextCodeGeneration.wasm,
+ } : undefined,
};
validateString(contextOptions.name, 'options.contextName');
validateString(contextOptions.origin, 'options.contextOrigin');
+ if (contextOptions.codeGeneration) {
+ validateBool(contextOptions.codeGeneration.strings,
+ 'options.contextCodeGeneration.strings');
+ validateBool(contextOptions.codeGeneration.wasm,
+ 'options.contextCodeGeneration.wasm');
+ }
return contextOptions;
}
return {};
@@ -109,10 +131,21 @@ function createContext(sandbox, options) {
if (typeof options !== 'object' || options === null) {
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
}
+ validateObject(options.codeGeneration, 'options.codeGeneration');
options = {
name: options.name,
- origin: options.origin
+ origin: options.origin,
+ codeGeneration: typeof options.codeGeneration === 'object' ? {
+ strings: options.codeGeneration.strings,
+ wasm: options.codeGeneration.wasm,
+ } : undefined,
};
+ if (options.codeGeneration !== undefined) {
+ validateBool(options.codeGeneration.strings,
+ 'options.codeGeneration.strings');
+ validateBool(options.codeGeneration.wasm,
+ 'options.codeGeneration.wasm');
+ }
if (options.name === undefined) {
options.name = `VM Context ${defaultContextNameIndex++}`;
} else if (typeof options.name !== 'string') {
diff --git a/src/node.cc b/src/node.cc
index 2e44e249a0..c03c753c37 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -28,6 +28,7 @@
#include "node_revert.h"
#include "node_debug_options.h"
#include "node_perf.h"
+#include "node_context_data.h"
#if defined HAVE_PERFCTR
#include "node_counters.h"
@@ -4432,6 +4433,8 @@ Local<Context> NewContext(Isolate* isolate,
HandleScope handle_scope(isolate);
auto intl_key = FIXED_ONE_BYTE_STRING(isolate, "Intl");
auto break_iter_key = FIXED_ONE_BYTE_STRING(isolate, "v8BreakIterator");
+ context->SetEmbedderData(
+ ContextEmbedderIndex::kAllowWasmCodeGeneration, True(isolate));
Local<Value> intl_v;
if (context->Global()->Get(context, intl_key).ToLocal(&intl_v) &&
intl_v->IsObject()) {
@@ -4509,6 +4512,13 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data,
return exit_code;
}
+bool AllowWasmCodeGenerationCallback(
+ Local<Context> context, Local<String>) {
+ Local<Value> wasm_code_gen =
+ context->GetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration);
+ return wasm_code_gen->IsUndefined() || wasm_code_gen->IsTrue();
+}
+
inline int Start(uv_loop_t* event_loop,
int argc, const char* const* argv,
int exec_argc, const char* const* exec_argv) {
@@ -4527,6 +4537,7 @@ inline int Start(uv_loop_t* event_loop,
isolate->SetAbortOnUncaughtExceptionCallback(ShouldAbortOnUncaughtException);
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
isolate->SetFatalErrorHandler(OnFatalError);
+ isolate->SetAllowWasmCodeGenerationCallback(AllowWasmCodeGenerationCallback);
{
Mutex::ScopedLock scoped_lock(node_isolate_mutex);
diff --git a/src/node_context_data.h b/src/node_context_data.h
index 9d3145bb80..522ce292d2 100644
--- a/src/node_context_data.h
+++ b/src/node_context_data.h
@@ -15,9 +15,14 @@ namespace node {
#define NODE_CONTEXT_SANDBOX_OBJECT_INDEX 33
#endif
+#ifndef NODE_CONTEXT_ALLOW_WASM_CODE_GENERATION_INDEX
+#define NODE_CONTEXT_ALLOW_WASM_CODE_GENERATION_INDEX 34
+#endif
+
enum ContextEmbedderIndex {
kEnvironment = NODE_CONTEXT_EMBEDDER_DATA_INDEX,
kSandboxObject = NODE_CONTEXT_SANDBOX_OBJECT_INDEX,
+ kAllowWasmCodeGeneration = NODE_CONTEXT_ALLOW_WASM_CODE_GENERATION_INDEX,
};
} // namespace node
diff --git a/src/node_contextify.cc b/src/node_contextify.cc
index d267a5180f..a7f46f6126 100644
--- a/src/node_contextify.cc
+++ b/src/node_contextify.cc
@@ -185,6 +185,30 @@ Local<Context> ContextifyContext::CreateV8Context(
CHECK(name->IsString());
Utf8Value name_val(env->isolate(), name);
+ Local<Value> codegen = options_obj->Get(env->context(),
+ FIXED_ONE_BYTE_STRING(env->isolate(), "codeGeneration"))
+ .ToLocalChecked();
+
+ if (!codegen->IsUndefined()) {
+ CHECK(codegen->IsObject());
+ Local<Object> codegen_obj = codegen.As<Object>();
+
+ Local<Value> allow_code_gen_from_strings =
+ codegen_obj->Get(env->context(),
+ FIXED_ONE_BYTE_STRING(env->isolate(), "strings"))
+ .ToLocalChecked();
+ ctx->AllowCodeGenerationFromStrings(
+ allow_code_gen_from_strings->IsUndefined() ||
+ allow_code_gen_from_strings->IsTrue());
+
+ Local<Value> allow_wasm_code_gen = codegen_obj->Get(env->context(),
+ FIXED_ONE_BYTE_STRING(env->isolate(), "wasm"))
+ .ToLocalChecked();
+ ctx->SetEmbedderData(ContextEmbedderIndex::kAllowWasmCodeGeneration,
+ Boolean::New(env->isolate(), allow_wasm_code_gen->IsUndefined() ||
+ allow_wasm_code_gen->IsTrue()));
+ }
+
ContextInfo info(*name_val);
Local<Value> origin =
diff --git a/src/node_contextify.h b/src/node_contextify.h
index b25e1a75d4..cf3e6452fd 100644
--- a/src/node_contextify.h
+++ b/src/node_contextify.h
@@ -24,6 +24,9 @@ class ContextifyContext {
v8::Local<v8::Object> sandbox_obj, v8::Local<v8::Object> options_obj);
static void Init(Environment* env, v8::Local<v8::Object> target);
+ static bool AllowWasmCodeGeneration(
+ v8::Local<v8::Context> context, v8::Local<v8::String>);
+
static ContextifyContext* ContextFromContextifiedSandbox(
Environment* env,
const v8::Local<v8::Object>& sandbox);
diff --git a/test/parallel/test-vm-codegen.js b/test/parallel/test-vm-codegen.js
new file mode 100644
index 0000000000..ebafe30c07
--- /dev/null
+++ b/test/parallel/test-vm-codegen.js
@@ -0,0 +1,110 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+
+const { createContext, runInContext, runInNewContext } = require('vm');
+
+const WASM_BYTES = Buffer.from(
+ [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
+
+
+function expectsError(fn, type) {
+ try {
+ fn();
+ assert.fail('expected fn to error');
+ } catch (err) {
+ if (typeof type === 'string')
+ assert.strictEqual(err.name, type);
+ else
+ assert(err instanceof type);
+ }
+}
+
+{
+ const ctx = createContext({ WASM_BYTES });
+ const test = 'eval(""); new WebAssembly.Module(WASM_BYTES);';
+ runInContext(test, ctx);
+
+ runInNewContext(test, { WASM_BYTES }, {
+ contextCodeGeneration: undefined,
+ });
+}
+
+{
+ const ctx = createContext({}, {
+ codeGeneration: {
+ strings: false,
+ },
+ });
+
+ const EvalError = runInContext('EvalError', ctx);
+ expectsError(() => {
+ runInContext('eval("x")', ctx);
+ }, EvalError);
+}
+
+{
+ const ctx = createContext({ WASM_BYTES }, {
+ codeGeneration: {
+ wasm: false,
+ },
+ });
+
+ const CompileError = runInContext('WebAssembly.CompileError', ctx);
+ expectsError(() => {
+ runInContext('new WebAssembly.Module(WASM_BYTES)', ctx);
+ }, CompileError);
+}
+
+expectsError(() => {
+ runInNewContext('eval("x")', {}, {
+ contextCodeGeneration: {
+ strings: false,
+ },
+ });
+}, 'EvalError');
+
+expectsError(() => {
+ runInNewContext('new WebAssembly.Module(WASM_BYTES)', { WASM_BYTES }, {
+ contextCodeGeneration: {
+ wasm: false,
+ },
+ });
+}, 'CompileError');
+
+common.expectsError(() => {
+ createContext({}, {
+ codeGeneration: {
+ strings: 0,
+ },
+ });
+}, {
+ code: 'ERR_INVALID_ARG_TYPE',
+});
+
+common.expectsError(() => {
+ runInNewContext('eval("x")', {}, {
+ contextCodeGeneration: {
+ wasm: 1,
+ },
+ });
+}, {
+ code: 'ERR_INVALID_ARG_TYPE'
+});
+
+common.expectsError(() => {
+ createContext({}, {
+ codeGeneration: 1,
+ });
+}, {
+ code: 'ERR_INVALID_ARG_TYPE',
+});
+
+common.expectsError(() => {
+ createContext({}, {
+ codeGeneration: null,
+ });
+}, {
+ code: 'ERR_INVALID_ARG_TYPE',
+});