From cab47f580a02a42d7ad26b0c3202e71d883ecfcf Mon Sep 17 00:00:00 2001 From: abracadvr8 Date: Tue, 2 Oct 2018 01:33:18 +0100 Subject: [PATCH] code to handle FasterXML/jackson-databind/issues/2141 Guards against numbers causing CPU or OOM issues when deserializing large exponential numbers into Instant or Duration by either: - Scientific notation too large (eg 10000e100000) --- .../jsr310/deser/DurationDeserializer.java | 13 +++++ .../jsr310/deser/InstantDeserializer.java | 17 +++++- .../jsr310/TestDurationDeserialization.java | 14 +++++ .../jsr310/TestInstantSerialization.java | 56 +++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java index be0ad8bd..7a838b9f 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/DurationDeserializer.java @@ -16,6 +16,7 @@ package com.fasterxml.jackson.datatype.jsr310.deser; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.JsonTokenId; @@ -40,6 +41,11 @@ public class DurationDeserializer extends JSR310DeserializerBase public static final DurationDeserializer INSTANCE = new DurationDeserializer(); + private static final BigDecimal INSTANT_MAX = new BigDecimal( + java.time.Instant.MAX.getEpochSecond() + "." + java.time.Instant.MAX.getNano()); + private static final BigDecimal INSTANT_MIN = new BigDecimal( + java.time.Instant.MIN.getEpochSecond() + "." + java.time.Instant.MIN.getNano()); + private DurationDeserializer() { super(Duration.class); @@ -52,6 +58,13 @@ public Duration deserialize(JsonParser parser, DeserializationContext context) t { case JsonTokenId.ID_NUMBER_FLOAT: BigDecimal value = parser.getDecimalValue(); + // If the decimal isnt within the bounds of a float, bail out + if(value.compareTo(INSTANT_MAX) > 0 || + value.compareTo(INSTANT_MIN) < 0) { + throw new JsonParseException(context.getParser(), + "Value of Float too large to be converted to Duration"); + } + long seconds = value.longValue(); int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds); return Duration.ofSeconds(seconds, nanoseconds); diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java index bec6a0f1..6fce08d0 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserializer.java @@ -17,6 +17,7 @@ package com.fasterxml.jackson.datatype.jsr310.deser; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.JsonTokenId; @@ -29,6 +30,8 @@ import java.io.IOException; import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.NumberFormat; import java.time.DateTimeException; import java.time.Instant; import java.time.OffsetDateTime; @@ -52,6 +55,11 @@ public class InstantDeserializer { private static final long serialVersionUID = 1L; + private static final BigDecimal INSTANT_MAX = new BigDecimal( + java.time.Instant.MAX.getEpochSecond() + "." + java.time.Instant.MAX.getNano()); + private static final BigDecimal INSTANT_MIN = new BigDecimal( + java.time.Instant.MIN.getEpochSecond() + "." + java.time.Instant.MIN.getNano()); + /** * Constants used to check if the time offset is zero. See [jackson-modules-java8#18] * @@ -277,8 +285,15 @@ protected T _fromLong(DeserializationContext context, long timestamp) timestamp, this.getZone(context))); } - protected T _fromDecimal(DeserializationContext context, BigDecimal value) + protected T _fromDecimal(DeserializationContext context, BigDecimal value) throws JsonParseException { + // If the decimal isnt within the bounds of an Instant, bail out + if(value.compareTo(INSTANT_MAX) > 0 || + value.compareTo(INSTANT_MIN) < 0) { + throw new JsonParseException(context.getParser(), + "Value of String too large to be converted to Instant"); + } + long seconds = value.longValue(); int nanoseconds = DecimalUtils.extractNanosecondDecimal(value, seconds); return fromNanoseconds.apply(new FromDecimalArguments( diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDurationDeserialization.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDurationDeserialization.java index 487f24bd..d8371904 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDurationDeserialization.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestDurationDeserialization.java @@ -3,6 +3,7 @@ import java.time.Duration; import java.time.temporal.TemporalAmount; +import com.fasterxml.jackson.core.JsonParseException; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -52,6 +53,19 @@ public void testDeserializationAsFloat03() throws Exception assertEquals("The value is not correct.", Duration.ofSeconds(13498L, 8374), value); } + /** + * This test can potentially hang the VM, so exit if it doesn't finish + * within a few seconds. + * @throws Exception + */ + @Test(timeout=3000, expected = JsonParseException.class) + public void testDeserializationAsFloatWhereStringTooLarge() throws Exception + { + String customDuration = "1000000000e1000000000"; + READER.without(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS) + .readValue(customDuration); + } + @Test public void testDeserializationAsFloat04() throws Exception { diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestInstantSerialization.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestInstantSerialization.java index 989934ff..03e0d014 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestInstantSerialization.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/TestInstantSerialization.java @@ -17,6 +17,7 @@ package com.fasterxml.jackson.datatype.jsr310; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -376,6 +377,61 @@ public void testDeserializationWithTypeInfo04() throws Exception assertEquals("The value is not correct.", date, value); } + /** + * This should be within the range of a max Instant and should pass + * @throws Exception + */ + @Test(timeout=3000) + public void testDeserializationWithTypeInfo05() throws Exception + { + Instant date = Instant.MAX; + String customInstant = date.getEpochSecond() +"."+ date.getNano(); + ObjectMapper m = newMapper() + .addMixIn(Temporal.class, MockObjectConfiguration.class); + Temporal value = m.readValue( + "[\"" + Instant.class.getName() + "\","+customInstant+"]", Temporal.class + ); + assertTrue("The value should be an Instant.", value instanceof Instant); + assertEquals("The value is not correct.", date, value); + } + + /** + * This test can potentially hang the VM, so exit if it doesn't finish + * within a few seconds. + * + * @throws Exception + */ + @Test(timeout=3000, expected = JsonParseException.class) + public void testDeserializationWithTypeInfoAndStringTooLarge01() throws Exception + { + String customInstant = "1000000000000e1000000000000"; + ObjectMapper m = newMapper() + .addMixIn(Temporal.class, MockObjectConfiguration.class); + m.readValue( + "[\"" + Instant.class.getName() + "\","+customInstant+"]", Temporal.class + ); + } + + /** + * This test can potentially hang the VM, so exit if it doesn't finish + * within a few seconds. + * + * @throws Exception + */ + @Test(timeout=3000, expected = JsonParseException.class) + public void testDeserializationWithTypeInfoAndStringTooLarge02() throws Exception + { + Instant date = Instant.MAX; + // Add in an few extra zeros to be longer than what an epoch should be + String customInstant = date.getEpochSecond() +"0000000000000000."+ date.getNano(); + ObjectMapper m = newMapper() + .addMixIn(Temporal.class, MockObjectConfiguration.class); + m.readValue( + "[\"" + Instant.class.getName() + "\","+customInstant+"]", Temporal.class + ); + } + + @Test public void testCustomPatternWithAnnotations01() throws Exception {