Skip to content

Commit a648e4c

Browse files
aduh95ruyadorno
authored andcommittedJan 5, 2025
util: harden more built-in classes against prototype pollution
PR-URL: #56225 Reviewed-By: Jordan Harband <ljharb@gmail.com> Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br>
1 parent 5c2e061 commit a648e4c

File tree

3 files changed

+67
-4
lines changed

3 files changed

+67
-4
lines changed
 

‎lib/buffer.js

+9-1
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,
@@ -910,7 +911,14 @@ Buffer.prototype[customInspectSymbol] = function inspect(recurseTimes, ctx) {
910911
}), 27, -2);
911912
}
912913
}
913-
return `<${this.constructor.name} ${str}>`;
914+
let constructorName = 'Buffer';
915+
try {
916+
const { constructor } = this;
917+
if (typeof constructor === 'function' && ObjectPrototypeHasOwnProperty(constructor, 'name')) {
918+
constructorName = constructor.name;
919+
}
920+
} catch { /* Ignore error and use default name */ }
921+
return `<${constructorName} ${str}>`;
914922
};
915923
Buffer.prototype.inspect = Buffer.prototype[customInspectSymbol];
916924

‎lib/internal/util/inspect.js

+17-3
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

+41
Original file line numberDiff line numberDiff line change
@@ -3353,3 +3353,44 @@ assert.strictEqual(
33533353
);
33543354
Object.defineProperty(BuiltinPrototype, 'constructor', desc);
33553355
}
3356+
{
3357+
const prototypes = [
3358+
Array.prototype,
3359+
ArrayBuffer.prototype,
3360+
Buffer.prototype,
3361+
Function.prototype,
3362+
Map.prototype,
3363+
Object.prototype,
3364+
Reflect.getPrototypeOf(Uint8Array.prototype),
3365+
Set.prototype,
3366+
Uint8Array.prototype,
3367+
];
3368+
const descriptors = new Map();
3369+
const buffer = Buffer.from('Hello');
3370+
const o = {
3371+
arrayBuffer: new ArrayBuffer(), buffer, typedArray: Uint8Array.from(buffer),
3372+
array: [], func() {}, set: new Set([1]), map: new Map(),
3373+
};
3374+
for (const BuiltinPrototype of prototypes) {
3375+
descriptors.set(BuiltinPrototype, Reflect.getOwnPropertyDescriptor(BuiltinPrototype, 'constructor'));
3376+
Object.defineProperty(BuiltinPrototype, 'constructor', {
3377+
get: () => BuiltinPrototype,
3378+
configurable: true,
3379+
});
3380+
}
3381+
assert.strictEqual(
3382+
util.inspect(o),
3383+
'{\n' +
3384+
' arrayBuffer: ArrayBuffer { [Uint8Contents]: <>, byteLength: 0 },\n' +
3385+
' buffer: <Buffer 48 65 6c 6c 6f>,\n' +
3386+
' typedArray: TypedArray(5) [Uint8Array] [ 72, 101, 108, 108, 111 ],\n' +
3387+
' array: [],\n' +
3388+
' func: [Function: func],\n' +
3389+
' set: Set(1) { 1 },\n' +
3390+
' map: Map(0) {}\n' +
3391+
'}',
3392+
);
3393+
for (const [BuiltinPrototype, desc] of descriptors) {
3394+
Object.defineProperty(BuiltinPrototype, 'constructor', desc);
3395+
}
3396+
}

0 commit comments

Comments
 (0)
Please sign in to comment.