Skip to content

Use-after-free in KeyDB RM_RegisterClusterMessageReceiver head-node delete — KeyDB #978

Description

@entropy1337

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions