mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
MDEV-39343 Add Grant_tables_intact status variable
Add a status variable that reflects whether the grant tables' structure matches what this server expects, recomputed on startup and FLUSH PRIVILEGES. Also widen roles_mapping.Role and proxies_priv.Proxied_host in mysql_system_tables_fix.sql, which were never widened before.
This commit is contained in:
@@ -56,7 +56,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
|
||||
../sql/password.c ../sql/discover.cc ../sql/derror.cc
|
||||
../sql/field.cc ../sql/field_conv.cc ../sql/field_comp.cc
|
||||
../sql/filesort_utils.cc ../sql/sql_digest.cc
|
||||
../sql/filesort.cc ../sql/grant.cc
|
||||
../sql/filesort.cc ../sql/grant.cc ../sql/grant_table_defs.cc
|
||||
../sql/gstream.cc ../sql/slave.cc
|
||||
../sql/signal_handler.cc
|
||||
../sql/handler.cc ../sql/hash_filo.cc ../sql/hostname.cc
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
# On 10.4 the local root was additionally given an empty password,
|
||||
# to avoid using unix_socket.
|
||||
|
||||
# Older dumps have genuinely narrower grant tables than this server expects
|
||||
# (e.g. 10.4's roles_mapping/proxies_priv columns are shorter than today's),
|
||||
# and dropping Delete_history_priv further below also shrinks mysql.db by
|
||||
# one column - FLUSH PRIVILEGES during the upgrade will correctly log both
|
||||
# message shapes for each, which Table_check_intact/Grant_tables_intact are
|
||||
# designed to catch, not something gone wrong.
|
||||
call mtr.add_suppression("is wrong. Expected");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
|
||||
# Backup the existing mysql schema, to restore it later
|
||||
--exec $MYSQL_DUMP mysql > $MYSQL_TMP_DIR/mysql_database_backup
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# switching from mysql.global_priv to mysql.user
|
||||
set global sql_mode="";
|
||||
set local sql_mode="";
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
alter table mysql.user modify User char(16) binary not null default '';
|
||||
alter table mysql.db modify User char(16) binary not null default '';
|
||||
alter table mysql.tables_priv modify User char(16) binary not null default '';
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
set global sql_mode="";
|
||||
set local sql_mode="";
|
||||
|
||||
# The narrowed User columns below make Table_check_intact log a mismatch
|
||||
# on the FLUSH PRIVILEGES just after - expected, not a new bug.
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
|
||||
alter table mysql.user modify User char(16) binary not null default '';
|
||||
alter table mysql.db modify User char(16) binary not null default '';
|
||||
alter table mysql.tables_priv modify User char(16) binary not null default '';
|
||||
|
||||
@@ -9,6 +9,7 @@ connection default;
|
||||
disconnect conn1;
|
||||
drop user user1@localhost;
|
||||
call mtr.add_suppression("Incorrect database name");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
alter table mysql.db modify User char(16) default "", modify Db varchar(200) default "";
|
||||
insert mysql.db set db=concat('=>', repeat(_utf8 'й', 200));
|
||||
Warnings:
|
||||
|
||||
@@ -18,6 +18,9 @@ drop user user1@localhost;
|
||||
|
||||
# in acl_load()
|
||||
call mtr.add_suppression("Incorrect database name");
|
||||
# The narrowed columns below also make Table_check_intact log a mismatch
|
||||
# on the FLUSH PRIVILEGES just after - expected, not a new bug.
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
alter table mysql.db modify User char(16) default "", modify Db varchar(200) default "";
|
||||
insert mysql.db set db=concat('=>', repeat(_utf8 'й', 200));
|
||||
flush privileges; # shouldn't crash here
|
||||
|
||||
112
mysql-test/main/grant_tables_intact.result
Normal file
112
mysql-test/main/grant_tables_intact.result
Normal file
@@ -0,0 +1,112 @@
|
||||
#
|
||||
# MDEV-39343 Grant_tables_intact status variable
|
||||
#
|
||||
# Grant_tables_intact reflects whether mysql.db, mysql.roles_mapping,
|
||||
# mysql.proxies_priv, mysql.tables_priv, mysql.columns_priv,
|
||||
# mysql.procs_priv and mysql.global_priv structurally match this
|
||||
# server's compiled-in column definitions. It is recomputed in
|
||||
# acl_reload()/grant_reload(), i.e. on every FLUSH PRIVILEGES.
|
||||
#
|
||||
# Both message shapes below are expected - they're exactly what
|
||||
# Table_check_intact logs when it finds the mismatches this test
|
||||
# deliberately introduces.
|
||||
call mtr.add_suppression("is wrong. Expected");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
# Baseline: a freshly bootstrapped server must report intact.
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact ON
|
||||
#
|
||||
# mysql.db has real historical tolerance (min_columns=9 against a
|
||||
# current 23 columns), so dropping one privilege column still lets
|
||||
# the table load - it just no longer matches exactly.
|
||||
#
|
||||
ALTER TABLE mysql.db DROP COLUMN Delete_history_priv;
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact OFF
|
||||
ALTER TABLE mysql.db ADD COLUMN Delete_history_priv enum('N','Y') COLLATE utf8mb3_general_ci DEFAULT 'N' NOT NULL AFTER Trigger_priv;
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact ON
|
||||
#
|
||||
# mysql.roles_mapping, mysql.proxies_priv, mysql.tables_priv,
|
||||
# mysql.columns_priv, mysql.procs_priv and mysql.global_priv all
|
||||
# have min_columns pinned to their current column count (no
|
||||
# tolerance at all), so dropping a column would instead trip the
|
||||
# pre-existing ER_CANNOT_LOAD_FROM_TABLE_V2 check in
|
||||
# Grant_table_base::set_table() before Grant_tables_intact's own
|
||||
# logic ever runs (see grant_repair.test for that mechanism).
|
||||
# A type change on Host, instead, leaves the column count and key
|
||||
# structure untouched, so the table still loads and our check
|
||||
# actually gets exercised.
|
||||
#
|
||||
# mysql.roles_mapping
|
||||
ALTER TABLE mysql.roles_mapping MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact OFF
|
||||
ALTER TABLE mysql.roles_mapping MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact ON
|
||||
# mysql.proxies_priv
|
||||
ALTER TABLE mysql.proxies_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact OFF
|
||||
ALTER TABLE mysql.proxies_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact ON
|
||||
# mysql.tables_priv
|
||||
ALTER TABLE mysql.tables_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact OFF
|
||||
ALTER TABLE mysql.tables_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact ON
|
||||
# mysql.columns_priv
|
||||
ALTER TABLE mysql.columns_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact OFF
|
||||
ALTER TABLE mysql.columns_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact ON
|
||||
# mysql.procs_priv
|
||||
ALTER TABLE mysql.procs_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact OFF
|
||||
ALTER TABLE mysql.procs_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact ON
|
||||
# mysql.global_priv
|
||||
ALTER TABLE mysql.global_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact OFF
|
||||
ALTER TABLE mysql.global_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
Variable_name Value
|
||||
Grant_tables_intact ON
|
||||
# End of 10.11 tests
|
||||
95
mysql-test/main/grant_tables_intact.test
Normal file
95
mysql-test/main/grant_tables_intact.test
Normal file
@@ -0,0 +1,95 @@
|
||||
--echo #
|
||||
--echo # MDEV-39343 Grant_tables_intact status variable
|
||||
--echo #
|
||||
--echo # Grant_tables_intact reflects whether mysql.db, mysql.roles_mapping,
|
||||
--echo # mysql.proxies_priv, mysql.tables_priv, mysql.columns_priv,
|
||||
--echo # mysql.procs_priv and mysql.global_priv structurally match this
|
||||
--echo # server's compiled-in column definitions. It is recomputed in
|
||||
--echo # acl_reload()/grant_reload(), i.e. on every FLUSH PRIVILEGES.
|
||||
--echo #
|
||||
|
||||
source include/not_embedded.inc;
|
||||
|
||||
--echo # Both message shapes below are expected - they're exactly what
|
||||
--echo # Table_check_intact logs when it finds the mismatches this test
|
||||
--echo # deliberately introduces.
|
||||
call mtr.add_suppression("is wrong. Expected");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
|
||||
--echo # Baseline: a freshly bootstrapped server must report intact.
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
|
||||
--echo #
|
||||
--echo # mysql.db has real historical tolerance (min_columns=9 against a
|
||||
--echo # current 23 columns), so dropping one privilege column still lets
|
||||
--echo # the table load - it just no longer matches exactly.
|
||||
--echo #
|
||||
ALTER TABLE mysql.db DROP COLUMN Delete_history_priv;
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
ALTER TABLE mysql.db ADD COLUMN Delete_history_priv enum('N','Y') COLLATE utf8mb3_general_ci DEFAULT 'N' NOT NULL AFTER Trigger_priv;
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
|
||||
--echo #
|
||||
--echo # mysql.roles_mapping, mysql.proxies_priv, mysql.tables_priv,
|
||||
--echo # mysql.columns_priv, mysql.procs_priv and mysql.global_priv all
|
||||
--echo # have min_columns pinned to their current column count (no
|
||||
--echo # tolerance at all), so dropping a column would instead trip the
|
||||
--echo # pre-existing ER_CANNOT_LOAD_FROM_TABLE_V2 check in
|
||||
--echo # Grant_table_base::set_table() before Grant_tables_intact's own
|
||||
--echo # logic ever runs (see grant_repair.test for that mechanism).
|
||||
--echo # A type change on Host, instead, leaves the column count and key
|
||||
--echo # structure untouched, so the table still loads and our check
|
||||
--echo # actually gets exercised.
|
||||
--echo #
|
||||
|
||||
--echo # mysql.roles_mapping
|
||||
ALTER TABLE mysql.roles_mapping MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
ALTER TABLE mysql.roles_mapping MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
|
||||
--echo # mysql.proxies_priv
|
||||
ALTER TABLE mysql.proxies_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
ALTER TABLE mysql.proxies_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
|
||||
--echo # mysql.tables_priv
|
||||
ALTER TABLE mysql.tables_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
ALTER TABLE mysql.tables_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
|
||||
--echo # mysql.columns_priv
|
||||
ALTER TABLE mysql.columns_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
ALTER TABLE mysql.columns_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
|
||||
--echo # mysql.procs_priv
|
||||
ALTER TABLE mysql.procs_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
ALTER TABLE mysql.procs_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
|
||||
--echo # mysql.global_priv
|
||||
ALTER TABLE mysql.global_priv MODIFY COLUMN Host char(60) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
ALTER TABLE mysql.global_priv MODIFY COLUMN Host char(255) binary NOT NULL DEFAULT '';
|
||||
FLUSH PRIVILEGES;
|
||||
SHOW STATUS LIKE 'Grant_tables_intact';
|
||||
|
||||
--echo # End of 10.11 tests
|
||||
@@ -2522,6 +2522,8 @@ name dl
|
||||
call mtr.add_suppression("InnoDB: Fetch of persistent statistics requested for table `mysql`\\.`gtid_slave_pos` but the required system tables mysql\\.innodb_table_stats and mysql\\.innodb_index_stats are not present or have unexpected structure");
|
||||
call mtr.add_suppression("InnoDB: Unexpected length of mysql\\.innodb_table_stats\\.table_name");
|
||||
call mtr.add_suppression("Column count of mysql.proc is wrong. Expected 21, found 20.");
|
||||
call mtr.add_suppression("is wrong. Expected");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
#
|
||||
# Upgrade from version 5.5
|
||||
#
|
||||
@@ -2531,6 +2533,8 @@ call mtr.add_suppression("Column count of mysql.proc is wrong. Expected 21, foun
|
||||
SELECT COUNT(*) > 0 AS `mysql.user has data` FROM mysql.user;
|
||||
mysql.user has data
|
||||
1
|
||||
call mtr.add_suppression("is wrong. Expected");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
#
|
||||
# Upgrade from version 10.0
|
||||
#
|
||||
@@ -2540,6 +2544,8 @@ mysql.user has data
|
||||
SELECT COUNT(*) > 0 AS `mysql.user has data` FROM mysql.user;
|
||||
mysql.user has data
|
||||
1
|
||||
call mtr.add_suppression("is wrong. Expected");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
#
|
||||
# Upgrade from version 10.1
|
||||
#
|
||||
@@ -2549,6 +2555,8 @@ mysql.user has data
|
||||
SELECT COUNT(*) > 0 AS `mysql.user has data` FROM mysql.user;
|
||||
mysql.user has data
|
||||
1
|
||||
call mtr.add_suppression("is wrong. Expected");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
#
|
||||
# Upgrade from version 10.2
|
||||
#
|
||||
@@ -2558,6 +2566,8 @@ mysql.user has data
|
||||
SELECT COUNT(*) > 0 AS `mysql.user has data` FROM mysql.user;
|
||||
mysql.user has data
|
||||
1
|
||||
call mtr.add_suppression("is wrong. Expected");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
#
|
||||
# Upgrade from version 10.3
|
||||
#
|
||||
@@ -2567,6 +2577,8 @@ mysql.user has data
|
||||
SELECT COUNT(*) > 0 AS `mysql.user has data` FROM mysql.user;
|
||||
mysql.user has data
|
||||
1
|
||||
call mtr.add_suppression("is wrong. Expected");
|
||||
call mtr.add_suppression("Incorrect definition of table");
|
||||
#
|
||||
# Upgrade from version 10.4
|
||||
#
|
||||
|
||||
@@ -150,6 +150,7 @@ SET (SQL_SOURCE
|
||||
|
||||
# added in MariaDB:
|
||||
grant.cc
|
||||
grant_table_defs.cc
|
||||
sql_explain.cc
|
||||
sql_analyze_stmt.cc
|
||||
sql_join_cache.cc
|
||||
|
||||
384
sql/grant_table_defs.cc
Normal file
384
sql/grant_table_defs.cc
Normal file
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
Copyright (c) 2026, MariaDB
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
Expected structure of the grant tables in the 'mysql' schema.
|
||||
|
||||
These definitions are used by Table_check_intact, when the grant tables
|
||||
are loaded (acl_reload() / grant_reload()), to compute the status
|
||||
variable Grant_tables_intact.
|
||||
|
||||
IMPORTANT: they must be kept in sync with the CREATE TABLE statements in
|
||||
scripts/mysql_system_tables.sql (and the upgrade statements in
|
||||
scripts/mysql_system_tables_fix.sql). Whoever adds, removes or changes a
|
||||
column (name, type or character set) of one of the tables below must
|
||||
update the matching definition here, otherwise a freshly installed server
|
||||
will report its own grant tables as not intact.
|
||||
*/
|
||||
|
||||
#include "mariadb.h"
|
||||
#include "sql_acl.h" // MYSQL_DB_FIELD_COUNT
|
||||
#include "grant_table_defs.h"
|
||||
|
||||
static const
|
||||
TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = {
|
||||
{
|
||||
{ STRING_WITH_LEN("Host") },
|
||||
{ STRING_WITH_LEN("char(255)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Db") },
|
||||
{ STRING_WITH_LEN("char(64)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("User") },
|
||||
{ STRING_WITH_LEN("char(128)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Select_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Insert_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Update_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Delete_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Create_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Drop_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Grant_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("References_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Index_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Alter_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Create_tmp_table_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Lock_tables_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Create_view_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Show_view_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Create_routine_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Alter_routine_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Execute_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Event_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Trigger_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Delete_history_priv") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
}
|
||||
};
|
||||
|
||||
const TABLE_FIELD_DEF
|
||||
mysql_db_table_def= {MYSQL_DB_FIELD_COUNT, mysql_db_table_fields, 0, (uint*) 0 };
|
||||
|
||||
static const
|
||||
TABLE_FIELD_TYPE mysql_roles_mapping_table_fields[4] = {
|
||||
{
|
||||
{ STRING_WITH_LEN("Host") },
|
||||
{ STRING_WITH_LEN("char(255)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("User") },
|
||||
{ STRING_WITH_LEN("char(128)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Role") },
|
||||
{ STRING_WITH_LEN("char(128)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Admin_option") },
|
||||
{ STRING_WITH_LEN("enum('N','Y')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
}
|
||||
};
|
||||
|
||||
const TABLE_FIELD_DEF
|
||||
mysql_roles_mapping_table_def= {4, mysql_roles_mapping_table_fields, 0, (uint*) 0 };
|
||||
|
||||
static const
|
||||
TABLE_FIELD_TYPE mysql_proxies_priv_table_fields[7] = {
|
||||
{
|
||||
{ STRING_WITH_LEN("Host") },
|
||||
{ STRING_WITH_LEN("char(255)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("User") },
|
||||
{ STRING_WITH_LEN("char(128)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Proxied_host") },
|
||||
{ STRING_WITH_LEN("char(255)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Proxied_user") },
|
||||
{ STRING_WITH_LEN("char(128)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("With_grant") },
|
||||
{ STRING_WITH_LEN("tinyint(1)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Grantor") },
|
||||
{ STRING_WITH_LEN("varchar(384)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Timestamp") },
|
||||
{ STRING_WITH_LEN("timestamp") },
|
||||
{NULL, 0}
|
||||
}
|
||||
};
|
||||
|
||||
const TABLE_FIELD_DEF
|
||||
mysql_proxies_priv_table_def= {7, mysql_proxies_priv_table_fields, 0, (uint*) 0 };
|
||||
|
||||
static const
|
||||
TABLE_FIELD_TYPE mysql_tables_priv_table_fields[8] = {
|
||||
{
|
||||
{ STRING_WITH_LEN("Host") },
|
||||
{ STRING_WITH_LEN("char(255)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Db") },
|
||||
{ STRING_WITH_LEN("char(64)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("User") },
|
||||
{ STRING_WITH_LEN("char(128)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Table_name") },
|
||||
{ STRING_WITH_LEN("char(64)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Grantor") },
|
||||
{ STRING_WITH_LEN("varchar(384)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Timestamp") },
|
||||
{ STRING_WITH_LEN("timestamp") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Table_priv") },
|
||||
{ STRING_WITH_LEN("set('Select','Insert','Update','Delete','Create','Drop','Grant','References','Index','Alter','Create View','Show view','Trigger','Delete versioning rows')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Column_priv") },
|
||||
{ STRING_WITH_LEN("set('Select','Insert','Update','References')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
}
|
||||
};
|
||||
|
||||
const TABLE_FIELD_DEF
|
||||
mysql_tables_priv_table_def= {8, mysql_tables_priv_table_fields, 0, (uint*) 0 };
|
||||
|
||||
static const
|
||||
TABLE_FIELD_TYPE mysql_columns_priv_table_fields[7] = {
|
||||
{
|
||||
{ STRING_WITH_LEN("Host") },
|
||||
{ STRING_WITH_LEN("char(255)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Db") },
|
||||
{ STRING_WITH_LEN("char(64)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("User") },
|
||||
{ STRING_WITH_LEN("char(128)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Table_name") },
|
||||
{ STRING_WITH_LEN("char(64)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Column_name") },
|
||||
{ STRING_WITH_LEN("char(64)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Timestamp") },
|
||||
{ STRING_WITH_LEN("timestamp") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Column_priv") },
|
||||
{ STRING_WITH_LEN("set('Select','Insert','Update','References')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
}
|
||||
};
|
||||
|
||||
const TABLE_FIELD_DEF
|
||||
mysql_columns_priv_table_def= {7, mysql_columns_priv_table_fields, 0, (uint*) 0 };
|
||||
|
||||
static const
|
||||
TABLE_FIELD_TYPE mysql_procs_priv_table_fields[8] = {
|
||||
{
|
||||
{ STRING_WITH_LEN("Host") },
|
||||
{ STRING_WITH_LEN("char(255)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Db") },
|
||||
{ STRING_WITH_LEN("char(64)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("User") },
|
||||
{ STRING_WITH_LEN("char(128)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Routine_name") },
|
||||
{ STRING_WITH_LEN("char(64)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Routine_type") },
|
||||
{ STRING_WITH_LEN("enum('FUNCTION','PROCEDURE','PACKAGE','PACKAGE BODY')") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Grantor") },
|
||||
{ STRING_WITH_LEN("varchar(384)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Proc_priv") },
|
||||
{ STRING_WITH_LEN("set('Execute','Alter Routine','Grant')") },
|
||||
{ STRING_WITH_LEN("utf8") }
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Timestamp") },
|
||||
{ STRING_WITH_LEN("timestamp") },
|
||||
{NULL, 0}
|
||||
}
|
||||
};
|
||||
|
||||
const TABLE_FIELD_DEF
|
||||
mysql_procs_priv_table_def= {8, mysql_procs_priv_table_fields, 0, (uint*) 0 };
|
||||
|
||||
static const
|
||||
TABLE_FIELD_TYPE mysql_global_priv_table_fields[3] = {
|
||||
{
|
||||
{ STRING_WITH_LEN("Host") },
|
||||
{ STRING_WITH_LEN("char(255)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("User") },
|
||||
{ STRING_WITH_LEN("char(128)") },
|
||||
{NULL, 0}
|
||||
},
|
||||
{
|
||||
{ STRING_WITH_LEN("Priv") },
|
||||
{ STRING_WITH_LEN("longtext") },
|
||||
{NULL, 0}
|
||||
}
|
||||
};
|
||||
|
||||
const TABLE_FIELD_DEF
|
||||
mysql_global_priv_table_def= {3, mysql_global_priv_table_fields, 0, (uint*) 0 };
|
||||
34
sql/grant_table_defs.h
Normal file
34
sql/grant_table_defs.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Copyright (c) 2026, MariaDB
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
|
||||
|
||||
#ifndef GRANT_TABLE_DEFS_INCLUDED
|
||||
#define GRANT_TABLE_DEFS_INCLUDED
|
||||
|
||||
#include "table.h" // TABLE_FIELD_DEF
|
||||
|
||||
/*
|
||||
Expected structure of the grant tables, see grant_table_defs.cc.
|
||||
Keep the definitions there in sync with scripts/mysql_system_tables.sql.
|
||||
*/
|
||||
extern const TABLE_FIELD_DEF mysql_db_table_def;
|
||||
extern const TABLE_FIELD_DEF mysql_roles_mapping_table_def;
|
||||
extern const TABLE_FIELD_DEF mysql_proxies_priv_table_def;
|
||||
extern const TABLE_FIELD_DEF mysql_tables_priv_table_def;
|
||||
extern const TABLE_FIELD_DEF mysql_columns_priv_table_def;
|
||||
extern const TABLE_FIELD_DEF mysql_procs_priv_table_def;
|
||||
extern const TABLE_FIELD_DEF mysql_global_priv_table_def;
|
||||
|
||||
#endif /* GRANT_TABLE_DEFS_INCLUDED */
|
||||
@@ -7613,6 +7613,7 @@ SHOW_VAR status_vars[]= {
|
||||
{"Aborted_connects", (char*) &aborted_connects, SHOW_LONG},
|
||||
{"Aborted_connects_preauth", (char*) &aborted_connects_preauth, SHOW_LONG},
|
||||
{"Acl", (char*) acl_statistics, SHOW_ARRAY},
|
||||
{"Grant_tables_intact", (char*) &grant_tables_intact, SHOW_MY_BOOL},
|
||||
{"Access_denied_errors", (char*) offsetof(STATUS_VAR, access_denied_errors), SHOW_LONG_STATUS},
|
||||
{"Binlog_bytes_written", (char*) offsetof(STATUS_VAR, binlog_bytes_written), SHOW_LONGLONG_STATUS},
|
||||
{"Binlog_cache_disk_use", (char*) &binlog_cache_disk_use, SHOW_LONG},
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "mariadb.h" /* NO_EMBEDDED_ACCESS_CHECKS */
|
||||
#include "sql_priv.h"
|
||||
#include "sql_acl.h" // MYSQL_DB_FIELD_COUNT, ACL_ACCESS
|
||||
#include "grant_table_defs.h"
|
||||
#include "sql_base.h" // close_mysql_tables
|
||||
#include "key.h" // key_copy, key_cmp_if_same, key_restore
|
||||
#include "sql_show.h" // append_identifier
|
||||
@@ -64,6 +65,17 @@
|
||||
bool mysql_user_table_is_in_short_password_format= false;
|
||||
bool using_global_priv_table= true;
|
||||
|
||||
/*
|
||||
Set at the end of acl_reload()/grant_reload() (so recomputed on every
|
||||
server startup and every FLUSH PRIVILEGES) to reflect whether the
|
||||
on-disk grant tables' structure matches what this server binary
|
||||
expects. Exposed read-only via SHOW STATUS (see acl_statistics /
|
||||
status_vars in mysqld.cc) so a remote mariadb-upgrade can check it
|
||||
without local filesystem access.
|
||||
*/
|
||||
my_bool grant_tables_intact= FALSE;
|
||||
static Table_check_intact_log_error table_intact;
|
||||
|
||||
// set that from field length in acl_load?
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
const uint max_hostname_length= HOSTNAME_LENGTH;
|
||||
@@ -2973,6 +2985,34 @@ void acl_free(bool end)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Check that a grant table has the structure this server expects.
|
||||
|
||||
A table that wasn't opened is reported in the error log and treated as
|
||||
not intact, the same as a table with a wrong structure.
|
||||
Table_check_intact::check() must not be called with a NULL TABLE.
|
||||
|
||||
@param table Grant table, as returned by Grant_tables
|
||||
@param name Name of the table in the mysql schema, for the message
|
||||
@param table_def Expected structure (see grant_table_defs.cc)
|
||||
|
||||
@retval true Table is intact
|
||||
@retval false Table is missing or its structure is wrong
|
||||
*/
|
||||
|
||||
static bool grant_table_is_intact(const Grant_table_base &table,
|
||||
const char *name,
|
||||
const TABLE_FIELD_DEF *table_def)
|
||||
{
|
||||
if (!table.table_exists())
|
||||
{
|
||||
sql_print_error("Table mysql.%s is missing", name);
|
||||
return false;
|
||||
}
|
||||
return !table_intact.check(table.table(), table_def);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Forget current user/db-level privileges and read new privileges
|
||||
from the privilege tables.
|
||||
@@ -3071,6 +3111,33 @@ bool acl_reload(THD *thd)
|
||||
delete_dynamic_with_callback(&old_acl_users, free_acl_user);
|
||||
delete_dynamic(&old_acl_proxy_users);
|
||||
my_hash_free(&old_acl_roles_mappings);
|
||||
|
||||
/*
|
||||
Check every table, even if an earlier one isn't intact, so that all
|
||||
the problems are written to the error log.
|
||||
|
||||
The 'user' view is never opened here, it is only a client-facing
|
||||
compatibility layer. Instead user_table() is mysql.global_priv, or the
|
||||
old tabular mysql.user table on installations that haven't been
|
||||
upgraded to global_priv yet (using_global_priv_table is false, set in
|
||||
User_table_tabular::setup_sysvars()). Only global_priv has a
|
||||
definition to compare with, so the legacy table is not checked.
|
||||
*/
|
||||
bool db_intact=
|
||||
grant_table_is_intact(tables.db_table(), "db", &mysql_db_table_def);
|
||||
bool roles_mapping_intact=
|
||||
grant_table_is_intact(tables.roles_mapping_table(), "roles_mapping",
|
||||
&mysql_roles_mapping_table_def);
|
||||
bool proxies_priv_intact=
|
||||
grant_table_is_intact(tables.proxies_priv_table(), "proxies_priv",
|
||||
&mysql_proxies_priv_table_def);
|
||||
bool global_priv_intact=
|
||||
(!using_global_priv_table ||
|
||||
grant_table_is_intact(tables.user_table(), "global_priv",
|
||||
&mysql_global_priv_table_def));
|
||||
|
||||
grant_tables_intact= (db_intact && roles_mapping_intact &&
|
||||
proxies_priv_intact && global_priv_intact);
|
||||
}
|
||||
mysql_mutex_unlock(&acl_cache->lock);
|
||||
end:
|
||||
@@ -8388,6 +8455,27 @@ bool grant_reload(THD *thd)
|
||||
my_hash_free(&old_package_spec_priv_hash);
|
||||
my_hash_free(&old_package_body_priv_hash);
|
||||
free_root(&old_mem,MYF(0));
|
||||
|
||||
/*
|
||||
acl_reload() always runs immediately before grant_reload(), both at
|
||||
server startup and on FLUSH PRIVILEGES so it's safe to AND our tables'
|
||||
results onto the value it just set rather than overwrite it.
|
||||
|
||||
Check every table, even if an earlier one isn't intact, so that all
|
||||
the problems are written to the error log.
|
||||
*/
|
||||
bool tables_priv_intact=
|
||||
grant_table_is_intact(tables.tables_priv_table(), "tables_priv",
|
||||
&mysql_tables_priv_table_def);
|
||||
bool columns_priv_intact=
|
||||
grant_table_is_intact(tables.columns_priv_table(), "columns_priv",
|
||||
&mysql_columns_priv_table_def);
|
||||
bool procs_priv_intact=
|
||||
grant_table_is_intact(tables.procs_priv_table(), "procs_priv",
|
||||
&mysql_procs_priv_table_def);
|
||||
|
||||
grant_tables_intact= (grant_tables_intact && tables_priv_intact &&
|
||||
columns_priv_intact && procs_priv_intact);
|
||||
}
|
||||
|
||||
mysql_mutex_lock(&acl_cache->lock);
|
||||
|
||||
@@ -51,7 +51,6 @@ enum mysql_db_table_field
|
||||
MYSQL_DB_FIELD_COUNT
|
||||
};
|
||||
|
||||
extern const TABLE_FIELD_DEF mysql_db_table_def;
|
||||
extern bool mysql_user_table_is_in_short_password_format;
|
||||
|
||||
extern LEX_CSTRING host_not_specified;
|
||||
@@ -284,6 +283,7 @@ int acl_set_default_role(THD *thd, const char *host, const char *user,
|
||||
const char *rolename);
|
||||
|
||||
extern SHOW_VAR acl_statistics[];
|
||||
extern my_bool grant_tables_intact;
|
||||
|
||||
/* Check if a role is granted to a user/role.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user