From af673bb6dff871dcf5eeb1bbf2a324cf5acf8512 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Mon, 27 Jul 2026 17:22:14 +0000
Subject: [PATCH] Fix java/redos CodeQL alert in LDIFChangeRecordReader (#764)
---
opendj-core/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java | 95 +++++++++++++++++++++++++++++++++++++++++++++++
opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java | 3 +
2 files changed, 97 insertions(+), 1 deletions(-)
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java b/opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java
index 58fe0ba..65d3b9f 100644
--- a/opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldif/LDIFChangeRecordReader.java
@@ -14,6 +14,7 @@
* Copyright 2009-2010 Sun Microsystems, Inc.
* Portions copyright 2011-2016 ForgeRock AS.
* Portions copyright 2016 Matthew Stevenson
+ * Portions Copyrighted 2026 3A Systems, LLC.
*/
package org.forgerock.opendj.ldif;
@@ -82,7 +83,7 @@
*/
public final class LDIFChangeRecordReader extends AbstractLDIFReader implements ChangeRecordReader {
private static final Pattern CONTROL_REGEX = Pattern
- .compile("^\\s*(\\d+(.\\d+)*)(\\s+((true)|(false)))?\\s*(:(:)?\\s*?\\S+)?\\s*$");
+ .compile("^\\s*+(\\d++(\\.\\d++)*+)(\\s++((true)|(false)))?\\s*+(:(:)?\\s*?\\S++)?\\s*+$");
/** Poison used to indicate end of LDIF. */
private static final ChangeRecord EOF = Requests.newAddRequest(DN.rootDN());
diff --git a/opendj-core/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java b/opendj-core/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java
index afed07c..0b05bc0 100644
--- a/opendj-core/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java
+++ b/opendj-core/src/test/java/org/forgerock/opendj/ldif/LDIFChangeRecordReaderTestCase.java
@@ -14,6 +14,7 @@
* Copyright 2011-2016 ForgeRock AS.
* Portions Copyright 2014 Manuel Gaupp
* Portions copyright 2016 Matthew Stevenson
+ * Portions Copyrighted 2026 3A Systems, LLC.
*/
package org.forgerock.opendj.ldif;
@@ -1176,6 +1177,100 @@
}
/**
+ * Test to read a record containing a control with a malformed OID
+ * (non-digit inside an arc). Must throw an error.
+ *
+ * @throws Exception
+ */
+ @Test(expectedExceptions = DecodeException.class)
+ public void testParseChangeRecordEntryWithMalformedControlOID() throws Exception {
+
+ // @formatter:off
+ final LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
+ "dn: ou=Product Development, dc=airius, dc=com",
+ "control: 1a2.3 true :cn",
+ "changetype: add",
+ "objectClass: top",
+ "objectClass: organization",
+ "o: testing"
+ );
+ // @formatter:on
+
+ reader.setSchema(Schema.getDefaultSchema());
+ reader.setSchemaValidationPolicy(SchemaValidationPolicy.defaultPolicy());
+ // Read the record
+ reader.readChangeRecord();
+ reader.close();
+ }
+
+ /**
+ * Test to read a record containing a control whose OID has thousands of
+ * arcs. Must parse without StackOverflowError.
+ *
+ * @throws Exception
+ */
+ @Test(timeOut = 10000)
+ public void testParseChangeRecordEntryWithLongControlOID() throws Exception {
+
+ final StringBuilder oid = new StringBuilder("1");
+ for (int i = 0; i < 3000; i++) {
+ oid.append(".1");
+ }
+
+ // @formatter:off
+ final LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
+ "dn: ou=Product Development, dc=airius, dc=com",
+ "control: " + oid,
+ "changetype: add",
+ "objectClass: top",
+ "objectClass: organization",
+ "o: testing"
+ );
+ // @formatter:on
+
+ reader.setSchema(Schema.getDefaultSchema());
+ reader.setSchemaValidationPolicy(SchemaValidationPolicy.defaultPolicy());
+ final ChangeRecord record = reader.readChangeRecord();
+ assertThat(record).isInstanceOf(AddRequest.class);
+ assertThat(record.getControls().get(0).getOID()).isEqualTo(oid.toString());
+ reader.close();
+ }
+
+ /**
+ * Test to read a record containing an invalid control followed by tens of
+ * thousands of spaces. Must throw an error without catastrophic regex
+ * backtracking.
+ *
+ * @throws Exception
+ */
+ @Test(expectedExceptions = DecodeException.class, timeOut = 10000)
+ public void testParseChangeRecordEntryWithControlTrailingWhitespace() throws Exception {
+
+ final StringBuilder control = new StringBuilder("control: 1");
+ for (int i = 0; i < 60000; i++) {
+ control.append(' ');
+ }
+ control.append('x');
+
+ // @formatter:off
+ final LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
+ "dn: ou=Product Development, dc=airius, dc=com",
+ control.toString(),
+ "changetype: add",
+ "objectClass: top",
+ "objectClass: organization",
+ "o: testing"
+ );
+ // @formatter:on
+
+ reader.setSchema(Schema.getDefaultSchema());
+ reader.setSchemaValidationPolicy(SchemaValidationPolicy.defaultPolicy());
+ // Read the record
+ reader.readChangeRecord();
+ reader.close();
+ }
+
+ /**
* Test to read an record containing a add control.
*
* @throws Exception
--
Gitblit v1.10.0