Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom inspector for objects, fix #314 #315

Closed
wants to merge 17 commits into from
3 changes: 2 additions & 1 deletion .travis.yml
Expand Up @@ -4,4 +4,5 @@ node_js:
- "6"
- "8"
- "10"
- "12"
- "12"
- "14"
16 changes: 15 additions & 1 deletion lib/contextify.js
Expand Up @@ -200,6 +200,19 @@ Decontextify.instance = (instance, klass, deepTraps, flags, toStringTag) => {
if (key === '__lookupSetter__') return host.Object.prototype.__lookupSetter__;
if (key === host.Symbol.toStringTag && toStringTag) return toStringTag;

if (key === host.inspect.custom) {
return (depth, options) => {
try {
options = host.Object.assign(host.Object.create(null), options);
options.customInspect = false;
return host.inspect(instance, options);
} catch (e) {
if (e instanceof host.Error) throw e;
throw Decontextify.value(e);
}
};
}

try {
return Decontextify.value(instance[key], null, deepTraps, flags);
} catch (e) {
Expand Down Expand Up @@ -625,7 +638,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);
}

Expand Down Expand Up @@ -979,6 +992,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;
Expand Down
7 changes: 6 additions & 1 deletion lib/main.js
Expand Up @@ -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');
Expand Down Expand Up @@ -1246,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,
Expand Down Expand Up @@ -1286,7 +1288,10 @@ const HOST = {
Set,
WeakSet,
Promise,
inspect,
Symbol,
NODE_MAJOR: +major,
NODE_MINOR: +minor,
INSPECT_MAX_BYTES,
VM,
NodeVM,
Expand Down
114 changes: 107 additions & 7 deletions test/vm.js
Expand Up @@ -280,6 +280,66 @@ 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)));
});

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', () => {
let vm;

Expand Down Expand Up @@ -420,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(`
Expand Down Expand Up @@ -888,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;
});
Expand Down