protobufjs: Denial of service through unbounded Any expansion during JSON conversion
Description
protobufjs lacks recursion depth limit during Any-to-JSON conversion, allowing stack exhaustion via crafted deeply nested protobuf messages.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
protobufjs lacks recursion depth limit during Any-to-JSON conversion, allowing stack exhaustion via crafted deeply nested protobuf messages.
## Vulnerability protobufjs versions prior to the fix lack a recursion depth limit when converting decoded protobuf messages containing google.protobuf.Any values to plain objects or JSON. The vulnerable code paths are the generated toObject() method and the custom Any JSON conversion logic. When an application converts a message to JSON via JSON.stringify(message), Message#toJSON(), or Type.toObject(message, { json: true }), the library recursively expands Any fields without a depth limit, leading to a stack overflow if the payload contains deeply nested Any values.
Exploitation
An attacker must provide a protobuf binary payload that is decoded by the target application. The application's schema must include google.protobuf.Any and the type_url must resolve to a message type in the loaded protobuf root. The attacker crafts the binary data with deeply nested Any values (e.g., each Any contains another Any). When the application subsequently converts the decoded message to JSON via any of the affected conversion paths, the recursion exhausts the JavaScript call stack, resulting in a stack overflow.
Impact
A successful exploit can cause the Node.js process (or browser if used client-side) to crash due to a stack overflow, denying service to users. The vulnerability is a denial-of-service condition; it does not lead to code execution or data disclosure.
Mitigation
No patched version has been released as of the advisory date. Workarounds include avoiding conversion of untrusted protobuf messages containing google.protobuf.Any to JSON with affected versions, rejecting or limiting messages with deeply nested Any payloads at an outer protocol boundary, avoiding JSON conversion of untrusted Any values, or isolating message conversion in a process that can be safely restarted [1][2]. Users should monitor the protobufjs repository for a future fix.
AI Insight generated on Jun 15, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1- Range: >= 8.0.0, <= 8.4.0
Patches
38a45c13d22ecfix: Backport misc utility hardening (#2280)
37 files changed · +1626 −416
bench/data/static_pbjs.js+23 −7 modified@@ -21,15 +21,19 @@ $root.Test = (function() { Test.prototype.inner = null; Test.prototype.float = 0; - Test.encode = function encode(message, writer) { + Test.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.string != null && Object.hasOwnProperty.call(message, "string")) writer.uint32(10).string(message.string); if (message.uint32 != null && Object.hasOwnProperty.call(message, "uint32")) writer.uint32(16).uint32(message.uint32); if (message.inner != null && Object.hasOwnProperty.call(message, "inner")) - $root.Test.Inner.encode(message.inner, writer.uint32(26).fork()).ldelim(); + $root.Test.Inner.encode(message.inner, writer.uint32(26).fork(), q + 1).ldelim(); if (message.float != null && Object.hasOwnProperty.call(message, "float")) writer.uint32(37).float(message.float); return writer; @@ -85,15 +89,19 @@ $root.Test = (function() { Inner.prototype.innerInner = null; Inner.prototype.outer = null; - Inner.encode = function encode(message, writer) { + Inner.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.int32 != null && Object.hasOwnProperty.call(message, "int32")) writer.uint32(8).int32(message.int32); if (message.innerInner != null && Object.hasOwnProperty.call(message, "innerInner")) - $root.Test.Inner.InnerInner.encode(message.innerInner, writer.uint32(18).fork()).ldelim(); + $root.Test.Inner.InnerInner.encode(message.innerInner, writer.uint32(18).fork(), q + 1).ldelim(); if (message.outer != null && Object.hasOwnProperty.call(message, "outer")) - $root.Outer.encode(message.outer, writer.uint32(26).fork()).ldelim(); + $root.Outer.encode(message.outer, writer.uint32(26).fork(), q + 1).ldelim(); return writer; }; @@ -143,9 +151,13 @@ $root.Test = (function() { InnerInner.prototype["enum"] = 0; InnerInner.prototype.sint32 = 0; - InnerInner.encode = function encode(message, writer) { + InnerInner.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.long != null && Object.hasOwnProperty.call(message, "long")) writer.uint32(8).int64(message.long); if (message["enum"] != null && Object.hasOwnProperty.call(message, "enum")) @@ -220,9 +232,13 @@ $root.Outer = (function() { Outer.prototype.bool = $util.emptyArray; Outer.prototype.double = 0; - Outer.encode = function encode(message, writer) { + Outer.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.bool != null && message.bool.length) { writer.uint32(10).fork(); for (var i = 0; i < message.bool.length; ++i)
ext/descriptor/index.js+7 −2 modified@@ -223,9 +223,14 @@ var unnamedMessageIndex = 0; * @param {IDescriptorProto|Reader|Uint8Array} descriptor Descriptor * @param {string} [edition="proto2"] The syntax or edition to use * @param {boolean} [nested=false] Whether or not this is a nested object + * @param {number} [depth] Current nesting depth, defaults to `0` * @returns {Type} Type instance */ -Type.fromDescriptor = function fromDescriptor(descriptor, edition, nested) { +Type.fromDescriptor = function fromDescriptor(descriptor, edition, nested, depth) { + if (depth === undefined) + depth = 0; + if (depth > $protobuf.util.nestingLimit) + throw Error("max depth exceeded"); // Decode the descriptor message if specified as a buffer: if (typeof descriptor.length === "number") descriptor = exports.DescriptorProto.decode(descriptor); @@ -252,7 +257,7 @@ Type.fromDescriptor = function fromDescriptor(descriptor, edition, nested) { type.add(Field.fromDescriptor(descriptor.extension[i], edition, true)); /* Nested types */ if (descriptor.nestedType) for (i = 0; i < descriptor.nestedType.length; ++i) { - type.add(Type.fromDescriptor(descriptor.nestedType[i], edition, true)); + type.add(Type.fromDescriptor(descriptor.nestedType[i], edition, true, depth + 1)); if (descriptor.nestedType[i].options && descriptor.nestedType[i].options.mapEntry) type.setOption("map_entry", true); }
index.d.ts+12 −3 modified@@ -2033,6 +2033,13 @@ export namespace util { public length(): number; } + /** + * Tests if the specified key can affect object prototypes. + * @param key Key to test + * @returns `true` if the key is unsafe + */ + function isUnsafeProperty(key: string): boolean; + /** Whether running within node or not. */ let isNode: boolean; @@ -2126,11 +2133,13 @@ export namespace util { /** * Merges the properties of the source object into the destination object. * @param dst Destination object - * @param src Source object - * @param [ifNotSet=false] Merges only if the key is not already set + * @param src Source objects, optionally followed by an `ifNotSet` flag * @returns Destination object */ - function merge(dst: { [k: string]: any }, src: { [k: string]: any }, ifNotSet?: boolean): { [k: string]: any }; + function merge(dst: { [k: string]: any }, ...src: any[]): { [k: string]: any }; + + /** Schema declaration nesting limit. */ + let nestingLimit: number; /** Recursion limit. */ let recursionLimit: number;
lib/eventemitter/index.js+4 −2 modified@@ -14,7 +14,7 @@ function EventEmitter() { * @type {Object.<string,*>} * @private */ - this._listeners = {}; + this._listeners = Object.create(null); } /** @@ -48,12 +48,14 @@ EventEmitter.prototype.on = function on(evt, fn, ctx) { */ EventEmitter.prototype.off = function off(evt, fn) { if (evt === undefined) - this._listeners = {}; + this._listeners = Object.create(null); else { if (fn === undefined) this._listeners[evt] = []; else { var listeners = this._listeners[evt]; + if (!listeners) + return this; for (var i = 0; i < listeners.length;) if (listeners[i].fn === fn) listeners.splice(i, 1);
lib/eventemitter/tests/index.js+39 −3 modified@@ -8,6 +8,8 @@ tape.test("eventemitter", function(test) { var fn; var ctx = {}; + test.equal(Object.getPrototypeOf(ee._listeners), null, "should not inherit listener lookup keys"); + test.doesNotThrow(function() { ee.emit("a", 1); ee.off(); @@ -22,18 +24,21 @@ tape.test("eventemitter", function(test) { ee.emit("a", 1); ee.off("a"); - test.same(ee._listeners, { a: [] }, "should remove all listeners of the respective event when calling off(evt)"); + test.same(Object.keys(ee._listeners), [ "a" ], "should keep the event key when calling off(evt)"); + test.same(ee._listeners.a, [], "should remove all listeners of the respective event when calling off(evt)"); ee.off(); - test.same(ee._listeners, {}, "should remove all listeners when just calling off()"); + test.equal(Object.getPrototypeOf(ee._listeners), null, "should keep the listener table isolated when just calling off()"); + test.same(Object.keys(ee._listeners), [], "should remove all listeners when just calling off()"); ee.on("a", fn = function(arg1) { test.equal(this, ctx, "should be called with this = ctx"); test.equal(arg1, 1, "should be called with arg1 = 1"); }, ctx).emit("a", 1); ee.off("a", fn); - test.same(ee._listeners, { a: [] }, "should remove the exact listener when calling off(evt, fn)"); + test.same(Object.keys(ee._listeners), [ "a" ], "should keep the event key when calling off(evt, fn)"); + test.same(ee._listeners.a, [], "should remove the exact listener when calling off(evt, fn)"); ee.on("a", function() { test.equal(this, ee, "should be called with this = ee"); @@ -43,5 +48,36 @@ tape.test("eventemitter", function(test) { ee.off("a", fn); }, "should not throw if no such listener is found"); + test.test(test.name + " - special event names", function(test) { + var ee = new EventEmitter(); + var calls = 0; + + test.doesNotThrow(function() { + ee.off("__proto__", function() {}); + }, "should not throw when removing an absent special event listener"); + + ee.on("__proto__", function(arg) { + ++calls; + test.equal(arg, 1, "should pass arguments for __proto__ events"); + }); + ee.on("constructor", function(arg) { + ++calls; + test.equal(arg, 2, "should pass arguments for constructor events"); + }); + ee.emit("__proto__", 1); + ee.emit("constructor", 2); + + test.equal(calls, 2, "should dispatch special event names"); + test.equal(Object.getPrototypeOf(ee._listeners), null, "should keep the listener table isolated"); + test.ok(Object.prototype.hasOwnProperty.call(ee._listeners, "__proto__"), "should store __proto__ as an own event key"); + test.ok(Object.prototype.hasOwnProperty.call(ee._listeners, "constructor"), "should store constructor as an own event key"); + + ee.off("__proto__"); + ee.off("constructor"); + test.same(ee._listeners.__proto__, [], "should clear __proto__ listeners"); + test.same(ee._listeners.constructor, [], "should clear constructor listeners"); + test.end(); + }); + test.end(); });
src/converter.js+5 −2 modified@@ -172,7 +172,7 @@ function genValuePartial_toObject(gen, field, fieldIndex, prop) { if (field.resolvedType instanceof Enum) gen ("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); else gen - ("d%s=types[%i].toObject(m%s,o)", prop, fieldIndex, prop); + ("d%s=types[%i].toObject(m%s,o,q+1)", prop, fieldIndex, prop); } else { var isUnsigned = false; switch (field.type) { @@ -216,9 +216,12 @@ converter.toObject = function toObject(mtype) { var fields = mtype.fieldsArray.slice().sort(util.compareFieldsById); if (!fields.length) return util.codegen()("return {}"); - var gen = util.codegen(["m", "o"], mtype.name + "$toObject") + var gen = util.codegen(["m", "o", "q"], mtype.name + "$toObject") ("if(!o)") ("o={}") + ("if(q===undefined)q=0") + ("if(q>util.recursionLimit)") + ("throw Error(\"max depth exceeded\")") ("var d={}"); var repeatedFields = [],
src/encoder.js+8 −5 modified@@ -16,8 +16,8 @@ var Enum = require("./enum"), */ function genTypePartial(gen, field, fieldIndex, ref) { return field.delimited - ? gen("types[%i].encode(%s,w.uint32(%i)).uint32(%i)", fieldIndex, ref, (field.id << 3 | 3) >>> 0, (field.id << 3 | 4) >>> 0) - : gen("types[%i].encode(%s,w.uint32(%i).fork()).ldelim()", fieldIndex, ref, (field.id << 3 | 2) >>> 0); + ? gen("types[%i].encode(%s,w.uint32(%i),q+1).uint32(%i)", fieldIndex, ref, (field.id << 3 | 3) >>> 0, (field.id << 3 | 4) >>> 0) + : gen("types[%i].encode(%s,w.uint32(%i).fork(),q+1).ldelim()", fieldIndex, ref, (field.id << 3 | 2) >>> 0); } /** @@ -27,9 +27,12 @@ function genTypePartial(gen, field, fieldIndex, ref) { */ function encoder(mtype) { /* eslint-disable no-unexpected-multiline, block-scoped-var, no-redeclare */ - var gen = util.codegen(["m", "w"], mtype.name + "$encode") + var gen = util.codegen(["m", "w", "q"], mtype.name + "$encode") ("if(!w)") - ("w=Writer.create()"); + ("w=Writer.create()") + ("if(q===undefined)q=0") + ("if(q>util.recursionLimit)") + ("throw Error(\"max depth exceeded\")"); var i, ref; @@ -50,7 +53,7 @@ function encoder(mtype) { ("for(var ks=Object.keys(%s),i=0;i<ks.length;++i){", ref) ("w.uint32(%i).fork().uint32(%i).%s(ks[i])", (field.id << 3 | 2) >>> 0, 8 | types.mapKey[field.keyType], field.keyType); if (wireType === undefined) gen - ("types[%i].encode(%s[ks[i]],w.uint32(18).fork()).ldelim().ldelim()", index, ref); // can't be groups + ("types[%i].encode(%s[ks[i]],w.uint32(18).fork(),q+1).ldelim().ldelim()", index, ref); // can't be groups else gen (".uint32(%i).%s(%s[ks[i]]).ldelim()", 16 | wireType, type, ref); gen
src/enum.js+2 −2 modified@@ -86,8 +86,8 @@ Enum.prototype._resolveFeatures = function _resolveFeatures(edition) { ReflectionObject.prototype._resolveFeatures.call(this, edition); Object.keys(this.values).forEach(key => { - var parentFeaturesCopy = Object.assign({}, this._features); - this._valuesFeatures[key] = Object.assign(parentFeaturesCopy, this.valuesOptions && this.valuesOptions[key] && this.valuesOptions[key].features); + var parentFeaturesCopy = util.merge({}, this._features); + this._valuesFeatures[key] = util.merge(parentFeaturesCopy, this.valuesOptions && this.valuesOptions[key] && this.valuesOptions[key].features || {}); }); return this;
src/namespace.js+2 −0 modified@@ -337,6 +337,8 @@ Namespace.prototype.define = function define(path, json) { throw TypeError("illegal path"); if (path && path.length && path[0] === "") throw Error("path must be relative"); + if (path.length > util.recursionLimit) + throw Error("max depth exceeded"); var ptr = this; while (path.length > 0) {
src/object.js+6 −6 modified@@ -213,7 +213,7 @@ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition) throw new Error("Unknown edition for " + this.fullName); } - var protoFeatures = Object.assign(this.options ? Object.assign({}, this.options.features) : {}, + var protoFeatures = util.merge({}, this.options && this.options.features, this._inferLegacyProtoFeatures(edition)); if (this._edition) { @@ -228,7 +228,7 @@ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition) } else { throw new Error("Unknown edition: " + edition); } - this._features = Object.assign(defaults, protoFeatures || {}); + this._features = util.merge(defaults, protoFeatures); this._featuresResolved = true; return; } @@ -237,13 +237,13 @@ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition) // special-case it /* istanbul ignore else */ if (this.partOf instanceof OneOf) { - var lexicalParentFeaturesCopy = Object.assign({}, this.partOf._features); - this._features = Object.assign(lexicalParentFeaturesCopy, protoFeatures || {}); + var lexicalParentFeaturesCopy = util.merge({}, this.partOf._features); + this._features = util.merge(lexicalParentFeaturesCopy, protoFeatures); } else if (this.declaringField) { // Skip feature resolution of sister fields. } else if (this.parent) { - var parentFeaturesCopy = Object.assign({}, this.parent._features); - this._features = Object.assign(parentFeaturesCopy, protoFeatures || {}); + var parentFeaturesCopy = util.merge({}, this.parent._features); + this._features = util.merge(parentFeaturesCopy, protoFeatures); } else { throw new Error("Unable to find a parent for " + this.fullName); }
src/parse.js+19 −5 modified@@ -302,7 +302,9 @@ function parse(source, root, options) { function parseCommon(parent, token, depth) { - depth = util.checkDepth(depth); + if (depth === undefined) + depth = 0; + // depth is checked by dispatched functions switch (token) { case "option": @@ -352,7 +354,10 @@ function parse(source, root, options) { } function parseType(parent, token, depth) { - depth = util.checkDepth(depth); + if (depth === undefined) + depth = 0; + if (depth > util.nestingLimit) + throw Error("max depth exceeded"); /* istanbul ignore if */ if (!nameRe.test(token = next())) @@ -478,7 +483,10 @@ function parse(source, root, options) { } function parseGroup(parent, rule, depth) { - depth = util.checkDepth(depth); + if (depth === undefined) + depth = 0; + if (depth > util.nestingLimit) + throw Error("max depth exceeded"); if (edition >= 2023) { throw illegal("group"); } @@ -697,7 +705,10 @@ function parse(source, root, options) { } function parseOptionValue(parent, name, depth) { - depth = util.checkDepth(depth); + if (depth === undefined) + depth = 0; + if (depth > util.recursionLimit) + throw Error("max depth exceeded"); // { a: "foo" b { c: "bar" } } if (skip("{", true)) { var objectResult = {}; @@ -786,7 +797,10 @@ function parse(source, root, options) { } function parseService(parent, token, depth) { - depth = util.checkDepth(depth); + if (depth === undefined) + depth = 0; + if (depth > util.recursionLimit) + throw Error("max depth exceeded"); /* istanbul ignore if */ if (!nameRe.test(token = next()))
src/root.js+14 −8 modified@@ -138,8 +138,12 @@ Root.prototype.load = function load(filename, options, callback) { } // Processes a single file - function process(filename, source) { + function process(filename, source, depth) { + if (depth === undefined) + depth = 0; try { + if (depth > util.recursionLimit) + throw Error("max depth exceeded"); if (util.isString(source) && source.charAt(0) === "{") source = JSON.parse(source); if (!util.isString(source)) @@ -152,11 +156,11 @@ Root.prototype.load = function load(filename, options, callback) { if (parsed.imports) for (; i < parsed.imports.length; ++i) if (resolved = getBundledFileName(parsed.imports[i]) || self.resolvePath(filename, parsed.imports[i])) - fetch(resolved); + fetch(resolved, false, depth + 1); if (parsed.weakImports) for (i = 0; i < parsed.weakImports.length; ++i) if (resolved = getBundledFileName(parsed.weakImports[i]) || self.resolvePath(filename, parsed.weakImports[i])) - fetch(resolved, true); + fetch(resolved, true, depth + 1); } } catch (err) { finish(err); @@ -167,7 +171,9 @@ Root.prototype.load = function load(filename, options, callback) { } // Fetches a single file - function fetch(filename, weak) { + function fetch(filename, weak, depth) { + if (depth === undefined) + depth = 0; filename = getBundledFileName(filename) || filename; // Skip if already loaded / attempted @@ -179,12 +185,12 @@ Root.prototype.load = function load(filename, options, callback) { // Shortcut bundled definitions if (filename in common) { if (sync) { - process(filename, common[filename]); + process(filename, common[filename], depth); } else { ++queued; setTimeout(function() { --queued; - process(filename, common[filename]); + process(filename, common[filename], depth); }); } return; @@ -200,7 +206,7 @@ Root.prototype.load = function load(filename, options, callback) { finish(err); return; } - process(filename, source); + process(filename, source, depth); } else { ++queued; self.fetch(filename, function(err, source) { @@ -217,7 +223,7 @@ Root.prototype.load = function load(filename, options, callback) { finish(null, self); return; } - process(filename, source); + process(filename, source, depth); }); } }
src/type.js+8 −5 modified@@ -237,7 +237,10 @@ function clearCache(type) { * @returns {Type} Created message type */ Type.fromJSON = function fromJSON(name, json, depth) { - depth = util.checkDepth(depth); + if (depth === undefined) + depth = 0; + if (depth > util.nestingLimit) + throw Error("max depth exceeded"); var type = new Type(name, json.options); type.extensions = json.extensions; type.reserved = json.reserved; @@ -515,8 +518,8 @@ Type.prototype.setup = function setup() { * @param {Writer} [writer] Writer to encode to * @returns {Writer} writer */ -Type.prototype.encode = function encode_setup(message, writer) { - return this.setup().encode(message, writer); // overrides this method +Type.prototype.encode = function encode_setup(message, writer) { // eslint-disable-line no-unused-vars + return this.setup().encode.apply(this, arguments); // overrides this method }; /** @@ -601,8 +604,8 @@ Type.prototype.fromObject = function fromObject(object, depth) { * @param {IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ -Type.prototype.toObject = function toObject(message, options) { - return this.setup().toObject(message, options); +Type.prototype.toObject = function toObject(message, options) { // eslint-disable-line no-unused-vars + return this.setup().toObject.apply(this, arguments); }; /**
src/util.js+4 −3 modified@@ -16,8 +16,7 @@ util.fetch = require("@protobufjs/fetch"); util.path = require("@protobufjs/path"); util.patterns = require("./util/patterns"); -var reservedRe = util.patterns.reservedRe, - unsafePropertyRe = util.patterns.unsafePropertyRe; +var reservedRe = util.patterns.reservedRe; /** * Node's fs module if available. @@ -192,7 +191,7 @@ util.decorateEnum = function decorateEnum(object) { util.setProperty = function setProperty(dst, path, value, ifNotSet) { function setProp(dst, path, value) { var part = path.shift(); - if (unsafePropertyRe.test(part)) + if (util.isUnsafeProperty(part)) return dst; if (path.length > 0) { dst[part] = setProp(dst[part] || {}, path, value); @@ -213,6 +212,8 @@ util.setProperty = function setProperty(dst, path, value, ifNotSet) { throw TypeError("path must be specified"); path = path.split("."); + if (path.length > util.recursionLimit) + throw Error("max depth exceeded"); return setProp(dst, path, value); };
src/util/minimal.js+32 −7 modified@@ -25,6 +25,18 @@ util.pool = require("@protobufjs/pool"); // utility to work with the low and high bits of a 64 bit value util.LongBits = require("./longbits"); +/** + * Tests if the specified key can affect object prototypes. + * @memberof util + * @param {string} key Key to test + * @returns {boolean} `true` if the key is unsafe + */ +function isUnsafeProperty(key) { + return key === "__proto__" || key === "prototype" || key === "constructor"; +} + +util.isUnsafeProperty = isUnsafeProperty; + /** * Whether running within node or not. * @memberof util @@ -238,26 +250,39 @@ util.longFromHash = function longFromHash(hash, unsigned) { * Merges the properties of the source object into the destination object. * @memberof util * @param {Object.<string,*>} dst Destination object - * @param {Object.<string,*>} src Source object - * @param {boolean} [ifNotSet=false] Merges only if the key is not already set + * @param {...(Object.<string,*>|boolean)} src Source objects, optionally followed by an `ifNotSet` flag * @returns {Object.<string,*>} Destination object */ -function merge(dst, src, ifNotSet) { // used by converters - for (var keys = Object.keys(src), i = 0; i < keys.length; ++i) - if (dst[keys[i]] === undefined || !ifNotSet) - if (keys[i] !== "__proto__") +function merge(dst) { // used by converters + var ifNotSet = typeof arguments[arguments.length - 1] === "boolean", + limit = ifNotSet ? arguments.length - 1 : arguments.length; + ifNotSet = ifNotSet && arguments[arguments.length - 1]; + for (var a = 1; a < limit; ++a) { + var src = arguments[a]; + if (!src) + continue; + for (var keys = Object.keys(src), i = 0; i < keys.length; ++i) + if (!isUnsafeProperty(keys[i]) && (dst[keys[i]] === undefined || !ifNotSet)) dst[keys[i]] = src[keys[i]]; + } return dst; } util.merge = merge; +/** + * Schema declaration nesting limit. + * @memberof util + * @type {number} + */ +util.nestingLimit = 32; // protoc: MaxMessageDeclarationNestingDepth + /** * Recursion limit. * @memberof util * @type {number} */ -util.recursionLimit = 100; +util.recursionLimit = 100; // protoc: CodedInputStream::default_recursion_limit_ /** * Makes a property safe for assignment as an own property.
src/util/patterns.js+0 −1 modified@@ -5,4 +5,3 @@ var patterns = exports; patterns.numberRe = /^(?![eE])[0-9]*(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?$/; patterns.typeRefRe = /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)(?:\.[a-zA-Z_][a-zA-Z_0-9]*)*$/; patterns.reservedRe = /^(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/; -patterns.unsafePropertyRe = /^(?:__proto__|prototype|constructor)$/;
src/wrappers.js+11 −7 modified@@ -7,7 +7,8 @@ */ var wrappers = exports; -var Message = require("./message"); +var Message = require("./message"), + util = require("./util/minimal"); /** * From object converter part of an {@link IWrapper}. @@ -54,18 +55,21 @@ wrappers[".google.protobuf.Any"] = { if (type_url.indexOf("/") === -1) { type_url = "/" + type_url; } - var nextDepth = depth === undefined ? 1 : depth + 1; return this.create({ type_url: type_url, - value: type.encode(type.fromObject(object, nextDepth)).finish() + value: type.encode(type.fromObject(object, depth === undefined ? 1 : depth + 1)).finish() }); } } return this.fromObject(object, depth); }, - toObject: function(message, options) { + toObject: function(message, options, depth) { + if (depth === undefined) + depth = 0; + if (depth > util.recursionLimit) + throw Error("max depth exceeded"); // Default prefix var googleApi = "type.googleapis.com/"; @@ -81,12 +85,12 @@ wrappers[".google.protobuf.Any"] = { var type = this.lookup(name); /* istanbul ignore else */ if (type) - message = type.decode(message.value); + message = type.decode(message.value, undefined, undefined, depth + 1); } // wrap value if unmapped if (!(message instanceof this.ctor) && message instanceof Message) { - var object = message.$type.toObject(message, options); + var object = message.$type.toObject(message, options, depth + 1); var messageName = message.$type.fullName[0] === "." ? message.$type.fullName.slice(1) : message.$type.fullName; // Default to type.googleapis.com prefix if no prefix is used @@ -98,6 +102,6 @@ wrappers[".google.protobuf.Any"] = { return object; } - return this.toObject(message, options); + return this.toObject(message, options, depth); } };
src/writer.js+11 −9 modified@@ -225,7 +225,7 @@ Writer.prototype.uint32 = function write_uint32(value) { * @returns {Writer} `this` */ Writer.prototype.int32 = function write_int32(value) { - return value < 0 + return (value |= 0) < 0 ? this._push(writeVarint64, 10, LongBits.fromNumber(value)) // 10 bytes per spec : this.uint32(value); }; @@ -240,16 +240,18 @@ Writer.prototype.sint32 = function write_sint32(value) { }; function writeVarint64(val, buf, pos) { - while (val.hi) { - buf[pos++] = val.lo & 127 | 128; - val.lo = (val.lo >>> 7 | val.hi << 25) >>> 0; - val.hi >>>= 7; + var lo = val.lo, + hi = val.hi; + while (hi) { + buf[pos++] = lo & 127 | 128; + lo = (lo >>> 7 | hi << 25) >>> 0; + hi >>>= 7; } - while (val.lo > 127) { - buf[pos++] = val.lo & 127 | 128; - val.lo = val.lo >>> 7; + while (lo > 127) { + buf[pos++] = lo & 127 | 128; + lo = lo >>> 7; } - buf[pos++] = val.lo; + buf[pos++] = lo; } /**
tests/api_converters.js+32 −0 modified@@ -290,6 +290,38 @@ tape.test("converters", function(test) { test.end(); }); + test.test(test.name + " - Type.toObject recursion limit", function(test) { + var recursionLimit = protobuf.util.recursionLimit; + protobuf.util.recursionLimit = 3; + try { + var nestedRoot = protobuf.Root.fromJSON({ + nested: { + Recursive: { + fields: { + next: { + type: "Recursive", + id: 1 + } + } + } + } + }); + var Recursive = nestedRoot.lookupType("Recursive"); + var msg = Recursive.create(); + var cursor = msg; + for (var i = 0; i < 5; ++i) + cursor = cursor.next = Recursive.create(); + + test.throws(function() { + Recursive.toObject(msg); + }, /max depth exceeded/, "should reject excessive object conversion depth"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); + }); + test.test(test.name + " - Message#toJSON", function(test) { var msg = Message.create(); msg.$type = {
tests/api_descriptor.js+56 −0 added@@ -0,0 +1,56 @@ +var tape = require("tape"); + +var protobuf = require(".."), + descriptor = require("../ext/descriptor"); + +tape.test("descriptor nesting", function(test) { + function nestedMessageDescriptor(depth) { + var root = descriptor.DescriptorProto.create({ name: "Message0" }), + nested = root; + for (var i = 1; i <= depth; ++i) { + var child = descriptor.DescriptorProto.create({ name: "Message" + i }); + nested.nestedType.push(child); + nested = child; + } + return root; + } + + function packageDescriptor(depth) { + var packageName = "a"; + for (var i = 1; i < depth; ++i) + packageName += ".a"; + return descriptor.FileDescriptorSet.create({ + file: [ descriptor.FileDescriptorProto.create({ + name: "test.proto", + "package": packageName + }) ] + }); + } + + var recursionLimit = protobuf.util.recursionLimit, + nestingLimit = protobuf.util.nestingLimit; + try { + protobuf.util.recursionLimit = 100; + protobuf.util.nestingLimit = 3; + test.doesNotThrow(function() { + protobuf.Type.fromDescriptor(nestedMessageDescriptor(3), "proto3"); + }, "should load descriptor message nesting up to the nesting limit"); + test.throws(function() { + protobuf.Type.fromDescriptor(nestedMessageDescriptor(4), "proto3"); + }, /max depth exceeded/, "should reject excessively nested descriptor messages"); + + protobuf.util.recursionLimit = 3; + protobuf.util.nestingLimit = 100; + test.doesNotThrow(function() { + protobuf.Root.fromDescriptor(packageDescriptor(3)); + }, "should load descriptor package paths up to the recursion limit"); + test.throws(function() { + protobuf.Root.fromDescriptor(packageDescriptor(4)); + }, /max depth exceeded/, "should reject excessively nested descriptor package paths"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + protobuf.util.nestingLimit = nestingLimit; + } + + test.end(); +});
tests/api_enum.js+50 −0 modified@@ -136,6 +136,56 @@ tape.test("feature resolution legacy proto3", function(test) { test.end(); }); +tape.test("feature resolution skips unsafe feature keys", function(test) { + var enumFeatures = { enum_type: "CLOSED" }; + Object.defineProperty(enumFeatures, "__proto__", { + value: { polluted: true }, + enumerable: true + }); + enumFeatures.prototype = { polluted: true }; + enumFeatures.constructor = { polluted: true }; + + var valueFeatures = { field_presence: "EXPLICIT" }; + Object.defineProperty(valueFeatures, "__proto__", { + value: { polluted: true }, + enumerable: true + }); + valueFeatures.prototype = { polluted: true }; + valueFeatures.constructor = { polluted: true }; + + var root = protobuf.Root.fromJSON({ + nested: { + Enum: { + edition: "2023", + options: { features: enumFeatures }, + values: { + a: 0 + }, + valuesOptions: { + a: { + features: valueFeatures + } + } + } + } + }).resolveAll(); + var Enum = root.lookupEnum("Enum"); + + test.equal(Enum._features.enum_type, "CLOSED", "should keep regular enum features"); + test.equal(Object.getPrototypeOf(Enum._features).polluted, undefined, "should not alter enum feature prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._features, "__proto__"), "should skip unsafe enum feature key __proto__"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._features, "prototype"), "should skip unsafe enum feature key prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._features, "constructor"), "should skip unsafe enum feature key constructor"); + + test.equal(Enum._valuesFeatures.a.field_presence, "EXPLICIT", "should keep regular enum value features"); + test.equal(Object.getPrototypeOf(Enum._valuesFeatures.a).polluted, undefined, "should not alter enum value feature prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._valuesFeatures.a, "__proto__"), "should skip unsafe enum value feature key __proto__"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._valuesFeatures.a, "prototype"), "should skip unsafe enum value feature key prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._valuesFeatures.a, "constructor"), "should skip unsafe enum value feature key constructor"); + + test.end(); +}); + tape.test("feature resolution proto2", function(test) { var json = { edition: "proto2",
tests/api_namespace.js+38 −3 modified@@ -86,6 +86,19 @@ tape.test("reflected namespaces", function(test) { var sub = ns.define("sub", {}); test.equal(ns.lookup("sub"), sub, "should define sub namespaces"); + var recursionLimit = protobuf.util.recursionLimit; + protobuf.util.recursionLimit = 3; + try { + test.doesNotThrow(function() { + ns.define("a.b.c"); + }, "should define namespace paths up to the recursion limit"); + test.throws(function() { + ns.define("a.b.c.d"); + }, /max depth exceeded/, "should reject excessively nested namespace paths"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + } + test.throws(function() { ns.add(new protobuf.ReflectionObject("invalid")); }, TypeError, "should throw when adding invalid nested objects"); @@ -188,29 +201,51 @@ tape.test("JSON descriptor nesting", function(test) { return root; } - var recursionLimit = protobuf.util.recursionLimit; - protobuf.util.recursionLimit = 3; + function nestedOptionPathDescriptor(depth) { + var path = "features"; + for (var i = 0; i < depth; ++i) + path += ".a"; + var descriptor = { options: {}, nested: {} }; + descriptor.options[path] = true; + return descriptor; + } + + var recursionLimit = protobuf.util.recursionLimit, + nestingLimit = protobuf.util.nestingLimit; try { + protobuf.util.recursionLimit = 3; + protobuf.util.nestingLimit = 100; test.doesNotThrow(function() { protobuf.Root.fromJSON(nestedNamespaceDescriptor(3)); }, "should load namespace descriptors up to the recursion limit"); test.throws(function() { protobuf.Root.fromJSON(nestedNamespaceDescriptor(4)); }, /max depth exceeded/, "should reject excessively nested namespace descriptors"); + protobuf.util.recursionLimit = 100; + protobuf.util.nestingLimit = 3; test.doesNotThrow(function() { protobuf.Root.fromJSON(nestedTypeDescriptor(3)); - }, "should load type descriptors up to the recursion limit"); + }, "should load type descriptors up to the nesting limit"); test.throws(function() { protobuf.Root.fromJSON(nestedTypeDescriptor(4)); }, /max depth exceeded/, "should reject excessively nested type descriptors"); + protobuf.util.recursionLimit = 3; + protobuf.util.nestingLimit = 100; test.doesNotThrow(function() { protobuf.Root.fromJSON(nestedServiceDescriptor(3)); }, "should load service descriptors up to the recursion limit"); test.throws(function() { protobuf.Root.fromJSON(nestedServiceDescriptor(4)); }, /max depth exceeded/, "should reject excessively nested service descriptors"); + test.doesNotThrow(function() { + protobuf.Root.fromJSON(nestedOptionPathDescriptor(2)); + }, "should load option paths up to the recursion limit"); + test.throws(function() { + protobuf.Root.fromJSON(nestedOptionPathDescriptor(3)); + }, /max depth exceeded/, "should reject excessively nested option paths"); } finally { protobuf.util.recursionLimit = recursionLimit; + protobuf.util.nestingLimit = nestingLimit; } test.end();
tests/api_type.js+83 −0 modified@@ -186,6 +186,89 @@ tape.test("decode nesting", function(test) { test.end(); }); +tape.test("encode nesting", function(test) { + function nestedObject(depth, field) { + var object = { value: 42 }; + for (var i = 0; i < depth; ++i) { + if (field === "child") + object = { child: object }; + else if (field === "children") + object = { children: [ object ] }; + else + object = { childMap: { child: object } }; + } + return object; + } + + var root = protobuf.Root.fromJSON({ + nested: { + Node: { + fields: { + child: { type: "Node", id: 1 }, + children: { rule: "repeated", type: "Node", id: 2 }, + childMap: { keyType: "string", type: "Node", id: 3 }, + value: { type: "int32", id: 4 } + } + } + } + }); + var Node = root.lookupType("Node"); + var recursionLimit = protobuf.util.recursionLimit; + + protobuf.util.recursionLimit = 3; + try { + test.ok(Node.encode(nestedObject(2, "child")).finish().length, "should encode singular messages below the limit"); + test.throws(function() { + Node.encode(nestedObject(4, "child")).finish(); + }, /max depth exceeded/, "should reject excessive singular message nesting"); + + test.ok(Node.encode(nestedObject(2, "children")).finish().length, "should encode repeated messages below the limit"); + test.throws(function() { + Node.encode(nestedObject(4, "children")).finish(); + }, /max depth exceeded/, "should reject excessive repeated message nesting"); + + test.ok(Node.encode(nestedObject(2, "childMap")).finish().length, "should encode map message values below the limit"); + test.throws(function() { + Node.encode(nestedObject(4, "childMap")).finish(); + }, /max depth exceeded/, "should reject excessive map message value nesting"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); +}); + +tape.test("encode setup preserves nesting", function(test) { + var root = protobuf.Root.fromJSON({ + nested: { + Parent: { + fields: { + child: { type: "Child", id: 1 } + } + }, + Child: { + fields: { + next: { type: "Child", id: 1 }, + value: { type: "int32", id: 2 } + } + } + } + }); + var Parent = root.lookupType("Parent"); + var recursionLimit = protobuf.util.recursionLimit; + + protobuf.util.recursionLimit = 1; + try { + test.throws(function() { + Parent.encode({ child: { next: { value: 42 } } }).finish(); + }, /max depth exceeded/, "should preserve depth through nested type setup"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); +}); + tape.test("object conversion nesting", function(test) { function nestedObject(depth) { var object = { value: 42 };
tests/api_util.js+32 −3 modified@@ -16,10 +16,26 @@ tape.test("util", function(test) { test.same(o, { a: 2 }, "should merge existing keys"); util.merge(o, { a: 3 }, true); test.same(o, { a: 2 }, "should not merge existing keys"); - util.merge(o, JSON.parse("{\"__proto__\":{\"marker\":true}}")); + util.merge(o, { b: 1 }, { c: 2 }); + test.same(o, { a: 2, b: 1, c: 2 }, "should merge multiple sources"); + util.merge(o, { c: 3 }, { d: 4 }, true); + test.same(o, { a: 2, b: 1, c: 2, d: 4 }, "should merge multiple sources without overwriting existing keys"); + util.merge(o, JSON.parse("{\"__proto__\":{\"marker\":true},\"prototype\":{\"marker\":true},\"constructor\":{\"marker\":true}}")); test.equal(Object.getPrototypeOf(o), Object.prototype, "should keep the target object shape"); - test.notOk(Object.prototype.hasOwnProperty.call(o, "__proto__"), "should skip reserved keys"); + test.notOk(Object.prototype.hasOwnProperty.call(o, "__proto__"), "should skip reserved key __proto__"); + test.notOk(Object.prototype.hasOwnProperty.call(o, "prototype"), "should skip reserved key prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(o, "constructor"), "should skip reserved key constructor"); test.equal(o.marker, undefined, "should not expose skipped values"); + var guarded = {}; + var accessed = false; + Object.defineProperty(guarded, "constructor", { + get: function() { + accessed = true; + } + }); + util.merge(guarded, { constructor: 1, safe: 2 }); + test.notOk(accessed, "should skip reserved keys before checking target values"); + test.equal(guarded.safe, 2, "should still merge regular keys"); test.end(); }); @@ -99,7 +115,20 @@ tape.test("util", function(test) { util.setProperty(o, 'prop.subprop', { subsub2: 7}); test.same(o, {prop1: [5, 6], prop: {subprop: [{subsub: [5,6]}, {subsub2: 7}]}}, "should convert nested properties to array"); - + + var recursionLimit = util.recursionLimit; + util.recursionLimit = 3; + try { + test.doesNotThrow(function() { + util.setProperty({}, 'a.b.c', 1); + }, "should set property paths up to the recursion limit"); + test.throws(function() { + util.setProperty({}, 'a.b.c.d', 1); + }, /max depth exceeded/, "should reject excessively nested property paths"); + } finally { + util.recursionLimit = recursionLimit; + } + util.setProperty({}, "__proto__.test", "value"); test.is({}.test, undefined);
tests/api_writer-reader.js+39 −0 modified@@ -39,6 +39,8 @@ tape.test("writer & reader", function(test) { test.ok(expect("uint32", -1 >>> 0, [ 255, 255, 255, 255, 15 ]), "should write -1 as an unsigned varint of length 5"); test.ok(expect("int32", -1, [ 255, 255, 255, 255, 255, 255, 255, 255, 255, 1 ]), "should write -1 as a signed varint of length 10"); test.ok(expect("sint32", -1, [ 1 ]), "should write -1 as a signed zig-zag encoded varint of length 1"); + test.ok(expectCoerced("int32", -0.1, [ 0 ], 0), "should coerce fractional signed varints before sizing"); + test.ok(expectCoerced("int32", 2147483648, [ 128, 128, 128, 128, 248, 255, 255, 255, 255, 1 ], -2147483648), "should coerce out-of-range signed varints before sizing"); // fixed32, sfixed32 @@ -145,6 +147,14 @@ tape.test("writer & reader", function(test) { MyMessage.decode(payload); }, /maximum nesting depth exceeded/, "limits recursion in reader"); + test.test(test.name + " - repeated finish", function(test) { + [ "int32", "uint64", "int64", "sint64" ].forEach(function(type) { + var writer = Writer.create()[type](-1); + test.same(Array.prototype.slice.call(writer.finish()), Array.prototype.slice.call(writer.finish()), "should preserve " + type + " operation state"); + }); + test.end(); + }); + test.end(); }); @@ -195,3 +205,32 @@ function expect(type, value, expected, WriterToTest) { } return true; } + +function expectCoerced(type, value, expected, expectedValue, WriterToTest) { + if (!WriterToTest) + WriterToTest = Writer.create().constructor; + var writer = new WriterToTest(); + var actual = writer[type](value).finish(); + if (actual.length !== expected.length) { + console.error("actual", Array.prototype.slice.call(actual), "!= expected", expected); + return false; + } + for (var i = 0; i < expected.length; ++i) + if (actual[i] !== expected[i]) { + console.error("actual", Array.prototype.slice.call(actual), "!= expected", expected); + return false; + } + var reader = Reader.create(actual); + var actualValue = reader[type](); + if (actualValue !== expectedValue) { + console.error("actual value", actualValue, "!= expected", expectedValue); + return false; + } + if (WriterToTest !== protobuf.Writer) { + if (!expectCoerced(type, value, expected, expectedValue, Writer)) { + console.error("in browser writer"); + return false; + } + } + return true; +}
tests/comp_google_protobuf_any.js+36 −1 modified@@ -18,12 +18,21 @@ var root = new protobuf.Root().addJSON(protobuf.common["google/protobuf/any.prot type: "string" } } + }, + Loop: { + fields: { + next: { + id: 1, + type: "google.protobuf.Any" + } + } } }).resolveAll(); var Any = root.lookupType("protobuf.Any"), Foo = root.lookupType(".Foo"), - Bar = root.lookupType(".Bar"); + Bar = root.lookupType(".Bar"), + Loop = root.lookupType(".Loop"); tape.test("google.protobuf.Any", function(test) { @@ -62,3 +71,29 @@ tape.test("google.protobuf.Any", function(test) { test.end(); }); + +tape.test("google.protobuf.Any - toObject recursion limit", function(test) { + + var recursionLimit = protobuf.util.recursionLimit; + protobuf.util.recursionLimit = 3; + try { + var value = Loop.encode(Loop.create()).finish(); + for (var i = 0; i < 5; ++i) + value = Loop.encode(Loop.create({ + next: Any.create({ + type_url: "type.googleapis.com/Loop", + value: value + }) + })).finish(); + + var message = Loop.decode(value); + + test.throws(function() { + JSON.stringify(message); + }, /max depth exceeded/, "should reject excessive Any JSON expansion depth"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); +});
tests/comp_parse-uncommon.js+40 −4 modified@@ -72,29 +72,65 @@ tape.test("parser nesting", function(test) { return source + "}; }"; } - var recursionLimit = protobuf.util.recursionLimit; - protobuf.util.recursionLimit = 3; + function dottedOptionPath(depth) { + var source = "syntax = \"proto2\"; message M { optional int32 a = 1 [(.foo)"; + for (var i = 0; i < depth; ++i) + source += ".a"; + return source + " = 1]; }"; + } + + function packagePath(depth) { + var source = "syntax = \"proto3\"; package a"; + for (var i = 1; i < depth; ++i) + source += ".a"; + return source + ";"; + } + + var recursionLimit = protobuf.util.recursionLimit, + nestingLimit = protobuf.util.nestingLimit; try { + protobuf.util.recursionLimit = 100; + protobuf.util.nestingLimit = 3; test.doesNotThrow(function() { protobuf.parse(nestedMessages(3)); - }, "should parse message nesting up to the recursion limit"); + }, "should parse message nesting up to the nesting limit"); test.throws(function() { protobuf.parse(nestedMessages(4)); }, /max depth exceeded/, "should reject excessively nested messages"); + protobuf.util.recursionLimit = 1; + test.doesNotThrow(function() { + protobuf.parse(nestedMessages(3)); + }, "should not apply recursion limit to message declarations"); + protobuf.util.recursionLimit = 100; test.doesNotThrow(function() { protobuf.parse(nestedGroups(2)); - }, "should parse group nesting up to the recursion limit"); + }, "should parse group nesting up to the nesting limit"); test.throws(function() { protobuf.parse(nestedGroups(3)); }, /max depth exceeded/, "should reject excessively nested groups"); + protobuf.util.recursionLimit = 3; + protobuf.util.nestingLimit = 100; test.doesNotThrow(function() { protobuf.parse(nestedOptionValue(3)); }, "should parse aggregate option nesting up to the recursion limit"); test.throws(function() { protobuf.parse(nestedOptionValue(4)); }, /max depth exceeded/, "should reject excessively nested aggregate options"); + test.doesNotThrow(function() { + protobuf.parse(dottedOptionPath(3)); + }, "should parse dotted option paths up to the recursion limit"); + test.throws(function() { + protobuf.parse(dottedOptionPath(4)); + }, /max depth exceeded/, "should reject excessively nested dotted option paths"); + test.doesNotThrow(function() { + protobuf.parse(packagePath(3)); + }, "should parse package paths up to the recursion limit"); + test.throws(function() { + protobuf.parse(packagePath(4)); + }, /max depth exceeded/, "should reject excessively nested package paths"); } finally { protobuf.util.recursionLimit = recursionLimit; + protobuf.util.nestingLimit = nestingLimit; } test.end();
tests/data/comments.js+15 −3 modified@@ -83,9 +83,13 @@ $root.Test1 = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Test1.encode = function encode(message, writer) { + Test1.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.field1 != null && Object.hasOwnProperty.call(message, "field1")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.field1); if (message.field2 != null && Object.hasOwnProperty.call(message, "field2")) @@ -229,9 +233,13 @@ $root.Test1 = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Test1.toObject = function toObject(message, options) { + Test1.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) { object.field1 = ""; @@ -320,9 +328,13 @@ $root.Test2 = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Test2.encode = function encode(message, writer) { + Test2.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); return writer; };
tests/data/convert.js+10 −2 modified@@ -139,9 +139,13 @@ $root.Message = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Message.encode = function encode(message, writer) { + Message.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.stringVal != null && Object.hasOwnProperty.call(message, "stringVal")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.stringVal); if (message.stringRepeated != null && message.stringRepeated.length) @@ -515,9 +519,13 @@ $root.Message = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Message.toObject = function toObject(message, options) { + Message.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) { object.stringRepeated = [];
tests/data/mapbox/vector_tile.js+46 −14 modified@@ -72,12 +72,16 @@ $root.vector_tile = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Tile.encode = function encode(message, writer) { + Tile.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.layers != null && message.layers.length) for (var i = 0; i < message.layers.length; ++i) - $root.vector_tile.Tile.Layer.encode(message.layers[i], writer.uint32(/* id 3, wireType 2 =*/26).fork()).ldelim(); + $root.vector_tile.Tile.Layer.encode(message.layers[i], writer.uint32(/* id 3, wireType 2 =*/26).fork(), q + 1).ldelim(); return writer; }; @@ -213,16 +217,20 @@ $root.vector_tile = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Tile.toObject = function toObject(message, options) { + Tile.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) object.layers = []; if (message.layers && message.layers.length) { object.layers = []; for (var j = 0; j < message.layers.length; ++j) - object.layers[j] = $root.vector_tile.Tile.Layer.toObject(message.layers[j], options); + object.layers[j] = $root.vector_tile.Tile.Layer.toObject(message.layers[j], options, q + 1); } return object; }; @@ -378,9 +386,13 @@ $root.vector_tile = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Value.encode = function encode(message, writer) { + Value.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.stringValue != null && Object.hasOwnProperty.call(message, "stringValue")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.stringValue); if (message.floatValue != null && Object.hasOwnProperty.call(message, "floatValue")) @@ -589,9 +601,13 @@ $root.vector_tile = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Value.toObject = function toObject(message, options) { + Value.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) { object.stringValue = ""; @@ -757,9 +773,13 @@ $root.vector_tile = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Feature.encode = function encode(message, writer) { + Feature.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.id != null && Object.hasOwnProperty.call(message, "id")) writer.uint32(/* id 1, wireType 0 =*/8).uint64(message.id); if (message.tags != null && message.tags.length) { @@ -990,9 +1010,13 @@ $root.vector_tile = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Feature.toObject = function toObject(message, options) { + Feature.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) { object.tags = []; @@ -1158,19 +1182,23 @@ $root.vector_tile = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Layer.encode = function encode(message, writer) { + Layer.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); writer.uint32(/* id 1, wireType 2 =*/10).string(message.name); if (message.features != null && message.features.length) for (var i = 0; i < message.features.length; ++i) - $root.vector_tile.Tile.Feature.encode(message.features[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); + $root.vector_tile.Tile.Feature.encode(message.features[i], writer.uint32(/* id 2, wireType 2 =*/18).fork(), q + 1).ldelim(); if (message.keys != null && message.keys.length) for (var i = 0; i < message.keys.length; ++i) writer.uint32(/* id 3, wireType 2 =*/26).string(message.keys[i]); if (message.values != null && message.values.length) for (var i = 0; i < message.values.length; ++i) - $root.vector_tile.Tile.Value.encode(message.values[i], writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim(); + $root.vector_tile.Tile.Value.encode(message.values[i], writer.uint32(/* id 4, wireType 2 =*/34).fork(), q + 1).ldelim(); if (message.extent != null && Object.hasOwnProperty.call(message, "extent")) writer.uint32(/* id 5, wireType 0 =*/40).uint32(message.extent); writer.uint32(/* id 15, wireType 0 =*/120).uint32(message.version); @@ -1383,9 +1411,13 @@ $root.vector_tile = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Layer.toObject = function toObject(message, options) { + Layer.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) { object.features = []; @@ -1402,7 +1434,7 @@ $root.vector_tile = (function() { if (message.features && message.features.length) { object.features = []; for (var j = 0; j < message.features.length; ++j) - object.features[j] = $root.vector_tile.Tile.Feature.toObject(message.features[j], options); + object.features[j] = $root.vector_tile.Tile.Feature.toObject(message.features[j], options, q + 1); } if (message.keys && message.keys.length) { object.keys = []; @@ -1412,7 +1444,7 @@ $root.vector_tile = (function() { if (message.values && message.values.length) { object.values = []; for (var j = 0; j < message.values.length; ++j) - object.values[j] = $root.vector_tile.Tile.Value.toObject(message.values[j], options); + object.values[j] = $root.vector_tile.Tile.Value.toObject(message.values[j], options, q + 1); } if (message.extent != null && message.hasOwnProperty("extent")) object.extent = message.extent;
tests/data/package.js+22 −6 modified@@ -212,9 +212,13 @@ $root.Package = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Package.encode = function encode(message, writer) { + Package.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.name != null && Object.hasOwnProperty.call(message, "name")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.name); if (message.version != null && Object.hasOwnProperty.call(message, "version")) @@ -226,7 +230,7 @@ $root.Package = (function() { if (message.license != null && Object.hasOwnProperty.call(message, "license")) writer.uint32(/* id 5, wireType 2 =*/42).string(message.license); if (message.repository != null && Object.hasOwnProperty.call(message, "repository")) - $root.Package.Repository.encode(message.repository, writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim(); + $root.Package.Repository.encode(message.repository, writer.uint32(/* id 6, wireType 2 =*/50).fork(), q + 1).ldelim(); if (message.bugs != null && Object.hasOwnProperty.call(message, "bugs")) writer.uint32(/* id 7, wireType 2 =*/58).string(message.bugs); if (message.homepage != null && Object.hasOwnProperty.call(message, "homepage")) @@ -681,9 +685,13 @@ $root.Package = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Package.toObject = function toObject(message, options) { + Package.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) { object.keywords = []; @@ -719,7 +727,7 @@ $root.Package = (function() { if (message.license != null && message.hasOwnProperty("license")) object.license = message.license; if (message.repository != null && message.hasOwnProperty("repository")) - object.repository = $root.Package.Repository.toObject(message.repository, options); + object.repository = $root.Package.Repository.toObject(message.repository, options, q + 1); if (message.bugs != null && message.hasOwnProperty("bugs")) object.bugs = message.bugs; if (message.homepage != null && message.hasOwnProperty("homepage")) @@ -864,9 +872,13 @@ $root.Package = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Repository.encode = function encode(message, writer) { + Repository.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.type != null && Object.hasOwnProperty.call(message, "type")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.type); if (message.url != null && Object.hasOwnProperty.call(message, "url")) @@ -999,9 +1011,13 @@ $root.Package = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Repository.toObject = function toObject(message, options) { + Repository.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) { object.type = "";
tests/data/rpc-es6.js+20 −4 modified@@ -128,9 +128,13 @@ export const MyRequest = $root.MyRequest = (() => { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyRequest.encode = function encode(message, writer) { + MyRequest.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.path != null && Object.hasOwnProperty.call(message, "path")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.path); return writer; @@ -252,9 +256,13 @@ export const MyRequest = $root.MyRequest = (() => { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyRequest.toObject = function toObject(message, options) { + MyRequest.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); let object = {}; if (options.defaults) object.path = ""; @@ -345,9 +353,13 @@ export const MyResponse = $root.MyResponse = (() => { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyResponse.encode = function encode(message, writer) { + MyResponse.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.status != null && Object.hasOwnProperty.call(message, "status")) writer.uint32(/* id 2, wireType 0 =*/16).int32(message.status); return writer; @@ -469,9 +481,13 @@ export const MyResponse = $root.MyResponse = (() => { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyResponse.toObject = function toObject(message, options) { + MyResponse.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); let object = {}; if (options.defaults) object.status = 0;
tests/data/rpc.js+20 −4 modified@@ -130,9 +130,13 @@ $root.MyRequest = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyRequest.encode = function encode(message, writer) { + MyRequest.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.path != null && Object.hasOwnProperty.call(message, "path")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.path); return writer; @@ -254,9 +258,13 @@ $root.MyRequest = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyRequest.toObject = function toObject(message, options) { + MyRequest.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.path = ""; @@ -347,9 +355,13 @@ $root.MyResponse = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyResponse.encode = function encode(message, writer) { + MyResponse.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.status != null && Object.hasOwnProperty.call(message, "status")) writer.uint32(/* id 2, wireType 0 =*/16).int32(message.status); return writer; @@ -471,9 +483,13 @@ $root.MyResponse = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyResponse.toObject = function toObject(message, options) { + MyResponse.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.status = 0;
tests/data/rpc-reserved.js+20 −4 modified@@ -130,9 +130,13 @@ $root.MyRequest = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyRequest.encode = function encode(message, writer) { + MyRequest.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.path != null && Object.hasOwnProperty.call(message, "path")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.path); return writer; @@ -254,9 +258,13 @@ $root.MyRequest = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyRequest.toObject = function toObject(message, options) { + MyRequest.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.path = ""; @@ -347,9 +355,13 @@ $root.MyResponse = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyResponse.encode = function encode(message, writer) { + MyResponse.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.status != null && Object.hasOwnProperty.call(message, "status")) writer.uint32(/* id 2, wireType 0 =*/16).int32(message.status); return writer; @@ -471,9 +483,13 @@ $root.MyResponse = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyResponse.toObject = function toObject(message, options) { + MyResponse.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.status = 0;
tests/data/test.js+809 −285 modifiedtests/data/type_url.js+22 −6 modified@@ -62,11 +62,15 @@ $root.TypeUrlTest = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - TypeUrlTest.encode = function encode(message, writer) { + TypeUrlTest.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.nested != null && Object.hasOwnProperty.call(message, "nested")) - $root.TypeUrlTest.Nested.encode(message.nested, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); + $root.TypeUrlTest.Nested.encode(message.nested, writer.uint32(/* id 1, wireType 2 =*/10).fork(), q + 1).ldelim(); return writer; }; @@ -191,14 +195,18 @@ $root.TypeUrlTest = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - TypeUrlTest.toObject = function toObject(message, options) { + TypeUrlTest.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.nested = null; if (message.nested != null && message.hasOwnProperty("nested")) - object.nested = $root.TypeUrlTest.Nested.toObject(message.nested, options); + object.nested = $root.TypeUrlTest.Nested.toObject(message.nested, options, q + 1); return object; }; @@ -281,9 +289,13 @@ $root.TypeUrlTest = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Nested.encode = function encode(message, writer) { + Nested.encode = function encode(message, writer, q) { if (!writer) writer = $Writer.create(); + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.a != null && Object.hasOwnProperty.call(message, "a")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.a); return writer; @@ -405,9 +417,13 @@ $root.TypeUrlTest = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Nested.toObject = function toObject(message, options) { + Nested.toObject = function toObject(message, options, q) { if (!options) options = {}; + if (q === undefined) + q = 0; + if (q > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.a = "";
tests/node/api_load-sync.js+29 −0 modified@@ -36,6 +36,35 @@ tape.test("load sync", function(test) { test.end(); }); +tape.test("load sync import recursion limit", function(test) { + var fs = protobuf.util.fs, + readFileSync = fs.readFileSync, + recursionLimit = protobuf.util.recursionLimit; + + protobuf.util.recursionLimit = 3; + fs.readFileSync = function(filename) { + var match = /chain-(\d+)\.proto$/.exec(filename); + if (match) + return "syntax = \"proto3\";\nimport \"chain-" + (Number(match[1]) + 1) + ".proto\";\n"; + return readFileSync.apply(this, arguments); + }; + + try { + var root = new protobuf.Root(); + root.resolvePath = function(origin, target) { + return target; + }; + test.throws(function() { + root.loadSync("chain-0.proto"); + }, /max depth exceeded/, "should reject excessive import nesting"); + } finally { + fs.readFileSync = readFileSync; + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); +}); + tape.test("should load bundled definitions even if resolvePath method was overrided", function(test) { var protoFilePath = "tests/data/common.proto"; var root = new protobuf.Root();
9a78a4a195c0fix: Omit proto3 synthetic oneofs in toObject (#2273)
2 files changed · +21 −1
src/converter.js+1 −1 modified@@ -329,7 +329,7 @@ converter.toObject = function toObject(mtype) { } else { gen ("if(m%s!=null&&m.hasOwnProperty(%j)){", prop, field.name); // !== undefined && !== null genValuePartial_toObject(gen, field, /* sorted */ index, prop); - if (field.partOf) gen + if (field.partOf && !field.partOf.isProto3Optional) gen ("if(o.oneofs)") ("d%s=%j", util.safeProp(field.partOf.name), field.name); }
tests/comp_optional.js+20 −0 modified@@ -43,6 +43,26 @@ tape.test("proto3 optional", function(test) { test.end(); }); +tape.test("proto3 optional does not emit synthetic oneof discriminator", function(test) { + var root = protobuf.parse(proto).root; + root.resolveAll(); + + var Message = root.lookupType("Message"); + + test.same( + Message.toObject(Message.create({ optionalInt32: 1 }), { oneofs: true }), + { optionalInt32: 1 }, + "should not emit synthetic oneof discriminator" + ); + test.same( + Message.toObject(Message.create({ oneofInt32: 1 }), { oneofs: true }), + { oneofInt32: 1, _oneofInt32: "oneofInt32" }, + "should still emit real oneof discriminator" + ); + + test.end(); +}); + tape.test("proto3 implicit scalar defaults", function(test) { var root = protobuf.parse(proto).root; root.resolveAll();
3de631b233dafix: Misc utility hardening (#2272)
28 files changed · +1370 −395
index.d.ts+9 −3 modified@@ -2026,6 +2026,13 @@ export interface IFetchOptions { xhr?: boolean; } +/** + * Tests if the specified key can affect object prototypes. + * @param key Key to test + * @returns `true` if the key is unsafe + */ +export function isUnsafeProperty(key: string): boolean; + /** * Any compatible Buffer instance. * This is a minimal stand-alone definition of a Buffer instance. The actual type is that exported by node's typings. @@ -2467,11 +2474,10 @@ export namespace util { /** * Merges the properties of the source object into the destination object. * @param dst Destination object - * @param src Source object - * @param [ifNotSet=false] Merges only if the key is not already set + * @param src Source objects, optionally followed by an `ifNotSet` flag * @returns Destination object */ - function merge(dst: { [k: string]: any }, src: { [k: string]: any }, ifNotSet?: boolean): { [k: string]: any }; + function merge(dst: { [k: string]: any }, ...src: any[]): { [k: string]: any }; /** Schema declaration nesting limit. */ let nestingLimit: number;
src/converter.js+5 −2 modified@@ -194,7 +194,7 @@ function genValuePartial_toObject(gen, field, fieldIndex, dstProp, srcProp) { if (field.resolvedType instanceof Enum) gen ("d%s=o.enums===String?(types[%i].values[m%s]===undefined?m%s:types[%i].values[m%s]):m%s", dstProp, fieldIndex, srcProp, srcProp, fieldIndex, srcProp, srcProp); else gen - ("d%s=types[%i].toObject(m%s,o)", dstProp, fieldIndex, srcProp); + ("d%s=types[%i].toObject(m%s,o,q+1)", dstProp, fieldIndex, srcProp); } else { var isUnsigned = false; switch (field.type) { @@ -238,9 +238,12 @@ converter.toObject = function toObject(mtype) { var fields = mtype.fieldsArray.slice().sort(util.compareFieldsById); if (!fields.length) return util.codegen()("return {}"); - var gen = util.codegen(["m", "o"], mtype.name + "$toObject") + var gen = util.codegen(["m", "o", "q"], mtype.name + "$toObject") ("if(!o)") ("o={}") + ("if(q===undefined)q=0") + ("if(q>util.recursionLimit)") + ("throw Error(\"max depth exceeded\")") ("var d={}"); var repeatedFields = [],
src/encoder.js+8 −5 modified@@ -16,8 +16,8 @@ var Enum = require("./enum"), */ function genTypePartial(gen, field, fieldIndex, ref) { return field.delimited - ? gen("types[%i].encode(%s,w.uint32(%i)).uint32(%i)", fieldIndex, ref, (field.id << 3 | 3) >>> 0, (field.id << 3 | 4) >>> 0) - : gen("types[%i].encode(%s,w.uint32(%i).fork()).ldelim()", fieldIndex, ref, (field.id << 3 | 2) >>> 0); + ? gen("types[%i].encode(%s,w.uint32(%i),q+1).uint32(%i)", fieldIndex, ref, (field.id << 3 | 3) >>> 0, (field.id << 3 | 4) >>> 0) + : gen("types[%i].encode(%s,w.uint32(%i).fork(),q+1).ldelim()", fieldIndex, ref, (field.id << 3 | 2) >>> 0); } /** @@ -27,9 +27,12 @@ function genTypePartial(gen, field, fieldIndex, ref) { */ function encoder(mtype) { /* eslint-disable no-unexpected-multiline, block-scoped-var, no-redeclare */ - var gen = util.codegen(["m", "w"], mtype.name + "$encode") + var gen = util.codegen(["m", "w", "q"], mtype.name + "$encode") ("if(!w)") - ("w=Writer.create()"); + ("w=Writer.create()") + ("if(q===undefined)q=0") + ("if(q>util.recursionLimit)") + ("throw Error(\"max depth exceeded\")"); var i, ref; @@ -55,7 +58,7 @@ function encoder(mtype) { else gen ("w.uint32(%i).fork().uint32(%i).%s(ks[i])", (field.id << 3 | 2) >>> 0, 8 | types.mapKey[field.keyType], field.keyType); if (wireType === undefined) gen - ("types[%i].encode(%s[ks[i]],w.uint32(18).fork()).ldelim().ldelim()", index, ref); // can't be groups + ("types[%i].encode(%s[ks[i]],w.uint32(18).fork(),q+1).ldelim().ldelim()", index, ref); // can't be groups else gen (".uint32(%i).%s(%s[ks[i]]).ldelim()", 16 | wireType, type, ref); gen
src/enum.js+2 −2 modified@@ -86,8 +86,8 @@ Enum.prototype._resolveFeatures = function _resolveFeatures(edition) { ReflectionObject.prototype._resolveFeatures.call(this, edition); Object.keys(this.values).forEach(key => { - var parentFeaturesCopy = Object.assign({}, this._features); - this._valuesFeatures[key] = Object.assign(parentFeaturesCopy, this.valuesOptions && this.valuesOptions[key] && this.valuesOptions[key].features); + var parentFeaturesCopy = util.merge({}, this._features); + this._valuesFeatures[key] = util.merge(parentFeaturesCopy, this.valuesOptions && this.valuesOptions[key] && this.valuesOptions[key].features || {}); }); return this;
src/object.js+6 −6 modified@@ -213,7 +213,7 @@ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition) throw new Error("Unknown edition for " + this.fullName); } - var protoFeatures = Object.assign(this.options ? Object.assign({}, this.options.features) : {}, + var protoFeatures = util.merge({}, this.options && this.options.features, this._inferLegacyProtoFeatures(edition)); if (this._edition) { @@ -230,19 +230,19 @@ ReflectionObject.prototype._resolveFeatures = function _resolveFeatures(edition) } else { throw new Error("Unknown edition: " + edition); } - this._features = Object.assign(defaults, protoFeatures || {}); + this._features = util.merge(defaults, protoFeatures); } else { // fields in Oneofs aren't actually children of them, so we have to // special-case it /* istanbul ignore else */ if (this.partOf instanceof OneOf) { - var lexicalParentFeaturesCopy = Object.assign({}, this.partOf._features); - this._features = Object.assign(lexicalParentFeaturesCopy, protoFeatures || {}); + var lexicalParentFeaturesCopy = util.merge({}, this.partOf._features); + this._features = util.merge(lexicalParentFeaturesCopy, protoFeatures); } else if (this.declaringField) { // Skip feature resolution of sister fields. } else if (this.parent) { - var parentFeaturesCopy = Object.assign({}, this.parent._features); - this._features = Object.assign(parentFeaturesCopy, protoFeatures || {}); + var parentFeaturesCopy = util.merge({}, this.parent._features); + this._features = util.merge(parentFeaturesCopy, protoFeatures); } else { throw new Error("Unable to find a parent for " + this.fullName); }
src/root.js+14 −8 modified@@ -142,8 +142,12 @@ Root.prototype.load = function load(filename, options, callback) { } // Processes a single file - function process(filename, source) { + function process(filename, source, depth) { + if (depth === undefined) + depth = 0; try { + if (depth > util.recursionLimit) + throw Error("max depth exceeded"); if (util.isString(source) && source.charAt(0) === "{") source = JSON.parse(source); if (!util.isString(source)) @@ -156,11 +160,11 @@ Root.prototype.load = function load(filename, options, callback) { if (parsed.imports) for (; i < parsed.imports.length; ++i) if (resolved = getBundledFileName(parsed.imports[i]) || self.resolvePath(filename, parsed.imports[i])) - fetch(resolved); + fetch(resolved, false, depth + 1); if (parsed.weakImports) for (i = 0; i < parsed.weakImports.length; ++i) if (resolved = getBundledFileName(parsed.weakImports[i]) || self.resolvePath(filename, parsed.weakImports[i])) - fetch(resolved, true); + fetch(resolved, true, depth + 1); } } catch (err) { finish(err); @@ -171,7 +175,9 @@ Root.prototype.load = function load(filename, options, callback) { } // Fetches a single file - function fetch(filename, weak) { + function fetch(filename, weak, depth) { + if (depth === undefined) + depth = 0; filename = getBundledFileName(filename) || filename; // Skip if already loaded / attempted @@ -183,12 +189,12 @@ Root.prototype.load = function load(filename, options, callback) { // Shortcut bundled definitions if (Object.prototype.hasOwnProperty.call(common, filename)) { if (sync) { - process(filename, common[filename]); + process(filename, common[filename], depth); } else { ++queued; setTimeout(function() { --queued; - process(filename, common[filename]); + process(filename, common[filename], depth); }); } return; @@ -204,7 +210,7 @@ Root.prototype.load = function load(filename, options, callback) { finish(err); return; } - process(filename, source); + process(filename, source, depth); } else { ++queued; self.fetch(filename, function(err, source) { @@ -221,7 +227,7 @@ Root.prototype.load = function load(filename, options, callback) { finish(null, self); return; } - process(filename, source); + process(filename, source, depth); }); } }
src/type.js+4 −4 modified@@ -524,8 +524,8 @@ Type.prototype.setup = function setup() { * @param {Writer} [writer] Writer to encode to * @returns {Writer} writer */ -Type.prototype.encode = function encode_setup(message, writer) { - return this.setup().encode(message, writer); // overrides this method +Type.prototype.encode = function encode_setup(message, writer) { // eslint-disable-line no-unused-vars + return this.setup().encode.apply(this, arguments); // overrides this method }; /** @@ -606,8 +606,8 @@ Type.prototype.fromObject = function fromObject(object) { // eslint-disable-line * @param {IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ -Type.prototype.toObject = function toObject(message, options) { - return this.setup().toObject(message, options); +Type.prototype.toObject = function toObject(message, options) { // eslint-disable-line no-unused-vars + return this.setup().toObject.apply(this, arguments); }; /**
src/util/eventemitter.js+4 −2 modified@@ -14,7 +14,7 @@ function EventEmitter() { * @type {Object.<string,*>} * @private */ - this._listeners = {}; + this._listeners = Object.create(null); } /** @@ -48,12 +48,14 @@ EventEmitter.prototype.on = function on(evt, fn, ctx) { */ EventEmitter.prototype.off = function off(evt, fn) { if (evt === undefined) - this._listeners = {}; + this._listeners = Object.create(null); else { if (fn === undefined) this._listeners[evt] = []; else { var listeners = this._listeners[evt]; + if (!listeners) + return this; for (var i = 0; i < listeners.length;) if (listeners[i].fn === fn) listeners.splice(i, 1);
src/util.js+2 −3 modified@@ -16,8 +16,7 @@ util.fetch = require("./util/fetch"); util.path = require("./util/path"); util.patterns = require("./util/patterns"); -var reservedRe = util.patterns.reservedRe, - unsafePropertyRe = util.patterns.unsafePropertyRe; +var reservedRe = util.patterns.reservedRe; /** * Node's fs module if available. @@ -180,7 +179,7 @@ util.decorateEnum = function decorateEnum(object) { util.setProperty = function setProperty(dst, path, value, ifNotSet) { function setProp(dst, path, value) { var part = path.shift(); - if (unsafePropertyRe.test(part)) + if (util.isUnsafeProperty(part)) return dst; if (path.length > 0) { dst[part] = setProp(dst[part] || {}, path, value);
src/util/minimal.js+23 −6 modified@@ -25,6 +25,17 @@ util.pool = require("./pool"); // utility to work with the low and high bits of a 64 bit value util.LongBits = require("./longbits"); +/** + * Tests if the specified key can affect object prototypes. + * @param {string} key Key to test + * @returns {boolean} `true` if the key is unsafe + */ +function isUnsafeProperty(key) { + return key === "__proto__" || key === "prototype" || key === "constructor"; +} + +util.isUnsafeProperty = isUnsafeProperty; + /** * Whether running within node or not. * @memberof util @@ -259,15 +270,21 @@ util.boolFromKey = function boolFromKey(key) { * Merges the properties of the source object into the destination object. * @memberof util * @param {Object.<string,*>} dst Destination object - * @param {Object.<string,*>} src Source object - * @param {boolean} [ifNotSet=false] Merges only if the key is not already set + * @param {...(Object.<string,*>|boolean)} src Source objects, optionally followed by an `ifNotSet` flag * @returns {Object.<string,*>} Destination object */ -function merge(dst, src, ifNotSet) { // used by converters - for (var keys = Object.keys(src), i = 0; i < keys.length; ++i) - if (dst[keys[i]] === undefined || !ifNotSet) - if (keys[i] !== "__proto__") +function merge(dst) { // used by converters + var ifNotSet = typeof arguments[arguments.length - 1] === "boolean", + limit = ifNotSet ? arguments.length - 1 : arguments.length; + ifNotSet = ifNotSet && arguments[arguments.length - 1]; + for (var a = 1; a < limit; ++a) { + var src = arguments[a]; + if (!src) + continue; + for (var keys = Object.keys(src), i = 0; i < keys.length; ++i) + if (!isUnsafeProperty(keys[i]) && (dst[keys[i]] === undefined || !ifNotSet)) dst[keys[i]] = src[keys[i]]; + } return dst; }
src/util/patterns.js+0 −1 modified@@ -5,4 +5,3 @@ var patterns = exports; patterns.numberRe = /^(?![eE])[0-9]*(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?$/; patterns.typeRefRe = /^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)(?:\.[a-zA-Z_][a-zA-Z_0-9]*)*$/; patterns.reservedRe = /^(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/; -patterns.unsafePropertyRe = /^(?:__proto__|prototype|constructor)$/;
src/wrappers.js+11 −8 modified@@ -7,7 +7,8 @@ */ var wrappers = exports; -var Message = require("./message"); +var Message = require("./message"), + util = require("./util/minimal"); /** * From object converter part of an {@link IWrapper}. @@ -54,24 +55,26 @@ wrappers[".google.protobuf.Any"] = { if (type_url.indexOf("/") === -1) { type_url = "/" + type_url; } - var nextDepth = depth === undefined ? 1 : depth + 1; return this.create({ type_url: type_url, - value: type.encode(type.fromObject(object, nextDepth)).finish() + value: type.encode(type.fromObject(object, depth === undefined ? 1 : depth + 1)).finish() }); } } return this.fromObject(object, depth); }, - toObject: function(message, options) { + toObject: function(message, options, depth) { + if (depth === undefined) + depth = 0; + if (depth > util.recursionLimit) + throw Error("max depth exceeded"); // Default prefix var googleApi = "type.googleapis.com/"; var prefix = ""; var name = ""; - // decode value if requested and unmapped if (options && options.json && message.type_url && message.value) { // Only use fully qualified type name after the last '/' @@ -81,12 +84,12 @@ wrappers[".google.protobuf.Any"] = { var type = this.lookup(name); /* istanbul ignore else */ if (type) - message = type.decode(message.value); + message = type.decode(message.value, undefined, undefined, depth + 1); } // wrap value if unmapped if (!(message instanceof this.ctor) && message instanceof Message) { - var object = message.$type.toObject(message, options); + var object = message.$type.toObject(message, options, depth + 1); var messageName = message.$type.fullName[0] === "." ? message.$type.fullName.slice(1) : message.$type.fullName; // Default to type.googleapis.com prefix if no prefix is used @@ -98,6 +101,6 @@ wrappers[".google.protobuf.Any"] = { return object; } - return this.toObject(message, options); + return this.toObject(message, options, depth); } };
tests/api_converters.js+32 −0 modified@@ -290,6 +290,38 @@ tape.test("converters", function(test) { test.end(); }); + test.test(test.name + " - Type.toObject recursion limit", function(test) { + var recursionLimit = protobuf.util.recursionLimit; + protobuf.util.recursionLimit = 3; + try { + var nestedRoot = protobuf.Root.fromJSON({ + nested: { + Recursive: { + fields: { + next: { + type: "Recursive", + id: 1 + } + } + } + } + }); + var Recursive = nestedRoot.lookupType("Recursive"); + var msg = Recursive.create(); + var cursor = msg; + for (var i = 0; i < 5; ++i) + cursor = cursor.next = Recursive.create(); + + test.throws(function() { + Recursive.toObject(msg); + }, /max depth exceeded/, "should reject excessive object conversion depth"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); + }); + test.test(test.name + " - Message#toJSON", function(test) { var msg = Message.create(); msg.$type = {
tests/api_enum.js+50 −0 modified@@ -142,6 +142,56 @@ tape.test("feature resolution legacy proto3", function(test) { test.end(); }); +tape.test("feature resolution skips unsafe feature keys", function(test) { + var enumFeatures = { enum_type: "CLOSED" }; + Object.defineProperty(enumFeatures, "__proto__", { + value: { polluted: true }, + enumerable: true + }); + enumFeatures.prototype = { polluted: true }; + enumFeatures.constructor = { polluted: true }; + + var valueFeatures = { field_presence: "EXPLICIT" }; + Object.defineProperty(valueFeatures, "__proto__", { + value: { polluted: true }, + enumerable: true + }); + valueFeatures.prototype = { polluted: true }; + valueFeatures.constructor = { polluted: true }; + + var root = protobuf.Root.fromJSON({ + nested: { + Enum: { + edition: "2023", + options: { features: enumFeatures }, + values: { + a: 0 + }, + valuesOptions: { + a: { + features: valueFeatures + } + } + } + } + }).resolveAll(); + var Enum = root.lookupEnum("Enum"); + + test.equal(Enum._features.enum_type, "CLOSED", "should keep regular enum features"); + test.equal(Object.getPrototypeOf(Enum._features).polluted, undefined, "should not alter enum feature prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._features, "__proto__"), "should skip unsafe enum feature key __proto__"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._features, "prototype"), "should skip unsafe enum feature key prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._features, "constructor"), "should skip unsafe enum feature key constructor"); + + test.equal(Enum._valuesFeatures.a.field_presence, "EXPLICIT", "should keep regular enum value features"); + test.equal(Object.getPrototypeOf(Enum._valuesFeatures.a).polluted, undefined, "should not alter enum value feature prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._valuesFeatures.a, "__proto__"), "should skip unsafe enum value feature key __proto__"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._valuesFeatures.a, "prototype"), "should skip unsafe enum value feature key prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(Enum._valuesFeatures.a, "constructor"), "should skip unsafe enum value feature key constructor"); + + test.end(); +}); + tape.test("feature resolution proto2", function(test) { var json = { edition: "proto2",
tests/api_type.js+83 −0 modified@@ -201,6 +201,89 @@ tape.test("decode nesting", function(test) { test.end(); }); +tape.test("encode nesting", function(test) { + function nestedObject(depth, field) { + var object = { value: 42 }; + for (var i = 0; i < depth; ++i) { + if (field === "child") + object = { child: object }; + else if (field === "children") + object = { children: [ object ] }; + else + object = { childMap: { child: object } }; + } + return object; + } + + var root = protobuf.Root.fromJSON({ + nested: { + Node: { + fields: { + child: { type: "Node", id: 1 }, + children: { rule: "repeated", type: "Node", id: 2 }, + childMap: { keyType: "string", type: "Node", id: 3 }, + value: { type: "int32", id: 4 } + } + } + } + }); + var Node = root.lookupType("Node"); + var recursionLimit = protobuf.util.recursionLimit; + + protobuf.util.recursionLimit = 3; + try { + test.ok(Node.encode(nestedObject(2, "child")).finish().length, "should encode singular messages below the limit"); + test.throws(function() { + Node.encode(nestedObject(4, "child")).finish(); + }, /max depth exceeded/, "should reject excessive singular message nesting"); + + test.ok(Node.encode(nestedObject(2, "children")).finish().length, "should encode repeated messages below the limit"); + test.throws(function() { + Node.encode(nestedObject(4, "children")).finish(); + }, /max depth exceeded/, "should reject excessive repeated message nesting"); + + test.ok(Node.encode(nestedObject(2, "childMap")).finish().length, "should encode map message values below the limit"); + test.throws(function() { + Node.encode(nestedObject(4, "childMap")).finish(); + }, /max depth exceeded/, "should reject excessive map message value nesting"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); +}); + +tape.test("encode setup preserves nesting", function(test) { + var root = protobuf.Root.fromJSON({ + nested: { + Parent: { + fields: { + child: { type: "Child", id: 1 } + } + }, + Child: { + fields: { + next: { type: "Child", id: 1 }, + value: { type: "int32", id: 2 } + } + } + } + }); + var Parent = root.lookupType("Parent"); + var recursionLimit = protobuf.util.recursionLimit; + + protobuf.util.recursionLimit = 1; + try { + test.throws(function() { + Parent.encode({ child: { next: { value: 42 } } }).finish(); + }, /max depth exceeded/, "should preserve depth through nested type setup"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); +}); + tape.test("decode known fields by wire type", function(test) { var root = protobuf.Root.fromJSON({ nested: {
tests/api_util.js+18 −2 modified@@ -16,10 +16,26 @@ tape.test("util", function(test) { test.same(o, { a: 2 }, "should merge existing keys"); util.merge(o, { a: 3 }, true); test.same(o, { a: 2 }, "should not merge existing keys"); - util.merge(o, JSON.parse("{\"__proto__\":{\"marker\":true}}")); + util.merge(o, { b: 1 }, { c: 2 }); + test.same(o, { a: 2, b: 1, c: 2 }, "should merge multiple sources"); + util.merge(o, { c: 3 }, { d: 4 }, true); + test.same(o, { a: 2, b: 1, c: 2, d: 4 }, "should merge multiple sources without overwriting existing keys"); + util.merge(o, JSON.parse("{\"__proto__\":{\"marker\":true},\"prototype\":{\"marker\":true},\"constructor\":{\"marker\":true}}")); test.equal(Object.getPrototypeOf(o), Object.prototype, "should keep the target object shape"); - test.notOk(Object.prototype.hasOwnProperty.call(o, "__proto__"), "should skip reserved keys"); + test.notOk(Object.prototype.hasOwnProperty.call(o, "__proto__"), "should skip reserved key __proto__"); + test.notOk(Object.prototype.hasOwnProperty.call(o, "prototype"), "should skip reserved key prototype"); + test.notOk(Object.prototype.hasOwnProperty.call(o, "constructor"), "should skip reserved key constructor"); test.equal(o.marker, undefined, "should not expose skipped values"); + var guarded = {}; + var accessed = false; + Object.defineProperty(guarded, "constructor", { + get: function() { + accessed = true; + } + }); + util.merge(guarded, { constructor: 1, safe: 2 }); + test.notOk(accessed, "should skip reserved keys before checking target values"); + test.equal(guarded.safe, 2, "should still merge regular keys"); test.end(); });
tests/comp_google_protobuf_any.js+36 −1 modified@@ -18,12 +18,21 @@ var root = new protobuf.Root().addJSON(protobuf.common["google/protobuf/any.prot type: "string" } } + }, + Loop: { + fields: { + next: { + id: 1, + type: "google.protobuf.Any" + } + } } }).resolveAll(); var Any = root.lookupType("protobuf.Any"), Foo = root.lookupType(".Foo"), - Bar = root.lookupType(".Bar"); + Bar = root.lookupType(".Bar"), + Loop = root.lookupType(".Loop"); tape.test("google.protobuf.Any", function(test) { @@ -62,3 +71,29 @@ tape.test("google.protobuf.Any", function(test) { test.end(); }); + +tape.test("google.protobuf.Any - toObject recursion limit", function(test) { + + var recursionLimit = protobuf.util.recursionLimit; + protobuf.util.recursionLimit = 3; + try { + var value = Loop.encode(Loop.create()).finish(); + for (var i = 0; i < 5; ++i) + value = Loop.encode(Loop.create({ + next: Any.create({ + type_url: "type.googleapis.com/Loop", + value: value + }) + })).finish(); + + var message = Loop.decode(value); + + test.throws(function() { + JSON.stringify(message); + }, /max depth exceeded/, "should reject excessive Any JSON expansion depth"); + } finally { + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); +});
tests/data/comments.js+16 −4 modified@@ -1,4 +1,4 @@ -/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-mixed-operators, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ "use strict"; var $protobuf = require("../../minimal"); @@ -100,9 +100,13 @@ $root.Test1 = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Test1.encode = function encode(message, writer) { + Test1.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.field1 != null && Object.hasOwnProperty.call(message, "field1")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.field1); if (message.field2 != null && Object.hasOwnProperty.call(message, "field2")) @@ -273,9 +277,13 @@ $root.Test1 = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Test1.toObject = function toObject(message, options) { + Test1.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) { object.field1 = ""; @@ -380,9 +388,13 @@ $root.Test2 = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Test2.encode = function encode(message, writer) { + Test2.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) for (var i = 0; i < message.$unknowns.length; ++i) writer.raw(message.$unknowns[i]);
tests/data/convert.js+12 −4 modified@@ -1,4 +1,4 @@ -/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-mixed-operators, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ "use strict"; var $protobuf = require("../../minimal"); @@ -156,9 +156,13 @@ $root.Message = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Message.encode = function encode(message, writer) { + Message.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.stringVal != null && Object.hasOwnProperty.call(message, "stringVal")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.stringVal); if (message.stringRepeated != null && message.stringRepeated.length) @@ -326,7 +330,7 @@ $root.Message = (function() { message.int64Map = {}; var end2 = reader.uint32() + reader.pos; key = ""; - value = 0; + value = $util.Long ? $util.Long.fromNumber(0, false) : 0; while (reader.pos < end2) { var tag2 = reader.tag(); wireType = tag2 & 7; @@ -584,9 +588,13 @@ $root.Message = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Message.toObject = function toObject(message, options) { + Message.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) { object.stringRepeated = [];
tests/data/mapbox/vector_tile.js+47 −15 modified@@ -1,4 +1,4 @@ -/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-mixed-operators, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ "use strict"; var $protobuf = require("../../../minimal"); @@ -89,12 +89,16 @@ $root.vector_tile = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Tile.encode = function encode(message, writer) { + Tile.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.layers != null && message.layers.length) for (var i = 0; i < message.layers.length; ++i) - $root.vector_tile.Tile.Layer.encode(message.layers[i], writer.uint32(/* id 3, wireType 2 =*/26).fork()).ldelim(); + $root.vector_tile.Tile.Layer.encode(message.layers[i], writer.uint32(/* id 3, wireType 2 =*/26).fork(), _depth + 1).ldelim(); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) for (var i = 0; i < message.$unknowns.length; ++i) writer.raw(message.$unknowns[i]); @@ -241,16 +245,20 @@ $root.vector_tile = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Tile.toObject = function toObject(message, options) { + Tile.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) object.layers = []; if (message.layers && message.layers.length) { object.layers = Array(message.layers.length); for (var j = 0; j < message.layers.length; ++j) - object.layers[j] = $root.vector_tile.Tile.Layer.toObject(message.layers[j], options); + object.layers[j] = $root.vector_tile.Tile.Layer.toObject(message.layers[j], options, _depth + 1); } return object; }; @@ -422,9 +430,13 @@ $root.vector_tile = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Value.encode = function encode(message, writer) { + Value.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.stringValue != null && Object.hasOwnProperty.call(message, "stringValue")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.stringValue); if (message.floatValue != null && Object.hasOwnProperty.call(message, "floatValue")) @@ -656,9 +668,13 @@ $root.vector_tile = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Value.toObject = function toObject(message, options) { + Value.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) { object.stringValue = ""; @@ -840,9 +856,13 @@ $root.vector_tile = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Feature.encode = function encode(message, writer) { + Feature.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.id != null && Object.hasOwnProperty.call(message, "id")) writer.uint32(/* id 1, wireType 0 =*/8).uint64(message.id); if (message.tags != null && message.tags.length) { @@ -1096,9 +1116,13 @@ $root.vector_tile = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Feature.toObject = function toObject(message, options) { + Feature.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) { object.tags = []; @@ -1280,19 +1304,23 @@ $root.vector_tile = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Layer.encode = function encode(message, writer) { + Layer.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); writer.uint32(/* id 1, wireType 2 =*/10).string(message.name); if (message.features != null && message.features.length) for (var i = 0; i < message.features.length; ++i) - $root.vector_tile.Tile.Feature.encode(message.features[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); + $root.vector_tile.Tile.Feature.encode(message.features[i], writer.uint32(/* id 2, wireType 2 =*/18).fork(), _depth + 1).ldelim(); if (message.keys != null && message.keys.length) for (var i = 0; i < message.keys.length; ++i) writer.uint32(/* id 3, wireType 2 =*/26).string(message.keys[i]); if (message.values != null && message.values.length) for (var i = 0; i < message.values.length; ++i) - $root.vector_tile.Tile.Value.encode(message.values[i], writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim(); + $root.vector_tile.Tile.Value.encode(message.values[i], writer.uint32(/* id 4, wireType 2 =*/34).fork(), _depth + 1).ldelim(); if (message.extent != null && Object.hasOwnProperty.call(message, "extent")) writer.uint32(/* id 5, wireType 0 =*/40).uint32(message.extent); writer.uint32(/* id 15, wireType 0 =*/120).uint32(message.version); @@ -1526,9 +1554,13 @@ $root.vector_tile = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Layer.toObject = function toObject(message, options) { + Layer.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) { object.features = []; @@ -1545,7 +1577,7 @@ $root.vector_tile = (function() { if (message.features && message.features.length) { object.features = Array(message.features.length); for (var j = 0; j < message.features.length; ++j) - object.features[j] = $root.vector_tile.Tile.Feature.toObject(message.features[j], options); + object.features[j] = $root.vector_tile.Tile.Feature.toObject(message.features[j], options, _depth + 1); } if (message.keys && message.keys.length) { object.keys = Array(message.keys.length); @@ -1555,7 +1587,7 @@ $root.vector_tile = (function() { if (message.values && message.values.length) { object.values = Array(message.values.length); for (var j = 0; j < message.values.length; ++j) - object.values[j] = $root.vector_tile.Tile.Value.toObject(message.values[j], options); + object.values[j] = $root.vector_tile.Tile.Value.toObject(message.values[j], options, _depth + 1); } if (message.extent != null && message.hasOwnProperty("extent")) object.extent = message.extent;
tests/data/package.js+23 −7 modified@@ -1,4 +1,4 @@ -/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-mixed-operators, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ "use strict"; var $protobuf = require("../../minimal"); @@ -229,9 +229,13 @@ $root.Package = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Package.encode = function encode(message, writer) { + Package.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.name != null && Object.hasOwnProperty.call(message, "name")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.name); if (message.version != null && Object.hasOwnProperty.call(message, "version")) @@ -243,7 +247,7 @@ $root.Package = (function() { if (message.license != null && Object.hasOwnProperty.call(message, "license")) writer.uint32(/* id 5, wireType 2 =*/42).string(message.license); if (message.repository != null && Object.hasOwnProperty.call(message, "repository")) - $root.Package.Repository.encode(message.repository, writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim(); + $root.Package.Repository.encode(message.repository, writer.uint32(/* id 6, wireType 2 =*/50).fork(), _depth + 1).ldelim(); if (message.bugs != null && Object.hasOwnProperty.call(message, "bugs")) writer.uint32(/* id 7, wireType 2 =*/58).string(message.bugs); if (message.homepage != null && Object.hasOwnProperty.call(message, "homepage")) @@ -793,9 +797,13 @@ $root.Package = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Package.toObject = function toObject(message, options) { + Package.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.arrays || options.defaults) { object.keywords = []; @@ -831,7 +839,7 @@ $root.Package = (function() { if (message.license != null && message.hasOwnProperty("license")) object.license = message.license; if (message.repository != null && message.hasOwnProperty("repository")) - object.repository = $root.Package.Repository.toObject(message.repository, options); + object.repository = $root.Package.Repository.toObject(message.repository, options, _depth + 1); if (message.bugs != null && message.hasOwnProperty("bugs")) object.bugs = message.bugs; if (message.homepage != null && message.hasOwnProperty("homepage")) @@ -992,9 +1000,13 @@ $root.Package = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Repository.encode = function encode(message, writer) { + Repository.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.type != null && Object.hasOwnProperty.call(message, "type")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.type); if (message.url != null && Object.hasOwnProperty.call(message, "url")) @@ -1148,9 +1160,13 @@ $root.Package = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Repository.toObject = function toObject(message, options) { + Repository.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) { object.type = "";
tests/data/rpc-es6.js+21 −5 modified@@ -1,4 +1,4 @@ -/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-mixed-operators, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ import $protobuf from "protobufjs/minimal.js"; // Common aliases @@ -145,9 +145,13 @@ export const MyRequest = $root.MyRequest = (() => { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyRequest.encode = function encode(message, writer) { + MyRequest.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.path != null && Object.hasOwnProperty.call(message, "path")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.path); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) @@ -284,9 +288,13 @@ export const MyRequest = $root.MyRequest = (() => { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyRequest.toObject = function toObject(message, options) { + MyRequest.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); let object = {}; if (options.defaults) object.path = ""; @@ -393,9 +401,13 @@ export const MyResponse = $root.MyResponse = (() => { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyResponse.encode = function encode(message, writer) { + MyResponse.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.status != null && Object.hasOwnProperty.call(message, "status")) writer.uint32(/* id 2, wireType 0 =*/16).int32(message.status); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) @@ -532,9 +544,13 @@ export const MyResponse = $root.MyResponse = (() => { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyResponse.toObject = function toObject(message, options) { + MyResponse.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); let object = {}; if (options.defaults) object.status = 0;
tests/data/rpc.js+21 −5 modified@@ -1,4 +1,4 @@ -/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-mixed-operators, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ "use strict"; var $protobuf = require("../../minimal"); @@ -147,9 +147,13 @@ $root.MyRequest = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyRequest.encode = function encode(message, writer) { + MyRequest.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.path != null && Object.hasOwnProperty.call(message, "path")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.path); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) @@ -286,9 +290,13 @@ $root.MyRequest = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyRequest.toObject = function toObject(message, options) { + MyRequest.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.path = ""; @@ -395,9 +403,13 @@ $root.MyResponse = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyResponse.encode = function encode(message, writer) { + MyResponse.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.status != null && Object.hasOwnProperty.call(message, "status")) writer.uint32(/* id 2, wireType 0 =*/16).int32(message.status); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) @@ -534,9 +546,13 @@ $root.MyResponse = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyResponse.toObject = function toObject(message, options) { + MyResponse.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.status = 0;
tests/data/rpc-reserved.js+21 −5 modified@@ -1,4 +1,4 @@ -/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-mixed-operators, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ "use strict"; var $protobuf = require("../../minimal"); @@ -147,9 +147,13 @@ $root.MyRequest = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyRequest.encode = function encode(message, writer) { + MyRequest.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.path != null && Object.hasOwnProperty.call(message, "path")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.path); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) @@ -286,9 +290,13 @@ $root.MyRequest = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyRequest.toObject = function toObject(message, options) { + MyRequest.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.path = ""; @@ -395,9 +403,13 @@ $root.MyResponse = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - MyResponse.encode = function encode(message, writer) { + MyResponse.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.status != null && Object.hasOwnProperty.call(message, "status")) writer.uint32(/* id 2, wireType 0 =*/16).int32(message.status); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) @@ -534,9 +546,13 @@ $root.MyResponse = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - MyResponse.toObject = function toObject(message, options) { + MyResponse.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.status = 0;
tests/data/test.js+811 −287 modifiedtests/data/type_url.js+23 −7 modified@@ -1,4 +1,4 @@ -/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-mixed-operators, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars, default-case, jsdoc/require-param*/ "use strict"; var $protobuf = require("../../minimal"); @@ -79,11 +79,15 @@ $root.TypeUrlTest = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - TypeUrlTest.encode = function encode(message, writer) { + TypeUrlTest.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.nested != null && Object.hasOwnProperty.call(message, "nested")) - $root.TypeUrlTest.Nested.encode(message.nested, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); + $root.TypeUrlTest.Nested.encode(message.nested, writer.uint32(/* id 1, wireType 2 =*/10).fork(), _depth + 1).ldelim(); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) for (var i = 0; i < message.$unknowns.length; ++i) writer.raw(message.$unknowns[i]); @@ -219,14 +223,18 @@ $root.TypeUrlTest = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - TypeUrlTest.toObject = function toObject(message, options) { + TypeUrlTest.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.nested = null; if (message.nested != null && message.hasOwnProperty("nested")) - object.nested = $root.TypeUrlTest.Nested.toObject(message.nested, options); + object.nested = $root.TypeUrlTest.Nested.toObject(message.nested, options, _depth + 1); return object; }; @@ -325,9 +333,13 @@ $root.TypeUrlTest = (function() { * @param {$protobuf.Writer} [writer] Writer to encode to * @returns {$protobuf.Writer} Writer */ - Nested.encode = function encode(message, writer) { + Nested.encode = function encode(message, writer, _depth) { if (!writer) writer = $Writer.create(); + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); if (message.a != null && Object.hasOwnProperty.call(message, "a")) writer.uint32(/* id 1, wireType 2 =*/10).string(message.a); if (message.$unknowns != null && Object.hasOwnProperty.call(message, "$unknowns")) @@ -464,9 +476,13 @@ $root.TypeUrlTest = (function() { * @param {$protobuf.IConversionOptions} [options] Conversion options * @returns {Object.<string,*>} Plain object */ - Nested.toObject = function toObject(message, options) { + Nested.toObject = function toObject(message, options, _depth) { if (!options) options = {}; + if (_depth === undefined) + _depth = 0; + if (_depth > $util.recursionLimit) + throw Error("max depth exceeded"); var object = {}; if (options.defaults) object.a = "";
tests/node/api_load-sync.js+29 −0 modified@@ -36,6 +36,35 @@ tape.test("load sync", function(test) { test.end(); }); +tape.test("load sync import recursion limit", function(test) { + var fs = protobuf.util.fs, + readFileSync = fs.readFileSync, + recursionLimit = protobuf.util.recursionLimit; + + protobuf.util.recursionLimit = 3; + fs.readFileSync = function(filename) { + var match = /chain-(\d+)\.proto$/.exec(filename); + if (match) + return "syntax = \"proto3\";\nimport \"chain-" + (Number(match[1]) + 1) + ".proto\";\n"; + return readFileSync.apply(this, arguments); + }; + + try { + var root = new protobuf.Root(); + root.resolvePath = function(origin, target) { + return target; + }; + test.throws(function() { + root.loadSync("chain-0.proto"); + }, /max depth exceeded/, "should reject excessive import nesting"); + } finally { + fs.readFileSync = readFileSync; + protobuf.util.recursionLimit = recursionLimit; + } + + test.end(); +}); + tape.test("should load bundled definitions even if resolvePath method was overrided", function(test) { var protoFilePath = "tests/data/common.proto"; var root = new protobuf.Root();
tests/util_eventemitter.js+39 −3 modified@@ -8,6 +8,8 @@ tape.test("eventemitter", function(test) { var fn; var ctx = {}; + test.equal(Object.getPrototypeOf(ee._listeners), null, "should not inherit listener lookup keys"); + test.doesNotThrow(function() { ee.emit("a", 1); ee.off(); @@ -22,18 +24,21 @@ tape.test("eventemitter", function(test) { ee.emit("a", 1); ee.off("a"); - test.same(ee._listeners, { a: [] }, "should remove all listeners of the respective event when calling off(evt)"); + test.same(Object.keys(ee._listeners), [ "a" ], "should keep the event key when calling off(evt)"); + test.same(ee._listeners.a, [], "should remove all listeners of the respective event when calling off(evt)"); ee.off(); - test.same(ee._listeners, {}, "should remove all listeners when just calling off()"); + test.equal(Object.getPrototypeOf(ee._listeners), null, "should keep the listener table isolated when just calling off()"); + test.same(Object.keys(ee._listeners), [], "should remove all listeners when just calling off()"); ee.on("a", fn = function(arg1) { test.equal(this, ctx, "should be called with this = ctx"); test.equal(arg1, 1, "should be called with arg1 = 1"); }, ctx).emit("a", 1); ee.off("a", fn); - test.same(ee._listeners, { a: [] }, "should remove the exact listener when calling off(evt, fn)"); + test.same(Object.keys(ee._listeners), [ "a" ], "should keep the event key when calling off(evt, fn)"); + test.same(ee._listeners.a, [], "should remove the exact listener when calling off(evt, fn)"); ee.on("a", function() { test.equal(this, ee, "should be called with this = ee"); @@ -43,5 +48,36 @@ tape.test("eventemitter", function(test) { ee.off("a", fn); }, "should not throw if no such listener is found"); + test.test(test.name + " - special event names", function(test) { + var ee = new EventEmitter(); + var calls = 0; + + test.doesNotThrow(function() { + ee.off("__proto__", function() {}); + }, "should not throw when removing an absent special event listener"); + + ee.on("__proto__", function(arg) { + ++calls; + test.equal(arg, 1, "should pass arguments for __proto__ events"); + }); + ee.on("constructor", function(arg) { + ++calls; + test.equal(arg, 2, "should pass arguments for constructor events"); + }); + ee.emit("__proto__", 1); + ee.emit("constructor", 2); + + test.equal(calls, 2, "should dispatch special event names"); + test.equal(Object.getPrototypeOf(ee._listeners), null, "should keep the listener table isolated"); + test.ok(Object.prototype.hasOwnProperty.call(ee._listeners, "__proto__"), "should store __proto__ as an own event key"); + test.ok(Object.prototype.hasOwnProperty.call(ee._listeners, "constructor"), "should store constructor as an own event key"); + + ee.off("__proto__"); + ee.off("constructor"); + test.same(ee._listeners.__proto__, [], "should clear __proto__ listeners"); + test.same(ee._listeners.constructor, [], "should clear constructor listeners"); + test.end(); + }); + test.end(); });
Vulnerability mechanics
No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.
References
2News mentions
0No linked articles in our index yet.