Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7d14bb1
Add database-level access control
dvkashapov Jul 4, 2025
97a27c5
Fix selector flag assignment and apply clang-format
dvkashapov Jul 7, 2025
b2fd61a
Extend serverCommand with get_dbid_args, unify ACL checks
dvkashapov Jul 14, 2025
b4adb23
Run generate commands
dvkashapov Oct 30, 2025
c0709a1
Merge remote-tracking branch 'upstream/unstable' into unstable
dvkashapov Oct 30, 2025
88d96ba
Fix tests and apply clang
dvkashapov Oct 30, 2025
98f4550
Delete CROSS_DB and NOT_IMPLEMENTED
dvkashapov Oct 30, 2025
e6c10dd
Delete CROSS_DB from commands.def
dvkashapov Oct 30, 2025
74ccbec
Add db+= and db-= syntax
dvkashapov Oct 31, 2025
8bc8d13
Fix comment style
dvkashapov Oct 31, 2025
a4397dc
clang-format fix
dvkashapov Oct 31, 2025
fa90b04
Refactor and add more tests
dvkashapov Nov 5, 2025
a3af4f6
Merge remote-tracking branch 'upstream/unstable' into unstable
dvkashapov Nov 6, 2025
15068cf
Fix reason in ACL LOG and add test
dvkashapov Nov 16, 2025
ec93e49
Use cmd->fullname when no argpos
dvkashapov Nov 16, 2025
711b4f3
Add module api for db-level check and test
dvkashapov Nov 16, 2025
fc5bc3a
apply pr suggestions
dvkashapov Nov 17, 2025
10d7499
apply clang-format
dvkashapov Nov 17, 2025
7f0e020
add ALL_DBS flag to migration/slot commands
dvkashapov Nov 17, 2025
b6ab15b
db= syntax, acl getuser support
dvkashapov Nov 26, 2025
0562230
Return ACL_DENIED_DB only for R/W commands in forbidden db
dvkashapov Nov 26, 2025
04568af
Use sds
dvkashapov Nov 26, 2025
19186e7
Merge remote-tracking branch 'upstream/unstable' into unstable
dvkashapov Nov 27, 2025
d35a3a7
Merge remote-tracking branch 'upstream/unstable' into unstable
dvkashapov Nov 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 216 additions & 12 deletions src/acl.c

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/cli_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

/* Definitions to configure commands.c to generate the above structs. */
#define MAKE_CMD(name, summary, complexity, since, doc_flags, replaced, deprecated, group, group_enum, history, \
num_history, tips, num_tips, function, arity, flags, acl, key_specs, key_specs_num, get_keys, \
numargs) \
num_history, tips, num_tips, function, arity, flags, acl, get_dbid_args, key_specs, \
key_specs_num, get_keys, numargs) \
name, summary, group, since, numargs
#define MAKE_ARG(name, type, key_spec_index, token, summary, since, flags, numsubargs, deprecated_since) \
name, type, token, since, flags, numsubargs
Expand Down
6 changes: 3 additions & 3 deletions src/commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
#include "server.h"

#define MAKE_CMD(name, summary, complexity, since, doc_flags, replaced, deprecated, group, group_enum, history, \
num_history, tips, num_tips, function, arity, flags, acl, key_specs, key_specs_num, get_keys, \
numargs) \
num_history, tips, num_tips, function, arity, flags, acl, get_dbid_args, key_specs, \
key_specs_num, get_keys, numargs) \
name, summary, complexity, since, doc_flags, replaced, deprecated, group_enum, history, num_history, tips, \
num_tips, function, arity, flags, acl, key_specs, key_specs_num, get_keys, numargs
num_tips, function, arity, flags, acl, get_dbid_args, key_specs, key_specs_num, get_keys, numargs
#define MAKE_ARG(name, type, key_spec_index, token, summary, since, flags, numsubargs, deprecated_since) \
name, type, key_spec_index, token, summary, since, flags, deprecated_since, numsubargs
#define COMMAND_STRUCT serverCommand
Expand Down
844 changes: 422 additions & 422 deletions src/commands.def

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/commands/flushall.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
]
],
"command_flags": [
"WRITE"
"WRITE",
"ALL_DBS"
],
"acl_categories": [
"KEYSPACE",
Expand Down
1 change: 1 addition & 0 deletions src/commands/move.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"acl_categories": [
"KEYSPACE"
],
"get_dbid_args": "moveDbIdArgs",
"key_specs": [
{
"flags": [
Expand Down
1 change: 1 addition & 0 deletions src/commands/select.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"acl_categories": [
"CONNECTION"
],
"get_dbid_args": "selectDbIdArgs",
"reply_schema": {
"const": "OK"
},
Expand Down
1 change: 1 addition & 0 deletions src/commands/swapdb.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"KEYSPACE",
"DANGEROUS"
],
"get_dbid_args": "swapdbDbIdArgs",
"arguments": [
{
"name": "index1",
Expand Down
46 changes: 46 additions & 0 deletions src/db.c
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,10 @@ void selectCommand(client *c) {

if (getIntFromObjectOrReply(c, c->argv[1], &id, NULL) != C_OK) return;

if (c->flag.multi) {
c->mstate->transaction_db_id = id;
}

if (selectDb(c, id) == C_ERR) {
addReplyError(c, "DB index is out of range");
} else {
Expand Down Expand Up @@ -2961,3 +2965,45 @@ int bitfieldGetKeys(struct serverCommand *cmd, robj **argv, int argc, getKeysRes
}
return 1;
}


int *selectDbIdArgs(robj **argv, int argc, int *count) {
if (argc < 2) return NULL;

long long dbid;
if (getLongLongFromObject(argv[1], &dbid) != C_OK) return NULL;
if (dbid < 0 || dbid >= server.dbnum) return NULL;

int *result = zmalloc(sizeof(int));
result[0] = (int)dbid;
*count = 1;
return result;
}

int *swapdbDbIdArgs(robj **argv, int argc, int *count) {
if (argc < 3) return NULL;

long long db1, db2;
if (getLongLongFromObject(argv[1], &db1) != C_OK ||
getLongLongFromObject(argv[2], &db2) != C_OK) return NULL;
if (db1 < 0 || db1 >= server.dbnum || db2 < 0 || db2 >= server.dbnum) return NULL;

int *result = zmalloc(2 * sizeof(int));
result[0] = (int)db1;
result[1] = (int)db2;
*count = 2;
return result;
}

int *moveDbIdArgs(robj **argv, int argc, int *count) {
if (argc < 3) return NULL;

long long dbid;
if (getLongLongFromObject(argv[2], &dbid) != C_OK) return NULL;
if (dbid < 0 || dbid >= server.dbnum) return NULL;

int *result = zmalloc(sizeof(int));
result[0] = (int)dbid;
*count = 1;
return result;
}
17 changes: 17 additions & 0 deletions src/intset.c
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,20 @@ int intsetValidateIntegrity(const unsigned char *p, size_t size, int deep) {

return 1;
}

/* Free an intset */
void intsetFree(intset *is) {
if (is) zfree(is);
}

/* Duplicate an intset */
intset *intsetDup(intset *is) {
if (!is) return intsetNew();

size_t size = intsetBlobLen(is);
intset *copy = zmalloc(size);
if (!copy) return NULL;

memcpy(copy, is, size);
return copy;
}
2 changes: 2 additions & 0 deletions src/intset.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,7 @@ uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
uint32_t intsetLen(const intset *is);
size_t intsetBlobLen(intset *is);
int intsetValidateIntegrity(const unsigned char *is, size_t size, int deep);
void intsetFree(intset *is);
intset *intsetDup(intset *is);

#endif // __INTSET_H
3 changes: 2 additions & 1 deletion src/lua/script_lua.c
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,8 @@ static int luaRedisAclCheckCmdPermissionsCommand(lua_State *lua) {
raise_error = 1;
} else {
int keyidxptr;
if (ACLCheckAllUserCommandPerm(rctx->original_client->user, cmd, argv, argc, &keyidxptr) != ACL_OK) {
int dbid = (rctx->original_client->flag.multi) ? rctx->original_client->mstate->transaction_db_id : (rctx->original_client->db ? rctx->original_client->db->id : -1);
if (ACLCheckAllUserCommandPerm(rctx->original_client->user, cmd, argv, argc, dbid, &keyidxptr) != ACL_OK) {
lua_pushboolean(lua, 0);
} else {
lua_pushboolean(lua, 1);
Expand Down
7 changes: 4 additions & 3 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -6555,7 +6555,8 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const
int acl_errpos;
int acl_retval;

acl_retval = ACLCheckAllUserCommandPerm(user, c->cmd, c->argv, c->argc, &acl_errpos);
int dbid = (c->flag.multi) ? c->mstate->transaction_db_id : (c->db ? c->db->id : -1);
acl_retval = ACLCheckAllUserCommandPerm(user, c->cmd, c->argv, c->argc, dbid, &acl_errpos);
if (acl_retval != ACL_OK) {
sds object = (acl_retval == ACL_DENIED_CMD) ? sdsdup(c->cmd->fullname) : sdsdup(c->argv[acl_errpos]->ptr);
addACLLogEntry(ctx->client, acl_retval, ACL_LOG_CTX_MODULE, -1, c->user->name, object);
Expand Down Expand Up @@ -10048,7 +10049,7 @@ ValkeyModuleUser *VM_GetModuleUserFromUserName(ValkeyModuleString *name) {
* * ENOENT: Specified command does not exist.
* * EACCES: Command cannot be executed, according to ACL rules
*/
int VM_ACLCheckCommandPermissions(ValkeyModuleUser *user, ValkeyModuleString **argv, int argc) {
int VM_ACLCheckCommandPermissions(ValkeyModuleUser *user, ValkeyModuleString **argv, int argc, int dbid) {
int keyidxptr;
struct serverCommand *cmd;

Expand All @@ -10058,7 +10059,7 @@ int VM_ACLCheckCommandPermissions(ValkeyModuleUser *user, ValkeyModuleString **a
return VALKEYMODULE_ERR;
}

if (ACLCheckAllUserCommandPerm(user->user, cmd, argv, argc, &keyidxptr) != ACL_OK) {
if (ACLCheckAllUserCommandPerm(user->user, cmd, argv, argc, dbid, &keyidxptr) != ACL_OK) {
errno = EACCES;
return VALKEYMODULE_ERR;
}
Expand Down
12 changes: 12 additions & 0 deletions src/multi.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ void resetClientMultiState(client *c) {
c->mstate->cmd_inv_flags = 0;
c->mstate->argv_len_sums = 0;
c->mstate->alloc_count = 0;
c->mstate->transaction_db_id = c->db->id;
}

/* Add a new command into the MULTI commands queue */
Expand Down Expand Up @@ -98,6 +99,15 @@ void queueMultiCommand(client *c, uint64_t cmd_flags) {
mc->argv_len = c->argv_len;
mc->slot = c->slot;

if (mc->cmd->proc == selectCommand && mc->argc > 1) {
long long target_db;
if (getLongLongFromObject(mc->argv[1], &target_db) == C_OK) {
if (target_db >= 0 && target_db < server.dbnum) {
c->mstate->transaction_db_id = (int)target_db;
}
}
}

c->mstate->count++;
c->mstate->cmd_flags |= cmd_flags;
c->mstate->cmd_inv_flags |= ~cmd_flags;
Expand Down Expand Up @@ -131,6 +141,7 @@ void flagTransaction(client *c) {
void multiCommand(client *c) {
if (!c->mstate) initClientMultiState(c);
c->flag.multi = 1;
c->mstate->transaction_db_id = c->db->id;
addReply(c, shared.ok);
}

Expand Down Expand Up @@ -207,6 +218,7 @@ void execCommand(client *c) {
orig_argv_len = c->argv_len;
orig_argc = c->argc;
orig_cmd = c->cmd;
c->mstate->transaction_db_id = c->db->id;
addReplyArrayLen(c, c->mstate->count);
for (j = 0; j < c->mstate->count; j++) {
c->argc = c->mstate->commands[j].argc;
Expand Down
1 change: 1 addition & 0 deletions src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -2973,6 +2973,7 @@ void initServer(void) {
server.acl_info.user_auth_failures = 0;
server.acl_info.invalid_channel_accesses = 0;
server.acl_info.acl_access_denied_tls_cert = 0;
server.acl_info.invalid_db_accesses = 0;

/* Create the timer callback, this is our way to process many background
* operations incrementally, like eviction of unaccessed expired keys, etc. */
Expand Down
37 changes: 26 additions & 11 deletions src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
#define CMD_ALLOW_BUSY ((1ULL << 26))
#define CMD_MODULE_GETCHANNELS (1ULL << 27) /* Use the modules getchannels interface. */
#define CMD_TOUCHES_ARBITRARY_KEYS (1ULL << 28)
#define CMD_ALL_DBS (1ULL << 29)
/* Command flags. Please don't forget to add command flag documentation in struct
* serverCommand in this file. */

Expand Down Expand Up @@ -924,6 +925,7 @@ typedef struct multiState {
size_t argv_len_sums; /* mem used by all commands arguments */
int alloc_count; /* total number of multiCmd struct memory reserved. */
list watched_keys;
int transaction_db_id; /* Currently SELECTed DB id in transaction context */
} multiState;

/* This structure holds the blocking operation state for a client.
Expand Down Expand Up @@ -1005,6 +1007,8 @@ typedef struct readyList {
#define SELECTOR_FLAG_ALLCOMMANDS (1 << 2) /* The user can run all commands. */
#define SELECTOR_FLAG_ALLCHANNELS (1 << 3) /* The user can mention any Pub/Sub \
channel. */
#define SELECTOR_FLAG_ALLDBS (1 << 4) /* Allow all databases */


typedef struct {
sds name; /* The username as an SDS string. */
Expand Down Expand Up @@ -1380,6 +1384,7 @@ typedef struct aclInfo {
long long invalid_key_accesses; /* Invalid key accesses that user doesn't have permission to */
long long invalid_channel_accesses; /* Invalid channel accesses that user doesn't have permission to */
long long acl_access_denied_tls_cert; /* TLS clients with cert not matching any existing user. */
long long invalid_db_accesses; /* Invalid database accesses that user doesn't have permission to */
} aclInfo;

struct saveparam {
Expand Down Expand Up @@ -2467,6 +2472,7 @@ typedef enum {

typedef void serverCommandProc(client *c);
typedef int serverGetKeysProc(struct serverCommand *cmd, robj **argv, int argc, getKeysResult *result);
typedef int *commandDbIdArgs(robj **argv, int argc, int *count);

/* Command structure.
*
Expand Down Expand Up @@ -2552,6 +2558,8 @@ typedef int serverGetKeysProc(struct serverCommand *cmd, robj **argv, int argc,
* CMD_TOUCHES_ARBITRARY_KEYS: The command may touch (and cause lazy-expire)
* arbitrary key (i.e not provided in argv)
*
* CMD_ALL_DBS: The command works with all databases.
*
* The following additional flags are only used in order to put commands
* in a specific ACL category. Commands can have multiple ACL categories.
* See valkey.conf for the exact meaning of each.
Expand Down Expand Up @@ -2588,10 +2596,11 @@ struct serverCommand {
int num_history;
const char **tips; /* An array of strings that are meant to be tips for clients/proxies regarding this command */
int num_tips;
serverCommandProc *proc; /* Command implementation */
int arity; /* Number of arguments, it is possible to use -N to say >= N */
uint64_t flags; /* Command flags, see CMD_*. */
uint64_t acl_categories; /* ACl categories, see ACL_CATEGORY_*. */
serverCommandProc *proc; /* Command implementation */
int arity; /* Number of arguments, it is possible to use -N to say >= N */
uint64_t flags; /* Command flags, see CMD_*. */
uint64_t acl_categories; /* ACl categories, see ACL_CATEGORY_*. */
commandDbIdArgs *get_dbid_args; /* Function to get database IDs used by this command */
keySpec *key_specs;
int key_specs_num;
/* Use a function to determine keys arguments in a command line.
Expand Down Expand Up @@ -3187,11 +3196,12 @@ extern user *DefaultUser;
void ACLInit(void);
/* Return values for ACLCheckAllPerm(). */
#define ACL_OK 0
#define ACL_DENIED_CMD 1
#define ACL_DENIED_KEY 2
#define ACL_DENIED_AUTH 3 /* Only used for ACL LOG entries. */
#define ACL_DENIED_CHANNEL 4 /* Only used for pub/sub commands */
#define ACL_INVALID_TLS_CERT_AUTH 5 /* Only used for TLS Auto-authentication */
#define ACL_DENIED_DB 1 /* Database can't be accessed */
#define ACL_DENIED_CMD 2
#define ACL_DENIED_KEY 3
#define ACL_DENIED_AUTH 4 /* Only used for ACL LOG entries. */
#define ACL_DENIED_CHANNEL 5 /* Only used for pub/sub commands */
#define ACL_INVALID_TLS_CERT_AUTH 6 /* Only used for TLS Auto-authentication */

/* Context values for addACLLogEntry(). */
#define ACL_LOG_CTX_TOPLEVEL 0
Expand Down Expand Up @@ -3219,8 +3229,8 @@ unsigned long ACLGetCommandID(sds cmdname);
user *ACLGetUserByName(const char *name, size_t namelen);
int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags);
int ACLUserCheckChannelPerm(user *u, sds channel, int literal);
int ACLCheckAllUserCommandPerm(user *u, struct serverCommand *cmd, robj **argv, int argc, int *idxptr);
int ACLUserCheckCmdWithUnrestrictedKeyAccess(user *u, struct serverCommand *cmd, robj **argv, int argc, int flags);
int ACLCheckAllUserCommandPerm(user *u, struct serverCommand *cmd, robj **argv, int argc, int dbid, int *idxptr);
int ACLUserCheckCmdWithUnrestrictedKeyAccess(user *u, struct serverCommand *cmd, robj **argv, int argc, int dbid, int flags);
int ACLCheckAllPerm(client *c, int *idxptr);
int ACLSetUser(user *u, const char *op, ssize_t oplen);
sds ACLStringSetUser(user *u, sds username, sds *argv, int argc);
Expand Down Expand Up @@ -4066,6 +4076,11 @@ void quitCommand(client *c);
void resetCommand(client *c);
void failoverCommand(client *c);

/* Helper functions for getting database id args from argv, argc */
int *selectDbIdArgs(robj **argv, int argc, int *count);
int *swapdbDbIdArgs(robj **argv, int argc, int *count);
int *moveDbIdArgs(robj **argv, int argc, int *count);

#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__((deprecated));
void free(void *ptr) __attribute__((deprecated));
Expand Down
3 changes: 2 additions & 1 deletion src/sort.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,9 @@ void sortCommandGeneric(client *c, int readonly) {
listSetFreeMethod(operations, zfree);
j = 2; /* options start at argv[2] */

int dbid = (c->flag.multi) ? c->mstate->transaction_db_id : (c->db ? c->db->id : -1);
user_has_full_key_access =
ACLUserCheckCmdWithUnrestrictedKeyAccess(c->user, c->cmd, c->argv, c->argc, CMD_KEY_ACCESS);
ACLUserCheckCmdWithUnrestrictedKeyAccess(c->user, c->cmd, c->argv, c->argc, dbid, CMD_KEY_ACCESS);

/* The SORT command has an SQL-alike syntax, parse it */
while (j < c->argc) {
Expand Down
Loading