Skip to content

[BUG] Off-by-one global buffer overflow in RM_RegisterClusterMessageReceiver #975

@BreakingBad6

Description

@BreakingBad6

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

  1. 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)
  1. 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;
}
  1. Compile the module:
gcc -shared -fPIC -o xxx.so xxx.c -I./src -DREDISMODULE_EXPERIMENTAL_API
  1. 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

Image

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions