diff --git a/doc/STYLE_GUIDE.md b/doc/STYLE_GUIDE.md index 7da30743cf8..009b0ee2b5e 100644 --- a/doc/STYLE_GUIDE.md +++ b/doc/STYLE_GUIDE.md @@ -14,9 +14,8 @@ "color" vs. "colour", etc. * Use [serial commas][]. * Avoid personal pronouns in reference documentation ("I", "you", "we"). - * Pronouns are acceptable in more colloquial documentation, like guides. - * Use gender-neutral pronouns and mass nouns. Non-comprehensive - examples: + * Personal pronouns are acceptable in colloquial documentation such as guides. + * Use gender-neutral pronouns and gender-neutral plural nouns. * OK: "they", "their", "them", "folks", "people", "developers" * NOT OK: "his", "hers", "him", "her", "guys", "dudes" * When combining wrapping elements (parentheses and quotes), terminal diff --git a/doc/api/vm.md b/doc/api/vm.md index 12e35e9f727..b9ccf93b7be 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 554aff8bfcc..5266dbd29fb 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/node.gyp b/node.gyp index 81df6d25353..ade2f84d7fe 100644 --- a/node.gyp +++ b/node.gyp @@ -205,6 +205,9 @@ 'sources': [ 'src/node_main.cc' ], + 'includes': [ + 'node.gypi' + ], 'include_dirs': [ 'src', ], @@ -235,9 +238,6 @@ }], [ 'node_intermediate_lib_type=="static_library" and ' 'node_shared=="false"', { - 'includes': [ - 'node.gypi' - ], 'xcode_settings': { 'OTHER_LDFLAGS': [ '-Wl,-force_load,<(PRODUCT_DIR)/<(STATIC_LIB_PREFIX)' @@ -500,19 +500,6 @@ ], }], ], - 'defines!': [ - 'NODE_PLATFORM="win"', - ], - 'defines': [ - 'FD_SETSIZE=1024', - # we need to use node's preferred "win32" rather than gyp's preferred "win" - 'NODE_PLATFORM="win32"', - # Stop from defining macros that conflict with - # std::min() and std::max(). We don't use (much) - # but we still inherit it from uv.h. - 'NOMINMAX', - '_UNICODE=1', - ], 'libraries': [ '-lpsapi.lib' ], 'conditions': [ # this is only necessary for chakra on windows because chakra is dynamically linked on windows @@ -521,7 +508,6 @@ }], ], }, { # POSIX - 'defines': [ '__POSIX__' ], 'sources': [ 'src/backtrace_posix.cc' ], }], [ 'node_use_etw=="true"', { diff --git a/node.gypi b/node.gypi index 238f577da0c..afc9a268855 100644 --- a/node.gypi +++ b/node.gypi @@ -37,6 +37,24 @@ 'NODE_SHARED_MODE', ], }], + [ 'OS=="win"', { + 'defines!': [ + 'NODE_PLATFORM="win"', + ], + 'defines': [ + 'FD_SETSIZE=1024', + # we need to use node's preferred "win32" rather than gyp's preferred "win" + 'NODE_PLATFORM="win32"', + # Stop from defining macros that conflict with + # std::min() and std::max(). We don't use (much) + # but we still inherit it from uv.h. + 'NOMINMAX', + '_UNICODE=1', + ], + }, { # POSIX + 'defines': [ '__POSIX__' ], + }], + [ 'node_enable_d8=="true"', { 'dependencies': [ 'deps/v8/src/d8.gyp:d8' ], }], diff --git a/src/node.cc b/src/node.cc index df4c9cd0f09..f4106c9009c 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" @@ -4577,6 +4578,8 @@ Local 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 intl_v; if (context->Global()->Get(context, intl_key).ToLocal(&intl_v) && intl_v->IsObject()) { @@ -4686,6 +4689,13 @@ inline int Start(Isolate* isolate, void* isolate_context, return exit_code; } +bool AllowWasmCodeGenerationCallback( + Local context, Local) { + Local 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) { @@ -4718,6 +4728,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 9d3145bb800..522ce292d21 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 de910758437..bf50d72ebd5 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -190,6 +190,30 @@ Local ContextifyContext::CreateV8Context( CHECK(name->IsString()); Utf8Value name_val(env->isolate(), name); + Local codegen = options_obj->Get(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "codeGeneration")) + .ToLocalChecked(); + + if (!codegen->IsUndefined()) { + CHECK(codegen->IsObject()); + Local codegen_obj = codegen.As(); + + Local 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 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 origin = diff --git a/src/node_contextify.h b/src/node_contextify.h index b25e1a75d4f..cf3e6452fd0 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -24,6 +24,9 @@ class ContextifyContext { v8::Local sandbox_obj, v8::Local options_obj); static void Init(Environment* env, v8::Local target); + static bool AllowWasmCodeGeneration( + v8::Local context, v8::Local); + static ContextifyContext* ContextFromContextifiedSandbox( Environment* env, const v8::Local& sandbox); diff --git a/src/node_main.cc b/src/node_main.cc index 7ab612d53e5..8907c47ae0e 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -82,12 +82,30 @@ int wmain(int argc, wchar_t *wargv[]) { #endif // __LP64__ extern char** environ; #endif // __linux__ +#if defined(__POSIX__) && defined(NODE_SHARED_MODE) +#include +#include +#endif namespace node { extern bool linux_at_secure; } // namespace node int main(int argc, char *argv[]) { +#if defined(__POSIX__) && defined(NODE_SHARED_MODE) + // In node::PlatformInit(), we squash all signal handlers for non-shared lib + // build. In order to run test cases against shared lib build, we also need + // to do the same thing for shared lib build here, but only for SIGPIPE for + // now. If node::PlatformInit() is moved to here, then this section could be + // removed. + { + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, nullptr); + } +#endif + #if defined(__linux__) char** envp = environ; while (*envp++ != nullptr) {} diff --git a/test/addons-napi/test_new_target/binding.c b/test/addons-napi/test_new_target/binding.c index 3cee0d4d8a3..a74d4bb2f87 100644 --- a/test/addons-napi/test_new_target/binding.c +++ b/test/addons-napi/test_new_target/binding.c @@ -37,7 +37,7 @@ napi_value Constructor(napi_env env, napi_callback_info info) { NAPI_ASSERT(env, newTargetArg != NULL, "newTargetArg != NULL"); NAPI_CALL(env, napi_strict_equals(env, newTargetArg, undefined, &result)); NAPI_ASSERT(env, !result, "new.target !== undefined"); - + // arguments[0] should be Constructor itself (test harness passed it) NAPI_CALL(env, napi_strict_equals(env, newTargetArg, argv, &result)); NAPI_ASSERT(env, result, "new.target === Constructor"); diff --git a/test/parallel/test-regress-GH-4948.js b/test/parallel/test-regress-GH-4948.js deleted file mode 100644 index d4d63600868..00000000000 --- a/test/parallel/test-regress-GH-4948.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; -// https://github.com/joyent/node/issues/4948 - -require('../common'); -const http = require('http'); - -let reqCount = 0; -const server = http.createServer(function(serverReq, serverRes) { - if (reqCount) { - serverRes.end(); - server.close(); - return; - } - reqCount = 1; - - - // normally the use case would be to call an external site - // does not require connecting locally or to itself to fail - const r = http.request({ hostname: 'localhost', - port: this.address().port }, function(res) { - // required, just needs to be in the client response somewhere - serverRes.end(); - - // required for test to fail - res.on('data', () => {}); - - }); - r.on('error', () => {}); - r.end(); - - serverRes.write('some data'); -}).listen(0, function() { - // simulate a client request that closes early - const net = require('net'); - - const sock = new net.Socket(); - sock.connect(this.address().port, 'localhost'); - - sock.on('connect', function() { - sock.write('GET / HTTP/1.1\r\n\r\n'); - sock.end(); - }); -}); diff --git a/test/parallel/test-vm-codegen.js b/test/parallel/test-vm-codegen.js new file mode 100644 index 00000000000..ebafe30c076 --- /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', +}); diff --git a/tools/test-npm-package.js b/tools/test-npm-package.js index 00e9a81928a..0cf9700ef96 100755 --- a/tools/test-npm-package.js +++ b/tools/test-npm-package.js @@ -22,6 +22,7 @@ const { createWriteStream, mkdirSync, rmdirSync } = require('fs'); const path = require('path'); const common = require('../test/common'); +const tmpDir = require('../test/common/tmpdir'); const projectDir = path.resolve(__dirname, '..'); const npmBin = path.join(projectDir, 'deps', 'npm', 'bin', 'npm-cli.js'); @@ -39,15 +40,14 @@ function spawnCopyDeepSync(source, destination) { function runNPMPackageTests({ srcDir, install, rebuild, testArgs, logfile }) { // Make sure we don't conflict with concurrent test runs const srcHash = createHash('md5').update(srcDir).digest('hex'); - common.tmpDir = `${common.tmpDir}.npm.${srcHash}`; - common.refreshTmpDir(); + tmpDir.path = `${tmpDir.path}.npm.${srcHash}`; + tmpDir.refresh(); - const tmpDir = common.tmpDir; - const npmCache = path.join(tmpDir, 'npm-cache'); - const npmPrefix = path.join(tmpDir, 'npm-prefix'); - const npmTmp = path.join(tmpDir, 'npm-tmp'); - const npmUserconfig = path.join(tmpDir, 'npm-userconfig'); - const pkgDir = path.join(tmpDir, 'pkg'); + const npmCache = path.join(tmpDir.path, 'npm-cache'); + const npmPrefix = path.join(tmpDir.path, 'npm-prefix'); + const npmTmp = path.join(tmpDir.path, 'npm-tmp'); + const npmUserconfig = path.join(tmpDir.path, 'npm-userconfig'); + const pkgDir = path.join(tmpDir.path, 'pkg'); spawnCopyDeepSync(srcDir, pkgDir); @@ -63,10 +63,10 @@ function runNPMPackageTests({ srcDir, install, rebuild, testArgs, logfile }) { }; if (common.isWindows) { - npmOptions.env.home = tmpDir; + npmOptions.env.home = tmpDir.path; npmOptions.env.Path = `${nodePath};${process.env.Path}`; } else { - npmOptions.env.HOME = tmpDir; + npmOptions.env.HOME = tmpDir.path; npmOptions.env.PATH = `${nodePath}:${process.env.PATH}`; } @@ -102,8 +102,8 @@ function runNPMPackageTests({ srcDir, install, rebuild, testArgs, logfile }) { } testChild.on('exit', () => { - common.refreshTmpDir(); - rmdirSync(tmpDir); + tmpDir.refresh(); + rmdirSync(tmpDir.path); }); }