You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2026/09/07 14:59:59 UTC

(cxf) branch 4.1.x-fixes updated (43831e7b7ce -> 386ef39d991)

This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a change to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git


    from 43831e7b7ce Put a configurable limit on  deeply parenthesized sub-expressions in the FiqlParser (#3442)
     new c4cdb60d24f Put a max length on the Fiql search term and fix performance parsing issue (#3443)
     new 386ef39d991 Harden Content-Disposition filename parsing against ReDoS (#3448)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/cxf/attachment/ContentDisposition.java  |   3 +-
 .../cxf/attachment/ContentDispositionTest.java     |  37 ++++----
 .../cxf/jaxrs/ext/search/SearchContextImpl.java    |   4 +-
 .../cxf/jaxrs/ext/search/fiql/FiqlParser.java      |  31 +++++++
 .../jaxrs/ext/search/SearchContextImplTest.java    |   8 ++
 .../jaxrs/ext/search/fiql/FiqlParserReDoSTest.java | 102 +++++++++++++++++++++
 6 files changed, 165 insertions(+), 20 deletions(-)
 copy rt/bindings/corba/src/test/java/org/apache/cxf/binding/corba/runtime/CorbaStreamWriterTest.java => core/src/test/java/org/apache/cxf/attachment/ContentDispositionTest.java (56%)
 create mode 100644 rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserReDoSTest.java


(cxf) 02/02: Harden Content-Disposition filename parsing against ReDoS (#3448)

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 386ef39d9915f7e2f86f6d1ba6ec334c374d0deb
Author: Colm O hEigeartaigh <co...@users.noreply.github.com>
AuthorDate: Mon Sep 7 15:28:22 2026 +0100

    Harden Content-Disposition filename parsing against ReDoS (#3448)
    
    (cherry picked from commit 6d2b4231868979e12e68c4825d592e4c395d08ae)
---
 .../apache/cxf/attachment/ContentDisposition.java  |  3 +-
 .../cxf/attachment/ContentDispositionTest.java     | 48 ++++++++++++++++++++++
 2 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java b/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
index a6ae0f2b0a5..44a2d710270 100644
--- a/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
+++ b/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
@@ -32,8 +32,9 @@ public class ContentDisposition {
     private static final Pattern CD_HEADER_PARAMS_PATTERN =
             Pattern.compile(CD_HEADER_PARAMS_EXPRESSION);
 
+    // Keep the alternatives disjoint and prevent backtracking over the encoded value.
     private static final String CD_HEADER_EXT_PARAMS_EXPRESSION =
-            "(?i)(UTF-8|ISO-8859-1)''((?:%[0-9a-f]{2}|\\S)+)";
+            "(?i)(UTF-8|ISO-8859-1)''((?:%[0-9a-f]{2}|[^%\\s])++)";
     private static final Pattern CD_HEADER_EXT_PARAMS_PATTERN =
             Pattern.compile(CD_HEADER_EXT_PARAMS_EXPRESSION);
     private static final Pattern CODEPOINT_ENCODED_VALUE_PATTERN = Pattern.compile("&#[0-9]{4};|\\S");
diff --git a/core/src/test/java/org/apache/cxf/attachment/ContentDispositionTest.java b/core/src/test/java/org/apache/cxf/attachment/ContentDispositionTest.java
new file mode 100644
index 00000000000..45f503dac19
--- /dev/null
+++ b/core/src/test/java/org/apache/cxf/attachment/ContentDispositionTest.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.attachment;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class ContentDispositionTest {
+
+    @Test
+    public void testExtendedFilenameIsDecoded() {
+        ContentDisposition cd = new ContentDisposition("attachment;filename*=UTF-8''a%20file.txt");
+        assertEquals("attachment", cd.getType());
+        assertEquals("a file.txt", cd.getFilename());
+    }
+
+    @Test(timeout = 10000)
+    public void testMalformedExtendedFilenameDoesNotOverflow() {
+        StringBuilder value = new StringBuilder("attachment;filename*=UTF-8''");
+        for (int i = 0; i < 2000; i++) {
+            value.append("%41");
+        }
+        value.append(" x");
+
+        ContentDisposition cd = new ContentDisposition(value.toString());
+
+        assertNull(cd.getFilename());
+    }
+}
\ No newline at end of file


(cxf) 01/02: Put a max length on the Fiql search term and fix performance parsing issue (#3443)

Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit c4cdb60d24f3b23cc26f72f57e570680c33d6de1
Author: Colm O hEigeartaigh <co...@users.noreply.github.com>
AuthorDate: Mon Sep 7 15:13:32 2026 +0100

    Put a max length on the Fiql search term and fix performance parsing issue (#3443)
    
    (cherry picked from commit 913f72c74cfdcae0132c64ae36b7851921c17bc0)
---
 .../cxf/jaxrs/ext/search/SearchContextImpl.java    |   4 +-
 .../cxf/jaxrs/ext/search/fiql/FiqlParser.java      |  31 +++++++
 .../jaxrs/ext/search/SearchContextImplTest.java    |   8 ++
 .../jaxrs/ext/search/fiql/FiqlParserReDoSTest.java | 102 +++++++++++++++++++++
 4 files changed, 144 insertions(+), 1 deletion(-)

diff --git a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/SearchContextImpl.java b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/SearchContextImpl.java
index 00124ff69d1..14a831bfaea 100644
--- a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/SearchContextImpl.java
+++ b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/SearchContextImpl.java
@@ -185,7 +185,7 @@ public class SearchContextImpl implements SearchContext {
 
         final Map<String, String> props;
         if (parserProperties == null) {
-            props = new HashMap<>(6);
+            props = new HashMap<>(7);
             props.put(SearchUtils.DATE_FORMAT_PROPERTY,
                       (String)message.getContextualProperty(SearchUtils.DATE_FORMAT_PROPERTY));
             props.put(SearchUtils.TIMEZONE_SUPPORT_PROPERTY,
@@ -199,6 +199,8 @@ public class SearchContextImpl implements SearchContext {
                       (String)message.getContextualProperty(FiqlParser.SUPPORT_SINGLE_EQUALS));
             props.put(FiqlParser.MAX_PARENTHESIS_DEPTH,
                       (String)message.getContextualProperty(FiqlParser.MAX_PARENTHESIS_DEPTH));
+            props.put(FiqlParser.MAX_EXPRESSION_LENGTH,
+                      (String)message.getContextualProperty(FiqlParser.MAX_EXPRESSION_LENGTH));
         } else {
             props = parserProperties;
         }
diff --git a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java
index a71839402d0..19563717839 100644
--- a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java
+++ b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParser.java
@@ -70,10 +70,15 @@ public class FiqlParser<T> extends AbstractSearchConditionParser<T> {
      * the request thread. Must be a positive integer; the default is 64.
      */
     public static final String MAX_PARENTHESIS_DEPTH = "fiql.max.parenthesis.depth";
+    /**
+    * Context property limiting the length of a FIQL expression. The default is 4 KiB.
+     */
+    public static final String MAX_EXPRESSION_LENGTH = "fiql.max.expression.length";
     public static final String EXTENSION_COUNT = "count";
     protected static final String EXTENSION_COUNT_OPEN = EXTENSION_COUNT + "(";
 
     private static final int DEFAULT_MAX_PARENTHESIS_DEPTH = 64;
+    private static final int DEFAULT_MAX_EXPRESSION_LENGTH = 4 * 1024;
     private static final Map<String, ConditionType> OPERATORS_MAP;
     private static final Pattern COMPARATORS_PATTERN;
     private static final Pattern COMPARATORS_PATTERN_SINGLE_EQUALS;
@@ -110,6 +115,7 @@ public class FiqlParser<T> extends AbstractSearchConditionParser<T> {
     protected Pattern comparatorsPattern = COMPARATORS_PATTERN;
 
     private final int maxParenthesisDepth;
+    private final int maxExpressionLength;
 
     /**
      * Creates FIQL parser.
@@ -146,6 +152,8 @@ public class FiqlParser<T> extends AbstractSearchConditionParser<T> {
         super(tclass, contextProperties, beanProperties);
 
         this.maxParenthesisDepth = parseMaxParenthesisDepth(this.contextProperties.get(MAX_PARENTHESIS_DEPTH));
+        this.maxExpressionLength = parseMaxExpressionLength(
+            this.contextProperties.get(MAX_EXPRESSION_LENGTH));
 
         if (PropertyUtils.isTrue(this.contextProperties.get(SUPPORT_SINGLE_EQUALS))) {
             operatorsMap = new HashMap<>(operatorsMap);
@@ -172,6 +180,24 @@ public class FiqlParser<T> extends AbstractSearchConditionParser<T> {
         return depth;
     }
 
+    private static int parseMaxExpressionLength(String value) {
+        if (value == null || value.trim().isEmpty()) {
+            return DEFAULT_MAX_EXPRESSION_LENGTH;
+        }
+        final int length;
+        try {
+            length = Integer.parseInt(value.trim());
+        } catch (NumberFormatException ex) {
+            throw new IllegalArgumentException(MAX_EXPRESSION_LENGTH
+                + " must be a positive integer, got: " + value, ex);
+        }
+        if (length < 1) {
+            throw new IllegalArgumentException(MAX_EXPRESSION_LENGTH
+                + " must be a positive integer, got: " + value);
+        }
+        return length;
+    }
+
     /**
      * Parses expression and builds search filter. Names used in FIQL expression are names of getters/setters
      * in type T.
@@ -196,6 +222,11 @@ public class FiqlParser<T> extends AbstractSearchConditionParser<T> {
      */
     @Override
     public SearchCondition<T> parse(String fiqlExpression) throws SearchParseException {
+        if (fiqlExpression.length() > maxExpressionLength) {
+            throw new SearchParseException("Exceeded the maximum FIQL expression length of "
+                + maxExpressionLength + "; the limit can be adjusted with the "
+                + MAX_EXPRESSION_LENGTH + " property");
+        }
         ASTNode<T> ast = parseAndsOrsBrackets(fiqlExpression, 0);
         return ast.build();
     }
diff --git a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/SearchContextImplTest.java b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/SearchContextImplTest.java
index f30c9ea7b0c..b3c6b1c9e80 100644
--- a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/SearchContextImplTest.java
+++ b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/SearchContextImplTest.java
@@ -76,6 +76,14 @@ public class SearchContextImplTest {
         assertNotNull(new SearchContextImpl(m).getCondition(Book.class));
     }
 
+    @Test(expected = SearchParseException.class)
+    public void testMaxExpressionLengthPropertyIsHonoured() {
+        Message m = new MessageImpl();
+        m.put(FiqlParser.MAX_EXPRESSION_LENGTH, "10");
+        m.put(Message.QUERY_STRING, "_s=name==12345");
+        new SearchContextImpl(m).getCondition(Book.class);
+    }
+
     @Test
     public void testPlainQuery2() {
         Message m = new MessageImpl();
diff --git a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserReDoSTest.java b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserReDoSTest.java
new file mode 100644
index 00000000000..efdadbeb718
--- /dev/null
+++ b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/fiql/FiqlParserReDoSTest.java
@@ -0,0 +1,102 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.ext.search.fiql;
+
+import java.util.Collections;
+
+import org.apache.cxf.jaxrs.ext.search.SearchParseException;
+
+import org.junit.Test;
+
+public class FiqlParserReDoSTest {
+    @Test(timeout = 2000)
+    public void testLongExpressionWithoutComparatorIsRejectedQuickly() {
+        StringBuilder expression = new StringBuilder(32768);
+        for (int i = 0; i < 32768; i++) {
+            expression.append('a');
+        }
+        try {
+            new FiqlParser<>(Bean.class).parse(expression.toString());
+        } catch (SearchParseException ex) {
+            return;
+        }
+        throw new AssertionError("An expression without a comparator must be rejected");
+    }
+
+    @Test
+    public void testComparatorsRemainSupported() throws SearchParseException {
+        FiqlParser<Bean> parser = new FiqlParser<>(Bean.class);
+        parser.parse("name==value");
+        parser = new FiqlParser<>(Bean.class,
+            Collections.singletonMap(FiqlParser.SUPPORT_SINGLE_EQUALS, "true"));
+        parser.parse("name=first");
+    }
+
+    @Test
+    public void testConfiguredExpressionLengthLimit() throws SearchParseException {
+        FiqlParser<Bean> parser = new FiqlParser<>(Bean.class,
+            Collections.singletonMap(FiqlParser.MAX_EXPRESSION_LENGTH, "10"));
+        parser.parse("name==1234");
+        try {
+            parser.parse("name==12345");
+        } catch (SearchParseException ex) {
+            return;
+        }
+        throw new AssertionError("An expression over the configured length must be rejected");
+    }
+
+    @Test
+    public void testDefaultExpressionLengthLimitIsFourKiB() throws SearchParseException {
+        FiqlParser<Bean> parser = new FiqlParser<>(Bean.class);
+        parser.parse(expressionOfLength(4096));
+        try {
+            parser.parse(expressionOfLength(4097));
+        } catch (SearchParseException ex) {
+            return;
+        }
+        throw new AssertionError("The default expression length limit must be 4 KiB");
+    }
+
+    private static String expressionOfLength(int length) {
+        StringBuilder expression = new StringBuilder(length);
+        expression.append("name==");
+        while (expression.length() < length) {
+            expression.append('a');
+        }
+        return expression.substring(0, length);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidExpressionLengthLimitRejected() {
+        new FiqlParser<>(Bean.class,
+            Collections.singletonMap(FiqlParser.MAX_EXPRESSION_LENGTH, "0"));
+    }
+
+    public static class Bean {
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+}
\ No newline at end of file