util: restrict custom inspect function + vm.Context interaction · nodejs/node@8d0c21f · GitHub
Skip to content

Commit 8d0c21f

Browse files
committed
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 4a2accb commit 8d0c21f

3 files changed

Lines changed: 124 additions & 3 deletions

File tree

doc/api/util.md

Lines changed: 5 additions & 0 deletions

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,
@@ -212,8 +215,8 @@ const ansi = new RegExp(ansiPattern, 'g');
212215

213216
let getStringWidth;
214217

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

233263
/**
@@ -723,7 +753,10 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
723753
// This makes sure the recurseTimes are reported as before while using
724754
// a counter internally.
725755
const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
726-
const ret = maybeCustom.call(context, depth, getUserOptions(ctx));
756+
const isCrossContext =
757+
proxy !== undefined || !(context instanceof Object);
758+
const ret = FunctionPrototypeCall(
759+
maybeCustom, context, depth, getUserOptions(ctx, isCrossContext));
727760
// If the custom inspection method returned `this`, don't go into
728761
// infinite recursion.
729762
if (ret !== context) {

test/parallel/test-util-inspect.js

Lines changed: 83 additions & 0 deletions

0 commit comments

Comments
 (0)