Skip to content

Commit 8a45c13

Browse files
authored
fix: Backport misc utility hardening (#2280)
1 parent 479dfdc commit 8a45c13

37 files changed

Lines changed: 1626 additions & 416 deletions

bench/data/static_pbjs.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,19 @@ $root.Test = (function() {
2121
Test.prototype.inner = null;
2222
Test.prototype.float = 0;
2323

24-
Test.encode = function encode(message, writer) {
24+
Test.encode = function encode(message, writer, q) {
2525
if (!writer)
2626
writer = $Writer.create();
27+
if (q === undefined)
28+
q = 0;
29+
if (q > $util.recursionLimit)
30+
throw Error("max depth exceeded");
2731
if (message.string != null && Object.hasOwnProperty.call(message, "string"))
2832
writer.uint32(10).string(message.string);
2933
if (message.uint32 != null && Object.hasOwnProperty.call(message, "uint32"))
3034
writer.uint32(16).uint32(message.uint32);
3135
if (message.inner != null && Object.hasOwnProperty.call(message, "inner"))
32-
$root.Test.Inner.encode(message.inner, writer.uint32(26).fork()).ldelim();
36+
$root.Test.Inner.encode(message.inner, writer.uint32(26).fork(), q + 1).ldelim();
3337
if (message.float != null && Object.hasOwnProperty.call(message, "float"))
3438
writer.uint32(37).float(message.float);
3539
return writer;
@@ -85,15 +89,19 @@ $root.Test = (function() {
8589
Inner.prototype.innerInner = null;
8690
Inner.prototype.outer = null;
8791

88-
Inner.encode = function encode(message, writer) {
92+
Inner.encode = function encode(message, writer, q) {
8993
if (!writer)
9094
writer = $Writer.create();
95+
if (q === undefined)
96+
q = 0;
97+
if (q > $util.recursionLimit)
98+
throw Error("max depth exceeded");
9199
if (message.int32 != null && Object.hasOwnProperty.call(message, "int32"))
92100
writer.uint32(8).int32(message.int32);
93101
if (message.innerInner != null && Object.hasOwnProperty.call(message, "innerInner"))
94-
$root.Test.Inner.InnerInner.encode(message.innerInner, writer.uint32(18).fork()).ldelim();
102+
$root.Test.Inner.InnerInner.encode(message.innerInner, writer.uint32(18).fork(), q + 1).ldelim();
95103
if (message.outer != null && Object.hasOwnProperty.call(message, "outer"))
96-
$root.Outer.encode(message.outer, writer.uint32(26).fork()).ldelim();
104+
$root.Outer.encode(message.outer, writer.uint32(26).fork(), q + 1).ldelim();
97105
return writer;
98106
};
99107

@@ -143,9 +151,13 @@ $root.Test = (function() {
143151
InnerInner.prototype["enum"] = 0;
144152
InnerInner.prototype.sint32 = 0;
145153

146-
InnerInner.encode = function encode(message, writer) {
154+
InnerInner.encode = function encode(message, writer, q) {
147155
if (!writer)
148156
writer = $Writer.create();
157+
if (q === undefined)
158+
q = 0;
159+
if (q > $util.recursionLimit)
160+
throw Error("max depth exceeded");
149161
if (message.long != null && Object.hasOwnProperty.call(message, "long"))
150162
writer.uint32(8).int64(message.long);
151163
if (message["enum"] != null && Object.hasOwnProperty.call(message, "enum"))
@@ -220,9 +232,13 @@ $root.Outer = (function() {
220232
Outer.prototype.bool = $util.emptyArray;
221233
Outer.prototype.double = 0;
222234

223-
Outer.encode = function encode(message, writer) {
235+
Outer.encode = function encode(message, writer, q) {
224236
if (!writer)
225237
writer = $Writer.create();
238+
if (q === undefined)
239+
q = 0;
240+
if (q > $util.recursionLimit)
241+
throw Error("max depth exceeded");
226242
if (message.bool != null && message.bool.length) {
227243
writer.uint32(10).fork();
228244
for (var i = 0; i < message.bool.length; ++i)

ext/descriptor/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,14 @@ var unnamedMessageIndex = 0;
223223
* @param {IDescriptorProto|Reader|Uint8Array} descriptor Descriptor
224224
* @param {string} [edition="proto2"] The syntax or edition to use
225225
* @param {boolean} [nested=false] Whether or not this is a nested object
226+
* @param {number} [depth] Current nesting depth, defaults to `0`
226227
* @returns {Type} Type instance
227228
*/
228-
Type.fromDescriptor = function fromDescriptor(descriptor, edition, nested) {
229+
Type.fromDescriptor = function fromDescriptor(descriptor, edition, nested, depth) {
230+
if (depth === undefined)
231+
depth = 0;
232+
if (depth > $protobuf.util.nestingLimit)
233+
throw Error("max depth exceeded");
229234
// Decode the descriptor message if specified as a buffer:
230235
if (typeof descriptor.length === "number")
231236
descriptor = exports.DescriptorProto.decode(descriptor);
@@ -252,7 +257,7 @@ Type.fromDescriptor = function fromDescriptor(descriptor, edition, nested) {
252257
type.add(Field.fromDescriptor(descriptor.extension[i], edition, true));
253258
/* Nested types */ if (descriptor.nestedType)
254259
for (i = 0; i < descriptor.nestedType.length; ++i) {
255-
type.add(Type.fromDescriptor(descriptor.nestedType[i], edition, true));
260+
type.add(Type.fromDescriptor(descriptor.nestedType[i], edition, true, depth + 1));
256261
if (descriptor.nestedType[i].options && descriptor.nestedType[i].options.mapEntry)
257262
type.setOption("map_entry", true);
258263
}

index.d.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2033,6 +2033,13 @@ export namespace util {
20332033
public length(): number;
20342034
}
20352035

2036+
/**
2037+
* Tests if the specified key can affect object prototypes.
2038+
* @param key Key to test
2039+
* @returns `true` if the key is unsafe
2040+
*/
2041+
function isUnsafeProperty(key: string): boolean;
2042+
20362043
/** Whether running within node or not. */
20372044
let isNode: boolean;
20382045

@@ -2126,11 +2133,13 @@ export namespace util {
21262133
/**
21272134
* Merges the properties of the source object into the destination object.
21282135
* @param dst Destination object
2129-
* @param src Source object
2130-
* @param [ifNotSet=false] Merges only if the key is not already set
2136+
* @param src Source objects, optionally followed by an `ifNotSet` flag
21312137
* @returns Destination object
21322138
*/
2133-
function merge(dst: { [k: string]: any }, src: { [k: string]: any }, ifNotSet?: boolean): { [k: string]: any };
2139+
function merge(dst: { [k: string]: any }, ...src: any[]): { [k: string]: any };
2140+
2141+
/** Schema declaration nesting limit. */
2142+
let nestingLimit: number;
21342143

21352144
/** Recursion limit. */
21362145
let recursionLimit: number;

lib/eventemitter/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function EventEmitter() {
1414
* @type {Object.<string,*>}
1515
* @private
1616
*/
17-
this._listeners = {};
17+
this._listeners = Object.create(null);
1818
}
1919

2020
/**
@@ -48,12 +48,14 @@ EventEmitter.prototype.on = function on(evt, fn, ctx) {
4848
*/
4949
EventEmitter.prototype.off = function off(evt, fn) {
5050
if (evt === undefined)
51-
this._listeners = {};
51+
this._listeners = Object.create(null);
5252
else {
5353
if (fn === undefined)
5454
this._listeners[evt] = [];
5555
else {
5656
var listeners = this._listeners[evt];
57+
if (!listeners)
58+
return this;
5759
for (var i = 0; i < listeners.length;)
5860
if (listeners[i].fn === fn)
5961
listeners.splice(i, 1);

lib/eventemitter/tests/index.js

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ tape.test("eventemitter", function(test) {
88
var fn;
99
var ctx = {};
1010

11+
test.equal(Object.getPrototypeOf(ee._listeners), null, "should not inherit listener lookup keys");
12+
1113
test.doesNotThrow(function() {
1214
ee.emit("a", 1);
1315
ee.off();
@@ -22,18 +24,21 @@ tape.test("eventemitter", function(test) {
2224
ee.emit("a", 1);
2325

2426
ee.off("a");
25-
test.same(ee._listeners, { a: [] }, "should remove all listeners of the respective event when calling off(evt)");
27+
test.same(Object.keys(ee._listeners), [ "a" ], "should keep the event key when calling off(evt)");
28+
test.same(ee._listeners.a, [], "should remove all listeners of the respective event when calling off(evt)");
2629

2730
ee.off();
28-
test.same(ee._listeners, {}, "should remove all listeners when just calling off()");
31+
test.equal(Object.getPrototypeOf(ee._listeners), null, "should keep the listener table isolated when just calling off()");
32+
test.same(Object.keys(ee._listeners), [], "should remove all listeners when just calling off()");
2933

3034
ee.on("a", fn = function(arg1) {
3135
test.equal(this, ctx, "should be called with this = ctx");
3236
test.equal(arg1, 1, "should be called with arg1 = 1");
3337
}, ctx).emit("a", 1);
3438

3539
ee.off("a", fn);
36-
test.same(ee._listeners, { a: [] }, "should remove the exact listener when calling off(evt, fn)");
40+
test.same(Object.keys(ee._listeners), [ "a" ], "should keep the event key when calling off(evt, fn)");
41+
test.same(ee._listeners.a, [], "should remove the exact listener when calling off(evt, fn)");
3742

3843
ee.on("a", function() {
3944
test.equal(this, ee, "should be called with this = ee");
@@ -43,5 +48,36 @@ tape.test("eventemitter", function(test) {
4348
ee.off("a", fn);
4449
}, "should not throw if no such listener is found");
4550

51+
test.test(test.name + " - special event names", function(test) {
52+
var ee = new EventEmitter();
53+
var calls = 0;
54+
55+
test.doesNotThrow(function() {
56+
ee.off("__proto__", function() {});
57+
}, "should not throw when removing an absent special event listener");
58+
59+
ee.on("__proto__", function(arg) {
60+
++calls;
61+
test.equal(arg, 1, "should pass arguments for __proto__ events");
62+
});
63+
ee.on("constructor", function(arg) {
64+
++calls;
65+
test.equal(arg, 2, "should pass arguments for constructor events");
66+
});
67+
ee.emit("__proto__", 1);
68+
ee.emit("constructor", 2);
69+
70+
test.equal(calls, 2, "should dispatch special event names");
71+
test.equal(Object.getPrototypeOf(ee._listeners), null, "should keep the listener table isolated");
72+
test.ok(Object.prototype.hasOwnProperty.call(ee._listeners, "__proto__"), "should store __proto__ as an own event key");
73+
test.ok(Object.prototype.hasOwnProperty.call(ee._listeners, "constructor"), "should store constructor as an own event key");
74+
75+
ee.off("__proto__");
76+
ee.off("constructor");
77+
test.same(ee._listeners.__proto__, [], "should clear __proto__ listeners");
78+
test.same(ee._listeners.constructor, [], "should clear constructor listeners");
79+
test.end();
80+
});
81+
4682
test.end();
4783
});

src/converter.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ function genValuePartial_toObject(gen, field, fieldIndex, prop) {
172172
if (field.resolvedType instanceof Enum) gen
173173
("d%s=o.enums===String?(types[%i].values[m%s]===undefined?m%s:types[%i].values[m%s]):m%s", prop, fieldIndex, prop, prop, fieldIndex, prop, prop);
174174
else gen
175-
("d%s=types[%i].toObject(m%s,o)", prop, fieldIndex, prop);
175+
("d%s=types[%i].toObject(m%s,o,q+1)", prop, fieldIndex, prop);
176176
} else {
177177
var isUnsigned = false;
178178
switch (field.type) {
@@ -216,9 +216,12 @@ converter.toObject = function toObject(mtype) {
216216
var fields = mtype.fieldsArray.slice().sort(util.compareFieldsById);
217217
if (!fields.length)
218218
return util.codegen()("return {}");
219-
var gen = util.codegen(["m", "o"], mtype.name + "$toObject")
219+
var gen = util.codegen(["m", "o", "q"], mtype.name + "$toObject")
220220
("if(!o)")
221221
("o={}")
222+
("if(q===undefined)q=0")
223+
("if(q>util.recursionLimit)")
224+
("throw Error(\"max depth exceeded\")")
222225
("var d={}");
223226

224227
var repeatedFields = [],

src/encoder.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ var Enum = require("./enum"),
1616
*/
1717
function genTypePartial(gen, field, fieldIndex, ref) {
1818
return field.delimited
19-
? gen("types[%i].encode(%s,w.uint32(%i)).uint32(%i)", fieldIndex, ref, (field.id << 3 | 3) >>> 0, (field.id << 3 | 4) >>> 0)
20-
: gen("types[%i].encode(%s,w.uint32(%i).fork()).ldelim()", fieldIndex, ref, (field.id << 3 | 2) >>> 0);
19+
? gen("types[%i].encode(%s,w.uint32(%i),q+1).uint32(%i)", fieldIndex, ref, (field.id << 3 | 3) >>> 0, (field.id << 3 | 4) >>> 0)
20+
: gen("types[%i].encode(%s,w.uint32(%i).fork(),q+1).ldelim()", fieldIndex, ref, (field.id << 3 | 2) >>> 0);
2121
}
2222

2323
/**
@@ -27,9 +27,12 @@ function genTypePartial(gen, field, fieldIndex, ref) {
2727
*/
2828
function encoder(mtype) {
2929
/* eslint-disable no-unexpected-multiline, block-scoped-var, no-redeclare */
30-
var gen = util.codegen(["m", "w"], mtype.name + "$encode")
30+
var gen = util.codegen(["m", "w", "q"], mtype.name + "$encode")
3131
("if(!w)")
32-
("w=Writer.create()");
32+
("w=Writer.create()")
33+
("if(q===undefined)q=0")
34+
("if(q>util.recursionLimit)")
35+
("throw Error(\"max depth exceeded\")");
3336

3437
var i, ref;
3538

@@ -50,7 +53,7 @@ function encoder(mtype) {
5053
("for(var ks=Object.keys(%s),i=0;i<ks.length;++i){", ref)
5154
("w.uint32(%i).fork().uint32(%i).%s(ks[i])", (field.id << 3 | 2) >>> 0, 8 | types.mapKey[field.keyType], field.keyType);
5255
if (wireType === undefined) gen
53-
("types[%i].encode(%s[ks[i]],w.uint32(18).fork()).ldelim().ldelim()", index, ref); // can't be groups
56+
("types[%i].encode(%s[ks[i]],w.uint32(18).fork(),q+1).ldelim().ldelim()", index, ref); // can't be groups
5457
else gen
5558
(".uint32(%i).%s(%s[ks[i]]).ldelim()", 16 | wireType, type, ref);
5659
gen

src/enum.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ Enum.prototype._resolveFeatures = function _resolveFeatures(edition) {
8686
ReflectionObject.prototype._resolveFeatures.call(this, edition);
8787

8888
Object.keys(this.values).forEach(key => {
89-
var parentFeaturesCopy = Object.assign({}, this._features);
90-
this._valuesFeatures[key] = Object.assign(parentFeaturesCopy, this.valuesOptions && this.valuesOptions[key] && this.valuesOptions[key].features);
89+
var parentFeaturesCopy = util.merge({}, this._features);
90+
this._valuesFeatures[key] = util.merge(parentFeaturesCopy, this.valuesOptions && this.valuesOptions[key] && this.valuesOptions[key].features || {});
9191
});
9292

9393
return this;

src/namespace.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ Namespace.prototype.define = function define(path, json) {
337337
throw TypeError("illegal path");
338338
if (path && path.length && path[0] === "")
339339
throw Error("path must be relative");
340+
if (path.length > util.recursionLimit)
341+
throw Error("max depth exceeded");
340342

341343
var ptr = this;
342344
while (path.length > 0) {

src/object.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition)
213213
throw new Error("Unknown edition for " + this.fullName);
214214
}
215215

216-
var protoFeatures = Object.assign(this.options ? Object.assign({}, this.options.features) : {},
216+
var protoFeatures = util.merge({}, this.options && this.options.features,
217217
this._inferLegacyProtoFeatures(edition));
218218

219219
if (this._edition) {
@@ -228,7 +228,7 @@ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition)
228228
} else {
229229
throw new Error("Unknown edition: " + edition);
230230
}
231-
this._features = Object.assign(defaults, protoFeatures || {});
231+
this._features = util.merge(defaults, protoFeatures);
232232
this._featuresResolved = true;
233233
return;
234234
}
@@ -237,13 +237,13 @@ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition)
237237
// special-case it
238238
/* istanbul ignore else */
239239
if (this.partOf instanceof OneOf) {
240-
var lexicalParentFeaturesCopy = Object.assign({}, this.partOf._features);
241-
this._features = Object.assign(lexicalParentFeaturesCopy, protoFeatures || {});
240+
var lexicalParentFeaturesCopy = util.merge({}, this.partOf._features);
241+
this._features = util.merge(lexicalParentFeaturesCopy, protoFeatures);
242242
} else if (this.declaringField) {
243243
// Skip feature resolution of sister fields.
244244
} else if (this.parent) {
245-
var parentFeaturesCopy = Object.assign({}, this.parent._features);
246-
this._features = Object.assign(parentFeaturesCopy, protoFeatures || {});
245+
var parentFeaturesCopy = util.merge({}, this.parent._features);
246+
this._features = util.merge(parentFeaturesCopy, protoFeatures);
247247
} else {
248248
throw new Error("Unable to find a parent for " + this.fullName);
249249
}

0 commit comments

Comments
 (0)