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

Reply via email to