Skip to content

Commit e0206ba

Browse files
addaleaxMylesBorins
authored andcommitted
util: restrict custom inspect function + vm.Context interaction
When `util.inspect()` is called on an object with a custom inspect function, and that object is from a different `vm.Context`, that function will not receive any arguments that access context-specific data anymore. PR-URL: #33690 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 3b26809 commit e0206ba

3 files changed

Lines changed: 126 additions & 3 deletions

File tree

‎doc/api/util.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,13 @@ stream.write('With ES6');
398398
added: v0.3.0
399399
changes:
400400
- version: v14.0.0
401+
pr-url: https://github.com/nodejs/node/pull/33690
402+
description: If `object` is from a different `vm.Context` now, a custom
403+
inspection function on it will not receive context-specific
404+
arguments anymore.
405+
- version:
406+
- v13.13.0
407+
- v12.17.0
401408
pr-url: https://github.com/nodejs/node/pull/32392
402409
description: The `maxStringLength` option is supported now.
403410
- version:

‎lib/internal/util/inspect.js‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const {
1212
DatePrototypeToString,
1313
ErrorPrototypeToString,
1414
Float32Array,
15+
FunctionPrototypeCall,
1516
FunctionPrototypeToString,
1617
Int16Array,
1718
JSONStringify,
@@ -26,6 +27,7 @@ const {
2627
Number,
2728
NumberIsNaN,
2829
NumberPrototypeValueOf,
30+
Object,
2931
ObjectAssign,
3032
ObjectCreate,
3133
ObjectDefineProperty,
@@ -38,6 +40,7 @@ const {
3840
ObjectPrototypeHasOwnProperty,
3941
ObjectPrototypePropertyIsEnumerable,
4042
ObjectSeal,
43+
ObjectSetPrototypeOf,
4144
RegExp,
4245
RegExpPrototypeToString,
4346
Set,
@@ -213,8 +216,8 @@ const ansi = new RegExp(ansiPattern, 'g');
213216

214217
letgetStringWidth;
215218

216-
functiongetUserOptions(ctx){
217-
return{
219+
functiongetUserOptions(ctx,isCrossContext){
220+
constret={
218221
stylize: ctx.stylize,
219222
showHidden: ctx.showHidden,
220223
depth: ctx.depth,
@@ -229,6 +232,33 @@ function getUserOptions(ctx) {
229232
getters: ctx.getters,
230233
...ctx.userOptions
231234
};
235+
236+
// Typically, the target value will be an instance of `Object`. If that is
237+
// *not* the case, the object may come from another vm.Context, and we want
238+
// to avoid passing it objects from this Context in that case, so we remove
239+
// the prototype from the returned object itself + the `stylize()` function,
240+
// and remove all other non-primitives, including non-primitive user options.
241+
if(isCrossContext){
242+
ObjectSetPrototypeOf(ret,null);
243+
for(constkeyofObjectKeys(ret)){
244+
if((typeofret[key]==='object'||typeofret[key]==='function')&&
245+
ret[key]!==null){
246+
deleteret[key];
247+
}
248+
}
249+
ret.stylize=ObjectSetPrototypeOf((value,flavour)=>{
250+
letstylized;
251+
try{
252+
stylized=`${ctx.stylize(value,flavour)}`;
253+
}catch{}
254+
255+
if(typeofstylized!=='string')returnvalue;
256+
// `stylized` is a string as it should be, which is safe to pass along.
257+
returnstylized;
258+
},null);
259+
}
260+
261+
returnret;
232262
}
233263

234264
/**
@@ -728,7 +758,10 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
728758
// This makes sure the recurseTimes are reported as before while using
729759
// a counter internally.
730760
constdepth=ctx.depth===null ? null : ctx.depth-recurseTimes;
731-
constret=maybeCustom.call(context,depth,getUserOptions(ctx));
761+
constisCrossContext=
762+
proxy!==undefined||!(contextinstanceofObject);
763+
constret=FunctionPrototypeCall(
764+
maybeCustom,context,depth,getUserOptions(ctx,isCrossContext));
732765
// If the custom inspection method returned `this`, don't go into
733766
// infinite recursion.
734767
if(ret!==context){

‎test/parallel/test-util-inspect.js‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2906,3 +2906,86 @@ assert.strictEqual(
29062906
"'aaaa'... 999996 more characters"
29072907
);
29082908
}
2909+
2910+
{
2911+
// Verify that util.inspect() invokes custom inspect functions on objects
2912+
// from other vm.Contexts but does not pass data from its own Context to that
2913+
// function.
2914+
consttarget=vm.runInNewContext(`
2915+
({
2916+
[Symbol.for('nodejs.util.inspect.custom')](depth, ctx) {
2917+
this.depth = depth;
2918+
this.ctx = ctx;
2919+
try {
2920+
this.stylized = ctx.stylize('🐈');
2921+
} catch (e) {
2922+
this.stylizeException = e;
2923+
}
2924+
return this.stylized;
2925+
}
2926+
})
2927+
`,Object.create(null));
2928+
assert.strictEqual(target.ctx,undefined);
2929+
2930+
{
2931+
// Subtest 1: Just try to inspect the object with default options.
2932+
assert.strictEqual(util.inspect(target),'🐈');
2933+
assert.strictEqual(typeoftarget.ctx,'object');
2934+
constobjectGraph=fullObjectGraph(target);
2935+
assert(!objectGraph.has(Object));
2936+
assert(!objectGraph.has(Function));
2937+
}
2938+
2939+
{
2940+
// Subtest 2: Use a stylize function that returns a non-primitive.
2941+
constoutput=util.inspect(target,{
2942+
stylize: common.mustCall((str)=>{
2943+
return{};
2944+
})
2945+
});
2946+
assert.strictEqual(output,'[object Object]');
2947+
assert.strictEqual(typeoftarget.ctx,'object');
2948+
constobjectGraph=fullObjectGraph(target);
2949+
assert(!objectGraph.has(Object));
2950+
assert(!objectGraph.has(Function));
2951+
}
2952+
2953+
{
2954+
// Subtest 3: Use a stylize function that throws an exception.
2955+
constoutput=util.inspect(target,{
2956+
stylize: common.mustCall((str)=>{
2957+
thrownewError('oops');
2958+
})
2959+
});
2960+
assert.strictEqual(output,'🐈');
2961+
assert.strictEqual(typeoftarget.ctx,'object');
2962+
constobjectGraph=fullObjectGraph(target);
2963+
assert(!objectGraph.has(Object));
2964+
assert(!objectGraph.has(Function));
2965+
}
2966+
2967+
functionfullObjectGraph(value){
2968+
constgraph=newSet([value]);
2969+
2970+
for(constentryofgraph){
2971+
if((typeofentry!=='object'&&typeofentry!=='function')||
2972+
entry===null){
2973+
continue;
2974+
}
2975+
2976+
graph.add(Object.getPrototypeOf(entry));
2977+
constdescriptors=Object.values(
2978+
Object.getOwnPropertyDescriptors(entry));
2979+
for(constdescriptorofdescriptors){
2980+
graph.add(descriptor.value);
2981+
graph.add(descriptor.set);
2982+
graph.add(descriptor.get);
2983+
}
2984+
}
2985+
2986+
returngraph;
2987+
}
2988+
2989+
// Consistency check.
2990+
assert(fullObjectGraph(global).has(Function.prototype));
2991+
}

0 commit comments

Comments
 (0)