From e0494c370b8d6ce61e58b354707e8302626548e3 Mon Sep 17 00:00:00 2001 From: Luke Palmer Date: Fri, 12 Jun 2026 11:19:14 -0400 Subject: [PATCH 1/2] AOF loading must not check ACL permissions Signed-off-by: Luke Palmer --- src/multi.c | 6 ++++-- tests/integration/aof.tcl | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/multi.c b/src/multi.c index 7721db3bf51..f8053e9bf05 100644 --- a/src/multi.c +++ b/src/multi.c @@ -248,9 +248,11 @@ void execCommand(client *c) { c->cmd = c->realcmd = c->mstate->commands[j].cmd; /* ACL permissions are also checked at the time of execution in case - * they were changed after the commands were queued. */ + * they were changed after the commands were queued. The client + * replaying the AOF is exempt because its commands were permitted when + * they were first executed. */ int acl_errpos; - int acl_retval = ACLCheckAllPerm(c, &acl_errpos); + int acl_retval = c->id == CLIENT_ID_AOF ? ACL_OK : ACLCheckAllPerm(c, &acl_errpos); if (acl_retval != ACL_OK) { char *reason; switch (acl_retval) { diff --git a/tests/integration/aof.tcl b/tests/integration/aof.tcl index 6002641df90..96faa0ea0e7 100644 --- a/tests/integration/aof.tcl +++ b/tests/integration/aof.tcl @@ -376,6 +376,31 @@ tags {"aof external:skip logreqres:skip"} { } } + # A MULTI/EXEC block in the AOF must be replayed even when the default user + # is disabled. EXEC re-checks ACLs of the queued commands, but that check + # must not apply to the client used for loading the AOF, otherwise the + # transaction's writes are silently lost. + create_aof $aof_dirpath $aof_file { + append_to_aof [formatCommand set outside-tx 1] + append_to_aof [formatCommand multi] + append_to_aof [formatCommand set inside-tx-a 2] + append_to_aof [formatCommand set inside-tx-b 3] + append_to_aof [formatCommand exec] + } + + set acl_config_lines {user {default off} user {someuser on nopass ~* &* +@all}} + start_server_aof_ex [list dir $server_path] [list wait_ready false config_lines $acl_config_lines] { + test {AOF with MULTI/EXEC is fully loaded when the default user is disabled} { + set c [valkey [srv host] [srv port] 0 $::tls] + $c auth someuser somepass + wait_done_loading $c + assert_equal 1 [$c get outside-tx] + assert_equal 2 [$c get inside-tx-a] + assert_equal 3 [$c get inside-tx-b] + $c close + } + } + # The server could load AOF which has timestamp annotations inside create_aof $aof_dirpath $aof_file { append_to_aof "#TS:1628217470\r\n" From ee42c856e14b97b76d71a9595f615c026869ea62 Mon Sep 17 00:00:00 2001 From: Luke Palmer Date: Tue, 16 Jun 2026 13:10:58 -0400 Subject: [PATCH 2/2] Move AOF check inside ACLCheckAllPerms Signed-off-by: Luke Palmer --- src/acl.c | 3 +++ src/multi.c | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/acl.c b/src/acl.c index d39f9a5d444..7c6e3dec583 100644 --- a/src/acl.c +++ b/src/acl.c @@ -2094,6 +2094,9 @@ int ACLCheckAllUserCommandPerm(user *u, struct serverCommand *cmd, robj **argv, /* High level API for checking if a client can execute the queued up command */ int ACLCheckAllPerm(client *c, int *idxptr) { + /* AOF replay is not subject to ACLs because the commands were allowed at the + time they were executed */ + if (c->id == CLIENT_ID_AOF) return ACL_OK; int dbid = (c->flag.multi) ? c->mstate->transaction_db_id : c->db->id; return ACLCheckAllUserCommandPerm(c->user, c->cmd, c->argv, c->argc, dbid, idxptr); } diff --git a/src/multi.c b/src/multi.c index f8053e9bf05..7721db3bf51 100644 --- a/src/multi.c +++ b/src/multi.c @@ -248,11 +248,9 @@ void execCommand(client *c) { c->cmd = c->realcmd = c->mstate->commands[j].cmd; /* ACL permissions are also checked at the time of execution in case - * they were changed after the commands were queued. The client - * replaying the AOF is exempt because its commands were permitted when - * they were first executed. */ + * they were changed after the commands were queued. */ int acl_errpos; - int acl_retval = c->id == CLIENT_ID_AOF ? ACL_OK : ACLCheckAllPerm(c, &acl_errpos); + int acl_retval = ACLCheckAllPerm(c, &acl_errpos); if (acl_retval != ACL_OK) { char *reason; switch (acl_retval) {