Use-after-Free
Description
This commit appears to fix a genuine use-after-free vulnerability concern. The race occurs when evicting a blocked client during an unblock and re-executing a pending command. Previously, the engine could access a client after it had been freed during processCommandAndResetClient. The fix guards against this by checking the return value of processCommandAndResetClient; if the client was freed, it exits the execution unit and returns early, preventing use-after-free in subsequent logic. The accompanying test case exercises eviction of a blocked client to ensure no use-after-free occurs during unblock.
Proof of Concept
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 ensure memory usage is substantial
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"
}
# Evict by lowering maxmemory-clients and issuing a command to unblock
r MULTI
r CONFIG SET MAXMEMORY-CLIENTS 100000
r XADD mystream * field val
r EXEC
r PING
$rd close
}
}
Commit Details
Author: debing.sun
Date: 2025-12-29 08:20 UTC
Message:
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.
Triage Assessment
Vulnerability Type: Use-after-free
Confidence: HIGH
Reasoning:
Commit explicitly fixes a use-after-free scenario when evicting a blocked client during an unblock, by checking processCommandAndResetClient return value and guarding against the client being freed. This addresses a memory-safety vulnerability (use-after-free) likely exploitable as CVE-2026-23479.
Verification Assessment
Vulnerability Type: Use-after-Free
Confidence: HIGH
Affected Versions: <= 8.6.2 (pre-fix); vulnerable prior to this commit; patched in this change
Code Diff
diff --git a/src/blocked.c b/src/blocked.c
index b973adeaf75..74558b485dc 100644
--- a/src/blocked.c
+++ b/src/blocked.c
@@ -699,7 +699,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 2e08715b8d6..afe32e4f960 100644
--- a/tests/unit/client-eviction.tcl
+++ b/tests/unit/client-eviction.tcl
@@ -611,5 +611,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