From ebe3d7e96bbe69d9faadf038c8053611d93cc860 Mon Sep 17 00:00:00 2001 From: Lyin-OTTF <3037869661@qq.com> Date: Tue, 20 Oct 2026 18:15:38 +0800 Subject: [PATCH] Cap the length of numeric literals in filter expressions A numeric literal in a filter is converted to a BigDecimal while the path is compiled, and the compiler had no limit on the token length. A path text that is controlled by the caller could therefore force unbounded CPU and allocation work before any document was read. Literals longer than 1000 characters now fail with an InvalidPathException. --- .../internal/filter/FilterCompiler.java | 26 +++++++++++++++++++ .../jayway/jsonpath/FilterCompilerTest.java | 18 +++++++++++++ 2 files changed, 44 insertions(+) diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java index ecec3709..10902152 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java @@ -47,6 +47,25 @@ public class FilterCompiler { private static final char PATTERN = '/'; private static final char IGNORE_CASE = 'i'; + /** + * Upper bound for the length of a numeric literal inside a filter. The literal is turned into a + * {@link java.math.BigDecimal} during compilation, so an arbitrarily long token would let a caller + * spend unbounded CPU and memory before any document is evaluated. + */ + private static final int MAX_NUMBER_LITERAL_LENGTH = 1000; + + /** + * Signals that a numeric literal exceeds {@link #MAX_NUMBER_LITERAL_LENGTH}. It is an + * {@link InvalidPathException} so callers keep seeing the documented exception type, but it is + * rethrown by {@link #readExpression()} instead of being handled as "this is not a relational + * expression", which would hide the real problem and fail with a confusing message later on. + */ + private static final class NumberLiteralTooLongException extends InvalidPathException { + NumberLiteralTooLongException(String message) { + super(message); + } + } + private CharacterIndex filter; public static Filter compile(String filterString) { @@ -194,6 +213,9 @@ private RelationalExpressionNode readExpression() { ValueNode right = readValueNode(); return new RelationalExpressionNode(left, operator, right); } + catch (NumberLiteralTooLongException exc) { + throw exc; + } catch (InvalidPathException exc) { filter.setPosition(savepoint); } @@ -327,6 +349,10 @@ private NumberNode readNumberLiteral() { while (filter.inBounds() && filter.isNumberCharacter(filter.position())) { filter.incrementPosition(1); + if (filter.position() - begin > MAX_NUMBER_LITERAL_LENGTH) { + throw new NumberLiteralTooLongException( + "Number literal exceeds the maximum allowed length of " + MAX_NUMBER_LITERAL_LENGTH); + } } CharSequence numberLiteral = filter.subSequence(begin, filter.position()); logger.trace("NumberLiteral from {} to {} -> [{}]", begin, filter.position(), numberLiteral); diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterCompilerTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterCompilerTest.java index 30628015..701fd462 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/FilterCompilerTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/FilterCompilerTest.java @@ -102,4 +102,22 @@ private void assertInvalidPathException(String filter) { //e.printStackTrace(); } } + + @Test + public void number_literal_longer_than_the_limit_is_rejected() { + String number = "9".repeat(1001); + + InvalidPathException exception = assertThrows(InvalidPathException.class, + () -> compile("[?(@.x == " + number + ")]")); + + assertThat(exception.getMessage()).contains("maximum allowed length"); + } + + @Test + public void number_literal_at_the_limit_is_accepted() { + String number = "9".repeat(1000); + + assertThat(compile("[?(@.x == " + number + ")]").toString()) + .isEqualTo("[?(@['x'] == " + number + ")]"); + } }