Skip to content

Commit 54b593f

Browse files
authored
fix: Backport parser hardening to 7.x (#2245)
1 parent e88fcea commit 54b593f

9 files changed

Lines changed: 202 additions & 45 deletions

File tree

index.d.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -714,10 +714,11 @@ export class Namespace extends NamespaceBase {
714714
* Constructs a namespace from JSON.
715715
* @param name Namespace name
716716
* @param json JSON object
717+
* @param [depth] Current nesting depth, defaults to `0`
717718
* @returns Created namespace
718719
* @throws {TypeError} If arguments are invalid
719720
*/
720-
public static fromJSON(name: string, json: { [k: string]: any }): Namespace;
721+
public static fromJSON(name: string, json: { [k: string]: any }, depth?: number): Namespace;
721722

722723
/**
723724
* Converts an array of reflection objects to JSON.
@@ -769,9 +770,10 @@ export abstract class NamespaceBase extends ReflectionObject {
769770
/**
770771
* Adds nested objects to this namespace from nested object descriptors.
771772
* @param nestedJson Any nested object descriptors
773+
* @param [depth] Current nesting depth, defaults to `0`
772774
* @returns `this`
773775
*/
774-
public addJSON(nestedJson: { [k: string]: AnyNestedObject }): Namespace;
776+
public addJSON(nestedJson: { [k: string]: AnyNestedObject }, depth?: number): Namespace;
775777

776778
/**
777779
* Gets the nested object of the specified name.
@@ -1318,9 +1320,10 @@ export class Root extends NamespaceBase {
13181320
* Loads a namespace descriptor into a root namespace.
13191321
* @param json Namespace descriptor
13201322
* @param [root] Root namespace, defaults to create a new one if omitted
1323+
* @param [depth] Current nesting depth, defaults to `0`
13211324
* @returns Root namespace
13221325
*/
1323-
public static fromJSON(json: INamespace, root?: Root): Root;
1326+
public static fromJSON(json: INamespace, root?: Root, depth?: number): Root;
13241327

13251328
/**
13261329
* Resolves the path of an imported file, relative to the importing origin.
@@ -1471,10 +1474,11 @@ export class Service extends NamespaceBase {
14711474
* Constructs a service from a service descriptor.
14721475
* @param name Service name
14731476
* @param json Service descriptor
1477+
* @param [depth] Current nesting depth, defaults to `0`
14741478
* @returns Created service
14751479
* @throws {TypeError} If arguments are invalid
14761480
*/
1477-
public static fromJSON(name: string, json: IService): Service;
1481+
public static fromJSON(name: string, json: IService, depth?: number): Service;
14781482

14791483
/**
14801484
* Converts this service to a service descriptor.
@@ -1625,9 +1629,10 @@ export class Type extends NamespaceBase {
16251629
* Creates a message type from a message type descriptor.
16261630
* @param name Message name
16271631
* @param json Message type descriptor
1632+
* @param [depth] Current nesting depth, defaults to `0`
16281633
* @returns Created message type
16291634
*/
1630-
public static fromJSON(name: string, json: IType): Type;
1635+
public static fromJSON(name: string, json: IType, depth?: number): Type;
16311636

16321637
/**
16331638
* Converts this message type to a message type descriptor.
@@ -2199,6 +2204,14 @@ export namespace util {
21992204
/** Node's fs module if available. */
22002205
let fs: { [k: string]: any };
22012206

2207+
/**
2208+
* Checks a recursion depth.
2209+
* @param depth Depth of recursion
2210+
* @returns Depth of recursion
2211+
* @throws {Error} If depth exceeds util.recursionLimit
2212+
*/
2213+
function checkDepth(depth: (number|undefined)): number;
2214+
22022215
/**
22032216
* Converts an object's values to an array.
22042217
* @param object Object to convert

src/namespace.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ var Type, // cyclic
2929
* @function
3030
* @param {string} name Namespace name
3131
* @param {Object.<string,*>} json JSON object
32+
* @param {number} [depth] Current nesting depth, defaults to `0`
3233
* @returns {Namespace} Created namespace
3334
* @throws {TypeError} If arguments are invalid
3435
*/
35-
Namespace.fromJSON = function fromJSON(name, json) {
36-
return new Namespace(name, json.options).addJSON(json.nested);
36+
Namespace.fromJSON = function fromJSON(name, json, depth) {
37+
depth = util.checkDepth(depth);
38+
return new Namespace(name, json.options).addJSON(json.nested, depth);
3739
};
3840

3941
/**
@@ -191,9 +193,11 @@ Namespace.prototype.toJSON = function toJSON(toJSONOptions) {
191193
/**
192194
* Adds nested objects to this namespace from nested object descriptors.
193195
* @param {Object.<string,AnyNestedObject>} nestedJson Any nested object descriptors
196+
* @param {number} [depth] Current nesting depth, defaults to `0`
194197
* @returns {Namespace} `this`
195198
*/
196-
Namespace.prototype.addJSON = function addJSON(nestedJson) {
199+
Namespace.prototype.addJSON = function addJSON(nestedJson, depth) {
200+
depth = util.checkDepth(depth);
197201
var ns = this;
198202
/* istanbul ignore else */
199203
if (nestedJson) {
@@ -208,7 +212,7 @@ Namespace.prototype.addJSON = function addJSON(nestedJson) {
208212
? Service.fromJSON
209213
: nested.id !== undefined
210214
? Field.fromJSON
211-
: Namespace.fromJSON )(names[i], nested)
215+
: Namespace.fromJSON )(names[i], nested, depth + 1)
212216
);
213217
}
214218
}

src/parse.js

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,8 @@ function parse(source, root, options) {
301301
}
302302

303303

304-
function parseCommon(parent, token) {
304+
function parseCommon(parent, token, depth) {
305+
depth = util.checkDepth(depth);
305306
switch (token) {
306307

307308
case "option":
@@ -310,19 +311,19 @@ function parse(source, root, options) {
310311
return true;
311312

312313
case "message":
313-
parseType(parent, token);
314+
parseType(parent, token, depth + 1);
314315
return true;
315316

316317
case "enum":
317318
parseEnum(parent, token);
318319
return true;
319320

320321
case "service":
321-
parseService(parent, token);
322+
parseService(parent, token, depth + 1);
322323
return true;
323324

324325
case "extend":
325-
parseExtension(parent, token);
326+
parseExtension(parent, token, depth);
326327
return true;
327328
}
328329
return false;
@@ -350,15 +351,16 @@ function parse(source, root, options) {
350351
}
351352
}
352353

353-
function parseType(parent, token) {
354+
function parseType(parent, token, depth) {
355+
depth = util.checkDepth(depth);
354356

355357
/* istanbul ignore if */
356358
if (!nameRe.test(token = next()))
357359
throw illegal(token, "type name");
358360

359361
var type = new Type(token);
360362
ifBlock(type, function parseType_block(token) {
361-
if (parseCommon(type, token))
363+
if (parseCommon(type, token, depth))
362364
return;
363365

364366
switch (token) {
@@ -372,22 +374,22 @@ function parse(source, root, options) {
372374
throw illegal(token);
373375
/* eslint-disable no-fallthrough */
374376
case "repeated":
375-
parseField(type, token);
377+
parseField(type, token, undefined, depth + 1);
376378
break;
377379

378380
case "optional":
379381
/* istanbul ignore if */
380382
if (edition === "proto3") {
381-
parseField(type, "proto3_optional");
383+
parseField(type, "proto3_optional", undefined, depth + 1);
382384
} else if (edition !== "proto2") {
383385
throw illegal(token);
384386
} else {
385-
parseField(type, "optional");
387+
parseField(type, "optional", undefined, depth + 1);
386388
}
387389
break;
388390

389391
case "oneof":
390-
parseOneOf(type, token);
392+
parseOneOf(type, token, depth + 1);
391393
break;
392394

393395
case "extensions":
@@ -405,7 +407,7 @@ function parse(source, root, options) {
405407
}
406408

407409
push(token);
408-
parseField(type, "optional");
410+
parseField(type, "optional", undefined, depth + 1);
409411
break;
410412
}
411413
});
@@ -415,10 +417,10 @@ function parse(source, root, options) {
415417
}
416418
}
417419

418-
function parseField(parent, rule, extend) {
420+
function parseField(parent, rule, extend, depth) {
419421
var type = next();
420422
if (type === "group") {
421-
parseGroup(parent, rule);
423+
parseGroup(parent, rule, depth);
422424
return;
423425
}
424426
// Type names can consume multiple tokens, in multiple variants:
@@ -475,7 +477,8 @@ function parse(source, root, options) {
475477
}
476478
}
477479

478-
function parseGroup(parent, rule) {
480+
function parseGroup(parent, rule, depth) {
481+
depth = util.checkDepth(depth);
479482
if (edition >= 2023) {
480483
throw illegal("group");
481484
}
@@ -503,20 +506,20 @@ function parse(source, root, options) {
503506
break;
504507
case "required":
505508
case "repeated":
506-
parseField(type, token);
509+
parseField(type, token, undefined, depth + 1);
507510
break;
508511

509512
case "optional":
510513
/* istanbul ignore if */
511514
if (edition === "proto3") {
512-
parseField(type, "proto3_optional");
515+
parseField(type, "proto3_optional", undefined, depth + 1);
513516
} else {
514-
parseField(type, "optional");
517+
parseField(type, "optional", undefined, depth + 1);
515518
}
516519
break;
517520

518521
case "message":
519-
parseType(type, token);
522+
parseType(type, token, depth + 1);
520523
break;
521524

522525
case "enum":
@@ -575,7 +578,7 @@ function parse(source, root, options) {
575578
parent.add(field);
576579
}
577580

578-
function parseOneOf(parent, token) {
581+
function parseOneOf(parent, token, depth) {
579582

580583
/* istanbul ignore if */
581584
if (!nameRe.test(token = next()))
@@ -588,7 +591,7 @@ function parse(source, root, options) {
588591
skip(";");
589592
} else {
590593
push(token);
591-
parseField(oneof, "optional");
594+
parseField(oneof, "optional", undefined, depth);
592595
}
593596
});
594597
parent.add(oneof);
@@ -693,7 +696,8 @@ function parse(source, root, options) {
693696
setParsedOption(parent, option, optionValue, propName);
694697
}
695698

696-
function parseOptionValue(parent, name) {
699+
function parseOptionValue(parent, name, depth) {
700+
depth = util.checkDepth(depth);
697701
// { a: "foo" b { c: "bar" } }
698702
if (skip("{", true)) {
699703
var objectResult = {};
@@ -716,7 +720,7 @@ function parse(source, root, options) {
716720
// option (my_option) = {
717721
// repeated_value: [ "foo", "bar" ]
718722
// };
719-
value = parseOptionValue(parent, name + "." + token);
723+
value = parseOptionValue(parent, name + "." + token, depth + 1);
720724
} else if (peek() === "[") {
721725
value = [];
722726
var lastValue;
@@ -781,15 +785,16 @@ function parse(source, root, options) {
781785
return parent;
782786
}
783787

784-
function parseService(parent, token) {
788+
function parseService(parent, token, depth) {
789+
depth = util.checkDepth(depth);
785790

786791
/* istanbul ignore if */
787792
if (!nameRe.test(token = next()))
788793
throw illegal(token, "service name");
789794

790795
var service = new Service(token);
791796
ifBlock(service, function parseService_block(token) {
792-
if (parseCommon(service, token)) {
797+
if (parseCommon(service, token, depth)) {
793798
return;
794799
}
795800

@@ -855,7 +860,7 @@ function parse(source, root, options) {
855860
parent.add(method);
856861
}
857862

858-
function parseExtension(parent, token) {
863+
function parseExtension(parent, token, depth) {
859864

860865
/* istanbul ignore if */
861866
if (!typeRefRe.test(token = next()))
@@ -867,15 +872,15 @@ function parse(source, root, options) {
867872

868873
case "required":
869874
case "repeated":
870-
parseField(parent, token, reference);
875+
parseField(parent, token, reference, depth + 1);
871876
break;
872877

873878
case "optional":
874879
/* istanbul ignore if */
875880
if (edition === "proto3") {
876-
parseField(parent, "proto3_optional", reference);
881+
parseField(parent, "proto3_optional", reference, depth + 1);
877882
} else {
878-
parseField(parent, "optional", reference);
883+
parseField(parent, "optional", reference, depth + 1);
879884
}
880885
break;
881886

@@ -884,7 +889,7 @@ function parse(source, root, options) {
884889
if (edition === "proto2" || !typeRefRe.test(token))
885890
throw illegal(token);
886891
push(token);
887-
parseField(parent, "optional", reference);
892+
parseField(parent, "optional", reference, depth + 1);
888893
break;
889894
}
890895
});
@@ -936,7 +941,7 @@ function parse(source, root, options) {
936941
default:
937942

938943
/* istanbul ignore else */
939-
if (parseCommon(ptr, token)) {
944+
if (parseCommon(ptr, token, 0)) {
940945
head = false;
941946
continue;
942947
}

src/root.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,16 @@ function Root(options) {
5555
* Loads a namespace descriptor into a root namespace.
5656
* @param {INamespace} json Namespace descriptor
5757
* @param {Root} [root] Root namespace, defaults to create a new one if omitted
58+
* @param {number} [depth] Current nesting depth, defaults to `0`
5859
* @returns {Root} Root namespace
5960
*/
60-
Root.fromJSON = function fromJSON(json, root) {
61+
Root.fromJSON = function fromJSON(json, root, depth) {
62+
depth = util.checkDepth(depth);
6163
if (!root)
6264
root = new Root();
6365
if (json.options)
6466
root.setOptions(json.options);
65-
return root.addJSON(json.nested).resolveAll();
67+
return root.addJSON(json.nested, depth).resolveAll();
6668
};
6769

6870
/**

src/service.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,19 @@ function Service(name, options) {
4848
* Constructs a service from a service descriptor.
4949
* @param {string} name Service name
5050
* @param {IService} json Service descriptor
51+
* @param {number} [depth] Current nesting depth, defaults to `0`
5152
* @returns {Service} Created service
5253
* @throws {TypeError} If arguments are invalid
5354
*/
54-
Service.fromJSON = function fromJSON(name, json) {
55+
Service.fromJSON = function fromJSON(name, json, depth) {
56+
depth = util.checkDepth(depth);
5557
var service = new Service(name, json.options);
5658
/* istanbul ignore else */
5759
if (json.methods)
5860
for (var names = Object.keys(json.methods), i = 0; i < names.length; ++i)
5961
service.add(Method.fromJSON(names[i], json.methods[names[i]]));
6062
if (json.nested)
61-
service.addJSON(json.nested);
63+
service.addJSON(json.nested, depth);
6264
if (json.edition)
6365
service._edition = json.edition;
6466
service.comment = json.comment;

0 commit comments

Comments
 (0)