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

(directory-ldap-api) branch master updated: Fix a potential wrongly nested filters (a final filter that is related to another final filter, instead of being associated to a connector filter)

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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/directory-ldap-api.git


The following commit(s) were added to refs/heads/master by this push:
     new f82ec7e99 Fix a potential wrongly nested filters (a final filter that is related to another final filter, instead of being associated to a connector filter)
f82ec7e99 is described below

commit f82ec7e9936ac021ff299ca1a1eaedda32ddb5a8
Author: emmanuel lecharny <el...@apache.org>
AuthorDate: Mon Sep 7 09:08:16 2026 +0200

    Fix a potential wrongly nested filters (a final filter that is related
    to another final filter, instead of being associated to a connector
    filter)
---
 .../java/org/apache/directory/api/i18n/I18n.java   |   1 +
 .../apache/directory/api/i18n/errors.properties    |   1 +
 .../api/ldap/codec/api/LdapMessageContainer.java   |  24 +++-
 .../search/SearchRequestFilterNestingTest.java     | 142 +++++++++++++++++++++
 4 files changed, 167 insertions(+), 1 deletion(-)

diff --git a/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java b/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
index 47f7f3193..bd55a2b93 100644
--- a/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
+++ b/i18n/src/main/java/org/apache/directory/api/i18n/I18n.java
@@ -294,6 +294,7 @@ public enum I18n
     ERR_05158_INVALID_REQUEST_VALUE( "ERR_05158_INVALID_REQUEST_VALUE" ),
     ERR_05159_INVALID_RESPONSE_NAME_OID( "ERR_05159_INVALID_RESPONSE_NAME_OID" ),
     ERR_05160_MORE_THAN_ONE_VALUE_INCREMENT_MOD_OP( "ERR_05160_MORE_THAN_ONE_VALUE_INCREMENT_MOD_OP" ),
+    ERR_05161_MISPLACED_FILTER_TLV( "ERR_05161_MISPLACED_FILTER_TLV" ),
 
 
     //     api                          5200-5299
diff --git a/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties b/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
index 6e814753e..345538d48 100644
--- a/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
+++ b/i18n/src/main/resources/org/apache/directory/api/i18n/errors.properties
@@ -283,6 +283,7 @@ ERR_05157_INVALID_DN=The Dn {0} is invalid : {1}
 ERR_05158_INVALID_REQUEST_VALUE=The encoded value ''{0}'' cannot be decoded
 ERR_05159_INVALID_RESPONSE_NAME_OID=The Response name is not a valid OID : {0} ({1}) is invalid
 ERR_05160_MORE_THAN_ONE_VALUE_INCREMENT_MOD_OP=The Increment modification operation accepts only one value
+ERR_05161_MISPLACED_FILTER_TLV=The filter element {0} is not nested inside its expected enclosing filter element
 
 # api-ldap-codec-core api       5200-5299
 ERR_05200_NOT_INITIALIZED_YET=Not initialized yet!
diff --git a/ldap/codec/core/src/main/java/org/apache/directory/api/ldap/codec/api/LdapMessageContainer.java b/ldap/codec/core/src/main/java/org/apache/directory/api/ldap/codec/api/LdapMessageContainer.java
index f8f1c985a..25dcf4e5b 100644
--- a/ldap/codec/core/src/main/java/org/apache/directory/api/ldap/codec/api/LdapMessageContainer.java
+++ b/ldap/codec/core/src/main/java/org/apache/directory/api/ldap/codec/api/LdapMessageContainer.java
@@ -431,8 +431,18 @@ public class LdapMessageContainer<E extends Message> extends AbstractContainer
      */
     public void addCurrentFilter( Filter localFilter ) throws DecoderException
     {
+        // The filter element's TLV must occur at the structural position the grammar
+        // state assumes: directly nested inside the enclosing connector's TLV (or
+        // inside the SearchRequest TLV for the root filter). Without this check, a
+        // filter TLV physically nested inside another filter element's TLV would be
+        // attached as a sibling, decoding a filter a conformant parser rejects
+        // (parser differential / filter-structure smuggling).
+        TLV currentTlv = getCurrentTLV();
+        TLV parentTlv = currentTlv != null ? currentTlv.getParent() : null;
+
         if ( currentFilter != null )
         {
+            // Check that the current filter 
             // Guard against StackOverflowError : the nesting depth of the filter
             // is driven by the incoming PDU, so it has to be bounded before we
             // accept one more level.
@@ -452,6 +462,12 @@ public class LdapMessageContainer<E extends Message> extends AbstractContainer
             // this parent, and will become the currentFilter if it's a connector.
             if ( currentFilter instanceof ConnectorFilter )
             {
+                // Just check if the parent is null or is not the proper parent
+                if ( ( parentTlv == null ) || ( parentTlv.getId() != currentFilter.getTlvId() ) )
+                {
+                    throw new DecoderException( I18n.err( I18n.ERR_05161_MISPLACED_FILTER_TLV, localFilter ) );
+                }
+
                 ( ( ConnectorFilter ) currentFilter ).addFilter( localFilter );
             }
             else
@@ -468,7 +484,13 @@ public class LdapMessageContainer<E extends Message> extends AbstractContainer
         }
         else
         {
-            // No parent. This Filter will become the root.
+            // No parent. This Filter will become the root if the parent's TLV is correct (ie not null
+            // or with a wrong ID)
+            if ( ( parentTlv == null ) || ( parentTlv.getId() != tlvId ) )
+            {
+                throw new DecoderException( I18n.err( I18n.ERR_05161_MISPLACED_FILTER_TLV, localFilter ) );
+            }
+
             currentFilter = localFilter;
             currentFilter.setParent( null, tlvId );
             topFilter = localFilter;
diff --git a/ldap/codec/core/src/test/java/org/apache/directory/api/ldap/codec/search/SearchRequestFilterNestingTest.java b/ldap/codec/core/src/test/java/org/apache/directory/api/ldap/codec/search/SearchRequestFilterNestingTest.java
new file mode 100644
index 000000000..f03e645c8
--- /dev/null
+++ b/ldap/codec/core/src/test/java/org/apache/directory/api/ldap/codec/search/SearchRequestFilterNestingTest.java
@@ -0,0 +1,142 @@
+/*
+ *  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
+ *
+ *    https://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.directory.api.ldap.codec.search;
+
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.directory.api.asn1.DecoderException;
+import org.apache.directory.api.asn1.ber.Asn1Decoder;
+import org.apache.directory.api.asn1.ber.tlv.TLVStateEnum;
+import org.apache.directory.api.ldap.codec.api.LdapMessageContainer;
+import org.apache.directory.api.ldap.codec.osgi.AbstractCodecServiceTest;
+import org.apache.directory.api.ldap.model.filter.AndNode;
+import org.apache.directory.api.ldap.model.filter.ExprNode;
+import org.apache.directory.api.ldap.model.message.SearchRequest;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+
+
+/**
+ * Tests that the SearchRequest filter decoder only accepts a filter element
+ * at the structural (TLV) position the grammar state implies. A filter TLV
+ * physically nested inside another filter element's TLV must be rejected
+ * instead of being attached as a sibling of the enclosing element (filter
+ * structure smuggling / parser differential).
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+@Execution( ExecutionMode.CONCURRENT )
+public class SearchRequestFilterNestingTest extends AbstractCodecServiceTest
+{
+    /**
+     * A valid flat and-filter (&amp;(a=b)(c=d)) must still decode.
+     *
+     * @throws DecoderException If the ASN1 decoding failed
+     */
+    @Test
+    public void testDecodeValidSiblingFilters() throws DecoderException
+    {
+        ByteBuffer stream = ByteBuffer.allocate( 0x35 );
+        stream.put( new byte[]
+            {
+                0x30, 0x33,                     // LDAPMessage ::= SEQUENCE {
+                  0x02, 0x01, 0x01,             // messageID MessageID
+                  0x63, 0x2E,                   // searchRequest SearchRequest ::= APPLICATION[3] SEQUENCE {
+                    0x04, 0x09,                 // baseObject LDAPDN,
+                      'o', 'u', '=', 's', 'y', 's', 't', 'e', 'm',
+                    0x0A, 0x01, 0x00,           // scope ENUMERATED baseObject
+                    0x0A, 0x01, 0x00,           // derefAliases ENUMERATED never
+                    0x02, 0x01, 0x00,           // sizeLimit INTEGER 0
+                    0x02, 0x01, 0x00,           // timeLimit INTEGER 0
+                    0x01, 0x01, 0x00,           // typesOnly BOOLEAN false
+                    ( byte ) 0xA0, 0x10,        // filter and [0] SET OF Filter
+                      ( byte ) 0xA3, 0x06,      // equalityMatch [3] (a=b)
+                        0x04, 0x01, 'a',
+                        0x04, 0x01, 'b',
+                      ( byte ) 0xA3, 0x06,      // equalityMatch [3] (c=d), a real sibling
+                        0x04, 0x01, 'c',
+                        0x04, 0x01, 'd',
+                    0x30, 0x00                  // attributes AttributeDescriptionList (empty)
+            } );
+
+        stream.flip();
+
+        LdapMessageContainer<SearchRequest> ldapMessageContainer = new LdapMessageContainer<>( codec );
+
+        Asn1Decoder.decode( stream, ldapMessageContainer );
+
+        assertEquals( TLVStateEnum.PDU_DECODED, ldapMessageContainer.getState() );
+
+        SearchRequest searchRequest = ldapMessageContainer.getMessage();
+        ExprNode filter = searchRequest.getFilter();
+        List<ExprNode> children = ( ( AndNode ) filter ).getChildren();
+        assertEquals( 2, children.size() );
+    }
+
+
+    /**
+     * The same PDU, but the second equalityMatch TLV is physically nested
+     * INSIDE the first equalityMatch element's TLV (the first element's
+     * length is enlarged to cover it). Byte-count bookkeeping still
+     * balances, but a conformant parser rejects the PDU; the decoder must
+     * not silently attach the smuggled element as a sibling filter.
+     *
+     * @throws DecoderException If the ASN1 decoding failed
+     */
+    @Test
+    public void testDecodeSmuggledNestedFilterRejected()
+    {
+        ByteBuffer stream = ByteBuffer.allocate( 0x35 );
+        stream.put( new byte[]
+            {
+                0x30, 0x33,                     // LDAPMessage ::= SEQUENCE {
+                  0x02, 0x01, 0x01,             // messageID MessageID
+                  0x63, 0x2E,                   // searchRequest SearchRequest ::= APPLICATION[3] SEQUENCE {
+                    0x04, 0x09,                 // baseObject LDAPDN,
+                      'o', 'u', '=', 's', 'y', 's', 't', 'e', 'm',
+                    0x0A, 0x01, 0x00,           // scope ENUMERATED baseObject
+                    0x0A, 0x01, 0x00,           // derefAliases ENUMERATED never
+                    0x02, 0x01, 0x00,           // sizeLimit INTEGER 0
+                    0x02, 0x01, 0x00,           // timeLimit INTEGER 0
+                    0x01, 0x01, 0x00,           // typesOnly BOOLEAN false
+                    ( byte ) 0xA0, 0x10,        // filter and [0] SET OF Filter
+                      ( byte ) 0xA3, 0x0E,      // equalityMatch [3], length covers the smuggled TLV
+                        0x04, 0x01, 'a',
+                        0x04, 0x01, 'b',
+                        ( byte ) 0xA3, 0x06,    // smuggled equalityMatch INSIDE the previous element
+                          0x04, 0x01, 'c',
+                          0x04, 0x01, 'd',
+                    0x30, 0x00                  // attributes AttributeDescriptionList (empty)
+            } );
+
+        stream.flip();
+
+        LdapMessageContainer<SearchRequest> ldapMessageContainer = new LdapMessageContainer<>( codec );
+
+        assertThrows( DecoderException.class,
+            () -> Asn1Decoder.decode( stream, ldapMessageContainer ) );
+    }
+}