There is a classic off-by-one global buffer overflow (OOB Write/Read) vulnerability in the RM_RegisterClusterMessageReceiver API provided for Redis modules.
The global array clusterReceivers is declared with a fixed size of UINT8_MAX (255 elements) in module.cpp:
// @KeyDB/src/module.cpp:6184
static moduleClusterReceiver *clusterReceivers[UINT8_MAX];
Since the array size is 255, the valid array indices are 0 to 254.
However, the RM_RegisterClusterMessageReceiver function accepts the type parameter as a uint8_t:
// @KeyDB/src/module.cpp:6208
void RM_RegisterClusterMessageReceiver(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback) {
if (!g_pserver->cluster_enabled) return;
uint64_t module_id = moduleTypeEncodeId(ctx->module->name,0);
moduleClusterReceiver *r = clusterReceivers[type], *prev = NULL; // <--- OOB Read
Since a uint8_t variable can hold values up to 255 (UINT8_MAX), passing type = 255 causes an exact 8-byte out-of-bounds access (clusterReceivers[255]) in the BSS segment, overriding or leaking adjacent global variables.
To reproduce
- Compile KeyDB with AddressSanitizer enabled:
make MALLOC=libc \
OPTIMIZATION="-O1" \
CFLAGS="-fsanitize=address,undefined -g -fno-omit-frame-pointer" \
CXXFLAGS="-fsanitize=address,undefined -g -fno-omit-frame-pointer" \
LDFLAGS="-fsanitize=address,undefined" \
-j$(nproc)
- Create a minimal Redis Module to trigger the vulnerability:
#include "redismodule.h"
void MyClusterMessageReceiver(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len) {}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (RedisModule_Init(ctx, "oob_test", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) {
return REDISMODULE_ERR;
}
// Pass 255 (UINT8_MAX) as the type to trigger OOB write
RedisModule_RegisterClusterMessageReceiver(ctx, 255, MyClusterMessageReceiver);
return REDISMODULE_OK;
}
- Compile the module:
gcc -shared -fPIC -o xxx.so xxx.c -I./src -DREDISMODULE_EXPERIMENTAL_API
- Launch KeyDB in cluster mode and load the module:
./src/keydb-server --cluster-enabled yes --loadmodule ./oob_test.so
Expected behavior
The API should properly validate the bounds of type or the array should be declared with enough space to cover the entire uint8_t range.
For example, either changing the declaration to:
static moduleClusterReceiver *clusterReceivers[UINT8_MAX + 1];
Or adding a bounds check in the function:
if (type >= UINT8_MAX) return;
Additional information
The ASAN trace perfectly confirms the 8-byte out-of-bounds read/write on the global .bss segment immediately following the clusterReceivers array boundary.
We are fully aware that exploiting this bug requires the attacker to have the privilege to load a Redis module or leverage an existing trusted module's API that improperly passes user-controlled integers to this function. Therefore, its real-world exploitability is highly restricted. However, it remains a valid and concrete memory corruption flaw in the codebase.
Could you please help assess if this qualifies for a CVE assignment? If so, I would appreciate it if the credit/attribution could be given to Yanzhao Shen. Thanks!
There is a classic off-by-one global buffer overflow (OOB Write/Read) vulnerability in the
RM_RegisterClusterMessageReceiverAPI provided for Redis modules.The global array
clusterReceiversis declared with a fixed size ofUINT8_MAX(255 elements) inmodule.cpp:Since the array size is 255, the valid array indices are
0to254.However, the
RM_RegisterClusterMessageReceiverfunction accepts thetypeparameter as auint8_t:Since a
uint8_tvariable can hold values up to255(UINT8_MAX), passingtype = 255causes an exact 8-byte out-of-bounds access (clusterReceivers[255]) in the BSS segment, overriding or leaking adjacent global variables.To reproduce
make MALLOC=libc \ OPTIMIZATION="-O1" \ CFLAGS="-fsanitize=address,undefined -g -fno-omit-frame-pointer" \ CXXFLAGS="-fsanitize=address,undefined -g -fno-omit-frame-pointer" \ LDFLAGS="-fsanitize=address,undefined" \ -j$(nproc)Expected behavior
The API should properly validate the bounds of
typeor the array should be declared with enough space to cover the entireuint8_trange.For example, either changing the declaration to:
Or adding a bounds check in the function:
Additional information
The ASAN trace perfectly confirms the 8-byte out-of-bounds read/write on the global .bss segment immediately following the clusterReceivers array boundary.
We are fully aware that exploiting this bug requires the attacker to have the privilege to load a Redis module or leverage an existing trusted module's API that improperly passes user-controlled integers to this function. Therefore, its real-world exploitability is highly restricted. However, it remains a valid and concrete memory corruption flaw in the codebase.
Could you please help assess if this qualifies for a CVE assignment? If so, I would appreciate it if the credit/attribution could be given to Yanzhao Shen. Thanks!