Summary
When a module registers a cluster message receiver (RM_RegisterClusterMessageReceiver), an entry is stored in the global clusterReceivers[] list holding the module handle and a callback pointer into the module's .so. KeyDB's module-unload path (moduleUnload, src/module.cpp:8806) cleans up commands, shared APIs, notifications, etc., but never removes the module's cluster receivers. After MODULE UNLOAD, clusterReceivers[type] still points at a receiver whose module/callback reference the freed module and the dlclose'd .so. The next CLUSTERMSG_TYPE_MODULE packet of that type → moduleCallClusterReceivers dereferences the freed module and calls the stale callback → use-after-free.
Affected version
KeyDB @603ebb27fb (2024-04-04, dev "255.255.255"). Present since the Redis fork point; KeyDB is frozen at ~2024 and never took the Valkey fix (#4360). Confirm the affected release range (KeyDB 6.x) with the vendor.
Technical details
moduleUnload (src/module.cpp:8806+) calls moduleUnregisterCommands, moduleUnregisterSharedAPI, moduleUnsubscribeNotifications, ... — there is no clusterReceivers cleanup anywhere in the unload path (the only references to clusterReceivers are the register/dispatch sites at module.cpp:6184–6241). So the receiver node survives (leaked-but-live) with dangling module/callback.
Proof of concept
Dynamically reproduced on a 2-node ASan KeyDB cluster (@603ebb27, built SANITIZE=address MALLOC=libc BUILD_TLS=no NO_MOTD=yes):
Both nodes --loadmodule uaftest.so (registers a cluster receiver for type 42 + a sendbad command that broadcasts a type-42 cluster message).
CLUSTER MEET; wait for full connectivity (both nodes connected, no handshake).
MODULE UNLOAD uaftest on node1 → clusterReceivers[42] dangles.
sendbad on node2 → broadcasts a type-42 cluster message.
node1 crashes — SIGSEGV on the cluster-message thread executing the dlclose'd module's callback: Module uaftest unloaded → crashed by signal: 11 ... Crashed running the instruction at: 0x.
(evidence/node1-crash-backtrace.txt; POC/uaftest.c; POC/repro-2node-cluster.sh.) Matches the ASan-confirmed Redis finding ID-REDIS-CLUSTERRECV-UNLOAD-UAF-01.
Summary
When a module registers a cluster message receiver (RM_RegisterClusterMessageReceiver), an entry is stored in the global clusterReceivers[] list holding the module handle and a callback pointer into the module's .so. KeyDB's module-unload path (moduleUnload, src/module.cpp:8806) cleans up commands, shared APIs, notifications, etc., but never removes the module's cluster receivers. After MODULE UNLOAD, clusterReceivers[type] still points at a receiver whose module/callback reference the freed module and the dlclose'd .so. The next CLUSTERMSG_TYPE_MODULE packet of that type → moduleCallClusterReceivers dereferences the freed module and calls the stale callback → use-after-free.
Affected version
KeyDB @603ebb27fb (2024-04-04, dev "255.255.255"). Present since the Redis fork point; KeyDB is frozen at ~2024 and never took the Valkey fix (#4360). Confirm the affected release range (KeyDB 6.x) with the vendor.
Technical details
moduleUnload (src/module.cpp:8806+) calls moduleUnregisterCommands, moduleUnregisterSharedAPI, moduleUnsubscribeNotifications, ... — there is no clusterReceivers cleanup anywhere in the unload path (the only references to clusterReceivers are the register/dispatch sites at module.cpp:6184–6241). So the receiver node survives (leaked-but-live) with dangling module/callback.
Proof of concept
Dynamically reproduced on a 2-node ASan KeyDB cluster (@603ebb27, built SANITIZE=address MALLOC=libc BUILD_TLS=no NO_MOTD=yes):
Both nodes --loadmodule uaftest.so (registers a cluster receiver for type 42 + a sendbad command that broadcasts a type-42 cluster message).
CLUSTER MEET; wait for full connectivity (both nodes connected, no handshake).
MODULE UNLOAD uaftest on node1 → clusterReceivers[42] dangles.
sendbad on node2 → broadcasts a type-42 cluster message.
node1 crashes — SIGSEGV on the cluster-message thread executing the dlclose'd module's callback: Module uaftest unloaded → crashed by signal: 11 ... Crashed running the instruction at: 0x.
(evidence/node1-crash-backtrace.txt; POC/uaftest.c; POC/repro-2node-cluster.sh.) Matches the ASan-confirmed Redis finding ID-REDIS-CLUSTERRECV-UNLOAD-UAF-01.