diff --git a/deps/xredis-gtid b/deps/xredis-gtid index 9b562529d9c..2cbc573ffe8 160000 --- a/deps/xredis-gtid +++ b/deps/xredis-gtid @@ -1 +1 @@ -Subproject commit 9b562529d9cdda5c10b17cda7e7335ac430af09e +Subproject commit 2cbc573ffe8c90797ebc24b8ee08162c41812faf diff --git a/src/commands/append.json b/src/commands/append.json index a8ec6bf82e7..b65060d19c5 100644 --- a/src/commands/append.json +++ b/src/commands/append.json @@ -9,7 +9,8 @@ "command_flags": [ "WRITE", "DENYOOM", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "STRING" diff --git a/src/commands/blmove.json b/src/commands/blmove.json index b316c5271b7..219efe4cd0d 100644 --- a/src/commands/blmove.json +++ b/src/commands/blmove.json @@ -9,7 +9,8 @@ "command_flags": [ "WRITE", "DENYOOM", - "BLOCKING" + "BLOCKING", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "LIST" diff --git a/src/commands/blmpop.json b/src/commands/blmpop.json index 48bfa3e8942..8b9953d214f 100644 --- a/src/commands/blmpop.json +++ b/src/commands/blmpop.json @@ -9,7 +9,8 @@ "get_keys_function": "blmpopGetKeys", "command_flags": [ "WRITE", - "BLOCKING" + "BLOCKING", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "LIST" diff --git a/src/commands/blpop.json b/src/commands/blpop.json index cf90f0aa4e7..6ce7d7d70a9 100644 --- a/src/commands/blpop.json +++ b/src/commands/blpop.json @@ -14,7 +14,8 @@ ], "command_flags": [ "WRITE", - "BLOCKING" + "BLOCKING", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "LIST" diff --git a/src/commands/brpop.json b/src/commands/brpop.json index 129f74dd172..70fa7598c6d 100644 --- a/src/commands/brpop.json +++ b/src/commands/brpop.json @@ -14,7 +14,8 @@ ], "command_flags": [ "WRITE", - "BLOCKING" + "BLOCKING", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "LIST" diff --git a/src/commands/brpoplpush.json b/src/commands/brpoplpush.json index dce1516e259..aa91711e1df 100644 --- a/src/commands/brpoplpush.json +++ b/src/commands/brpoplpush.json @@ -20,7 +20,8 @@ "command_flags": [ "WRITE", "DENYOOM", - "BLOCKING" + "BLOCKING", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "LIST" diff --git a/src/commands/bzmpop.json b/src/commands/bzmpop.json index 4ff5c110ac1..89a1f1fbf38 100644 --- a/src/commands/bzmpop.json +++ b/src/commands/bzmpop.json @@ -9,7 +9,8 @@ "get_keys_function": "blmpopGetKeys", "command_flags": [ "WRITE", - "BLOCKING" + "BLOCKING", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "SORTEDSET" diff --git a/src/commands/bzpopmax.json b/src/commands/bzpopmax.json index 70b0b70abc9..1eaacadf975 100644 --- a/src/commands/bzpopmax.json +++ b/src/commands/bzpopmax.json @@ -15,7 +15,8 @@ "command_flags": [ "WRITE", "FAST", - "BLOCKING" + "BLOCKING", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "SORTEDSET" diff --git a/src/commands/bzpopmin.json b/src/commands/bzpopmin.json index a2f305cd279..5533a19b1b5 100644 --- a/src/commands/bzpopmin.json +++ b/src/commands/bzpopmin.json @@ -15,7 +15,8 @@ "command_flags": [ "WRITE", "FAST", - "BLOCKING" + "BLOCKING", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "SORTEDSET" diff --git a/src/commands/expire.json b/src/commands/expire.json index bf80939e988..4e027611aa7 100644 --- a/src/commands/expire.json +++ b/src/commands/expire.json @@ -14,7 +14,8 @@ ], "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "KEYSPACE" diff --git a/src/commands/expireat.json b/src/commands/expireat.json index c2ba5d8c32c..a70e2f0bdb1 100644 --- a/src/commands/expireat.json +++ b/src/commands/expireat.json @@ -14,7 +14,8 @@ ], "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "KEYSPACE" diff --git a/src/commands/getdel.json b/src/commands/getdel.json index f3d86b0ecb3..9c61c7ced1c 100644 --- a/src/commands/getdel.json +++ b/src/commands/getdel.json @@ -8,7 +8,8 @@ "function": "getdelCommand", "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "STRING" diff --git a/src/commands/getex.json b/src/commands/getex.json index 978b9d10aec..b2e7029e5f1 100644 --- a/src/commands/getex.json +++ b/src/commands/getex.json @@ -8,7 +8,8 @@ "function": "getexCommand", "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "STRING" diff --git a/src/commands/getset.json b/src/commands/getset.json index 42823d5ab28..c131909159a 100644 --- a/src/commands/getset.json +++ b/src/commands/getset.json @@ -14,7 +14,8 @@ "command_flags": [ "WRITE", "DENYOOM", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "STRING" diff --git a/src/commands/hexpire.json b/src/commands/hexpire.json index a08a1d1f5dc..f1b88323f92 100644 --- a/src/commands/hexpire.json +++ b/src/commands/hexpire.json @@ -9,7 +9,8 @@ "history": [], "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "HASH" diff --git a/src/commands/hgetdel.json b/src/commands/hgetdel.json index af748fb52a9..f1137c82980 100644 --- a/src/commands/hgetdel.json +++ b/src/commands/hgetdel.json @@ -9,7 +9,8 @@ "history": [], "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "HASH" diff --git a/src/commands/hgetex.json b/src/commands/hgetex.json index 02889ca8392..e8a97ba7883 100644 --- a/src/commands/hgetex.json +++ b/src/commands/hgetex.json @@ -9,7 +9,8 @@ "history": [], "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "HASH" diff --git a/src/commands/hincrby.json b/src/commands/hincrby.json index a90f5baf070..99e921a268d 100644 --- a/src/commands/hincrby.json +++ b/src/commands/hincrby.json @@ -9,7 +9,8 @@ "command_flags": [ "WRITE", "DENYOOM", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "HASH" diff --git a/src/commands/hincrbyfloat.json b/src/commands/hincrbyfloat.json index 6a7d1fdcb85..0f4c63a7f29 100644 --- a/src/commands/hincrbyfloat.json +++ b/src/commands/hincrbyfloat.json @@ -9,7 +9,8 @@ "command_flags": [ "WRITE", "DENYOOM", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "HASH" diff --git a/src/commands/hpexpire.json b/src/commands/hpexpire.json index 269eb4eace0..9c87e9f5713 100644 --- a/src/commands/hpexpire.json +++ b/src/commands/hpexpire.json @@ -9,7 +9,8 @@ "history": [], "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "HASH" diff --git a/src/commands/hsetex.json b/src/commands/hsetex.json index 6f6a6c60079..e276611515e 100644 --- a/src/commands/hsetex.json +++ b/src/commands/hsetex.json @@ -9,7 +9,8 @@ "command_flags": [ "WRITE", "DENYOOM", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "HASH" diff --git a/src/commands/incrbyfloat.json b/src/commands/incrbyfloat.json index c594a1afe77..b11b30fc2d6 100644 --- a/src/commands/incrbyfloat.json +++ b/src/commands/incrbyfloat.json @@ -9,7 +9,8 @@ "command_flags": [ "WRITE", "DENYOOM", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "STRING" diff --git a/src/commands/pexpire.json b/src/commands/pexpire.json index a133f4fa2bd..7f23f2c324c 100644 --- a/src/commands/pexpire.json +++ b/src/commands/pexpire.json @@ -14,7 +14,8 @@ ], "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "KEYSPACE" diff --git a/src/commands/psetex.json b/src/commands/psetex.json index 8d88766025a..7d3c8b01581 100644 --- a/src/commands/psetex.json +++ b/src/commands/psetex.json @@ -13,7 +13,8 @@ ], "command_flags": [ "WRITE", - "DENYOOM" + "DENYOOM", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "STRING" diff --git a/src/commands/setex.json b/src/commands/setex.json index 1543a41d700..c05fcd952f1 100644 --- a/src/commands/setex.json +++ b/src/commands/setex.json @@ -13,7 +13,8 @@ ], "command_flags": [ "WRITE", - "DENYOOM" + "DENYOOM", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "STRING" diff --git a/src/commands/setrange.json b/src/commands/setrange.json index 32a8c521ae7..c69df1d8af1 100644 --- a/src/commands/setrange.json +++ b/src/commands/setrange.json @@ -8,7 +8,8 @@ "function": "setrangeCommand", "command_flags": [ "WRITE", - "DENYOOM" + "DENYOOM", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "STRING" diff --git a/src/commands/spop.json b/src/commands/spop.json index c3954bef5ea..79a45533114 100644 --- a/src/commands/spop.json +++ b/src/commands/spop.json @@ -14,7 +14,8 @@ ], "command_flags": [ "WRITE", - "FAST" + "FAST", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "SET" diff --git a/src/commands/zmpop.json b/src/commands/zmpop.json index 86dc3bab28e..377aacc1b18 100644 --- a/src/commands/zmpop.json +++ b/src/commands/zmpop.json @@ -8,7 +8,8 @@ "function": "zmpopCommand", "get_keys_function": "zmpopGetKeys", "command_flags": [ - "WRITE" + "WRITE", + "GTID_NON_DETERMINISM" ], "acl_categories": [ "SORTEDSET" diff --git a/src/module.c b/src/module.c index 057eec57a6c..f328a920998 100644 --- a/src/module.c +++ b/src/module.c @@ -1166,6 +1166,7 @@ int64_t commandFlagsFromString(char *s) { else if (!strcasecmp(t,"no-mandatory-keys")) flags |= CMD_NO_MANDATORY_KEYS; else if (!strcasecmp(t,"allow-busy")) flags |= CMD_ALLOW_BUSY; else if (!strcasecmp(t, "internal")) flags |= (CMD_INTERNAL|CMD_NOSCRIPT); /* We also disallow internal commands in scripts. */ + else if (!strcasecmp(t,"rewrite-command")) flags |= CMD_GTID_NON_DETERMINISM; else break; } sdsfreesplitres(tokens,count); diff --git a/src/server.c b/src/server.c index 61bf1fa5afc..f8e68f77805 100644 --- a/src/server.c +++ b/src/server.c @@ -5286,6 +5286,7 @@ void addReplyFlagsForCommand(client *c, struct redisCommand *cmd) { {CMD_MOVABLE_KEYS, "movablekeys"}, {CMD_ALLOW_BUSY, "allow_busy"}, /* {CMD_TOUCHES_ARBITRARY_KEYS, "TOUCHES_ARBITRARY_KEYS"}, Hidden on purpose */ + {CMD_GTID_NON_DETERMINISM, "rewrite_command"}, {0,NULL} }; addReplyCommandFlags(c, cmd->flags, flagNames); diff --git a/src/server.h b/src/server.h index d19f133c6b7..4de96313abb 100644 --- a/src/server.h +++ b/src/server.h @@ -267,6 +267,7 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT]; #define CMD_MODULE_GETCHANNELS (1ULL<<27) /* Use the modules getchannels interface. */ #define CMD_TOUCHES_ARBITRARY_KEYS (1ULL<<28) #define CMD_INTERNAL (1ULL<<29) /* Internal command. */ +#define CMD_GTID_NON_DETERMINISM (1ULL<<30) /* Command flags that describe ACLs categories. */ #define ACL_CATEGORY_KEYSPACE (1ULL<<0) diff --git a/tests/gtid/gtid.tcl b/tests/gtid/gtid.tcl index fdea50415f4..160498546d1 100644 --- a/tests/gtid/gtid.tcl +++ b/tests/gtid/gtid.tcl @@ -610,3 +610,88 @@ start_server {tags {"repl"} overrides} { } } } + +proc gtid_rewrite_cmd_list {} { + return { + append + blmove + blmpop + blpop + brpop + brpoplpush + bzmpop + bzpopmax + bzpopmin + expire + expireat + getdel + getex + getset + hexpire + hgetdel + hgetex + hincrby + hincrbyfloat + hpexpire + hsetex + incrbyfloat + pexpire + psetex + setex + setrange + spop + zmpop + } +} + +# Verify that GTID command rejects commands that would rewrite their argv +# (e.g. expire -> PEXPIREAT, setex -> SET PX, incrbyfloat -> SET). Rewriting +# argv inside the gtid command body is unsafe: the rewritten argv is dropped +# on gtidCommand exit (which restores orig_argv), so the unrewritten original +# is written to AOF/replication and breaks master-replica consistency. The +# command list is version-specific and supplied by gtid_rewrite_cmd_list. +start_server {tags {"gtid"} overrides {gtid-enabled yes}} { + test {GTID should reject commands that rewrite argv} { + set rewrite_cmds [gtid_rewrite_cmd_list] + set gno 1 + set now_seconds [clock seconds] + set now_ms [clock milliseconds] + + foreach cmd $rewrite_cmds { + switch -- $cmd { + "append" { set args [list $cmd rw_append v] } + "expire" { set args [list $cmd k1 1000] } + "pexpire" { set args [list $cmd k1 1000] } + "expireat" { set args [list $cmd k1 [expr {$now_seconds + 100}]] } + "setex" { set args [list $cmd k1 10 v] } + "psetex" { set args [list $cmd k1 10000 v] } + "getdel" { set args [list $cmd rw_getdel k] } + "getset" { set args [list $cmd getset_key new] } + "getex" { set args [list $cmd getset_key EX 100] } + "setrange" { set args [list $cmd rw_setrange 0 v] } + "hexpire" { set args [list $cmd hash_key 100 f1] } + "hpexpire" { set args [list $cmd hash_key 100 f1] } + "hsetex" { set args [list $cmd hash_key 100 f2 v2] } + "hgetdel" { set args [list $cmd hash_key f1] } + "hgetex" { set args [list $cmd hash_key f1 EX 100] } + "hincrby" { set args [list $cmd hash_key f1 1] } + "hincrbyfloat" { set args [list $cmd hash_key f1 1.1] } + "incrbyfloat" { set args [list $cmd getset_key 1.1] } + "blmove" { set args [list $cmd src_key dst_key LEFT RIGHT 1] } + "brpoplpush" { set args [list $cmd src_key dst_key 1] } + "blpop" { set args [list $cmd list_key 1] } + "brpop" { set args [list $cmd list_key 1] } + "blmpop" { set args [list $cmd list_key 2 LEFT 1] } + "bzpopmin" { set args [list $cmd zset_key 1] } + "bzpopmax" { set args [list $cmd zset_key 1] } + "bzmpop" { set args [list $cmd hash_key 2 MIN 1] } + "zmpop" { set args [list $cmd hash_key 2 MIN] } + "spop" { set args [list $cmd spop_key] } + default { fail "unexpected command $cmd" } + } + catch {r gtid A:$gno 0 {*}$args} result + assert_match {*ERR*} $result + incr gno + } + } +}