From f196d2fba80c2dd78ffe135eadd59265171a3d8e Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Aug 2026 09:07:42 +0100 Subject: [PATCH 1/4] validate length of input in InstantDeserializer --- .../jsr310/deser/InstantDeserializer.java | 2 + .../jsr310/deser/JSR310DeserializerBase.java | 21 ++++++++++ .../datatype/jsr310/ModuleTestBase.java | 8 ++++ .../jsr310/deser/InstantDeserTest.java | 38 +++++++++++++++++++ 4 files changed, 69 insertions(+) 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 ced787dd..2604ba36 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 @@ -424,9 +424,11 @@ protected T _fromString(JsonParser p, DeserializationContext ctxt, if (dots >= 0) { // negative if not simple number try { if (dots == 0) { + _validateTimestampLength(p, string); return _fromLong(ctxt, NumberInput.parseLong(string)); } if (dots == 1) { + _validateTimestampLength(p, string); return _fromDecimal(ctxt, NumberInput.parseBigDecimal(string, false)); } } catch (NumberFormatException e) { diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java index 27b2f009..929a4409 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java @@ -22,6 +22,8 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.core.io.NumberInput; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -238,4 +240,23 @@ protected DateTimeException _peelDTE(DateTimeException e) { } return e; } + + /** + * Helper method to validate length of a stringified numeric Date/Time value + * against {@link StreamReadConstraints} limits. + * + * @since 2.19 + */ + protected void _validateTimestampLength(JsonParser p, String value) + throws StreamConstraintsException + { + final int len = value.length(); + StreamReadConstraints constraints = p.streamReadConstraints(); + // If there's a decimal point, it's a floating-point value; otherwise integer + if (value.indexOf('.') >= 0) { + constraints.validateFPLength(len); + } else { + constraints.validateIntegerLength(len); + } + } } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java index cc6725a8..1ed01eb9 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/ModuleTestBase.java @@ -66,6 +66,14 @@ protected void verifyException(Throwable e, String... matches) throw new Error("Expected an exception with one of substrings ("+Arrays.asList(matches)+"): got one with message \""+msg+"\""); } + protected static String repeat(String s, int count) { + StringBuilder sb = new StringBuilder(s.length() * count); + for (int i = 0; i < count; i++) { + sb.append(s); + } + return sb.toString(); + } + protected static Map asMap(T key, String value) { return Collections.singletonMap(key, value); } diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java index cc0f7d12..87c3ae7e 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java @@ -9,6 +9,8 @@ import org.junit.Test; +import com.fasterxml.jackson.core.StreamReadConstraints; +import com.fasterxml.jackson.core.exc.StreamConstraintsException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.exc.MismatchedInputException; @@ -640,4 +642,40 @@ public void testISO8601ColonlessRegexDoesNotAffectNegativeYearsWithColonless() { assertTrue("Matcher finds +0100 as an colonless offset", matcher.find()); assertEquals("Matcher groups +0100 as an colonless offset", matcher.group(), "+0100"); } + + // [databind#6133]: StreamReadConstraints should limit numeric string lengths + // parsed via _fromString to prevent excessive BigDecimal construction. + // NOTE: values MUST be quoted -- unquoted ones are Number tokens, limits for + // which are enforced by the streaming parser and not by this deserializer. + @Test + public void testNumericStringRespectsStreamReadConstraints() throws Exception + { + final int MAX_ALLOWED_LEN = StreamReadConstraints.DEFAULT_MAX_NUM_LEN; + + // Normal epoch seconds as integer should work + assertNotNull(MAPPER.readValue(q("1234567890"), Instant.class)); + + // Normal epoch seconds with decimal should work + assertNotNull(MAPPER.readValue(q("1234567890.123456789"), Instant.class)); + + // A very long integer string (exceeding default 1000-digit limit) should fail + String longInt = repeat("1", MAX_ALLOWED_LEN + 1); + try { + MAPPER.readValue(q(longInt), Instant.class); + fail("Should not pass with excessively long integer string"); + } catch (StreamConstraintsException e) { + verifyException(e, "Number value length"); + verifyException(e, "exceeds the maximum allowed"); + } + + // A very long decimal string (exceeding default 1000-char limit) should fail + String longDecimal = "1234." + repeat("9", MAX_ALLOWED_LEN); + try { + MAPPER.readValue(q(longDecimal), Instant.class); + fail("Should not pass with excessively long decimal string"); + } catch (StreamConstraintsException e) { + verifyException(e, "Number value length"); + verifyException(e, "exceeds the maximum allowed"); + } + } } From 10342ef90511b45d18ccb99956f27b0484ce5d09 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 8 Aug 2026 09:09:17 +0100 Subject: [PATCH 2/4] Update @since annotation for _validateTimestampLength --- .../jackson/datatype/jsr310/deser/JSR310DeserializerBase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java index 929a4409..71e18c10 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java @@ -245,7 +245,7 @@ protected DateTimeException _peelDTE(DateTimeException e) { * Helper method to validate length of a stringified numeric Date/Time value * against {@link StreamReadConstraints} limits. * - * @since 2.19 + * @since 2.18.10 */ protected void _validateTimestampLength(JsonParser p, String value) throws StreamConstraintsException From bc6616b47604f53958cf7b53f277d5a932bd9b35 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sat, 8 Aug 2026 19:28:20 -0700 Subject: [PATCH 3/4] Add release notes --- release-notes/VERSION-2.x | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index ac77f20b..1a71b206 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -8,6 +8,11 @@ Modules: === Releases === ------------------------------------------------------------------------ +2.18.10 (not yet released) + +#387: Validate length of input in `InstantDeserializer` + (fix by @pjfanning) + 2.18.9 (07-Jul-2026) 2.18.8 (28-May-2026) 2.18.7 (24-Apr-2026) From fcb0b9dd3c76edcb49ccafd4c1afb1c8069bc956 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 9 Aug 2026 10:48:35 -0700 Subject: [PATCH 4/4] Minor tweaking --- .../datatype/jsr310/deser/InstantDeserializer.java | 4 ++-- .../datatype/jsr310/deser/JSR310DeserializerBase.java | 10 +++++++--- .../datatype/jsr310/deser/InstantDeserTest.java | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) 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 2604ba36..c80bd5e6 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 @@ -424,11 +424,11 @@ protected T _fromString(JsonParser p, DeserializationContext ctxt, if (dots >= 0) { // negative if not simple number try { if (dots == 0) { - _validateTimestampLength(p, string); + _validateTimestampLength(p, string, false); return _fromLong(ctxt, NumberInput.parseLong(string)); } if (dots == 1) { - _validateTimestampLength(p, string); + _validateTimestampLength(p, string, true); return _fromDecimal(ctxt, NumberInput.parseBigDecimal(string, false)); } } catch (NumberFormatException e) { diff --git a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java index 71e18c10..b48b9225 100644 --- a/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java +++ b/datetime/src/main/java/com/fasterxml/jackson/datatype/jsr310/deser/JSR310DeserializerBase.java @@ -245,15 +245,19 @@ protected DateTimeException _peelDTE(DateTimeException e) { * Helper method to validate length of a stringified numeric Date/Time value * against {@link StreamReadConstraints} limits. * + * @param p Parser to get constraints from + * @param value Stringified numeric value to validate + * @param isFP Whether {@code value} is a floating-point (has decimal point) + * or integer number; caller knows this already so we avoid re-scanning + * * @since 2.18.10 */ - protected void _validateTimestampLength(JsonParser p, String value) + protected void _validateTimestampLength(JsonParser p, String value, boolean isFP) throws StreamConstraintsException { final int len = value.length(); StreamReadConstraints constraints = p.streamReadConstraints(); - // If there's a decimal point, it's a floating-point value; otherwise integer - if (value.indexOf('.') >= 0) { + if (isFP) { constraints.validateFPLength(len); } else { constraints.validateIntegerLength(len); diff --git a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java index 87c3ae7e..6222ac4f 100644 --- a/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java +++ b/datetime/src/test/java/com/fasterxml/jackson/datatype/jsr310/deser/InstantDeserTest.java @@ -643,7 +643,7 @@ public void testISO8601ColonlessRegexDoesNotAffectNegativeYearsWithColonless() { assertEquals("Matcher groups +0100 as an colonless offset", matcher.group(), "+0100"); } - // [databind#6133]: StreamReadConstraints should limit numeric string lengths + // [modules-java8#387]: StreamReadConstraints should limit numeric string lengths // parsed via _fromString to prevent excessive BigDecimal construction. // NOTE: values MUST be quoted -- unquoted ones are Number tokens, limits for // which are enforced by the streaming parser and not by this deserializer.