Skip to content

Commit 9684204

Browse files
authored
Fix #4011: add maximum length, nesting limits for canonical type definitions (#4013)
1 parent badad56 commit 9684204

File tree

3 files changed

+81
-7
lines changed

3 files changed

+81
-7
lines changed

release-notes/VERSION-2.x

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Project: jackson-databind
2727
on serialization
2828
#4008: Optimize `ObjectNode` findValue(s) and findParent(s) fast paths
2929
(contributed by David S)
30+
#4011: Add guardrail setting for `TypeParser` handling of type parameters
3031

3132
2.15.3 (not yet released)
3233

src/main/java/com/fasterxml/jackson/databind/type/TypeParser.java

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,22 @@ public class TypeParser
1414
{
1515
private static final long serialVersionUID = 1L;
1616

17+
/**
18+
* Maximum length of canonical type definition we will try to parse.
19+
* Used as protection for malformed generic type declarations.
20+
*
21+
* @since 2.16
22+
*/
23+
protected static final int MAX_TYPE_LENGTH = 64_000;
24+
25+
/**
26+
* Maximum levels of nesting allowed for parameterized types.
27+
* Used as protection for malformed generic type declarations.
28+
*
29+
* @since 2.16
30+
*/
31+
protected static final int MAX_TYPE_NESTING = 1000;
32+
1733
protected final TypeFactory _factory;
1834

1935
public TypeParser(TypeFactory f) {
@@ -29,16 +45,24 @@ public TypeParser withFactory(TypeFactory f) {
2945

3046
public JavaType parse(String canonical) throws IllegalArgumentException
3147
{
48+
if (canonical.length() > MAX_TYPE_LENGTH) {
49+
throw new IllegalArgumentException(String.format(
50+
"Failed to parse type %s: too long (%d characters), maximum length allowed: %d",
51+
_quoteTruncated(canonical),
52+
canonical.length(),
53+
MAX_TYPE_LENGTH));
54+
55+
}
3256
MyTokenizer tokens = new MyTokenizer(canonical.trim());
33-
JavaType type = parseType(tokens);
57+
JavaType type = parseType(tokens, MAX_TYPE_NESTING);
3458
// must be end, now
3559
if (tokens.hasMoreTokens()) {
3660
throw _problem(tokens, "Unexpected tokens after complete type");
3761
}
3862
return type;
3963
}
4064

41-
protected JavaType parseType(MyTokenizer tokens)
65+
protected JavaType parseType(MyTokenizer tokens, int nestingAllowed)
4266
throws IllegalArgumentException
4367
{
4468
if (!tokens.hasMoreTokens()) {
@@ -50,7 +74,7 @@ protected JavaType parseType(MyTokenizer tokens)
5074
if (tokens.hasMoreTokens()) {
5175
String token = tokens.nextToken();
5276
if ("<".equals(token)) {
53-
List<JavaType> parameterTypes = parseTypes(tokens);
77+
List<JavaType> parameterTypes = parseTypes(tokens, nestingAllowed-1);
5478
TypeBindings b = TypeBindings.create(base, parameterTypes);
5579
return _factory._fromClass(null, base, b);
5680
}
@@ -60,12 +84,16 @@ protected JavaType parseType(MyTokenizer tokens)
6084
return _factory._fromClass(null, base, TypeBindings.emptyBindings());
6185
}
6286

63-
protected List<JavaType> parseTypes(MyTokenizer tokens)
87+
protected List<JavaType> parseTypes(MyTokenizer tokens, int nestingAllowed)
6488
throws IllegalArgumentException
6589
{
90+
if (nestingAllowed < 0) {
91+
throw _problem(tokens, "too deeply nested; exceeds maximum of "
92+
+MAX_TYPE_NESTING+" nesting levels");
93+
}
6694
ArrayList<JavaType> types = new ArrayList<JavaType>();
6795
while (tokens.hasMoreTokens()) {
68-
types.add(parseType(tokens));
96+
types.add(parseType(tokens, nestingAllowed));
6997
if (!tokens.hasMoreTokens()) break;
7098
String token = tokens.nextToken();
7199
if (">".equals(token)) return types;
@@ -88,10 +116,20 @@ protected Class<?> findClass(String className, MyTokenizer tokens)
88116

89117
protected IllegalArgumentException _problem(MyTokenizer tokens, String msg)
90118
{
91-
return new IllegalArgumentException(String.format("Failed to parse type '%s' (remaining: '%s'): %s",
92-
tokens.getAllInput(), tokens.getRemainingInput(), msg));
119+
return new IllegalArgumentException(String.format("Failed to parse type %s (remaining: %s): %s",
120+
_quoteTruncated(tokens.getAllInput()),
121+
_quoteTruncated(tokens.getRemainingInput()),
122+
msg));
93123
}
94124

125+
private static String _quoteTruncated(String str) {
126+
if (str.length() <= 1000) {
127+
return "'"+str+"'";
128+
}
129+
return String.format("'%s...'[truncated %d charaters]",
130+
str.substring(0, 1000), str.length() - 1000);
131+
}
132+
95133
final static class MyTokenizer extends StringTokenizer
96134
{
97135
protected final String _input;

src/test/java/com/fasterxml/jackson/databind/type/TestTypeFactory.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.fasterxml.jackson.databind.*;
99

1010
import static org.junit.Assert.assertNotEquals;
11+
import static org.junit.Assert.assertThrows;
1112

1213
/**
1314
* Simple tests to verify that the {@link TypeFactory} constructs
@@ -295,6 +296,40 @@ public void testCanonicalWithSpaces()
295296
assertEquals(t2, t1);
296297
}
297298

299+
// [databind#4011]
300+
public void testMalicousCanonical()
301+
{
302+
final TypeFactory tf = TypeFactory.defaultInstance();
303+
304+
// First: too deep nesting
305+
final int NESTING = TypeParser.MAX_TYPE_NESTING + 100;
306+
StringBuilder sb = new StringBuilder();
307+
for (int i = 0; i < NESTING; ++i) {
308+
sb.append("java.util.List<");
309+
}
310+
sb.append("java.lang.String");
311+
for (int i = 0; i < NESTING; ++i) {
312+
sb.append('>');
313+
}
314+
315+
final String deepCanonical = sb.toString();
316+
Exception e = assertThrows(IllegalArgumentException.class,
317+
() -> tf.constructFromCanonical(deepCanonical));
318+
verifyException(e, "too deeply nested");
319+
320+
// And second, too long in general
321+
final int MAX_LEN = TypeParser.MAX_TYPE_LENGTH + 100;
322+
sb = new StringBuilder().append("java.util.List<");
323+
while (sb.length() < MAX_LEN) {
324+
sb.append("java.lang.String,");
325+
}
326+
sb.append("java.lang.Integer>");
327+
final String longCanonical = sb.toString();
328+
e = assertThrows(IllegalArgumentException.class,
329+
() -> tf.constructFromCanonical(longCanonical));
330+
verifyException(e, "too long");
331+
}
332+
298333
/*
299334
/**********************************************************
300335
/* Unit tests: collection type parameter resolution

0 commit comments

Comments
 (0)