diff options
author | Anel Husakovic <anel@mariadb.org> | 2020-05-12 16:16:05 +0200 |
---|---|---|
committer | Anel Husakovic <anel@mariadb.org> | 2020-05-28 17:08:40 +0200 |
commit | 957cb7b7ba355184aebf0f5dc91b7f2aa620c0e0 (patch) | |
tree | 365a8d1e21f7d3b6ab4ce142b1119d25d17a756e /sql/sql_acl.cc | |
parent | dbe447a78908214614db53061dccbc6bde52764e (diff) | |
download | mariadb-git-957cb7b7ba355184aebf0f5dc91b7f2aa620c0e0.tar.gz |
MDEV-22312: Bad error message for SET DEFAULT ROLE when user account is not granted the role
- `SET DEFAULT ROLE xxx [FOR yyy]` should say:
"User yyy has not been granted a role xxx" if:
- The current user (not the user `yyy` in the FOR clause) can see the
role xxx. It can see the role if:
* role exists in `mysql.roles_mappings` (traverse the graph),
* If the current user has read access on `mysql.user` table - in
that case, it can see all roles, granted or not.
- Otherwise it should be "Invalid role specification".
In other words, it should not be possible to use `SET DEFAULT ROLE` to discover whether a specific role exist or not.
Diffstat (limited to 'sql/sql_acl.cc')
-rw-r--r-- | sql/sql_acl.cc | 120 |
1 files changed, 82 insertions, 38 deletions
diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index b6a6f806e50..af8685c458b 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -201,6 +201,8 @@ LEX_STRING current_user= { C_STRING_WITH_LEN("*current_user") }; LEX_STRING current_role= { C_STRING_WITH_LEN("*current_role") }; LEX_STRING current_user_and_current_role= { C_STRING_WITH_LEN("*current_user_and_current_role") }; +class ACL_USER; +static ACL_USER *find_user_or_anon(const char *host, const char *user, const char *ip); #ifndef NO_EMBEDDED_ACCESS_CHECKS static plugin_ref old_password_plugin; @@ -2034,8 +2036,21 @@ bool acl_getroot(Security_context *sctx, char *user, char *host, DBUG_RETURN(res); } -static int check_user_can_set_role(const char *user, const char *host, - const char *ip, const char *rolename, ulonglong *access) +static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data) +{ + LEX_CSTRING *rolename= static_cast<LEX_CSTRING *>(data); + if (rolename->length == grantee->user.length && + !strcmp(rolename->str, grantee->user.str)) + return -1; // End search, we've found our role. + + /* Keep looking, we haven't found our role yet. */ + return 0; +} + + +static int check_user_can_set_role(THD *thd, const char *user, const char *host, + const char *ip, const char *rolename, + ulonglong *access) { ACL_ROLE *role; ACL_USER_BASE *acl_user_base; @@ -2053,8 +2068,7 @@ static int check_user_can_set_role(const char *user, const char *host, acl_user= find_user_wild(host, user, ip); if (acl_user == NULL) { - my_error(ER_INVALID_CURRENT_USER, MYF(0), rolename); - result= -1; + result= ER_INVALID_CURRENT_USER; } else if (access) *access= acl_user->access; @@ -2065,9 +2079,9 @@ static int check_user_can_set_role(const char *user, const char *host, role= find_acl_role(rolename); /* According to SQL standard, the same error message must be presented */ - if (role == NULL) { - my_error(ER_INVALID_ROLE, MYF(0), rolename); - result= -1; + if (role == NULL) + { + result= ER_INVALID_ROLE; goto end; } @@ -2088,7 +2102,6 @@ static int check_user_can_set_role(const char *user, const char *host, /* According to SQL standard, the same error message must be presented */ if (!is_granted) { - my_error(ER_INVALID_ROLE, MYF(0), rolename); result= 1; goto end; } @@ -2097,17 +2110,66 @@ static int check_user_can_set_role(const char *user, const char *host, { *access = acl_user->access | role->access; } + end: mysql_mutex_unlock(&acl_cache->lock); - return result; + /* We present different error messages depending if the user has sufficient + privileges to know if the INVALID_ROLE exists. */ + switch (result) + { + case ER_INVALID_CURRENT_USER: + my_error(ER_INVALID_CURRENT_USER, MYF(0), rolename); + break; + case ER_INVALID_ROLE: + /* Role doesn't exist at all */ + my_error(ER_INVALID_ROLE, MYF(0), rolename); + break; + case 1: + StringBuffer<1024> c_usr; + LEX_CSTRING role_lex; + /* First, check if current user can see mysql database. */ + bool read_access= !check_access(thd, SELECT_ACL, "mysql", NULL, NULL, 1, 1); + + role_lex.str= rolename; + role_lex.length= strlen(rolename); + mysql_mutex_lock(&acl_cache->lock); + ACL_USER *cur_user= find_user_or_anon(thd->security_ctx->priv_host, + thd->security_ctx->priv_user, + thd->security_ctx->ip); + + /* If the current user does not have select priv to mysql database, + see if the current user can discover the role if it was granted to him. + */ + if (cur_user && (read_access || + traverse_role_graph_down(cur_user, &role_lex, + check_role_is_granted_callback, + NULL) == -1)) + { + /* Role is not granted but current user can see the role */ + c_usr.append(user, strlen(user)); + c_usr.append('@'); + c_usr.append(host, strlen(host)); + my_printf_error(ER_INVALID_ROLE, "User %`s has not been granted role %`s", + MYF(0), c_usr.c_ptr(), rolename); + } + else + { + /* Role is not granted and current user cannot see the role */ + my_error(ER_INVALID_ROLE, MYF(0), rolename); + } + mysql_mutex_unlock(&acl_cache->lock); + break; + } + + return result; } + int acl_check_setrole(THD *thd, char *rolename, ulonglong *access) { - /* Yes! priv_user@host. Don't ask why - that's what check_access() does. */ - return check_user_can_set_role(thd->security_ctx->priv_user, - thd->security_ctx->host, thd->security_ctx->ip, rolename, access); + return check_user_can_set_role(thd, thd->security_ctx->priv_user, + thd->security_ctx->host, thd->security_ctx->ip, rolename, access); } @@ -2886,9 +2948,12 @@ WSREP_ERROR_LABEL: DBUG_RETURN(result); } -int acl_check_set_default_role(THD *thd, const char *host, const char *user) +int acl_check_set_default_role(THD *thd, const char *host, const char *user, + const char *role) { - return check_alter_user(thd, host, user); + DBUG_ENTER("acl_check_set_default_role"); + DBUG_RETURN(check_alter_user(thd, host, user) || + check_user_can_set_role(thd, user, host, NULL, role, NULL)); } int acl_set_default_role(THD *thd, const char *host, const char *user, @@ -2910,16 +2975,6 @@ int acl_set_default_role(THD *thd, const char *host, const char *user, DBUG_PRINT("enter",("host: '%s' user: '%s' rolename: '%s'", safe_str(user), safe_str(host), safe_str(rolename))); - if (rolename == current_role.str) { - if (!thd->security_ctx->priv_role[0]) - rolename= "NONE"; - else - rolename= thd->security_ctx->priv_role; - } - - if (check_user_can_set_role(user, host, host, rolename, NULL)) - DBUG_RETURN(result); - if (!strcasecmp(rolename, "NONE")) clear_role= TRUE; @@ -3370,7 +3425,7 @@ static bool test_if_create_new_users(THD *thd) if (!(db_access & INSERT_ACL)) { if (check_grant(thd, INSERT_ACL, &tl, FALSE, UINT_MAX, TRUE)) - create_new_users=0; + create_new_users=0; } } return create_new_users; @@ -8508,17 +8563,6 @@ void get_mqh(const char *user, const char *host, USER_CONN *uc) mysql_mutex_unlock(&acl_cache->lock); } -static int check_role_is_granted_callback(ACL_USER_BASE *grantee, void *data) -{ - LEX_CSTRING *rolename= static_cast<LEX_CSTRING *>(data); - if (rolename->length == grantee->user.length && - !strcmp(rolename->str, grantee->user.str)) - return -1; // End search, we've found our role. - - /* Keep looking, we haven't found our role yet. */ - return 0; -} - /* Initialize a TABLE_LIST array and open grant tables @@ -10329,7 +10373,7 @@ acl_check_proxy_grant_access(THD *thd, const char *host, const char *user, Security context in THD contains two pairs of (user,host): 1. (user,host) pair referring to inbound connection. 2. (priv_user,priv_host) pair obtained from mysql.user table after doing - authnetication of incoming connection. + authentication of incoming connection. Privileges should be checked wrt (priv_user, priv_host) tuple, because (user,host) pair obtained from inbound connection may have different values than what is actually stored in mysql.user table and while granting @@ -10746,7 +10790,7 @@ int fill_schema_user_privileges(THD *thd, TABLE_LIST *tables, COND *cond) ulong j,test_access= want_access & ~GRANT_ACL; for (priv_id=0, j = SELECT_ACL;j <= GLOBAL_ACLS; priv_id++,j <<= 1) { - if (test_access & j) + if (test_access & j) { if (update_schema_privilege(thd, table, buff, 0, 0, 0, 0, command_array[priv_id], |