This is an automated email from the ASF dual-hosted git repository. jlmonteiro pushed a commit to branch johnzon-1.2.x in repository https://gitbox.apache.org/repos/asf/johnzon.git
commit 9f6ad2146a1a85cf9c5899ba6de9d3a624999f38 Author: Jean-Louis Monteiro <jlmonte...@tomitribe.com> AuthorDate: Wed May 10 23:19:41 2023 +0200 fix(JOHNZON-397): protection against very slow BigDecimal to BigInteger conversion at large scale Signed-off-by: Jean-Louis Monteiro <jlmonte...@tomitribe.com> --- .../org/apache/johnzon/core/JsonNumberImpl.java | 15 ++++++ .../org/apache/johnzon/core/JsonNumberTest.java | 56 +++++++++++++++++++--- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonNumberImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonNumberImpl.java index 4a1f5232..e5891cf3 100644 --- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonNumberImpl.java +++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonNumberImpl.java @@ -69,11 +69,13 @@ final class JsonNumberImpl implements JsonNumber, Serializable { @Override public BigInteger bigIntegerValue() { + checkBigIntegerScale(); return value.toBigInteger(); } @Override public BigInteger bigIntegerValueExact() { + checkBigIntegerScale(); return value.toBigIntegerExact(); } @@ -117,4 +119,17 @@ final class JsonNumberImpl implements JsonNumber, Serializable { throw new ArithmeticException("Not an int/long, use other value readers"); } } + + private void checkBigIntegerScale() { + // should be fine enough. Maybe we should externalize so users can pick something better if they need to + // it becomes their responsibility to fix the limit and may expose them to a DoS attack + final int limit = 1_000; + final int absScale = Math.abs(value.scale()); + + if (absScale > limit) { + throw new ArithmeticException(String.format( + "BigDecimal scale (%d) magnitude exceeds maximum allowed (%d)", + value.scale(), limit)); + } + } } diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonNumberTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonNumberTest.java index 2a2a12dc..57bbd560 100644 --- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonNumberTest.java +++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonNumberTest.java @@ -26,7 +26,9 @@ import javax.json.JsonArray; import javax.json.JsonNumber; import java.io.StringReader; import java.io.StringWriter; +import java.math.BigDecimal; import java.math.BigInteger; +import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; @@ -62,20 +64,62 @@ public class JsonNumberTest { @Test(expected=ArithmeticException.class) public void testBigIntegerExact() { - JsonArray array = Json.createArrayBuilder().add(100.0200).build(); array.getJsonNumber(0).bigIntegerValueExact(); - - } - + @Test public void testBigInteger() { - JsonArray array = Json.createArrayBuilder().add(100.0200).build(); Assert.assertEquals(new BigInteger("100"), array.getJsonNumber(0).bigIntegerValue()); + } + + @Test + public void testSlowBigIntegerConversion() { + JsonArray array = Json.createArrayBuilder() + .add(new BigDecimal("1000000000e1000")) + .add(Double.MAX_VALUE) + .build(); + + { // for Double + long start = System.nanoTime(); + for (int i = 1; i < 5; i++) { + // if it takes a few seconds in any machine, that's already too much + if (TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) > (3 * i)) { + fail("took too long: " + TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) + " s" + + " to compute " + i + " conversions toBigInteger"); + } + + array.getJsonNumber(1).bigIntegerValue(); + } + long end = System.nanoTime(); + System.out.println("took: " + TimeUnit.NANOSECONDS.toMillis(end - start) + " ms"); + } + + { // for Number + long start = System.nanoTime(); + for (int i = 1; i < 100; i++) { + // if it takes a second in any machine, that's already too much + // depends on the allowed scale in JsonNumberImpl#checkBigIntegerScale + if (TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) > (30 * i)) { + fail("took too long: " + TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) + " s" + + " to compute " + i + " conversions toBigInteger"); + } + + array.getJsonNumber(0).bigIntegerValue(); + } + long end = System.nanoTime(); + System.out.println("took: " + TimeUnit.NANOSECONDS.toMillis(end - start) + " ms"); + } + } + + @Test(expected = ArithmeticException.class) + public void testBigIntegerConversionLimit() { + JsonArray array = Json.createArrayBuilder() + .add(new BigDecimal("1e1001")) // limit is 1000 by default + .build(); - + array.getJsonNumber(0).bigIntegerValue(); } @Test