MDEV-20299 SET SESSION AUTHORIZATION

a.k.a. "sudo"
This commit is contained in:
Sergei Golubchik 2025-02-23 12:59:38 +01:00
parent 0f4a35a327
commit 07de0ac69e
12 changed files with 438 additions and 11 deletions

View File

@ -0,0 +1,136 @@
#
# MDEV-20299 SET SESSION AUTHORIZATION
#
create user foo@bar identified via mysql_native_password using password('foo');
connect con1, localhost, root;
select user(), current_user(), database();
user() current_user() database()
root@localhost root@localhost test
set session authorization bar@foo;
ERROR HY000: The user 'bar'@'foo' does not exist
select user(), current_user(), database();
user() current_user() database()
root@localhost root@localhost test
set session authorization foo@bar;
select user(), current_user(), database();
user() current_user() database()
foo@bar foo@bar NULL
set @a:='not changed';
set session authorization bar@foo;
ERROR 28000: Access denied trying to change to user 'bar'@'foo'
select @a;
@a
not changed
set session authorization foo@bar;
select @a;
@a
NULL
disconnect con1;
connection default;
drop user foo@bar;
create user ''@'l%t' identified via mysql_native_password using password('foo');
connect con1, localhost, root;
select user(), current_user(), database();
user() current_user() database()
root@localhost root@localhost test
set session authorization fist@list;
select user(), current_user(), database();
user() current_user() database()
fist@list @l%t NULL
set @a:='not changed';
set session authorization first@last;
ERROR 28000: Access denied trying to change to user 'first'@'last'
select @a;
@a
not changed
set session authorization fist@list;
select @a;
@a
NULL
disconnect con1;
connection default;
drop user ''@'l%t';
create user ''@'%' identified via mysql_native_password using password('foo');
connect con1, localhost, root;
select user(), current_user(), database();
user() current_user() database()
root@localhost root@localhost test
set session authorization ''@last;
ERROR HY000: The user ''@'last' does not exist
set session authorization foo@'';
ERROR HY000: The user 'foo'@'' does not exist
start transaction;
select user(), current_user(), database(), @@in_transaction;
user() current_user() database() @@in_transaction
root@localhost root@localhost test 1
set session authorization foo@bar;
ERROR 25001: SESSION AUTHORIZATION can't be set while a transaction is in progress
select user(), current_user(), database(), @@in_transaction;
user() current_user() database() @@in_transaction
root@localhost root@localhost test 1
disconnect con1;
connection default;
prepare s from 'set session authorization foo@bar';
ERROR HY000: This command is not supported in the prepared statement protocol yet
create procedure sudo_foobar() set session authorization foo@bar;
ERROR 0A000: SET SESSION AUTHORIZATION is not allowed in stored procedures
create procedure sudo_foobar()
execute immediate 'set session authorization foo@bar';
call sudo_foobar();
ERROR HY000: This command is not supported in the prepared statement protocol yet
drop procedure sudo_foobar;
drop user ''@'%';
# restart: --skip-grant-tables
set session authorization u@localhost;
ERROR HY000: The MariaDB server is running with the --skip-grant-tables option so it cannot execute this statement
flush privileges;
create user u1@localhost with max_statement_time 1;
connect u1,localhost,u1;
select @@max_statement_time;
@@max_statement_time
1.000000
disconnect u1;
connect u1,localhost,root;
select @@max_statement_time;
@@max_statement_time
0.000000
set session authorization u1@localhost;
select @@max_statement_time;
@@max_statement_time
1.000000
disconnect u1;
connection default;
drop user u1@localhost;
#
# MDEV-36399 SET SESSION AUTHORIZATION allows an unrpivileged user to bypass resource limits
#
create user u1 with max_queries_per_hour 2;
connect u1,localhost,u1;
set session authorization u1@localhost;
select 1;
1
1
select 2;
ERROR 42000: User 'u1' has exceeded the 'max_queries_per_hour' resource (current value: 2)
disconnect u1;
connection default;
drop user u1;
#
# MDEV-36401 Access denied errors produced by SET SESSION AUTHORIZATION not reflected in status values
#
flush global status;
set session authorization u1@localhost;
ERROR HY000: The user 'u1'@'localhost' does not exist
create user u1;
connect u1,localhost,u1;
set session authorization root@localhost;
ERROR 28000: Access denied trying to change to user 'root'@'localhost'
set session authorization foo@bar;
ERROR 28000: Access denied trying to change to user 'foo'@'bar'
disconnect u1;
connection default;
show global status like 'access_denied_errors';
Variable_name Value
Access_denied_errors 2
drop user u1;
# End of 11.8 tests

View File

@ -0,0 +1,139 @@
source include/not_embedded.inc;
source include/no_protocol.inc;
--echo #
--echo # MDEV-20299 SET SESSION AUTHORIZATION
--echo #
# simple tests
create user foo@bar identified via mysql_native_password using password('foo');
connect con1, localhost, root;
select user(), current_user(), database();
# sudo, with SET USER privilege, nonexistent user
--error ER_NO_SUCH_USER
set session authorization bar@foo;
select user(), current_user(), database();
# sudo, with SET USER privilege
set session authorization foo@bar;
select user(), current_user(), database();
set @a:='not changed';
# sudo without SET USER privilege
--error ER_ACCESS_DENIED_CHANGE_USER_ERROR
set session authorization bar@foo;
select @a;
# to self, no privileges needed
set session authorization foo@bar;
select @a;
disconnect con1;
connection default;
drop user foo@bar;
# user() != current_user() (w/ wildcards)
create user ''@'l%t' identified via mysql_native_password using password('foo');
connect con1, localhost, root;
select user(), current_user(), database();
# sudo, with SET USER privilege
set session authorization fist@list;
select user(), current_user(), database();
set @a:='not changed';
# sudo without SET USER privilege (note, same CURRENT_USER)
--error ER_ACCESS_DENIED_CHANGE_USER_ERROR
set session authorization first@last;
select @a;
# to self, no privileges needed
set session authorization fist@list;
select @a;
disconnect con1;
connection default;
drop user ''@'l%t';
create user ''@'%' identified via mysql_native_password using password('foo');
connect con1, localhost, root;
select user(), current_user(), database();
# empty username
--error ER_NO_SUCH_USER
set session authorization ''@last;
# empty hostname
--error ER_NO_SUCH_USER
set session authorization foo@'';
# in a transaction - an error
start transaction;
select user(), current_user(), database(), @@in_transaction;
--error ER_CANT_SET_IN_TRANSACTION
set session authorization foo@bar;
select user(), current_user(), database(), @@in_transaction;
disconnect con1;
connection default;
# cannot be prepared
--error ER_UNSUPPORTED_PS
prepare s from 'set session authorization foo@bar';
# cannot be in a stored routine
--error ER_SP_BADSTATEMENT
create procedure sudo_foobar() set session authorization foo@bar;
create procedure sudo_foobar()
execute immediate 'set session authorization foo@bar';
--error ER_UNSUPPORTED_PS
call sudo_foobar();
drop procedure sudo_foobar;
drop user ''@'%';
# doesn't work if --skip-grant-tables
--let $restart_parameters= --skip-grant-tables
--source include/restart_mysqld.inc
--error ER_OPTION_PREVENTS_STATEMENT
set session authorization u@localhost;
flush privileges;
# max_statement_time -> @@max_statement_time
create user u1@localhost with max_statement_time 1;
connect u1,localhost,u1;
select @@max_statement_time;
disconnect u1;
connect u1,localhost,root;
select @@max_statement_time;
set session authorization u1@localhost;
select @@max_statement_time;
disconnect u1;
connection default;
drop user u1@localhost;
--echo #
--echo # MDEV-36399 SET SESSION AUTHORIZATION allows an unrpivileged user to bypass resource limits
--echo #
create user u1 with max_queries_per_hour 2;
connect u1,localhost,u1;
set session authorization u1@localhost;
select 1;
--error ER_USER_LIMIT_REACHED
select 2;
disconnect u1;
connection default;
drop user u1;
--echo #
--echo # MDEV-36401 Access denied errors produced by SET SESSION AUTHORIZATION not reflected in status values
--echo #
flush global status;
--error ER_NO_SUCH_USER
set session authorization u1@localhost;
create user u1;
connect u1,localhost,u1;
--error ER_ACCESS_DENIED_CHANGE_USER_ERROR
set session authorization root@localhost;
--error ER_ACCESS_DENIED_CHANGE_USER_ERROR
set session authorization foo@bar;
disconnect u1;
connection default;
show global status like 'access_denied_errors';
drop user u1;
--echo # End of 11.8 tests

View File

@ -76,6 +76,7 @@ SYMBOL symbols[] = {
{ "AT", SYM(AT_SYM)},
{ "ATOMIC", SYM(ATOMIC_SYM)},
{ "AUTHORS", SYM(AUTHORS_SYM)},
{ "AUTHORIZATION", SYM(AUTHORIZATION_SYM)},
{ "AUTO", SYM(AUTO_SYM)},
{ "AUTO_INCREMENT", SYM(AUTO_INC)},
{ "AVG", SYM(AVG_SYM)},

View File

@ -332,6 +332,8 @@ constexpr privilege_t PRIV_DEFINER_CLAUSE= SET_USER_ACL;
*/
constexpr privilege_t PRIV_REVEAL_MISSING_DEFINER= SET_USER_ACL;
constexpr privilege_t PRIV_SUDO_CHANGE_USER= SET_USER_ACL;
/* Actions that require only the SUPER privilege */
constexpr privilege_t PRIV_DES_DECRYPT_ONE_ARG= SUPER_ACL;
constexpr privilege_t PRIV_LOG_BIN_TRUSTED_SP_CREATOR= SUPER_ACL;

View File

@ -973,6 +973,39 @@ int set_var_password::update(THD *thd)
#endif
}
/*****************************************************************************
Functions to handle SET SESSION AUTHORIZATION
*****************************************************************************/
int set_var_authorization::check(THD *thd)
{
/*
SET SESSION AUTHORIZATION cannot be combined with other variables,
so most of the checks are only done in update.
*/
if (!thd->stmt_arena->is_conventional())
my_error(ER_UNSUPPORTED_PS, MYF(0));
else
if (thd->in_active_multi_stmt_transaction())
my_error(ER_CANT_SET_IN_TRANSACTION, MYF(0), "SESSION AUTHORIZATION");
else
return 0;
return 1;
}
int set_var_authorization::update(THD *thd)
{
#ifndef NO_EMBEDDED_ACCESS_CHECKS
int res= acl_setauthorization(thd, user);
if (!res)
thd->session_tracker.state_change.mark_as_changed(thd);
return res;
#else
return 0;
#endif
}
/*****************************************************************************
Functions to handle SET ROLE
*****************************************************************************/

View File

@ -363,6 +363,17 @@ public:
int update(THD *thd) override;
};
/* For SET SESSION AUTHORIZATION */
class set_var_authorization: public set_var_base
{
LEX_USER *user;
public:
set_var_authorization(LEX_USER *user_arg) : user(user_arg) {}
int check(THD *thd) override;
int update(THD *thd) override;
};
/* For SET ROLE */
class set_var_role: public set_var_base

View File

@ -10102,11 +10102,11 @@ ER_SLAVE_RLI_INIT_REPOSITORY
sw "Mtumwa alishindwa kuanzisha muundo wa habari ya relay wa logi kutoka kwa hazina"
ER_ACCESS_DENIED_CHANGE_USER_ERROR 28000
bgn "Отказан достъп при опит за смяна към потребител %-.48s'@'%-.64s' (използвана парола: %s). Затваряне на връзката"
chi "访问拒绝尝试更改为用户'%-.48s'@'%-.64s'(使用密码:%s。断开连接"
eng "Access denied trying to change to user '%-.48s'@'%-.64s' (using password: %s). Disconnecting"
spa "Acceso denegado intentando cambiar a usuario '%-.48s'@'%-.64s' (usando contraseña: %s). Desconectando"
sw "Ufikiaji umekataliwa kujaribu kubadilisha hadi mtumiaji '%-.48s'@'%-.64s' (kwa kutumia nenosiri: %s). Inatenganisha"
bgn "Отказан достъп при опит за смяна към потребител %-.48s'@'%-.64s'"
chi "访问拒绝尝试更改为用户'%-.48s'@'%-.64s'"
eng "Access denied trying to change to user '%-.48s'@'%-.64s'"
spa "Acceso denegado intentando cambiar a usuario '%-.48s'@'%-.64s'"
sw "Ufikiaji umekataliwa kujaribu kubadilisha hadi mtumiaji '%-.48s'@'%-.64s'"
ER_INNODB_READ_ONLY
chi "innodb是只读模式"

View File

@ -53,6 +53,7 @@
#include "sql_db.h"
#include "sql_array.h"
#include "sql_hset.h"
#include "sql_audit.h"
#include "password.h"
#include "scope.h"
@ -12608,6 +12609,87 @@ bool Sql_cmd_grant_table::execute(THD *thd)
}
int acl_setauthorization(THD *thd, const LEX_USER *user)
{
if (!initialized)
{
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--skip-grant-tables");
return 1;
}
Security_context *sctx= thd->security_ctx, save_security_ctx= *sctx;
DBUG_ASSERT(*user->host.str); // guaranteed by the parser
if (user->host.str == host_not_specified.str)
{
my_error(ER_NO_SUCH_USER, MYF(0), user->user.str, "");
return 1;
}
if (!user->user.length)
{
my_error(ER_NO_SUCH_USER, MYF(0), "", user->host.str);
return 1;
}
mysql_mutex_lock(&acl_cache->lock);
ACL_USER *acl_user= find_user_or_anon(user->host.str, user->user.str, user->host.str);
if (acl_user)
acl_user= acl_user->copy(thd->mem_root);
mysql_mutex_unlock(&acl_cache->lock);
if (!acl_user)
{
if (!(sctx->master_access & PRIV_SUDO_CHANGE_USER))
goto access_denied;
my_error(ER_NO_SUCH_USER, MYF(0), user->user.str, user->host.str);
return 1;
}
if (!(sctx->master_access & PRIV_SUDO_CHANGE_USER) &&
(strcmp(user->user.str, sctx->user) ||
strcmp(user->host.str, sctx->host_or_ip) ||
strcmp(acl_user->user.str, sctx->priv_user) ||
strcmp(acl_user->host.hostname, sctx->priv_host)))
goto access_denied;
thd->change_user();
thd->clear_error(); // if errors from rollback
my_free(const_cast<char*>(thd->db.str));
thd->reset_db(&null_clex_str);
sctx->init();
sctx->user= *user->user.str
? my_strndup(key_memory_MPVIO_EXT_auth_info, user->user.str,
user->user.length, MYF(MY_WME))
: NULL;
if (strcmp(user->host.str, my_localhost) == 0)
sctx->host= my_localhost;
else
sctx->host= my_strndup(PSI_INSTRUMENT_ME, user->host.str,
user->host.length, MYF(MY_WME));
sctx->host_or_ip= sctx->host;
sctx->ip= 0;
if (set_privs_on_login(thd, acl_user))
{
sctx->destroy();
*sctx= save_security_ctx;
return 1;
}
mysql_audit_notify_connection_change_user(thd, &save_security_ctx);
save_security_ctx.destroy();
return 0;
access_denied:
my_error(ER_ACCESS_DENIED_CHANGE_USER_ERROR, MYF(0),
user->user.str, user->host.str);
status_var_increment(thd->status_var.access_denied_errors);
return 1;
}
#endif // NO_EMBEDDED_ACCESS_CHECKS

View File

@ -284,6 +284,7 @@ bool acl_check_proxy_grant_access (THD *thd,
const LEX_CSTRING &host,
const LEX_CSTRING &user,
bool with_grant);
int acl_setauthorization(THD *thd, const LEX_USER *user);
int acl_setrole(THD *thd, const LEX_CSTRING &rolename, privilege_t access);
int acl_check_setrole(THD *thd,
const LEX_CSTRING &rolename,

View File

@ -693,7 +693,7 @@ THD::THD(my_thread_id id, bool is_wsrep_applier)
/* statement id */ 0),
rli_fake(0), rgi_fake(0), rgi_slave(NULL),
protocol_text(this), protocol_binary(this), initial_status_var(0),
m_current_stage_key(0), m_psi(0),
m_current_stage_key(0), m_psi(0), start_time(0), start_time_sec_part(0),
in_sub_stmt(0), log_all_errors(0),
binlog_unsafe_warning_flags(0),
current_stmt_binlog_format(BINLOG_FORMAT_MIXED),
@ -1357,7 +1357,7 @@ void THD::init()
mysql_mutex_unlock(&LOCK_global_system_variables);
user_time.val= start_time= start_time_sec_part= 0;
user_time.val= 0;
server_status= 0;
if (variables.option_bits & OPTION_AUTOCOMMIT)
@ -1386,6 +1386,7 @@ void THD::init()
status_var.max_local_memory_used= status_var.local_memory_used;
bzero((char *) &org_status_var, sizeof(org_status_var));
status_in_global= 0;
bytes_sent_old= 0;
start_bytes_received= 0;
m_last_commit_gtid.seq_no= 0;
last_stmt= NULL;
@ -1571,6 +1572,8 @@ void THD::change_user(void)
get_stmt_da()->clear_warning_info(0);
init();
/* cannot clear map if it'll free the currently executing statement */
DBUG_ASSERT(stmt_arena->is_conventional());
stmt_map.reset();
my_hash_init(key_memory_user_var_entry, &user_vars,
Lex_ident_user_var::charset_info(),
@ -1580,6 +1583,8 @@ void THD::change_user(void)
Lex_ident_fs::charset_info(), SEQUENCES_HASH_SIZE, 0, 0,
get_sequence_last_key, free_sequence_last,
HASH_THREAD_SPECIFIC);
/* cannot clear caches if it'll free the currently running routine */
DBUG_ASSERT(!spcont);
sp_caches_clear();
statement_rcontext_reinit();
opt_trace.delete_traces();

View File

@ -89,8 +89,11 @@ void reset_mqh(LEX_USER *lu, bool get_them);
bool check_mqh(THD *thd, uint check_command);
void time_out_user_resource_limits(THD *thd, USER_CONN *uc);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
int get_or_create_user_conn(THD *thd, const char *user,
const char *host, const USER_RESOURCES *mqh);
void decrease_user_connections(USER_CONN *uc);
#else
#define get_or_create_user_conn(A,B,C,D) 0
#define decrease_user_connections(X) do { } while(0) /* nothing */
#endif
bool thd_init_client_charset(THD *thd, uint cs_number);
@ -105,8 +108,6 @@ int thd_set_peer_addr(THD *thd, sockaddr_storage *addr,
void prepare_new_connection_state(THD* thd);
void end_connection(THD *thd);
void update_global_user_stats(THD* thd, bool create_user, time_t now);
int get_or_create_user_conn(THD *thd, const char *user,
const char *host, const USER_RESOURCES *mqh);
int check_for_max_user_connections(THD *thd, USER_CONN *uc);
extern HASH global_user_stats;

View File

@ -358,9 +358,9 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
*/
%ifdef MARIADB
%expect 62
%else
%expect 63
%else
%expect 64
%endif
/*
@ -764,6 +764,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
%token <kwd> AT_SYM /* SQL-2003-R */
%token <kwd> ATOMIC_SYM /* SQL-2003-R */
%token <kwd> AUTHORS_SYM
%token <kwd> AUTHORIZATION_SYM /* SQL-2003-R */
%token <kwd> AUTO_INC
%token <kwd> AUTO_SYM
%token <kwd> AVG_ROW_LENGTH
@ -16328,6 +16329,7 @@ keyword_verb_clause:
keyword_set_special_case:
NAMES_SYM
| AUTHORIZATION_SYM
| ROLE_SYM
| PASSWORD_SYM
;
@ -17000,6 +17002,20 @@ set_param:
if (unlikely(sp_create_assignment_instr(thd, yychar == YYEMPTY)))
MYSQL_YYABORT;
}
| SESSION_SYM AUTHORIZATION_SYM user_name
{
if (Lex->sphead)
{
my_error(ER_SP_BADSTATEMENT, MYF(0), "SET SESSION AUTHORIZATION");
MYSQL_YYABORT;
}
if (sp_create_assignment_lex(thd, $1.pos()))
MYSQL_YYABORT;
auto var= new (thd->mem_root) set_var_authorization($3);
if (var == NULL || Lex->var_list.push_back(var, thd->mem_root) ||
sp_create_assignment_instr(thd, yychar == YYEMPTY))
MYSQL_YYABORT;
}
| option_type
{
Lex->option_type= $1;