From 0b51620ef1e0a34fb6239e328bfa15068e12caa4 Mon Sep 17 00:00:00 2001
From: "debing.sun" <debing.sun@redis.com>
Date: Mon, 29 Dec 2025 16:20:41 +0800
Subject: [PATCH] Fix use-after-free when evicting blocked client during
 unblock (CVE-2026-23479)

When re-executing a pending command after unblocking, check the return value
of `processCommandAndResetClient` and exit if needed.
---
 src/blocked.c                  |  8 +++++++-
 tests/unit/client-eviction.tcl | 29 +++++++++++++++++++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/src/blocked.c b/src/blocked.c
index 7b48fcab63b..9ad2002e0e6 100644
--- a/src/blocked.c
+++ b/src/blocked.c
@@ -664,7 +664,13 @@ static void unblockClientOnKey(client *c, robj *key) {
         client *old_client = server.current_client;
         server.current_client = c;
         enterExecutionUnit(1, 0);
-        processCommandAndResetClient(c);
+        if (processCommandAndResetClient(c) == C_ERR) {
+            /* Client was freed during command processing, exit immediately */
+            exitExecutionUnit();
+            server.current_client = old_client;
+            return;
+        }
+
         if (!(c->flags & CLIENT_BLOCKED)) {
             if (c->flags & CLIENT_MODULE) {
                 moduleCallCommandUnblockedHandler(c);
diff --git a/tests/unit/client-eviction.tcl b/tests/unit/client-eviction.tcl
index 1fc7c02ca97..16021d268c3 100644
--- a/tests/unit/client-eviction.tcl
+++ b/tests/unit/client-eviction.tcl
@@ -582,5 +582,34 @@ start_server {} {
     }
 }
 
+start_server {} {
+    r flushall
+    r client no-evict on
+    r config set maxmemory-clients 0
+
+    test "Verify blocked client eviction during unblock does not cause use-after-free" {
+        # Create a deferring client that will be blocked on stream
+        # Use a long stream name to make client memory usage exceed 200000 bytes
+        set rd [redis_deferring_client]
+        $rd XREAD BLOCK 0 STREAMS mystream stream_[string repeat x 200000] $ $
+
+        # Wait for the client to be blocked
+        wait_for_condition 50 100 {
+            [s blocked_clients] eq {1}
+        } else {
+            fail "Client was not blocked"
+        }
+
+        # Now lower MAXMEMORY-CLIENTS to a low value and use
+        # XADD to unblock the blocked client, triggering eviction.
+        r MULTI
+        r CONFIG SET MAXMEMORY-CLIENTS 100000 ;# Put in MULTI to defer blocked client eviction until after EXEC
+        r XADD mystream * field val
+        r EXEC
+        r PING
+        $rd close
+    }
+}
+
 } ;# tags
 
