Skip to content

Commit 57093a8

Browse files
Avoid toBigIntegerExact in JsonFormat to avoid degenerate parse behavior in the face of large exponents.
#26032 PiperOrigin-RevId: 874768607
1 parent 2a45d14 commit 57093a8

2 files changed

Lines changed: 56 additions & 9 deletions

File tree

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1792,11 +1792,13 @@ private int parseUint32(JsonElement json) throws InvalidProtocolBufferException
17921792
// "1.000" are treated as equal in JSON. For this reason we accept floating point values for
17931793
// integer fields as well as long as it actually is an integer (i.e., round(value) == value).
17941794
try {
1795-
BigDecimal decimalValue = new BigDecimal(json.getAsString());
1796-
BigInteger value = decimalValue.toBigIntegerExact();
1795+
BigDecimal value = new BigDecimal(json.getAsString());
17971796
if (value.signum() < 0 || value.compareTo(MAX_UINT32) > 0) {
17981797
throw new InvalidProtocolBufferException("Out of range uint32 value: " + json);
17991798
}
1799+
if (value.remainder(BigDecimal.ONE).signum() != 0) {
1800+
throw new InvalidProtocolBufferException("Not an uint32 value: " + json);
1801+
}
18001802
return value.intValue();
18011803
} catch (RuntimeException e) {
18021804
InvalidProtocolBufferException ex = new InvalidProtocolBufferException(
@@ -1806,16 +1808,19 @@ private int parseUint32(JsonElement json) throws InvalidProtocolBufferException
18061808
}
18071809
}
18081810

1809-
private static final BigInteger MAX_UINT32 = new BigInteger("FFFFFFFF", 16);
1810-
private static final BigInteger MAX_UINT64 = new BigInteger("FFFFFFFFFFFFFFFF", 16);
1811+
private static final BigDecimal MAX_UINT32 = new BigDecimal(0xFFFFFFFFL);
1812+
private static final BigDecimal MAX_UINT64 =
1813+
new BigDecimal(new BigInteger("FFFFFFFFFFFFFFFF", 16));
18111814

18121815
private long parseUint64(JsonElement json) throws InvalidProtocolBufferException {
18131816
try {
1814-
BigDecimal decimalValue = new BigDecimal(json.getAsString());
1815-
BigInteger value = decimalValue.toBigIntegerExact();
1816-
if (value.compareTo(BigInteger.ZERO) < 0 || value.compareTo(MAX_UINT64) > 0) {
1817+
BigDecimal value = new BigDecimal(json.getAsString());
1818+
if (value.signum() < 0 || value.compareTo(MAX_UINT64) > 0) {
18171819
throw new InvalidProtocolBufferException("Out of range uint64 value: " + json);
18181820
}
1821+
if (value.remainder(BigDecimal.ONE).signum() != 0) {
1822+
throw new InvalidProtocolBufferException("Not an uint64 value: " + json);
1823+
}
18191824
return value.longValue();
18201825
} catch (RuntimeException e) {
18211826
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
@@ -469,7 +469,6 @@ public void testRejectFractionalUnsignedInt32() throws IOException {
469469
assertWithMessage("Exception is expected.").fail();
470470
} catch (InvalidProtocolBufferException expected) {
471471
assertThat(expected).hasMessageThat().isEqualTo("Not an uint32 value: 1.5");
472-
assertThat(expected).hasCauseThat().isNotNull();
473472
}
474473
}
475474

@@ -481,7 +480,50 @@ public void testRejectFractionalUnsignedInt64() throws IOException {
481480
assertWithMessage("Exception is expected.").fail();
482481
} catch (InvalidProtocolBufferException expected) {
483482
assertThat(expected).hasMessageThat().isEqualTo("Not an uint64 value: 1.5");
484-
assertThat(expected).hasCauseThat().isNotNull();
483+
}
484+
}
485+
486+
@Test
487+
public void testRejectLargeQuotedExponentInt32() throws IOException {
488+
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
489+
try {
490+
mergeFromJson("{\"optionalInt32\": \"1e536870000\"}", builder);
491+
assertWithMessage("Exception is expected.").fail();
492+
} catch (InvalidProtocolBufferException expected) {
493+
assertThat(expected).hasMessageThat().isEqualTo("Not an int32 value: \"1e536870000\"");
494+
}
495+
}
496+
497+
@Test
498+
public void testRejectLargeQuotedExponentUnsignedUint32() throws IOException {
499+
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
500+
try {
501+
mergeFromJson("{\"optionalUint32\": \"1e536870000\"}", builder);
502+
assertWithMessage("Exception is expected.").fail();
503+
} catch (InvalidProtocolBufferException expected) {
504+
assertThat(expected).hasMessageThat().isEqualTo("Out of range uint32 value: \"1e536870000\"");
505+
}
506+
}
507+
508+
@Test
509+
public void testRejectLargeQuotedExponentInt64() throws IOException {
510+
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
511+
try {
512+
mergeFromJson("{\"optionalInt64\": \"1e536870000\"}", builder);
513+
assertWithMessage("Exception is expected.").fail();
514+
} catch (InvalidProtocolBufferException expected) {
515+
assertThat(expected).hasMessageThat().isEqualTo("Not an int64 value: \"1e536870000\"");
516+
}
517+
}
518+
519+
@Test
520+
public void testRejectLargeQuotedExponentUnsignedUint64() throws IOException {
521+
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
522+
try {
523+
mergeFromJson("{\"optionalUint64\": \"1e536870000\"}", builder);
524+
assertWithMessage("Exception is expected.").fail();
525+
} catch (InvalidProtocolBufferException expected) {
526+
assertThat(expected).hasMessageThat().isEqualTo("Out of range uint64 value: \"1e536870000\"");
485527
}
486528
}
487529

0 commit comments

Comments
 (0)