From e55af8fe2e8b3ce082a76e2b5bbfe8c523bc69d3 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 11 Aug 2020 03:28:28 +0800 Subject: [PATCH 01/17] support host inspect --- lib/contextify.js | 6 +++++ lib/main.js | 2 ++ test/vm.js | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/lib/contextify.js b/lib/contextify.js index 5695e0e..3b23080 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -199,6 +199,11 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; + if (key === host.inspect.custom) { + return (depth, options) => { + return host.inspect(instance, options.showHidden, depth, options.colors); + }; + } try { return Decontextify.value(instance[key], null, deepTraps, flags); @@ -979,6 +984,7 @@ BufferOverride.inspect = function inspect(recurseTimes, ctx) { const LocalBuffer = global.Buffer = Contextify.readonly(host.Buffer, BufferMock); Contextify.connect(host.Buffer.prototype.inspect, BufferOverride.inspect); +Proxy[host.inspect.custom] = () => '[Function: Proxy]'; const exportsMap = host.Object.create(null); exportsMap.Contextify = Contextify; diff --git a/lib/main.js b/lib/main.js index 746e499..e86933f 100644 --- a/lib/main.js +++ b/lib/main.js @@ -23,6 +23,7 @@ const fs = require('fs'); const vm = require('vm'); const pa = require('path'); +const {inspect} = require('util'); const {EventEmitter} = require('events'); const {INSPECT_MAX_BYTES} = require('buffer'); const helpers = require('./helpers.js'); @@ -1286,6 +1287,7 @@ const HOST = { Set, WeakSet, Promise, + inspect, Symbol, INSPECT_MAX_BYTES, VM, diff --git a/test/vm.js b/test/vm.js index 5c5d802..76b5421 100644 --- a/test/vm.js +++ b/test/vm.js @@ -280,6 +280,63 @@ describe('contextify', () => { }); }); +describe('inspect', () => { + let vm; + + before(() => { + const sandbox = { inspect }; + vm = new VM({ sandbox }); + }); + + it('boxed primitives', () => { + assert.equal(inspect(vm.run('new Number(1)')), inspect(new Number(1))); + assert.equal(vm.run('inspect(new Number(1))'), inspect(new Number(1))); + assert.equal(inspect(vm.run('new String(1)')), inspect(new String(1))); + assert.equal(vm.run('inspect(new String(1))'), inspect(new String(1))); + assert.equal(inspect(vm.run('new Boolean(1)')), inspect(new Boolean(1))); + assert.equal(vm.run('inspect(new Boolean(1))'), inspect(new Boolean(1))); + }); + + it('other built-in objects', () => { + assert.equal(inspect(vm.run('Object')), inspect(Object)); + assert.equal(vm.run('inspect(Object)'), inspect(Object)); + assert.equal(inspect(vm.run('Function')), inspect(Function)); + assert.equal(vm.run('inspect(Function)'), inspect(Function)); + assert.equal(inspect(vm.run('Symbol')), inspect(Symbol)); + assert.equal(vm.run('inspect(Symbol)'), inspect(Symbol)); + assert.equal(inspect(vm.run('Reflect')), inspect(Reflect)); + assert.equal(vm.run('inspect(Reflect)'), inspect(Reflect)); + assert.equal(inspect(vm.run('Proxy')), inspect(Proxy)); + assert.equal(vm.run('inspect(Proxy)'), inspect(Proxy)); + assert.equal(inspect(vm.run('JSON')), inspect(JSON)); + assert.equal(vm.run('inspect(JSON)'), inspect(JSON)); + }); + + it('built-in instances', () => { + assert.equal(inspect(vm.run('new Date')).slice(0, -3), inspect(new Date).slice(0, -3)); + assert.equal(vm.run('inspect(new Date)').slice(0, -3), inspect(new Date).slice(0, -3)); + assert.equal(inspect(vm.run('new RegExp("^", "g")')), inspect(new RegExp('^', 'g'))); + assert.equal(vm.run('inspect(new RegExp("^", "g"))'), inspect(new RegExp('^', 'g'))); + assert.equal(inspect(vm.run('new Array(50)')), inspect(new Array(50))); + assert.equal(vm.run('inspect(new Array(50))'), inspect(new Array(50))); + assert.equal(inspect(vm.run('new Set([1])')), inspect(new Set([1]))); + assert.equal(vm.run('inspect(new Set([1]))'), inspect(new Set([1]))); + assert.equal(inspect(vm.run('new Map([[1, 2]])')), inspect(new Map([[1, 2]]))); + assert.equal(vm.run('inspect(new Map([[1, 2]]))'), inspect(new Map([[1, 2]]))); + assert.equal(inspect(vm.run('Promise.resolve(1)')), inspect(Promise.resolve(1))); + assert.equal(vm.run('inspect(Promise.resolve(1))'), inspect(Promise.resolve(1))); + }); + + it('other objects', () => { + const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor; + const GeneratorFunction = Object.getPrototypeOf(function* f() {}).constructor; + assert.equal(inspect(vm.run('Object.getPrototypeOf(async () => {}).constructor')), inspect(AsyncFunction)); + assert.equal(vm.run('inspect(Object.getPrototypeOf(async () => {}).constructor)'), inspect(AsyncFunction)); + assert.equal(inspect(vm.run('Object.getPrototypeOf(function* f() {}).constructor')), inspect(GeneratorFunction)); + assert.equal(vm.run('inspect(Object.getPrototypeOf(function* f() {}).constructor)'), inspect(GeneratorFunction)); + }); +}); + describe('VM', () => { let vm; From a00b7d36113e2935051c4523a03ea2a84e70e075 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 11 Aug 2020 03:34:33 +0800 Subject: [PATCH 02/17] tweak for node 6 --- test/vm.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/vm.js b/test/vm.js index 76b5421..7b01748 100644 --- a/test/vm.js +++ b/test/vm.js @@ -327,14 +327,17 @@ describe('inspect', () => { assert.equal(vm.run('inspect(Promise.resolve(1))'), inspect(Promise.resolve(1))); }); - it('other objects', () => { - const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor; - const GeneratorFunction = Object.getPrototypeOf(function* f() {}).constructor; - assert.equal(inspect(vm.run('Object.getPrototypeOf(async () => {}).constructor')), inspect(AsyncFunction)); - assert.equal(vm.run('inspect(Object.getPrototypeOf(async () => {}).constructor)'), inspect(AsyncFunction)); - assert.equal(inspect(vm.run('Object.getPrototypeOf(function* f() {}).constructor')), inspect(GeneratorFunction)); - assert.equal(vm.run('inspect(Object.getPrototypeOf(function* f() {}).constructor)'), inspect(GeneratorFunction)); - }); + if (NODE_VERSION > 7) { + // Node until 7 had no async, see https://node.green/ + it('other objects', () => { + const AsyncFunction = eval('Object.getPrototypeOf(async () => {}).constructor'); + const GeneratorFunction = eval('Object.getPrototypeOf(function* f() {}).constructor'); + assert.equal(inspect(vm.run('Object.getPrototypeOf(async () => {}).constructor')), inspect(AsyncFunction)); + assert.equal(vm.run('inspect(Object.getPrototypeOf(async () => {}).constructor)'), inspect(AsyncFunction)); + assert.equal(inspect(vm.run('Object.getPrototypeOf(function* f() {}).constructor')), inspect(GeneratorFunction)); + assert.equal(vm.run('inspect(Object.getPrototypeOf(function* f() {}).constructor)'), inspect(GeneratorFunction)); + }); + } }); describe('VM', () => { From d9c89d477c0564ca41c0f486889fbf1e4959a5a7 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 11 Aug 2020 12:45:05 +0800 Subject: [PATCH 03/17] catch potential inspect error --- lib/contextify.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/contextify.js b/lib/contextify.js index 3b23080..4e8a847 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -201,7 +201,11 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; if (key === host.inspect.custom) { return (depth, options) => { - return host.inspect(instance, options.showHidden, depth, options.colors); + try { + return host.inspect(instance, options.showHidden, depth, options.colors); + } catch (e) { + throw Decontextify.value(e); + } }; } From 65d92f0524079477c3cf0031f8dd1022524b9079 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 11 Aug 2020 12:56:42 +0800 Subject: [PATCH 04/17] decontextify inspect arguments --- lib/contextify.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/contextify.js b/lib/contextify.js index 4e8a847..36f7416 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -201,6 +201,8 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; if (key === host.inspect.custom) { return (depth, options) => { + depth = Decontextify.value(depth); + options = Decontextify.value(options); try { return host.inspect(instance, options.showHidden, depth, options.colors); } catch (e) { From 6d7dce714c7146555d44dcd790f25ded4222b59e Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Tue, 11 Aug 2020 16:25:10 +0800 Subject: [PATCH 05/17] contextify inspect arguments --- lib/contextify.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/contextify.js b/lib/contextify.js index 36f7416..039f8fa 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -201,8 +201,8 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; if (key === host.inspect.custom) { return (depth, options) => { - depth = Decontextify.value(depth); - options = Decontextify.value(options); + depth = Contextify.value(depth); + options = Contextify.value(options); try { return host.inspect(instance, options.showHidden, depth, options.colors); } catch (e) { From 4ab2b63eb0b2a6921e629a588f8e11798fe95287 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Wed, 12 Aug 2020 02:58:25 +0800 Subject: [PATCH 06/17] add version restriction --- lib/contextify.js | 8 ++++---- lib/main.js | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/contextify.js b/lib/contextify.js index 039f8fa..b17e8cb 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -199,10 +199,10 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; - if (key === host.inspect.custom) { + // node v14.6 or above will provide a safer inspect for cross-context objects + // https://github.com/nodejs/node/commit/e0206bafe6 + if (key === host.inspect.custom && host.NODE_MAJOR >= 14 && host.NODE_MINOR >= 6) { return (depth, options) => { - depth = Contextify.value(depth); - options = Contextify.value(options); try { return host.inspect(instance, options.showHidden, depth, options.colors); } catch (e) { @@ -636,7 +636,7 @@ Contextify.function = (fnc, traps, deepTraps, flags, mock) => { }; base.construct = (target, args, newTarget) => { // Fixes buffer unsafe allocation for node v6/7 - if (host.version < 8 && fnc === host.Buffer && 'number' === typeof args[0]) { + if (host.NODE_MAJOR < 8 && fnc === host.Buffer && 'number' === typeof args[0]) { args[0] = new Array(args[0]).fill(0); } diff --git a/lib/main.js b/lib/main.js index e86933f..c9d52b8 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1247,13 +1247,14 @@ class VMError extends Error { } } +const [major, minor] = process.versions.node.split('.'); + /** * Host objects * * @private */ const HOST = { - version: parseInt(process.versions.node.split('.')[0]), require, process, console, @@ -1289,6 +1290,8 @@ const HOST = { Promise, inspect, Symbol, + NODE_MAJOR: +major, + NODE_MINOR: +minor, INSPECT_MAX_BYTES, VM, NodeVM, From 8e787c058c5d3abc25542ea0f86f96bae2153607 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Wed, 12 Aug 2020 11:56:16 +0800 Subject: [PATCH 07/17] update exception handler --- lib/contextify.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/contextify.js b/lib/contextify.js index b17e8cb..6b0ab32 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -206,6 +206,7 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { try { return host.inspect(instance, options.showHidden, depth, options.colors); } catch (e) { + if (e instanceof host.Error) throw e; throw Decontextify.value(e); } }; From 31f0d000820a206d1173b3023d26151de755911a Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 13 Aug 2020 00:57:08 +0800 Subject: [PATCH 08/17] add node 14 --- .travis.yml | 3 +- test/vm.js | 107 ++++++++++++++++++++++++++-------------------------- 2 files changed, 56 insertions(+), 54 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f54fc8..25919c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,5 @@ node_js: - "6" - "8" - "10" - - "12" \ No newline at end of file + - "12" + - "14" \ No newline at end of file diff --git a/test/vm.js b/test/vm.js index 7b01748..f807dcd 100644 --- a/test/vm.js +++ b/test/vm.js @@ -5,8 +5,10 @@ const assert = require('assert'); const {VM, VMScript} = require('..'); -const NODE_VERSION = parseInt(process.versions.node.split('.')[0]); const {inspect} = require('util'); +const [major, minor] = process.versions.node.split('.'); +const NODE_MAJOR = +major; +const NODE_MINOR = +minor; global.isVM = false; @@ -22,7 +24,7 @@ describe('node', () => { }); it('inspect', () => { assert.throws(() => inspect(doubleProxy), /Expected/); - if (NODE_VERSION !== 10) { + if (NODE_MAJOR !== 10) { // This failes on node 10 since they do not unwrap proxys. // And the hack to fix this is only in the inner proxy. // We could add another hack, but that one would require @@ -280,55 +282,54 @@ describe('contextify', () => { }); }); -describe('inspect', () => { - let vm; +if (NODE_MAJOR >= 14 && NODE_MINOR >= 6) { + describe('inspect', () => { + let vm; - before(() => { - const sandbox = { inspect }; - vm = new VM({ sandbox }); - }); + before(() => { + const sandbox = { inspect }; + vm = new VM({ sandbox }); + }); - it('boxed primitives', () => { - assert.equal(inspect(vm.run('new Number(1)')), inspect(new Number(1))); - assert.equal(vm.run('inspect(new Number(1))'), inspect(new Number(1))); - assert.equal(inspect(vm.run('new String(1)')), inspect(new String(1))); - assert.equal(vm.run('inspect(new String(1))'), inspect(new String(1))); - assert.equal(inspect(vm.run('new Boolean(1)')), inspect(new Boolean(1))); - assert.equal(vm.run('inspect(new Boolean(1))'), inspect(new Boolean(1))); - }); + it('boxed primitives', () => { + assert.equal(inspect(vm.run('new Number(1)')), inspect(new Number(1))); + assert.equal(vm.run('inspect(new Number(1))'), inspect(new Number(1))); + assert.equal(inspect(vm.run('new String(1)')), inspect(new String(1))); + assert.equal(vm.run('inspect(new String(1))'), inspect(new String(1))); + assert.equal(inspect(vm.run('new Boolean(1)')), inspect(new Boolean(1))); + assert.equal(vm.run('inspect(new Boolean(1))'), inspect(new Boolean(1))); + }); - it('other built-in objects', () => { - assert.equal(inspect(vm.run('Object')), inspect(Object)); - assert.equal(vm.run('inspect(Object)'), inspect(Object)); - assert.equal(inspect(vm.run('Function')), inspect(Function)); - assert.equal(vm.run('inspect(Function)'), inspect(Function)); - assert.equal(inspect(vm.run('Symbol')), inspect(Symbol)); - assert.equal(vm.run('inspect(Symbol)'), inspect(Symbol)); - assert.equal(inspect(vm.run('Reflect')), inspect(Reflect)); - assert.equal(vm.run('inspect(Reflect)'), inspect(Reflect)); - assert.equal(inspect(vm.run('Proxy')), inspect(Proxy)); - assert.equal(vm.run('inspect(Proxy)'), inspect(Proxy)); - assert.equal(inspect(vm.run('JSON')), inspect(JSON)); - assert.equal(vm.run('inspect(JSON)'), inspect(JSON)); - }); + it('other built-in objects', () => { + assert.equal(inspect(vm.run('Object')), inspect(Object)); + assert.equal(vm.run('inspect(Object)'), inspect(Object)); + assert.equal(inspect(vm.run('Function')), inspect(Function)); + assert.equal(vm.run('inspect(Function)'), inspect(Function)); + assert.equal(inspect(vm.run('Symbol')), inspect(Symbol)); + assert.equal(vm.run('inspect(Symbol)'), inspect(Symbol)); + assert.equal(inspect(vm.run('Reflect')), inspect(Reflect)); + assert.equal(vm.run('inspect(Reflect)'), inspect(Reflect)); + assert.equal(inspect(vm.run('Proxy')), inspect(Proxy)); + assert.equal(vm.run('inspect(Proxy)'), inspect(Proxy)); + assert.equal(inspect(vm.run('JSON')), inspect(JSON)); + assert.equal(vm.run('inspect(JSON)'), inspect(JSON)); + }); - it('built-in instances', () => { - assert.equal(inspect(vm.run('new Date')).slice(0, -3), inspect(new Date).slice(0, -3)); - assert.equal(vm.run('inspect(new Date)').slice(0, -3), inspect(new Date).slice(0, -3)); - assert.equal(inspect(vm.run('new RegExp("^", "g")')), inspect(new RegExp('^', 'g'))); - assert.equal(vm.run('inspect(new RegExp("^", "g"))'), inspect(new RegExp('^', 'g'))); - assert.equal(inspect(vm.run('new Array(50)')), inspect(new Array(50))); - assert.equal(vm.run('inspect(new Array(50))'), inspect(new Array(50))); - assert.equal(inspect(vm.run('new Set([1])')), inspect(new Set([1]))); - assert.equal(vm.run('inspect(new Set([1]))'), inspect(new Set([1]))); - assert.equal(inspect(vm.run('new Map([[1, 2]])')), inspect(new Map([[1, 2]]))); - assert.equal(vm.run('inspect(new Map([[1, 2]]))'), inspect(new Map([[1, 2]]))); - assert.equal(inspect(vm.run('Promise.resolve(1)')), inspect(Promise.resolve(1))); - assert.equal(vm.run('inspect(Promise.resolve(1))'), inspect(Promise.resolve(1))); - }); + it('built-in instances', () => { + assert.equal(inspect(vm.run('new Date')).slice(0, -3), inspect(new Date).slice(0, -3)); + assert.equal(vm.run('inspect(new Date)').slice(0, -3), inspect(new Date).slice(0, -3)); + assert.equal(inspect(vm.run('new RegExp("^", "g")')), inspect(new RegExp('^', 'g'))); + assert.equal(vm.run('inspect(new RegExp("^", "g"))'), inspect(new RegExp('^', 'g'))); + assert.equal(inspect(vm.run('new Array(50)')), inspect(new Array(50))); + assert.equal(vm.run('inspect(new Array(50))'), inspect(new Array(50))); + assert.equal(inspect(vm.run('new Set([1])')), inspect(new Set([1]))); + assert.equal(vm.run('inspect(new Set([1]))'), inspect(new Set([1]))); + assert.equal(inspect(vm.run('new Map([[1, 2]])')), inspect(new Map([[1, 2]]))); + assert.equal(vm.run('inspect(new Map([[1, 2]]))'), inspect(new Map([[1, 2]]))); + assert.equal(inspect(vm.run('Promise.resolve(1)')), inspect(Promise.resolve(1))); + assert.equal(vm.run('inspect(Promise.resolve(1))'), inspect(Promise.resolve(1))); + }); - if (NODE_VERSION > 7) { - // Node until 7 had no async, see https://node.green/ it('other objects', () => { const AsyncFunction = eval('Object.getPrototypeOf(async () => {}).constructor'); const GeneratorFunction = eval('Object.getPrototypeOf(function* f() {}).constructor'); @@ -337,8 +338,8 @@ describe('inspect', () => { assert.equal(inspect(vm.run('Object.getPrototypeOf(function* f() {}).constructor')), inspect(GeneratorFunction)); assert.equal(vm.run('inspect(Object.getPrototypeOf(function* f() {}).constructor)'), inspect(GeneratorFunction)); }); - } -}); + }); +} describe('VM', () => { let vm; @@ -386,7 +387,7 @@ describe('VM', () => { return true; }); - if (NODE_VERSION > 6) { + if (NODE_MAJOR > 6) { // async/await was not there in Node 6 assert.throws(() => vm.run('function test(){ return await Promise.resolve(); };'), err => { assert.ok(err instanceof Error); @@ -400,7 +401,7 @@ describe('VM', () => { }); it('timeout', () => { - const message = NODE_VERSION >= 11 ? /Script execution timed out after 10ms/ : /Script execution timed out\./; + const message = NODE_MAJOR >= 11 ? /Script execution timed out after 10ms/ : /Script execution timed out\./; assert.throws(() => new VM({ timeout: 10 @@ -414,7 +415,7 @@ describe('VM', () => { assert.equal(vm.run('global.setImmediate'), void 0); }); - if (NODE_VERSION >= 10) { + if (NODE_MAJOR >= 10) { it('eval/wasm', () => { assert.equal(vm.run('eval("1")'), 1); @@ -423,7 +424,7 @@ describe('VM', () => { }); } - if (NODE_VERSION > 7) { + if (NODE_MAJOR > 7) { // Node until 7 had no async, see https://node.green/ it('async', () => { const vm2 = new VM({fixAsync: true}); @@ -648,7 +649,7 @@ describe('VM', () => { Buffer.prototype.__defineGetter__ === {}.__defineGetter__; `), true, '#1'); - if (NODE_VERSION > 6) { + if (NODE_MAJOR > 6) { assert.throws(() => vm2.run(` Buffer.prototype.__defineGetter__("toString", () => {}); `), /'defineProperty' on proxy: trap returned falsish for property 'toString'/, '#2'); From b22876c8c4661a92e7cfa2f9562767f5c8a3a798 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 13 Aug 2020 10:51:45 +0800 Subject: [PATCH 09/17] be aware of custom inspect in sandbox --- lib/contextify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contextify.js b/lib/contextify.js index 6b0ab32..7bfb029 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -201,7 +201,7 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; // node v14.6 or above will provide a safer inspect for cross-context objects // https://github.com/nodejs/node/commit/e0206bafe6 - if (key === host.inspect.custom && host.NODE_MAJOR >= 14 && host.NODE_MINOR >= 6) { + if (key === host.inspect.custom && !(key in instance) && host.NODE_MAJOR >= 14 && host.NODE_MINOR >= 6) { return (depth, options) => { try { return host.inspect(instance, options.showHidden, depth, options.colors); From 96d44cb659b78f394b40a84ef610e063557f34c3 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Thu, 13 Aug 2020 10:59:42 +0800 Subject: [PATCH 10/17] and we do not need version checks any more --- lib/contextify.js | 4 +- test/vm.js | 107 +++++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 57 deletions(-) diff --git a/lib/contextify.js b/lib/contextify.js index 7bfb029..39cb9da 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -199,9 +199,7 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; - // node v14.6 or above will provide a safer inspect for cross-context objects - // https://github.com/nodejs/node/commit/e0206bafe6 - if (key === host.inspect.custom && !(key in instance) && host.NODE_MAJOR >= 14 && host.NODE_MINOR >= 6) { + if (key === host.inspect.custom && !(key in instance)) { return (depth, options) => { try { return host.inspect(instance, options.showHidden, depth, options.colors); diff --git a/test/vm.js b/test/vm.js index f807dcd..7b01748 100644 --- a/test/vm.js +++ b/test/vm.js @@ -5,10 +5,8 @@ const assert = require('assert'); const {VM, VMScript} = require('..'); +const NODE_VERSION = parseInt(process.versions.node.split('.')[0]); const {inspect} = require('util'); -const [major, minor] = process.versions.node.split('.'); -const NODE_MAJOR = +major; -const NODE_MINOR = +minor; global.isVM = false; @@ -24,7 +22,7 @@ describe('node', () => { }); it('inspect', () => { assert.throws(() => inspect(doubleProxy), /Expected/); - if (NODE_MAJOR !== 10) { + if (NODE_VERSION !== 10) { // This failes on node 10 since they do not unwrap proxys. // And the hack to fix this is only in the inner proxy. // We could add another hack, but that one would require @@ -282,54 +280,55 @@ describe('contextify', () => { }); }); -if (NODE_MAJOR >= 14 && NODE_MINOR >= 6) { - describe('inspect', () => { - let vm; +describe('inspect', () => { + let vm; - before(() => { - const sandbox = { inspect }; - vm = new VM({ sandbox }); - }); + before(() => { + const sandbox = { inspect }; + vm = new VM({ sandbox }); + }); - it('boxed primitives', () => { - assert.equal(inspect(vm.run('new Number(1)')), inspect(new Number(1))); - assert.equal(vm.run('inspect(new Number(1))'), inspect(new Number(1))); - assert.equal(inspect(vm.run('new String(1)')), inspect(new String(1))); - assert.equal(vm.run('inspect(new String(1))'), inspect(new String(1))); - assert.equal(inspect(vm.run('new Boolean(1)')), inspect(new Boolean(1))); - assert.equal(vm.run('inspect(new Boolean(1))'), inspect(new Boolean(1))); - }); + it('boxed primitives', () => { + assert.equal(inspect(vm.run('new Number(1)')), inspect(new Number(1))); + assert.equal(vm.run('inspect(new Number(1))'), inspect(new Number(1))); + assert.equal(inspect(vm.run('new String(1)')), inspect(new String(1))); + assert.equal(vm.run('inspect(new String(1))'), inspect(new String(1))); + assert.equal(inspect(vm.run('new Boolean(1)')), inspect(new Boolean(1))); + assert.equal(vm.run('inspect(new Boolean(1))'), inspect(new Boolean(1))); + }); - it('other built-in objects', () => { - assert.equal(inspect(vm.run('Object')), inspect(Object)); - assert.equal(vm.run('inspect(Object)'), inspect(Object)); - assert.equal(inspect(vm.run('Function')), inspect(Function)); - assert.equal(vm.run('inspect(Function)'), inspect(Function)); - assert.equal(inspect(vm.run('Symbol')), inspect(Symbol)); - assert.equal(vm.run('inspect(Symbol)'), inspect(Symbol)); - assert.equal(inspect(vm.run('Reflect')), inspect(Reflect)); - assert.equal(vm.run('inspect(Reflect)'), inspect(Reflect)); - assert.equal(inspect(vm.run('Proxy')), inspect(Proxy)); - assert.equal(vm.run('inspect(Proxy)'), inspect(Proxy)); - assert.equal(inspect(vm.run('JSON')), inspect(JSON)); - assert.equal(vm.run('inspect(JSON)'), inspect(JSON)); - }); + it('other built-in objects', () => { + assert.equal(inspect(vm.run('Object')), inspect(Object)); + assert.equal(vm.run('inspect(Object)'), inspect(Object)); + assert.equal(inspect(vm.run('Function')), inspect(Function)); + assert.equal(vm.run('inspect(Function)'), inspect(Function)); + assert.equal(inspect(vm.run('Symbol')), inspect(Symbol)); + assert.equal(vm.run('inspect(Symbol)'), inspect(Symbol)); + assert.equal(inspect(vm.run('Reflect')), inspect(Reflect)); + assert.equal(vm.run('inspect(Reflect)'), inspect(Reflect)); + assert.equal(inspect(vm.run('Proxy')), inspect(Proxy)); + assert.equal(vm.run('inspect(Proxy)'), inspect(Proxy)); + assert.equal(inspect(vm.run('JSON')), inspect(JSON)); + assert.equal(vm.run('inspect(JSON)'), inspect(JSON)); + }); - it('built-in instances', () => { - assert.equal(inspect(vm.run('new Date')).slice(0, -3), inspect(new Date).slice(0, -3)); - assert.equal(vm.run('inspect(new Date)').slice(0, -3), inspect(new Date).slice(0, -3)); - assert.equal(inspect(vm.run('new RegExp("^", "g")')), inspect(new RegExp('^', 'g'))); - assert.equal(vm.run('inspect(new RegExp("^", "g"))'), inspect(new RegExp('^', 'g'))); - assert.equal(inspect(vm.run('new Array(50)')), inspect(new Array(50))); - assert.equal(vm.run('inspect(new Array(50))'), inspect(new Array(50))); - assert.equal(inspect(vm.run('new Set([1])')), inspect(new Set([1]))); - assert.equal(vm.run('inspect(new Set([1]))'), inspect(new Set([1]))); - assert.equal(inspect(vm.run('new Map([[1, 2]])')), inspect(new Map([[1, 2]]))); - assert.equal(vm.run('inspect(new Map([[1, 2]]))'), inspect(new Map([[1, 2]]))); - assert.equal(inspect(vm.run('Promise.resolve(1)')), inspect(Promise.resolve(1))); - assert.equal(vm.run('inspect(Promise.resolve(1))'), inspect(Promise.resolve(1))); - }); + it('built-in instances', () => { + assert.equal(inspect(vm.run('new Date')).slice(0, -3), inspect(new Date).slice(0, -3)); + assert.equal(vm.run('inspect(new Date)').slice(0, -3), inspect(new Date).slice(0, -3)); + assert.equal(inspect(vm.run('new RegExp("^", "g")')), inspect(new RegExp('^', 'g'))); + assert.equal(vm.run('inspect(new RegExp("^", "g"))'), inspect(new RegExp('^', 'g'))); + assert.equal(inspect(vm.run('new Array(50)')), inspect(new Array(50))); + assert.equal(vm.run('inspect(new Array(50))'), inspect(new Array(50))); + assert.equal(inspect(vm.run('new Set([1])')), inspect(new Set([1]))); + assert.equal(vm.run('inspect(new Set([1]))'), inspect(new Set([1]))); + assert.equal(inspect(vm.run('new Map([[1, 2]])')), inspect(new Map([[1, 2]]))); + assert.equal(vm.run('inspect(new Map([[1, 2]]))'), inspect(new Map([[1, 2]]))); + assert.equal(inspect(vm.run('Promise.resolve(1)')), inspect(Promise.resolve(1))); + assert.equal(vm.run('inspect(Promise.resolve(1))'), inspect(Promise.resolve(1))); + }); + if (NODE_VERSION > 7) { + // Node until 7 had no async, see https://node.green/ it('other objects', () => { const AsyncFunction = eval('Object.getPrototypeOf(async () => {}).constructor'); const GeneratorFunction = eval('Object.getPrototypeOf(function* f() {}).constructor'); @@ -338,8 +337,8 @@ if (NODE_MAJOR >= 14 && NODE_MINOR >= 6) { assert.equal(inspect(vm.run('Object.getPrototypeOf(function* f() {}).constructor')), inspect(GeneratorFunction)); assert.equal(vm.run('inspect(Object.getPrototypeOf(function* f() {}).constructor)'), inspect(GeneratorFunction)); }); - }); -} + } +}); describe('VM', () => { let vm; @@ -387,7 +386,7 @@ describe('VM', () => { return true; }); - if (NODE_MAJOR > 6) { + if (NODE_VERSION > 6) { // async/await was not there in Node 6 assert.throws(() => vm.run('function test(){ return await Promise.resolve(); };'), err => { assert.ok(err instanceof Error); @@ -401,7 +400,7 @@ describe('VM', () => { }); it('timeout', () => { - const message = NODE_MAJOR >= 11 ? /Script execution timed out after 10ms/ : /Script execution timed out\./; + const message = NODE_VERSION >= 11 ? /Script execution timed out after 10ms/ : /Script execution timed out\./; assert.throws(() => new VM({ timeout: 10 @@ -415,7 +414,7 @@ describe('VM', () => { assert.equal(vm.run('global.setImmediate'), void 0); }); - if (NODE_MAJOR >= 10) { + if (NODE_VERSION >= 10) { it('eval/wasm', () => { assert.equal(vm.run('eval("1")'), 1); @@ -424,7 +423,7 @@ describe('VM', () => { }); } - if (NODE_MAJOR > 7) { + if (NODE_VERSION > 7) { // Node until 7 had no async, see https://node.green/ it('async', () => { const vm2 = new VM({fixAsync: true}); @@ -649,7 +648,7 @@ describe('VM', () => { Buffer.prototype.__defineGetter__ === {}.__defineGetter__; `), true, '#1'); - if (NODE_MAJOR > 6) { + if (NODE_VERSION > 6) { assert.throws(() => vm2.run(` Buffer.prototype.__defineGetter__("toString", () => {}); `), /'defineProperty' on proxy: trap returned falsish for property 'toString'/, '#2'); From 451330d337cf1b7a6d32e31a8f803d2bf1061639 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 14 Aug 2020 02:24:10 +0800 Subject: [PATCH 11/17] wrap instance operation in try-catch --- lib/contextify.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/contextify.js b/lib/contextify.js index 39cb9da..d4002f0 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -199,15 +199,22 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === '__lookupGetter__') return host.Object.prototype.__lookupGetter__; if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__; if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; - if (key === host.inspect.custom && !(key in instance)) { - return (depth, options) => { - try { - return host.inspect(instance, options.showHidden, depth, options.colors); - } catch (e) { - if (e instanceof host.Error) throw e; - throw Decontextify.value(e); - } - }; + + if (key === host.inspect.custom) { + let useHostInspect + try { + useHostInspect = !(key in instance) + } catch {} + if (useHostInspect) { + return (depth, options) => { + try { + return host.inspect(instance, options.showHidden, depth, options.colors); + } catch (e) { + if (e instanceof host.Error) throw e; + throw Decontextify.value(e); + } + }; + } } try { From 0448be5ca925340a32f9c348a7913486a58cb24e Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 14 Aug 2020 02:26:00 +0800 Subject: [PATCH 12/17] tweak --- lib/contextify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contextify.js b/lib/contextify.js index d4002f0..7aa6758 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -204,7 +204,7 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { let useHostInspect try { useHostInspect = !(key in instance) - } catch {} + } catch (e) {} if (useHostInspect) { return (depth, options) => { try { From 4d06c6bd8708d06bc7bbf379efe59b70136ed296 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 14 Aug 2020 02:31:46 +0800 Subject: [PATCH 13/17] fix lint --- lib/contextify.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/contextify.js b/lib/contextify.js index 7aa6758..5f24225 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -201,9 +201,9 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; if (key === host.inspect.custom) { - let useHostInspect + let useHostInspect; try { - useHostInspect = !(key in instance) + useHostInspect = !(key in instance); } catch (e) {} if (useHostInspect) { return (depth, options) => { From c7bb5953d1d54ce58ac03f95f4e0819db6b277d4 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 14 Aug 2020 13:10:04 +0800 Subject: [PATCH 14/17] just rule out custom inspect --- lib/contextify.js | 25 ++++++++++------------ test/vm.js | 54 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/lib/contextify.js b/lib/contextify.js index 5f24225..33049d2 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -201,20 +201,17 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === host.Symbol.toStringTag && toStringTag) return toStringTag; if (key === host.inspect.custom) { - let useHostInspect; - try { - useHostInspect = !(key in instance); - } catch (e) {} - if (useHostInspect) { - return (depth, options) => { - try { - return host.inspect(instance, options.showHidden, depth, options.colors); - } catch (e) { - if (e instanceof host.Error) throw e; - throw Decontextify.value(e); - } - }; - } + return (depth, options) => { + try { + return host.inspect(instance, { + ...options, + customInspect: false + }); + } catch (e) { + if (e instanceof host.Error) throw e; + throw Decontextify.value(e); + } + }; } try { diff --git a/test/vm.js b/test/vm.js index 7b01748..ace36f8 100644 --- a/test/vm.js +++ b/test/vm.js @@ -480,13 +480,13 @@ describe('VM', () => { `), /process is not defined/, '#1'); assert.throws(() => vm2.run(` - try { - boom(); - } - catch (e) { - const foreignFunction = e.constructor.constructor; - const process = foreignFunction("return process")(); - } + try { + boom(); + } + catch (e) { + const foreignFunction = e.constructor.constructor; + const process = foreignFunction("return process")(); + } `), /process is not defined/, '#2'); assert.doesNotThrow(() => vm2.run(` @@ -948,6 +948,46 @@ describe('VM', () => { `), /e is not a function/); }); + it('inspect attack', () => { + // https://github.com/patriksimek/vm2/pull/315#issuecomment-673708529 + let vm2 = new VM(); + let badObject = vm2.run(` + const customInspect = Symbol.for('nodejs.util.inspect.custom'); + Date.prototype[customInspect] = (depth, options) => { + const s = options.stylize; + function do_recursive() { + try { + s(); + } catch(e) { + return e; + } + const r = do_recursive(); + if (r) return r; + throw null; + } + throw do_recursive().constructor.constructor("return process;")(); + } + new Proxy(new Date(), { + has(target, key) { + return false; + } + }); + `) + assert.doesNotThrow(() => inspect(badObject)); + + // https://github.com/patriksimek/vm2/pull/315#issuecomment-673708529 + vm2 = new VM(); + badObject = vm2.run(` + const d = new Date(); + new Proxy(d, { + has(target, key) { + throw (f) => f.constructor("return process;")(); + } + }); + `) + assert.doesNotThrow(() => inspect(badObject)); + }); + after(() => { vm = null; }); From 0d4078280bd7c154950cf72168093070d88c42d8 Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Fri, 14 Aug 2020 13:14:26 +0800 Subject: [PATCH 15/17] fix lint --- lib/contextify.js | 7 +++---- test/vm.js | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/contextify.js b/lib/contextify.js index 33049d2..cc80c75 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -203,10 +203,9 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === host.inspect.custom) { return (depth, options) => { try { - return host.inspect(instance, { - ...options, - customInspect: false - }); + options = Object.assign({}, options); + options.customInspect = false; + return host.inspect(instance, options); } catch (e) { if (e instanceof host.Error) throw e; throw Decontextify.value(e); diff --git a/test/vm.js b/test/vm.js index ace36f8..4eb1a60 100644 --- a/test/vm.js +++ b/test/vm.js @@ -972,7 +972,7 @@ describe('VM', () => { return false; } }); - `) + `); assert.doesNotThrow(() => inspect(badObject)); // https://github.com/patriksimek/vm2/pull/315#issuecomment-673708529 @@ -984,7 +984,7 @@ describe('VM', () => { throw (f) => f.constructor("return process;")(); } }); - `) + `); assert.doesNotThrow(() => inspect(badObject)); }); From fb246753da324710e863993fec1427e2742ad3af Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Sun, 16 Aug 2020 00:45:55 +0800 Subject: [PATCH 16/17] use host object.assign --- lib/contextify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contextify.js b/lib/contextify.js index cc80c75..bc9c4b7 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -203,7 +203,7 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === host.inspect.custom) { return (depth, options) => { try { - options = Object.assign({}, options); + options = host.Object.assign({}, options); options.customInspect = false; return host.inspect(instance, options); } catch (e) { From 946698988aefc5c33d8d33c9d4532d034d013f2a Mon Sep 17 00:00:00 2001 From: Shigma <1700011071@pku.edu.cn> Date: Sun, 16 Aug 2020 01:15:06 +0800 Subject: [PATCH 17/17] use host null proto object --- lib/contextify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/contextify.js b/lib/contextify.js index bc9c4b7..2cd5be3 100644 --- a/lib/contextify.js +++ b/lib/contextify.js @@ -203,7 +203,7 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => { if (key === host.inspect.custom) { return (depth, options) => { try { - options = host.Object.assign({}, options); + options = host.Object.assign(host.Object.create(null), options); options.customInspect = false; return host.inspect(instance, options); } catch (e) {