Skip to content

Commit f125aa5

Browse files
authored
Avoid toBigIntegerExact in JsonFormat to avoid degenerate parse behavior in the face of large exponents. (#26435)
PiperOrigin-RevId: 874768607
1 parent 2418c40 commit f125aa5

2 files changed

Lines changed: 57 additions & 9 deletions

File tree

java/util/src/main/java/com/google/protobuf/util/JsonFormat.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,11 +1774,13 @@ private int parseUint32(JsonElement json) throws InvalidProtocolBufferException
17741774
// "1.000" are treated as equal in JSON. For this reason we accept floating point values for
17751775
// integer fields as well as long as it actually is an integer (i.e., round(value) == value).
17761776
try {
1777-
BigDecimal decimalValue = new BigDecimal(json.getAsString());
1778-
BigInteger value = decimalValue.toBigIntegerExact();
1779-
if (value.signum() < 0 || value.compareTo(new BigInteger("FFFFFFFF", 16)) > 0) {
1777+
BigDecimal value = new BigDecimal(json.getAsString());
1778+
if (value.signum() < 0 || value.compareTo(MAX_UINT32) > 0) {
17801779
throw new InvalidProtocolBufferException("Out of range uint32 value: " + json);
17811780
}
1781+
if (value.remainder(BigDecimal.ONE).signum() != 0) {
1782+
throw new InvalidProtocolBufferException("Not an uint32 value: " + json);
1783+
}
17821784
return value.intValue();
17831785
} catch (RuntimeException e) {
17841786
InvalidProtocolBufferException ex = new InvalidProtocolBufferException(
@@ -1788,15 +1790,19 @@ private int parseUint32(JsonElement json) throws InvalidProtocolBufferException
17881790
}
17891791
}
17901792

1791-
private static final BigInteger MAX_UINT64 = new BigInteger("FFFFFFFFFFFFFFFF", 16);
1793+
private static final BigDecimal MAX_UINT32 = new BigDecimal(0xFFFFFFFFL);
1794+
private static final BigDecimal MAX_UINT64 =
1795+
new BigDecimal(new BigInteger("FFFFFFFFFFFFFFFF", 16));
17921796

17931797
private long parseUint64(JsonElement json) throws InvalidProtocolBufferException {
17941798
try {
1795-
BigDecimal decimalValue = new BigDecimal(json.getAsString());
1796-
BigInteger value = decimalValue.toBigIntegerExact();
1797-
if (value.compareTo(BigInteger.ZERO) < 0 || value.compareTo(MAX_UINT64) > 0) {
1799+
BigDecimal value = new BigDecimal(json.getAsString());
1800+
if (value.signum() < 0 || value.compareTo(MAX_UINT64) > 0) {
17981801
throw new InvalidProtocolBufferException("Out of range uint64 value: " + json);
17991802
}
1803+
if (value.remainder(BigDecimal.ONE).signum() != 0) {
1804+
throw new InvalidProtocolBufferException("Not an uint64 value: " + json);
1805+
}
18001806
return value.longValue();
18011807
} catch (RuntimeException e) {
18021808
InvalidProtocolBufferException ex = new InvalidProtocolBufferException(

java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,6 @@ public void testRejectFractionalUnsignedInt32() throws IOException {
390390
assertWithMessage("Exception is expected.").fail();
391391
} catch (InvalidProtocolBufferException expected) {
392392
assertThat(expected).hasMessageThat().isEqualTo("Not an uint32 value: 1.5");
393-
assertThat(expected).hasCauseThat().isNotNull();
394393
}
395394
}
396395

@@ -402,7 +401,50 @@ public void testRejectFractionalUnsignedInt64() throws IOException {
402401
assertWithMessage("Exception is expected.").fail();
403402
} catch (InvalidProtocolBufferException expected) {
404403
assertThat(expected).hasMessageThat().isEqualTo("Not an uint64 value: 1.5");
405-
assertThat(expected).hasCauseThat().isNotNull();
404+
}
405+
}
406+
407+
@Test
408+
public void testRejectLargeQuotedExponentInt32() throws IOException {
409+
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
410+
try {
411+
mergeFromJson("{\"optionalInt32\": \"1e536870000\"}", builder);
412+
assertWithMessage("Exception is expected.").fail();
413+
} catch (InvalidProtocolBufferException expected) {
414+
assertThat(expected).hasMessageThat().isEqualTo("Not an int32 value: \"1e536870000\"");
415+
}
416+
}
417+
418+
@Test
419+
public void testRejectLargeQuotedExponentUnsignedUint32() throws IOException {
420+
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
421+
try {
422+
mergeFromJson("{\"optionalUint32\": \"1e536870000\"}", builder);
423+
assertWithMessage("Exception is expected.").fail();
424+
} catch (InvalidProtocolBufferException expected) {
425+
assertThat(expected).hasMessageThat().isEqualTo("Out of range uint32 value: \"1e536870000\"");
426+
}
427+
}
428+
429+
@Test
430+
public void testRejectLargeQuotedExponentInt64() throws IOException {
431+
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
432+
try {
433+
mergeFromJson("{\"optionalInt64\": \"1e536870000\"}", builder);
434+
assertWithMessage("Exception is expected.").fail();
435+
} catch (InvalidProtocolBufferException expected) {
436+
assertThat(expected).hasMessageThat().isEqualTo("Not an int64 value: \"1e536870000\"");
437+
}
438+
}
439+
440+
@Test
441+
public void testRejectLargeQuotedExponentUnsignedUint64() throws IOException {
442+
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
443+
try {
444+
mergeFromJson("{\"optionalUint64\": \"1e536870000\"}", builder);
445+
assertWithMessage("Exception is expected.").fail();
446+
} catch (InvalidProtocolBufferException expected) {
447+
assertThat(expected).hasMessageThat().isEqualTo("Out of range uint64 value: \"1e536870000\"");
406448
}
407449
}
408450

0 commit comments

Comments
 (0)