1
0
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:
mariadb-PranavTiwari
2026-10-05 10:37:17 +05:30
parent 307a82c973
commit fef56af873
15 changed files with 747 additions and 2 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 '';

View File

@@ -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 '';

View File

@@ -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:

View File

@@ -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

View 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

View 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

View File

@@ -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
#

View File

@@ -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
View 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
View 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 */

View File

@@ -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},

View File

@@ -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);

View File

@@ -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.