Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
abracadv8 committed Oct 3, 2018
1 parent cf53b82 commit cab47f5
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,6 +41,11 @@ public class DurationDeserializer extends JSR310DeserializerBase<Duration>

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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -52,6 +55,11 @@ public class InstantDeserializer<T extends Temporal>
{
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]
*
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down

0 comments on commit cab47f5

Please sign in to comment.