Skip to content

Commit 9c6b5b4

Browse files
Propagate recursion limit when parsing MessageSet extensions.
PiperOrigin-RevId: 949140156
1 parent baeb572 commit 9c6b5b4

8 files changed

Lines changed: 184 additions & 93 deletions

File tree

java/core/src/main/java/com/google/protobuf/CodedInputStreamReader.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ private CodedInputStreamReader(CodedInputStream input) {
4646
this.input.wrapper = this;
4747
}
4848

49+
/** Returns the remaining recursion budget for this CodedInputStream. */
50+
int getRemainingInputRecursionLimit() {
51+
return input.recursionLimit - input.messageDepth - input.groupDepth;
52+
}
53+
4954
public boolean shouldDiscardUnknownFields() {
5055
return input.shouldDiscardUnknownFields();
5156
}

java/core/src/main/java/com/google/protobuf/ExtensionSchema.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@ abstract void parseLengthPrefixedMessageSetItem(
6666
throws IOException;
6767

6868
/**
69-
* Parses the entire content of a {@link ByteString} as one MessageSet item. Unlike {@link
70-
* #parseLengthPrefixedMessageSetItem}, there isn't a length-prefix.
69+
* Parses the entire content of a {@link CodedInputStream} that wraps a {@link ByteString} as one
70+
* MessageSet item. Unlike {@link #parseLengthPrefixedMessageSetItem}, there isn't a
71+
* length-prefix.
7172
*/
7273
abstract void parseMessageSetItem(
73-
ByteString data,
74+
CodedInputStream input,
7475
Object extension,
7576
ExtensionRegistryLite extensionRegistry,
7677
FieldSet<T> extensions)

java/core/src/main/java/com/google/protobuf/ExtensionSchemaLite.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ void parseLengthPrefixedMessageSetItem(
541541

542542
@Override
543543
void parseMessageSetItem(
544-
ByteString data,
544+
CodedInputStream input,
545545
Object extensionObject,
546546
ExtensionRegistryLite extensionRegistry,
547547
FieldSet<ExtensionDescriptor> extensions)
@@ -551,8 +551,6 @@ void parseMessageSetItem(
551551

552552
MessageLite.Builder builder = extension.getMessageDefaultInstance().newBuilderForType();
553553

554-
final CodedInputStream input = data.newCodedInput();
555-
556554
builder.mergeFrom(input, extensionRegistry);
557555
extensions.setField(extension.descriptor, builder.buildPartial());
558556
input.checkLastTagWas(0);

java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,14 @@ private <Message2T extends MessageLite> void mergeMessageSetExtensionFromCodedSt
872872
// Process the raw bytes.
873873
if (rawBytes != null && typeId != 0) { // Zero is not a valid type ID.
874874
if (extension != null) { // We known the type
875-
mergeMessageSetExtensionFromBytes(rawBytes, extensionRegistry, extension);
875+
CodedInputStream subInput = rawBytes.newCodedInput();
876+
int remainingInputRecursionLimit =
877+
input.recursionLimit - input.messageDepth - input.groupDepth - 1;
878+
if (remainingInputRecursionLimit < 0) {
879+
throw InvalidProtocolBufferException.recursionLimitExceeded();
880+
}
881+
subInput.setRecursionLimit(remainingInputRecursionLimit);
882+
mergeMessageSetExtension(subInput, extensionRegistry, extension);
876883
} else { // We don't know how to parse this. Ignore it.
877884
if (rawBytes != null) {
878885
mergeLengthDelimitedField(typeId, rawBytes);
@@ -893,8 +900,8 @@ private void eagerlyMergeMessageSetExtension(
893900
boolean unused = parseExtension(input, extensionRegistry, extension, tag, fieldNumber);
894901
}
895902

896-
private void mergeMessageSetExtensionFromBytes(
897-
ByteString rawBytes,
903+
private void mergeMessageSetExtension(
904+
CodedInputStream input,
898905
ExtensionRegistryLite extensionRegistry,
899906
GeneratedExtension<?, ?> extension)
900907
throws IOException {
@@ -906,7 +913,7 @@ private void mergeMessageSetExtensionFromBytes(
906913
if (subBuilder == null) {
907914
subBuilder = extension.getMessageDefaultInstance().newBuilderForType();
908915
}
909-
subBuilder.mergeFrom(rawBytes, extensionRegistry);
916+
subBuilder.mergeFrom(input, extensionRegistry);
910917
MessageLite value = subBuilder.build();
911918

912919
ensureExtensionsAreMutable()

java/core/src/main/java/com/google/protobuf/MessageReflection.java

Lines changed: 48 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -294,19 +294,8 @@ Object parseMessage(
294294
CodedInputStream input,
295295
ExtensionRegistryLite registry,
296296
Descriptors.FieldDescriptor descriptor,
297-
Message defaultInstance)
298-
throws IOException;
299-
300-
/**
301-
* Parse from a ByteString into a sub field message defined based on either FieldDescriptor or
302-
* the default instance. There isn't a varint indicating the length of the message at the
303-
* beginning of the input ByteString.
304-
*/
305-
Object parseMessageFromBytes(
306-
ByteString bytes,
307-
ExtensionRegistryLite registry,
308-
Descriptors.FieldDescriptor descriptor,
309-
Message defaultInstance)
297+
Message defaultInstance,
298+
boolean inputLengthPrefixed)
310299
throws IOException;
311300

312301
/**
@@ -494,7 +483,8 @@ public Object parseMessage(
494483
CodedInputStream input,
495484
ExtensionRegistryLite extensionRegistry,
496485
Descriptors.FieldDescriptor field,
497-
Message defaultInstance)
486+
Message defaultInstance,
487+
boolean inputLengthPrefixed)
498488
throws IOException {
499489
Message.Builder subBuilder;
500490
// When default instance is not null. The field is an extension field.
@@ -509,31 +499,11 @@ public Object parseMessage(
509499
subBuilder.mergeFrom(originalMessage);
510500
}
511501
}
512-
input.readMessage(subBuilder, extensionRegistry);
513-
return subBuilder.buildPartial();
514-
}
515-
516-
@Override
517-
public Object parseMessageFromBytes(
518-
ByteString bytes,
519-
ExtensionRegistryLite extensionRegistry,
520-
Descriptors.FieldDescriptor field,
521-
Message defaultInstance)
522-
throws IOException {
523-
Message.Builder subBuilder;
524-
// When default instance is not null. The field is an extension field.
525-
if (defaultInstance != null) {
526-
subBuilder = defaultInstance.newBuilderForType();
502+
if (inputLengthPrefixed) {
503+
input.readMessage(subBuilder, extensionRegistry);
527504
} else {
528-
subBuilder = builder.newBuilderForField(field);
505+
subBuilder.mergeFrom(input, extensionRegistry);
529506
}
530-
if (!field.isRepeated()) {
531-
Message originalMessage = (Message) getField(field);
532-
if (originalMessage != null) {
533-
subBuilder.mergeFrom(originalMessage);
534-
}
535-
}
536-
subBuilder.mergeFrom(bytes, extensionRegistry);
537507
return subBuilder.buildPartial();
538508
}
539509

@@ -762,7 +732,8 @@ public Object parseMessage(
762732
CodedInputStream input,
763733
ExtensionRegistryLite registry,
764734
Descriptors.FieldDescriptor field,
765-
Message defaultInstance)
735+
Message defaultInstance,
736+
boolean inputLengthPrefixed)
766737
throws IOException {
767738
Message.Builder subBuilder = defaultInstance.newBuilderForType();
768739
if (!field.isRepeated()) {
@@ -771,7 +742,11 @@ public Object parseMessage(
771742
subBuilder.mergeFrom(originalMessage);
772743
}
773744
}
774-
input.readMessage(subBuilder, registry);
745+
if (inputLengthPrefixed) {
746+
input.readMessage(subBuilder, registry);
747+
} else {
748+
subBuilder.mergeFrom(input, registry);
749+
}
775750
return subBuilder.buildPartial();
776751
}
777752

@@ -823,24 +798,6 @@ public void mergeMessage(
823798
}
824799
}
825800

826-
@Override
827-
public Object parseMessageFromBytes(
828-
ByteString bytes,
829-
ExtensionRegistryLite registry,
830-
Descriptors.FieldDescriptor field,
831-
Message defaultInstance)
832-
throws IOException {
833-
Message.Builder subBuilder = defaultInstance.newBuilderForType();
834-
if (!field.isRepeated()) {
835-
Message originalMessage = (Message) getField(field);
836-
if (originalMessage != null) {
837-
subBuilder.mergeFrom(originalMessage);
838-
}
839-
}
840-
subBuilder.mergeFrom(bytes, registry);
841-
return subBuilder.buildPartial();
842-
}
843-
844801
@Override
845802
public MergeTarget newMergeTargetForField(
846803
Descriptors.FieldDescriptor descriptor, Message defaultInstance) {
@@ -977,7 +934,8 @@ public Object parseMessage(
977934
CodedInputStream input,
978935
ExtensionRegistryLite registry,
979936
Descriptors.FieldDescriptor field,
980-
Message defaultInstance)
937+
Message defaultInstance,
938+
boolean inputLengthPrefixed)
981939
throws IOException {
982940
Message.Builder subBuilder = defaultInstance.newBuilderForType();
983941
if (!field.isRepeated()) {
@@ -986,7 +944,11 @@ public Object parseMessage(
986944
subBuilder.mergeFrom(originalMessage);
987945
}
988946
}
989-
input.readMessage(subBuilder, registry);
947+
if (inputLengthPrefixed) {
948+
input.readMessage(subBuilder, registry);
949+
} else {
950+
subBuilder.mergeFrom(input, registry);
951+
}
990952
return subBuilder.buildPartial();
991953
}
992954

@@ -1067,24 +1029,6 @@ public void mergeMessage(
10671029
}
10681030
}
10691031

1070-
@Override
1071-
public Object parseMessageFromBytes(
1072-
ByteString bytes,
1073-
ExtensionRegistryLite registry,
1074-
Descriptors.FieldDescriptor field,
1075-
Message defaultInstance)
1076-
throws IOException {
1077-
Message.Builder subBuilder = defaultInstance.newBuilderForType();
1078-
if (!field.isRepeated()) {
1079-
Message originalMessage = (Message) getField(field);
1080-
if (originalMessage != null) {
1081-
subBuilder.mergeFrom(originalMessage);
1082-
}
1083-
}
1084-
subBuilder.mergeFrom(bytes, registry);
1085-
return subBuilder.buildPartial();
1086-
}
1087-
10881032
@Override
10891033
public MergeTarget newMergeTargetForField(
10901034
Descriptors.FieldDescriptor descriptor, Message defaultInstance) {
@@ -1366,10 +1310,17 @@ private static void mergeMessageSetExtensionFromCodedStream(
13661310
}
13671311
input.checkLastTagWas(WireFormat.MESSAGE_SET_ITEM_END_TAG);
13681312

1313+
int remainingInputRecursionLimit =
1314+
input.recursionLimit - input.messageDepth - input.groupDepth - 1;
1315+
if (remainingInputRecursionLimit < 0) {
1316+
throw InvalidProtocolBufferException.recursionLimitExceeded();
1317+
}
1318+
13691319
// Process the raw bytes.
13701320
if (rawBytes != null && typeId != 0) { // Zero is not a valid type ID.
13711321
if (extension != null) { // We known the type
1372-
mergeMessageSetExtensionFromBytes(rawBytes, extension, extensionRegistry, target);
1322+
mergeMessageSetExtensionFromBytes(
1323+
rawBytes, extension, extensionRegistry, target, remainingInputRecursionLimit);
13731324
} else { // We don't know how to parse this. Ignore it.
13741325
if (rawBytes != null && unknownFields != null) {
13751326
unknownFields.mergeField(
@@ -1383,17 +1334,26 @@ private static void mergeMessageSetExtensionFromBytes(
13831334
ByteString rawBytes,
13841335
ExtensionRegistry.ExtensionInfo extension,
13851336
ExtensionRegistryLite extensionRegistry,
1386-
MergeTarget target)
1337+
MergeTarget target,
1338+
int remainingInputRecursionLimit)
13871339
throws IOException {
13881340

13891341
Descriptors.FieldDescriptor field = extension.descriptor;
13901342
boolean hasOriginalValue = target.hasField(field);
13911343

1344+
// TODO: b/535485217 - Consider letting InternalLazyField to concat the bytes to avoid parsing
1345+
// if the existing field is a lazy field with lazy bytes.
13921346
if (hasOriginalValue || ExtensionRegistryLite.isEagerlyParseMessageSets()) {
13931347
// If the field already exists, we just parse the field.
1348+
CodedInputStream input = rawBytes.newCodedInput();
1349+
input.setRecursionLimit(remainingInputRecursionLimit);
13941350
Object value =
1395-
target.parseMessageFromBytes(
1396-
rawBytes, extensionRegistry, field, extension.defaultInstance);
1351+
target.parseMessage(
1352+
input,
1353+
extensionRegistry,
1354+
field,
1355+
extension.defaultInstance,
1356+
/* inputLengthPrefixed= */ false);
13971357
target.setField(field, value);
13981358
} else {
13991359
// Use InternalLazyField to load MessageSet lazily.
@@ -1410,7 +1370,13 @@ private static void eagerlyMergeMessageSetExtension(
14101370
MergeTarget target)
14111371
throws IOException {
14121372
Descriptors.FieldDescriptor field = extension.descriptor;
1413-
Object value = target.parseMessage(input, extensionRegistry, field, extension.defaultInstance);
1373+
Object value =
1374+
target.parseMessage(
1375+
input,
1376+
extensionRegistry,
1377+
field,
1378+
extension.defaultInstance,
1379+
/* inputLengthPrefixed= */ true);
14141380
target.setField(field, value);
14151381
}
14161382
}

java/core/src/main/java/com/google/protobuf/MessageSetSchema.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,15 @@ boolean parseMessageSetItemOrUnknownField(
360360
if (extension != null) { // We known the type
361361
// TODO: Instead of reading into a temporary ByteString, maybe there is a way
362362
// to read directly from CodedInputStreamReader to the submessage?
363-
extensionSchema.parseMessageSetItem(rawBytes, extension, extensionRegistry, extensions);
363+
int remainingInputRecursionLimit = reader.getRemainingInputRecursionLimit() - 1;
364+
if (remainingInputRecursionLimit < 0) {
365+
throw InvalidProtocolBufferException.recursionLimitExceeded();
366+
}
367+
CodedInputStream rawBytesInput = rawBytes.newCodedInput();
368+
rawBytesInput.setRecursionLimit(remainingInputRecursionLimit);
369+
370+
extensionSchema.parseMessageSetItem(
371+
rawBytesInput, extension, extensionRegistry, extensions);
364372
} else {
365373
unknownFieldSchema.addLengthDelimited(unknownFields, typeId, rawBytes);
366374
}

java/core/src/test/java/com/google/protobuf/LazilyParsedMessageSetTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,64 @@ public void testLoadCorruptedLazyField_getSerializedSize() throws Exception {
227227
assertThat(container)
228228
.isEqualTo(TestMessageSetWireFormatContainer.parseFrom(bytes, extensionRegistry));
229229
}
230+
231+
private ByteString makeRecursivePayload(int depth) throws Exception {
232+
ByteString payload = ByteString.copyFromUtf8("payload");
233+
for (int i = 0; i < depth; ++i) {
234+
ByteString.Output extOut = ByteString.newOutput();
235+
CodedOutputStream extCout = CodedOutputStream.newInstance(extOut);
236+
// Write to the `recursive` field.
237+
extCout.writeBytes(16, payload);
238+
extCout.flush();
239+
ByteString extPayload = extOut.toByteString();
240+
241+
ByteString.Output out = ByteString.newOutput();
242+
CodedOutputStream cout = CodedOutputStream.newInstance(out);
243+
244+
// Item 1: normal order, ID first
245+
cout.writeTag(1, WireFormat.WIRETYPE_START_GROUP);
246+
cout.writeUInt32(2, TestMessageSetExtension1.messageSetExtension.getNumber());
247+
cout.writeBytes(3, ByteString.EMPTY);
248+
cout.writeTag(1, WireFormat.WIRETYPE_END_GROUP);
249+
250+
// Item 2: reversed order, payload first
251+
cout.writeTag(1, WireFormat.WIRETYPE_START_GROUP);
252+
cout.writeBytes(3, extPayload);
253+
cout.writeUInt32(2, TestMessageSetExtension1.messageSetExtension.getNumber());
254+
cout.writeTag(1, WireFormat.WIRETYPE_END_GROUP);
255+
256+
cout.flush();
257+
payload = out.toByteString();
258+
}
259+
return payload;
260+
}
261+
262+
private void testMessageSetRecursionLimit(boolean eager) throws Exception {
263+
boolean originalEagerlyParse = ExtensionRegistryLite.isEagerlyParseMessageSets();
264+
ExtensionRegistryLite.setEagerlyParseMessageSets(eager);
265+
try {
266+
ByteString payload = makeRecursivePayload(2000);
267+
268+
ExtensionRegistry registry = ExtensionRegistry.newInstance();
269+
registry.add(TestMessageSetExtension1.messageSetExtension);
270+
271+
Throwable exception =
272+
assertThrows(
273+
InvalidProtocolBufferException.class,
274+
() -> TestMessageSet.parseFrom(payload, registry));
275+
assertThat(exception).hasMessageThat().contains("too many levels of nesting");
276+
} finally {
277+
ExtensionRegistryLite.setEagerlyParseMessageSets(originalEagerlyParse);
278+
}
279+
}
280+
281+
@Test
282+
public void testRecursionLimit_eagerMessageSet() throws Exception {
283+
testMessageSetRecursionLimit(/* eager= */ true);
284+
}
285+
286+
@Test
287+
public void testRecursionLimit_lazyMessageSet() throws Exception {
288+
testMessageSetRecursionLimit(/* eager= */ false);
289+
}
230290
}

0 commit comments

Comments
 (0)