Skip to content

Commit 80e3ef3

Browse files
authored
util: harden more built-in classes against prototype pollution
PR-URL: #56225 Reviewed-By: Jordan Harband <[email protected]> Reviewed-By: Vinícius Lourenço Claro Cardoso <[email protected]>
1 parent b171afe commit 80e3ef3

File tree

3 files changed

+67
-4
lines changed

3 files changed

+67
-4
lines changed

lib/buffer.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const {
3535
NumberMIN_SAFE_INTEGER,
3636
ObjectDefineProperties,
3737
ObjectDefineProperty,
38+
ObjectPrototypeHasOwnProperty,
3839
ObjectSetPrototypeOf,
3940
RegExpPrototypeSymbolReplace,
4041
StringPrototypeCharCodeAt,
@@ -911,7 +912,14 @@ Buffer.prototype[customInspectSymbol] = function inspect(recurseTimes, ctx) {
911912
}), 27, -2);
912913
}
913914
}
914-
return `<${this.constructor.name} ${str}>`;
915+
let constructorName = 'Buffer';
916+
try {
917+
const { constructor } = this;
918+
if (typeof constructor === 'function' && ObjectPrototypeHasOwnProperty(constructor, 'name')) {
919+
constructorName = constructor.name;
920+
}
921+
} catch { /* Ignore error and use default name */ }
922+
return `<${constructorName} ${str}>`;
915923
};
916924
Buffer.prototype.inspect = Buffer.prototype[customInspectSymbol];
917925

lib/internal/util/inspect.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
const {
44
Array,
5+
ArrayBuffer,
6+
ArrayBufferPrototype,
57
ArrayIsArray,
8+
ArrayPrototype,
69
ArrayPrototypeFilter,
710
ArrayPrototypeForEach,
811
ArrayPrototypeIncludes,
@@ -29,6 +32,8 @@ const {
2932
FunctionPrototypeSymbolHasInstance,
3033
FunctionPrototypeToString,
3134
JSONStringify,
35+
Map,
36+
MapPrototype,
3237
MapPrototypeEntries,
3338
MapPrototypeGetSize,
3439
MathFloor,
@@ -68,6 +73,8 @@ const {
6873
SafeMap,
6974
SafeSet,
7075
SafeStringIterator,
76+
Set,
77+
SetPrototype,
7178
SetPrototypeGetSize,
7279
SetPrototypeValues,
7380
String,
@@ -93,6 +100,8 @@ const {
93100
SymbolPrototypeValueOf,
94101
SymbolToPrimitive,
95102
SymbolToStringTag,
103+
TypedArray,
104+
TypedArrayPrototype,
96105
TypedArrayPrototypeGetLength,
97106
TypedArrayPrototypeGetSymbolToStringTag,
98107
Uint8Array,
@@ -599,8 +608,13 @@ function isInstanceof(object, proto) {
599608

600609
// Special-case for some builtin prototypes in case their `constructor` property has been tampered.
601610
const wellKnownPrototypes = new SafeMap();
602-
wellKnownPrototypes.set(ObjectPrototype, { name: 'Object', constructor: Object });
611+
wellKnownPrototypes.set(ArrayPrototype, { name: 'Array', constructor: Array });
612+
wellKnownPrototypes.set(ArrayBufferPrototype, { name: 'ArrayBuffer', constructor: ArrayBuffer });
603613
wellKnownPrototypes.set(FunctionPrototype, { name: 'Function', constructor: Function });
614+
wellKnownPrototypes.set(MapPrototype, { name: 'Map', constructor: Map });
615+
wellKnownPrototypes.set(ObjectPrototype, { name: 'Object', constructor: Object });
616+
wellKnownPrototypes.set(SetPrototype, { name: 'Set', constructor: Set });
617+
wellKnownPrototypes.set(TypedArrayPrototype, { name: 'TypedArray', constructor: TypedArray });
604618

605619
function getConstructorName(obj, ctx, recurseTimes, protoProps) {
606620
let firstProto;
@@ -825,12 +839,12 @@ function formatValue(ctx, value, recurseTimes, typedArray) {
825839
// Filter out the util module, its inspect function is special.
826840
maybeCustom !== inspect &&
827841
// Also filter out any prototype objects using the circular check.
828-
!(value.constructor && value.constructor.prototype === value)) {
842+
ObjectGetOwnPropertyDescriptor(value, 'constructor')?.value?.prototype !== value) {
829843
// This makes sure the recurseTimes are reported as before while using
830844
// a counter internally.
831845
const depth = ctx.depth === null ? null : ctx.depth - recurseTimes;
832846
const isCrossContext =
833-
proxy !== undefined || !(context instanceof Object);
847+
proxy !== undefined || !FunctionPrototypeSymbolHasInstance(Object, context);
834848
const ret = FunctionPrototypeCall(
835849
maybeCustom,
836850
context,

test/parallel/test-util-inspect.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3385,3 +3385,44 @@ assert.strictEqual(
33853385
);
33863386
Object.defineProperty(BuiltinPrototype, 'constructor', desc);
33873387
}
3388+
{
3389+
const prototypes = [
3390+
Array.prototype,
3391+
ArrayBuffer.prototype,
3392+
Buffer.prototype,
3393+
Function.prototype,
3394+
Map.prototype,
3395+
Object.prototype,
3396+
Reflect.getPrototypeOf(Uint8Array.prototype),
3397+
Set.prototype,
3398+
Uint8Array.prototype,
3399+
];
3400+
const descriptors = new Map();
3401+
const buffer = Buffer.from('Hello');
3402+
const o = {
3403+
arrayBuffer: new ArrayBuffer(), buffer, typedArray: Uint8Array.from(buffer),
3404+
array: [], func() {}, set: new Set([1]), map: new Map(),
3405+
};
3406+
for (const BuiltinPrototype of prototypes) {
3407+
descriptors.set(BuiltinPrototype, Reflect.getOwnPropertyDescriptor(BuiltinPrototype, 'constructor'));
3408+
Object.defineProperty(BuiltinPrototype, 'constructor', {
3409+
get: () => BuiltinPrototype,
3410+
configurable: true,
3411+
});
3412+
}
3413+
assert.strictEqual(
3414+
util.inspect(o),
3415+
'{\n' +
3416+
' arrayBuffer: ArrayBuffer { [Uint8Contents]: <>, byteLength: 0 },\n' +
3417+
' buffer: <Buffer 48 65 6c 6c 6f>,\n' +
3418+
' typedArray: TypedArray(5) [Uint8Array] [ 72, 101, 108, 108, 111 ],\n' +
3419+
' array: [],\n' +
3420+
' func: [Function: func],\n' +
3421+
' set: Set(1) { 1 },\n' +
3422+
' map: Map(0) {}\n' +
3423+
'}',
3424+
);
3425+
for (const [BuiltinPrototype, desc] of descriptors) {
3426+
Object.defineProperty(BuiltinPrototype, 'constructor', desc);
3427+
}
3428+
}

0 commit comments

Comments
 (0)