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 (&(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 ) );
+ }
+}