Fix crash on RM_Call inside module load (#11346)

PR #9320 introduces initialization order changes. Now cluster is initialized after modules.
This changes causes a crash if the module uses RM_Call inside the load function
on cluster mode (the code will try to access `server.cluster` which at this point is NULL).

To solve it, separate cluster initialization into 2 phases:
1. Structure initialization that happened before the modules initialization
2. Listener initialization that happened after.

Test was added to verify the fix.
This commit is contained in:
Meir Shpilraien (Spielrein) 2022-10-12 13:09:51 +03:00 committed by GitHub
parent a370bbe263
commit eb6accad40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 23 deletions

View File

@ -694,27 +694,6 @@ void clusterInit(void) {
exit(1);
}
if (connectionIndexByType(connTypeOfCluster()->get_type(NULL)) < 0) {
serverLog(LL_WARNING, "Missing connection type %s, but it is required for the Cluster bus.", connTypeOfCluster()->get_type(NULL));
exit(1);
}
connListener *listener = &server.clistener;
listener->count = 0;
listener->bindaddr = server.bindaddr;
listener->bindaddr_count = server.bindaddr_count;
listener->port = server.cluster_port ? server.cluster_port : port + CLUSTER_PORT_INCR;
listener->ct = connTypeOfCluster();
if (connListen(listener) == C_ERR ) {
/* Note: the following log text is matched by the test suite. */
serverLog(LL_WARNING, "Failed listening on port %u (cluster), aborting.", listener->port);
exit(1);
}
if (createSocketAcceptHandler(&server.clistener, clusterAcceptHandler) != C_OK) {
serverPanic("Unrecoverable error creating Redis Cluster socket accept handler.");
}
/* Initialize data for the Slot to key API. */
slotToKeyInit(server.db);
@ -733,6 +712,30 @@ void clusterInit(void) {
clusterUpdateMyselfHostname();
}
void clusterInitListeners(void) {
if (connectionIndexByType(connTypeOfCluster()->get_type(NULL)) < 0) {
serverLog(LL_WARNING, "Missing connection type %s, but it is required for the Cluster bus.", connTypeOfCluster()->get_type(NULL));
exit(1);
}
int port = server.tls_cluster ? server.tls_port : server.port;
connListener *listener = &server.clistener;
listener->count = 0;
listener->bindaddr = server.bindaddr;
listener->bindaddr_count = server.bindaddr_count;
listener->port = server.cluster_port ? server.cluster_port : port + CLUSTER_PORT_INCR;
listener->ct = connTypeOfCluster();
if (connListen(listener) == C_ERR ) {
/* Note: the following log text is matched by the test suite. */
serverLog(LL_WARNING, "Failed listening on port %u (cluster), aborting.", listener->port);
exit(1);
}
if (createSocketAcceptHandler(&server.clistener, clusterAcceptHandler) != C_OK) {
serverPanic("Unrecoverable error creating Redis Cluster socket accept handler.");
}
}
/* Reset a node performing a soft or hard reset:
*
* 1) All other nodes are forgotten.

View File

@ -383,6 +383,7 @@ static_assert(offsetof(clusterMsg, data) == 2256, "unexpected field offset");
/* ---------------------- API exported outside cluster.c -------------------- */
void clusterInit(void);
void clusterInitListeners(void);
void clusterCron(void);
void clusterBeforeSleep(void);
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);

View File

@ -7053,6 +7053,9 @@ int main(int argc, char **argv) {
if (server.set_proc_title) redisSetProcTitle(NULL);
redisAsciiArt();
checkTcpBacklogSettings();
if (server.cluster_enabled) {
clusterInit();
}
if (!server.sentinel_mode) {
moduleInitModulesSystemLast();
moduleLoadFromQueue();
@ -7060,7 +7063,7 @@ int main(int argc, char **argv) {
ACLLoadUsersAtStartup();
initListeners();
if (server.cluster_enabled) {
clusterInit();
clusterInitListeners();
}
InitServerLast();

View File

@ -893,6 +893,28 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_Init(ctx,"test",1,REDISMODULE_APIVER_1)
== REDISMODULE_ERR) return REDISMODULE_ERR;
/* Perform RM_Call inside the RedisModule_OnLoad
* to verify that it works as expected without crashing.
* The tests will verify it on different configurations
* options (cluster/no cluster). A simple ping command
* is enough for this test. */
RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", "");
if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) {
RedisModule_FreeCallReply(reply);
return REDISMODULE_ERR;
}
size_t len;
const char *reply_str = RedisModule_CallReplyStringPtr(reply, &len);
if (len != 4) {
RedisModule_FreeCallReply(reply);
return REDISMODULE_ERR;
}
if (memcmp(reply_str, "PONG", 4) != 0) {
RedisModule_FreeCallReply(reply);
return REDISMODULE_ERR;
}
RedisModule_FreeCallReply(reply);
if (RedisModule_CreateCommand(ctx,"test.call",
TestCall,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;

View File

@ -38,4 +38,4 @@ start_server {tags {"modules external:skip"} overrides {enable-module-command no
test {module command disabled} {
assert_error "ERR *MODULE command not allowed*" {r module load $testmodule}
}
}
}

View File

@ -163,3 +163,17 @@ start_cluster 3 0 [list config_lines $modules] {
$node2_rd close
}
}
set testmodule [file normalize tests/modules/basics.so]
set modules [list loadmodule $testmodule]
start_cluster 3 0 [list config_lines $modules] {
set node1 [srv 0 client]
set node2 [srv -1 client]
set node3 [srv -2 client]
test "Verify RM_Call inside module load function on cluster mode" {
assert_equal {PONG} [$node1 PING]
assert_equal {PONG} [$node2 PING]
assert_equal {PONG} [$node3 PING]
}
}