From 8b7cbc2dcced1334e2829a722d456cfa4105cf1c Mon Sep 17 00:00:00 2001
From: Valera V Harseko <vharseko@3a-systems.ru>
Date: Wed, 21 Oct 2026 08:53:55 +0000
Subject: [PATCH] GHSA-7778-jm7p-68g5 LDAP listener lets unauthenticated clients hold connections until the server stops accepting new ones

---
 opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java |   43 +++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 43 insertions(+), 0 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
index 53f799f..78427f9 100644
--- a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
@@ -38,6 +38,7 @@
 import java.util.Map;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -136,6 +137,10 @@
 
     /** The time that the last operation was completed. */
     private final AtomicLong lastCompletionTime;
+    /** Whether a complete request has arrived on this connection, which ends its first request time limit. */
+    private volatile boolean requestReceived;
+    /** The task which closes this connection unless its first request arrives in time, or {@code null}. */
+    private volatile Future<?> firstRequestTimer;
     /** The next operation ID that should be used for this connection. */
     private final AtomicLong nextOperationID;
 
@@ -645,6 +650,7 @@
 
         // Indicate that this connection is no longer valid.
         connectionValid = false;
+        cancelFirstRequestTimer();
 
         final LocalizableMessage cancelMessage;
         if (message != null) {
@@ -943,6 +949,39 @@
     }
 
     /**
+     * Indicates whether a complete request has arrived on this connection.
+     *
+     * @return {@code true} once the first complete request has arrived, {@code false} before
+     */
+    boolean hasReceivedRequest() {
+        return requestReceived;
+    }
+
+    /**
+     * Sets the task which closes this connection unless its first request arrives in time. The task is cancelled once
+     * the first request arrives or the connection ends, so that neither it nor this connection waits out the limit.
+     *
+     * @param timer
+     *            the scheduled task
+     */
+    void setFirstRequestTimer(final Future<?> timer) {
+        firstRequestTimer = timer;
+        // The connection may have ended before the task was set. Its first request cannot have come before: the task
+        // is set while the connection is accepted, and its requests are handled only once it is.
+        if (!connectionValid) {
+            cancelFirstRequestTimer();
+        }
+    }
+
+    private void cancelFirstRequestTimer() {
+        final Future<?> timer = firstRequestTimer;
+        if (timer != null) {
+            firstRequestTimer = null;
+            timer.cancel(false);
+        }
+    }
+
+    /**
      * Returns the total number of operations initiated on this connection.
      *
      * @return the total number of operations on this connection
@@ -966,6 +1005,10 @@
      */
     @Override
     public Stream<Response> handle(final QueueingStrategy queueingStrategy, final LdapRequestEnvelope message) {
+        if (!requestReceived) {
+            requestReceived = true;
+            cancelFirstRequestTimer();
+        }
         return streamFromPublisher(
                 new BlockingBackpressureSubscription(connectionHandler.getMaxBlockedWriteTimeLimit(),
                         Flowable.create(new FlowableOnSubscribe<Response>() {

--
Gitblit v1.10.0