Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,11 @@ public enum SpelMessage {

/** @since 6.2.19 */
MAX_OPERATIONS_EXCEEDED(Kind.ERROR, 1085,
"SpEL expression evaluation exceeded the threshold of ''{0}'' operations");
"SpEL expression evaluation exceeded the threshold of ''{0}'' operations"),

/** @since 7.0 */
MAX_NESTING_DEPTH_EXCEEDED(Kind.ERROR, 1086,
"SpEL expression structural nesting depth exceeded the threshold of ''{0}'' levels");


private final Kind kind;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ public class SpelParserConfiguration {
*/
public static final int DEFAULT_MAX_OPERATIONS = 10_000;

/**
* Default maximum structural nesting depth permitted for a SpEL expression: {@value}.
* <p>This guards against deeply nested inline lists, maps, and other recursive
* constructs that could cause excessive parser recursion.
* @since 7.0
* @see #SPRING_EXPRESSION_MAX_NESTING_DEPTH_PROPERTY_NAME
*/
public static final int DEFAULT_MAX_NESTING_DEPTH = 1_000;

/**
* System property to configure the default compiler mode for SpEL expression parsers: {@value}.
* <p><strong>NOTE</strong>: Instead of relying on a global default, applications
Expand All @@ -74,6 +83,20 @@ public class SpelParserConfiguration {
*/
public static final String SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME = "spring.expression.maxOperations";

/**
* System property to configure the default maximum structural nesting depth
* permitted for SpEL expression parsing: {@value}.
* <p><strong>NOTE</strong>: Instead of relying on a global default, applications
* and frameworks should ideally set an explicit custom value via the
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)}
* constructor which provides complete configuration control and the ability
* to override global defaults per use case.
* <p>Can also be configured via the {@link SpringProperties} mechanism.
* @since 7.0
* @see #DEFAULT_MAX_NESTING_DEPTH
*/
public static final String SPRING_EXPRESSION_MAX_NESTING_DEPTH_PROPERTY_NAME = "spring.expression.maxNestingDepth";


private static final SpelCompilerMode defaultCompilerMode;

Expand All @@ -98,6 +121,8 @@ public class SpelParserConfiguration {

private final int maximumOperations;

private final int maximumNestingDepth;


/**
* Create a new {@code SpelParserConfiguration} instance with default settings.
Expand Down Expand Up @@ -206,7 +231,7 @@ public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullabl
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength) {

this((compilerMode != null ? compilerMode : defaultCompilerMode), compilerClassLoader, autoGrowNullReferences,
autoGrowCollections, maximumAutoGrowSize, maximumExpressionLength, retrieveMaxOperations());
autoGrowCollections, maximumAutoGrowSize, maximumExpressionLength, retrieveMaxOperations(), retrieveMaxNestingDepth());
}

/**
Expand All @@ -223,14 +248,43 @@ public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullabl
* @param maximumOperations the maximum number of operations permitted during
* SpEL expression evaluation; must be a positive number
* @since 6.2.19
* @deprecated as of 7.0, in favor of
* {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)}
*/
@Deprecated(since = "7.0")
public SpelParserConfiguration(SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader,
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength,
int maximumOperations) {

this(compilerMode, compilerClassLoader, autoGrowNullReferences, autoGrowCollections,
maximumAutoGrowSize, maximumExpressionLength, maximumOperations, retrieveMaxNestingDepth());
}

/**
* Create a new {@code SpelParserConfiguration} instance.
* @param compilerMode the compiler mode that parsers using this configuration
* should use; must not be {@code null}
* @param compilerClassLoader the {@code ClassLoader} to use as the basis for
* expression compilation; or {@code null} to use the default {@code ClassLoader}
* @param autoGrowNullReferences if null references should automatically grow
* @param autoGrowCollections if collections should automatically grow
* @param maximumAutoGrowSize the maximum size to which a collection can auto grow
* @param maximumExpressionLength the maximum length of a SpEL expression;
* must be a positive number
* @param maximumOperations the maximum number of operations permitted during
* SpEL expression evaluation; must be a positive number
* @param maximumNestingDepth the maximum structural nesting depth permitted
* during SpEL expression parsing; must be a positive number
* @since 7.0
*/
public SpelParserConfiguration(SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader,
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength,
int maximumOperations, int maximumNestingDepth) {

Assert.notNull(compilerMode, "'compilerMode' must not be null");
Assert.isTrue(maximumExpressionLength > 0, "'maximumExpressionLength' must be a positive number");
Assert.isTrue(maximumOperations > 0, "'maximumOperations' must be a positive number");
Assert.isTrue(maximumNestingDepth > 0, "'maximumNestingDepth' must be a positive number");

this.compilerMode = compilerMode;
this.compilerClassLoader = compilerClassLoader;
Expand All @@ -239,6 +293,7 @@ public SpelParserConfiguration(SpelCompilerMode compilerMode, @Nullable ClassLoa
this.maximumAutoGrowSize = maximumAutoGrowSize;
this.maximumExpressionLength = maximumExpressionLength;
this.maximumOperations = maximumOperations;
this.maximumNestingDepth = maximumNestingDepth;
}


Expand Down Expand Up @@ -294,6 +349,14 @@ public int getMaximumOperations() {
return this.maximumOperations;
}

/**
* Return the maximum structural nesting depth permitted for a SpEL expression.
* @since 7.0
*/
public int getMaximumNestingDepth() {
return this.maximumNestingDepth;
}


private static int retrieveMaxOperations() {
String value = SpringProperties.getProperty(SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME);
Expand All @@ -313,4 +376,22 @@ private static int retrieveMaxOperations() {
}
}

private static int retrieveMaxNestingDepth() {
String value = SpringProperties.getProperty(SPRING_EXPRESSION_MAX_NESTING_DEPTH_PROPERTY_NAME);
if (!StringUtils.hasText(value)) {
return DEFAULT_MAX_NESTING_DEPTH;
}

try {
int maxDepth = Integer.parseInt(value.trim());
Assert.isTrue(maxDepth > 0, () -> "Value [" + maxDepth + "] for system property [" +
SPRING_EXPRESSION_MAX_NESTING_DEPTH_PROPERTY_NAME + "] must be positive");
return maxDepth;
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Failed to parse value for system property [" +
SPRING_EXPRESSION_MAX_NESTING_DEPTH_PROPERTY_NAME + "]: " + ex.getMessage(), ex);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
// The expression being parsed
private String expressionString = "";

// Current structural nesting depth (inline lists, maps) while parsing
private int nestingDepth;

// The token stream constructed from that expression string
private List<Token> tokenStream = Collections.emptyList();

Expand Down Expand Up @@ -134,6 +137,7 @@ protected SpelExpression doParseExpression(String expressionString, @Nullable Pa

try {
this.expressionString = expressionString;
this.nestingDepth = 0;
Tokenizer tokenizer = new Tokenizer(expressionString);
this.tokenStream = tokenizer.process();
this.tokenStreamLength = this.tokenStream.size();
Expand Down Expand Up @@ -161,6 +165,18 @@ private void checkExpressionLength(String string) {
}
}

private void enterNesting(int startPos) {
this.nestingDepth++;
int maxDepth = this.configuration.getMaximumNestingDepth();
if (this.nestingDepth > maxDepth) {
throw internalException(startPos, SpelMessage.MAX_NESTING_DEPTH_EXCEEDED, maxDepth);
}
}

private void exitNesting() {
this.nestingDepth--;
}

// expression
// : logicalOrExpression
// ( (ASSIGN^ logicalOrExpression)
Expand Down Expand Up @@ -636,6 +652,7 @@ private boolean maybeEatInlineListOrMap() {
if (t == null || !peekToken(TokenKind.LCURLY, true)) {
return false;
}
enterNesting(t.startPos);
SpelNodeImpl expr = null;
Token closingCurly = peekToken();
if (closingCurly != null && peekToken(TokenKind.RCURLY, true)) {
Expand Down Expand Up @@ -686,6 +703,7 @@ else if (peekToken(TokenKind.COLON, true)) { // map!
throw internalException(t.startPos, SpelMessage.OOD);
}
}
exitNesting();
this.constructedNodes.push(expr);
return true;
}
Expand Down
Loading