1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-25515 User Account Host Names using CIDR notation

CREATE USER u@'192.168.0.0/24';  -- same as '192.168.0.0/255.255.255.0'
CIDR is an input spelling only, normalised to the netmask form while parsing.
A malformed mask is now rejected instead of stored, and one already in
the privilege tables is skipped at load with a warning instead of being honoured.
Nothing rejected these before, and they are not merely
useless: a non-contiguous mask such as 10.0.0.0/255.0.255.0 matches the
scattered set 10.*0.*. Skipping rather than erroring keeps such a row droppable and renamable, except for a literal CIDR host stored by an older version (for example 10.0.0.0/24): the parser converts that name, so it can no longer be named and must be removed with a direct DELETE from the privilege tables.

New error ER_INVALID_HOST_NETMASK, ER_ACL_ENTRY_IGNORED_BAD_HOST_MASK
This commit is contained in:
PranavKTiwari
2026-08-06 13:47:33 +05:30
committed by mariadb-PranavTiwari
parent 327437ade1
commit f6d172c03b
6 changed files with 769 additions and 0 deletions

View File

@@ -0,0 +1,319 @@
#
# MDEV-25515 User Account Host Names using CIDR notation
#
call mtr.add_suppression("ignored, the host is not a valid ip/netmask");
# CIDR is stored in the netmask form
CREATE USER c1@'127.0.0.0/8' IDENTIFIED BY 'pw';
CREATE USER c2@'127.0.0.0/24' IDENTIFIED BY 'pw';
CREATE USER c3@'127.0.0.1/32' IDENTIFIED BY 'pw';
CREATE USER c4@'127.0.0.0/31' IDENTIFIED BY 'pw';
CREATE USER c5@'0.0.0.0/1' IDENTIFIED BY 'pw';
CREATE USER n1@'127.0.0.0/255.0.0.0' IDENTIFIED BY 'pw';
CREATE USER n2@'127.0.0.0/255.255.255.0' IDENTIFIED BY 'pw';
SELECT User, Host FROM mysql.global_priv WHERE User LIKE 'c_' OR User LIKE 'n_'
ORDER BY User;
User Host
c1 127.0.0.0/255.0.0.0
c2 127.0.0.0/255.255.255.0
c3 127.0.0.1/255.255.255.255
c4 127.0.0.0/255.255.255.254
c5 0.0.0.0/128.0.0.0
n1 127.0.0.0/255.0.0.0
n2 127.0.0.0/255.255.255.0
SHOW CREATE USER c1@'127.0.0.0/8';
CREATE USER for c1@127.0.0.0/255.0.0.0
CREATE USER `c1`@`127.0.0.0/255.0.0.0` IDENTIFIED BY PASSWORD '*D821809F681A40A6E379B50D0463EFAE20BDD122'
SHOW GRANTS FOR c2@'127.0.0.0/24';
Grants for c2@127.0.0.0/255.255.255.0
GRANT USAGE ON *.* TO `c2`@`127.0.0.0/255.255.255.0` IDENTIFIED BY PASSWORD '*D821809F681A40A6E379B50D0463EFAE20BDD122'
# Both spellings are the same account
CREATE USER dup@'127.0.0.0/8' IDENTIFIED BY 'pw';
CREATE USER dup@'127.0.0.0/255.0.0.0' IDENTIFIED BY 'other';
ERROR HY000: Operation CREATE USER failed for 'dup'@'127.0.0.0/255.0.0.0'
CREATE USER IF NOT EXISTS dup@'127.0.0.0/255.0.0.0' IDENTIFIED BY 'other';
Warnings:
Note 1973 Can't create user 'dup'@'127.0.0.0/255.0.0.0'; it already exists
CREATE USER dup@'127.000.0.0/8' IDENTIFIED BY 'other';
ERROR HY000: Operation CREATE USER failed for 'dup'@'127.0.0.0/255.0.0.0'
CREATE USER dup@'127.0.0.0/+8' IDENTIFIED BY 'other';
ERROR HY000: Operation CREATE USER failed for 'dup'@'127.0.0.0/255.0.0.0'
CREATE USER dup@'127.0.0.0/ 8' IDENTIFIED BY 'other';
ERROR HY000: Operation CREATE USER failed for 'dup'@'127.0.0.0/255.0.0.0'
CREATE USER dup@'127.0.0.0/255.000.0.0' IDENTIFIED BY 'other';
ERROR HY000: Operation CREATE USER failed for 'dup'@'127.0.0.0/255.0.0.0'
SELECT User, Host FROM mysql.global_priv WHERE User='dup';
User Host
dup 127.0.0.0/255.0.0.0
DROP USER dup@'127.0.0.0/8';
# Matching is unchanged
connect con1,127.0.0.1,c1,pw,,$MASTER_MYPORT,;
connection con1;
SELECT CURRENT_USER();
CURRENT_USER()
c1@127.0.0.0/255.0.0.0
disconnect con1;
connection default;
connect con1,127.0.0.1,c2,pw,,$MASTER_MYPORT,;
connection con1;
SELECT CURRENT_USER();
CURRENT_USER()
c2@127.0.0.0/255.255.255.0
disconnect con1;
connection default;
connect con1,127.0.0.1,c3,pw,,$MASTER_MYPORT,;
connection con1;
SELECT CURRENT_USER();
CURRENT_USER()
c3@127.0.0.1/255.255.255.255
disconnect con1;
connection default;
connect con1,127.0.0.1,c4,pw,,$MASTER_MYPORT,;
connection con1;
SELECT CURRENT_USER();
CURRENT_USER()
c4@127.0.0.0/255.255.255.254
disconnect con1;
connection default;
connect con1,127.0.0.1,c5,pw,,$MASTER_MYPORT,;
connection con1;
SELECT CURRENT_USER();
CURRENT_USER()
c5@0.0.0.0/128.0.0.0
disconnect con1;
connection default;
connect con1,127.0.0.1,n1,pw,,$MASTER_MYPORT,;
connection con1;
SELECT CURRENT_USER();
CURRENT_USER()
n1@127.0.0.0/255.0.0.0
disconnect con1;
connection default;
CREATE USER z1@'127.0.0.0/32' IDENTIFIED BY 'pw';
connect(127.0.0.1,z1,pw,test,MASTER_PORT,MASTER_SOCKET);
connect con1,127.0.0.1,z1,pw,,$MASTER_MYPORT,;
ERROR 28000: Access denied for user 'z1'@'localhost' (using password: YES)
DROP USER z1@'127.0.0.0/32';
# Either spelling resolves to the same row, in every privilege table
CREATE DATABASE cidr_db;
CREATE TABLE cidr_db.t (a INT);
GRANT SELECT ON cidr_db.* TO c1@'127.0.0.0/255.0.0.0';
SHOW GRANTS FOR c1@'127.0.0.0/8';
Grants for c1@127.0.0.0/255.0.0.0
GRANT USAGE ON *.* TO `c1`@`127.0.0.0/255.0.0.0` IDENTIFIED BY PASSWORD '*D821809F681A40A6E379B50D0463EFAE20BDD122'
GRANT SELECT ON `cidr_db`.* TO `c1`@`127.0.0.0/255.0.0.0`
GRANT INSERT ON cidr_db.* TO c1@'127.0.0.0/8';
REVOKE SELECT ON cidr_db.* FROM c1@'127.0.0.0/8';
SELECT User, Host FROM mysql.db WHERE User='c1';
User Host
c1 127.0.0.0/255.0.0.0
GRANT SELECT ON cidr_db.t TO c1@'127.0.0.0/8';
SELECT User, Host FROM mysql.tables_priv WHERE User='c1';
User Host
c1 127.0.0.0/255.0.0.0
REVOKE SELECT ON cidr_db.t FROM c1@'127.0.0.0/255.0.0.0';
SET PASSWORD FOR c1@'127.0.0.0/8' = PASSWORD('pw2');
ALTER USER c1@'127.0.0.0/255.0.0.0' IDENTIFIED BY 'pw';
RENAME USER c1@'127.0.0.0/8' TO c1@'127.0.0.0/16';
SELECT User, Host FROM mysql.global_priv WHERE User='c1';
User Host
c1 127.0.0.0/255.255.0.0
SELECT User, Host FROM mysql.db WHERE User='c1';
User Host
c1 127.0.0.0/255.255.0.0
RENAME USER c1@'127.0.0.0/255.255.0.0' TO c1@'127.0.0.0/8';
REVOKE ALL PRIVILEGES, GRANT OPTION FROM c1@'127.0.0.0/255.0.0.0';
SHOW GRANTS FOR c1@'127.0.0.0/8';
Grants for c1@127.0.0.0/255.0.0.0
GRANT USAGE ON *.* TO `c1`@`127.0.0.0/255.0.0.0` IDENTIFIED BY PASSWORD '*D821809F681A40A6E379B50D0463EFAE20BDD122'
DROP TABLE cidr_db.t;
DROP DATABASE cidr_db;
# GRANT normalises the host of an account it auto-creates
CREATE TABLE test.t (a INT);
CREATE PROCEDURE test.p1() SELECT 1;
CREATE ROLE cidr_role;
GRANT SELECT ON test.* TO g1@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT SELECT ON test.t TO g2@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT EXECUTE ON PROCEDURE test.p1 TO g3@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT cidr_role TO g4@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT PROXY ON ''@'%' TO g5@'10.0.0.0/24' IDENTIFIED BY 'pw';
SELECT User, Host FROM mysql.global_priv WHERE User LIKE 'g_' ORDER BY User;
User Host
g1 10.0.0.0/255.255.255.0
g2 10.0.0.0/255.255.255.0
g3 10.0.0.0/255.255.255.0
g4 10.0.0.0/255.255.255.0
g5 10.0.0.0/255.255.255.0
SELECT User, Host FROM mysql.db WHERE User='g1';
User Host
g1 10.0.0.0/255.255.255.0
SELECT User, Host FROM mysql.tables_priv WHERE User='g2';
User Host
g2 10.0.0.0/255.255.255.0
SELECT User, Host FROM mysql.procs_priv WHERE User='g3';
User Host
g3 10.0.0.0/255.255.255.0
SELECT User, Host FROM mysql.roles_mapping WHERE User='g4';
User Host
g4 10.0.0.0/255.255.255.0
SELECT User, Host FROM mysql.proxies_priv WHERE User='g5';
User Host
g5 10.0.0.0/255.255.255.0
DROP USER g1@'10.0.0.0/255.255.255.0', g2@'10.0.0.0/255.255.255.0',
g3@'10.0.0.0/255.255.255.0', g4@'10.0.0.0/255.255.255.0',
g5@'10.0.0.0/255.255.255.0';
DROP ROLE cidr_role;
DROP PROCEDURE test.p1;
DROP TABLE test.t;
# DEFINER reaches the same grammar rule
CREATE USER def@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT ALL ON test.* TO def@'10.0.0.0/255.255.255.0';
CREATE DEFINER=def@'10.0.0.0/24' VIEW test.v1 AS SELECT 1;
CREATE DEFINER=def@'10.0.0.0/24' PROCEDURE test.p2() SELECT 1;
SELECT definer FROM information_schema.views
WHERE table_schema='test' AND table_name='v1';
definer
def@10.0.0.0/255.255.255.0
SELECT definer FROM information_schema.routines
WHERE routine_schema='test' AND routine_name='p2';
definer
def@10.0.0.0/255.255.255.0
DROP VIEW test.v1;
DROP PROCEDURE test.p2;
DROP USER def@'10.0.0.0/255.255.255.0';
# A host without '/' is not a masked host and is untouched
CREATE USER v1@'%', v2@'10.0.0.%', v3@'127.0.0._', v4@'localhost',
v5@'127.0.0.1', v6@'::1';
SELECT User, Host FROM mysql.global_priv WHERE User LIKE 'v_' ORDER BY User;
User Host
v1 %
v2 10.0.0.%
v3 127.0.0._
v4 localhost
v5 127.0.0.1
v6 ::1
DROP USER v1@'%', v2@'10.0.0.%', v3@'127.0.0._', v4@'localhost',
v5@'127.0.0.1', v6@'::1';
# A malformed mask is rejected, quoting the host as it was written
CREATE USER e01@'10.0.0.0/33';
ERROR HY000: Invalid masked host '10.0.0.0/33' for user 'e01'
CREATE USER e02@'10.0.0.0/0';
ERROR HY000: Invalid masked host '10.0.0.0/0' for user 'e02'
CREATE USER e03@'10.0.0.0/-1';
ERROR HY000: Invalid masked host '10.0.0.0/-1' for user 'e03'
CREATE USER e04@'10.0.0.0/0.0.0.0';
ERROR HY000: Invalid masked host '10.0.0.0/0.0.0.0' for user 'e04'
CREATE USER e05@'10.0.0.0/255.0.255.0';
ERROR HY000: Invalid masked host '10.0.0.0/255.0.255.0' for user 'e05'
CREATE USER e06@'10.0.0.0/255.255.0.255';
ERROR HY000: Invalid masked host '10.0.0.0/255.255.0.255' for user 'e06'
CREATE USER e07@'10.1.2.3/24';
ERROR HY000: Invalid masked host '10.1.2.3/24' for user 'e07'
CREATE USER e08@'10.1.2.3/255.255.255.0';
ERROR HY000: Invalid masked host '10.1.2.3/255.255.255.0' for user 'e08'
CREATE USER e09@'127.0.0.0/1';
ERROR HY000: Invalid masked host '127.0.0.0/1' for user 'e09'
CREATE USER e10@'2001:db8::/32';
ERROR HY000: Invalid masked host '2001:db8::/32' for user 'e10'
CREATE USER e11@'10.0.0.%/24';
ERROR HY000: Invalid masked host '10.0.0.%/24' for user 'e11'
CREATE USER e12@'10.0.0.0/abc';
ERROR HY000: Invalid masked host '10.0.0.0/abc' for user 'e12'
CREATE USER e13@'10.0.0.0/';
ERROR HY000: Invalid masked host '10.0.0.0/' for user 'e13'
CREATE USER e14@'10.0.0.0/24x';
ERROR HY000: Invalid masked host '10.0.0.0/24x' for user 'e14'
GRANT SELECT ON test.* TO e20@'10.0.0.0/33' IDENTIFIED BY 'pw';
ERROR HY000: Invalid masked host '10.0.0.0/33' for user 'e20'
GRANT SELECT ON test.* TO e21@'10.1.2.3/24' IDENTIFIED BY 'pw';
ERROR HY000: Invalid masked host '10.1.2.3/24' for user 'e21'
SELECT User, Host FROM mysql.global_priv WHERE User LIKE 'e__';
User Host
ALTER USER e30@'10.0.0.0/33' IDENTIFIED BY 'pw';
ERROR HY000: Operation ALTER USER failed for 'e30'@'10.0.0.0/33'
REVOKE SELECT ON test.* FROM e30@'10.0.0.0/33';
ERROR 42000: There is no such grant defined for user 'e30' on host '10.0.0.0/33'
# RENAME USER validates the target only
CREATE USER r1@'127.0.0.0/8' IDENTIFIED BY 'pw';
RENAME USER r1@'127.0.0.0/8' TO r1@'10.1.2.3/24';
ERROR HY000: Operation RENAME USER failed for 'r1'@'10.1.2.3/24'
RENAME USER r1@'127.0.0.0/8' TO r1@'10.0.0.0/255.0.255.0';
ERROR HY000: Operation RENAME USER failed for 'r1'@'10.0.0.0/255.0.255.0'
SHOW WARNINGS;
Level Code Message
Warning 4274 Invalid masked host '10.0.0.0/255.0.255.0' for user 'r1'
Error 1396 Operation RENAME USER failed for 'r1'@'10.0.0.0/255.0.255.0'
RENAME USER r1@'127.0.0.0/8' TO r1@'10.0.0.0/16';
SELECT User, Host FROM mysql.global_priv WHERE User='r1';
User Host
r1 10.0.0.0/255.255.0.0
DROP USER r1@'10.0.0.0/255.255.0.0';
# acl_load() skips a stored row whose mask it cannot honour
CREATE USER leg@'10.0.0.0/24' IDENTIFIED BY 'pw';
UPDATE mysql.global_priv SET Host='10.0.0.0/255.0.255.0' WHERE User='leg';
FLUSH PRIVILEGES;
SHOW GRANTS FOR leg@'10.0.0.0/255.0.255.0';
ERROR 42000: There is no such grant defined for user 'leg' on host '10.0.0.0/255.0.255.0'
RENAME USER leg@'10.0.0.0/255.0.255.0' TO leg@'10.0.0.0/16';
FLUSH PRIVILEGES;
SHOW GRANTS FOR leg@'10.0.0.0/255.255.0.0';
Grants for leg@10.0.0.0/255.255.0.0
GRANT USAGE ON *.* TO `leg`@`10.0.0.0/255.255.0.0` IDENTIFIED BY PASSWORD '*D821809F681A40A6E379B50D0463EFAE20BDD122'
DROP USER leg@'10.0.0.0/255.255.0.0';
CREATE USER leg2@'10.0.0.0/24' IDENTIFIED BY 'pw';
UPDATE mysql.global_priv SET Host='10.1.2.3/255.255.255.0' WHERE User='leg2';
FLUSH PRIVILEGES;
DROP USER leg2@'10.1.2.3/255.255.255.0';
SELECT User, Host FROM mysql.global_priv WHERE User='leg2';
User Host
CREATE USER leg3@'10.0.0.0/24' IDENTIFIED BY 'pw';
UPDATE mysql.global_priv SET Host='10.0.0.0/24' WHERE User='leg3';
FLUSH PRIVILEGES;
SHOW GRANTS FOR leg3@'10.0.0.0/24';
ERROR 42000: There is no such grant defined for user 'leg3' on host '10.0.0.0/255.255.255.0'
DROP USER leg3@'10.0.0.0/24';
ERROR HY000: Operation DROP USER failed for 'leg3'@'10.0.0.0/255.255.255.0'
DELETE FROM mysql.global_priv WHERE User='leg3';
FLUSH PRIVILEGES;
# ... in tables_priv and procs_priv too
CREATE DATABASE legdb;
CREATE TABLE legdb.t (a INT);
CREATE PROCEDURE legdb.p() SELECT 1;
CREATE USER legt@'%' IDENTIFIED BY 'pw';
GRANT SELECT ON legdb.t TO legt@'%';
GRANT EXECUTE ON PROCEDURE legdb.p TO legt@'%';
connect con1,127.0.0.1,legt,pw,,$MASTER_MYPORT,;
connection con1;
SELECT * FROM legdb.t;
a
CALL legdb.p();
1
1
disconnect con1;
connection default;
UPDATE mysql.tables_priv SET Host='127.0.0.0/255.0.255.0' WHERE User='legt';
UPDATE mysql.procs_priv SET Host='127.0.0.0/255.0.255.0' WHERE User='legt';
FLUSH PRIVILEGES;
connect con1,127.0.0.1,legt,pw,,$MASTER_MYPORT,;
connection con1;
SELECT CURRENT_USER();
CURRENT_USER()
legt@%
SELECT * FROM legdb.t;
ERROR 42000: SELECT command denied to user 'legt'@'localhost' for table `legdb`.`t`
CALL legdb.p();
ERROR 42000: execute command denied to user 'legt'@'%' for routine 'legdb.p'
disconnect con1;
connection default;
DELETE FROM mysql.tables_priv WHERE User='legt';
DELETE FROM mysql.procs_priv WHERE User='legt';
DROP USER legt@'%';
DROP PROCEDURE legdb.p;
DROP TABLE legdb.t;
DROP DATABASE legdb;
DROP USER c1@'127.0.0.0/8', c2@'127.0.0.0/24', c3@'127.0.0.1/32',
c4@'127.0.0.0/31', c5@'0.0.0.0/1',
n1@'127.0.0.0/255.0.0.0', n2@'127.0.0.0/255.255.255.0';
FLUSH PRIVILEGES;
#
# End of MDEV-25515 tests
#

View File

@@ -0,0 +1,259 @@
--source include/not_embedded.inc
--echo #
--echo # MDEV-25515 User Account Host Names using CIDR notation
--echo #
call mtr.add_suppression("ignored, the host is not a valid ip/netmask");
--echo # CIDR is stored in the netmask form
CREATE USER c1@'127.0.0.0/8' IDENTIFIED BY 'pw';
CREATE USER c2@'127.0.0.0/24' IDENTIFIED BY 'pw';
CREATE USER c3@'127.0.0.1/32' IDENTIFIED BY 'pw';
CREATE USER c4@'127.0.0.0/31' IDENTIFIED BY 'pw';
CREATE USER c5@'0.0.0.0/1' IDENTIFIED BY 'pw';
CREATE USER n1@'127.0.0.0/255.0.0.0' IDENTIFIED BY 'pw';
CREATE USER n2@'127.0.0.0/255.255.255.0' IDENTIFIED BY 'pw';
SELECT User, Host FROM mysql.global_priv WHERE User LIKE 'c_' OR User LIKE 'n_'
ORDER BY User;
SHOW CREATE USER c1@'127.0.0.0/8';
SHOW GRANTS FOR c2@'127.0.0.0/24';
--echo # Both spellings are the same account
CREATE USER dup@'127.0.0.0/8' IDENTIFIED BY 'pw';
--error ER_CANNOT_USER
CREATE USER dup@'127.0.0.0/255.0.0.0' IDENTIFIED BY 'other';
CREATE USER IF NOT EXISTS dup@'127.0.0.0/255.0.0.0' IDENTIFIED BY 'other';
--error ER_CANNOT_USER
CREATE USER dup@'127.000.0.0/8' IDENTIFIED BY 'other';
--error ER_CANNOT_USER
CREATE USER dup@'127.0.0.0/+8' IDENTIFIED BY 'other';
--error ER_CANNOT_USER
CREATE USER dup@'127.0.0.0/ 8' IDENTIFIED BY 'other';
--error ER_CANNOT_USER
CREATE USER dup@'127.0.0.0/255.000.0.0' IDENTIFIED BY 'other';
SELECT User, Host FROM mysql.global_priv WHERE User='dup';
DROP USER dup@'127.0.0.0/8';
--echo # Matching is unchanged
connect (con1,127.0.0.1,c1,pw,,$MASTER_MYPORT,);
connection con1;
SELECT CURRENT_USER();
disconnect con1;
connection default;
connect (con1,127.0.0.1,c2,pw,,$MASTER_MYPORT,);
connection con1;
SELECT CURRENT_USER();
disconnect con1;
connection default;
connect (con1,127.0.0.1,c3,pw,,$MASTER_MYPORT,);
connection con1;
SELECT CURRENT_USER();
disconnect con1;
connection default;
connect (con1,127.0.0.1,c4,pw,,$MASTER_MYPORT,);
connection con1;
SELECT CURRENT_USER();
disconnect con1;
connection default;
connect (con1,127.0.0.1,c5,pw,,$MASTER_MYPORT,);
connection con1;
SELECT CURRENT_USER();
disconnect con1;
connection default;
connect (con1,127.0.0.1,n1,pw,,$MASTER_MYPORT,);
connection con1;
SELECT CURRENT_USER();
disconnect con1;
connection default;
CREATE USER z1@'127.0.0.0/32' IDENTIFIED BY 'pw';
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
--error ER_ACCESS_DENIED_ERROR
connect (con1,127.0.0.1,z1,pw,,$MASTER_MYPORT,);
DROP USER z1@'127.0.0.0/32';
--echo # Either spelling resolves to the same row, in every privilege table
CREATE DATABASE cidr_db;
CREATE TABLE cidr_db.t (a INT);
GRANT SELECT ON cidr_db.* TO c1@'127.0.0.0/255.0.0.0';
SHOW GRANTS FOR c1@'127.0.0.0/8';
GRANT INSERT ON cidr_db.* TO c1@'127.0.0.0/8';
REVOKE SELECT ON cidr_db.* FROM c1@'127.0.0.0/8';
SELECT User, Host FROM mysql.db WHERE User='c1';
GRANT SELECT ON cidr_db.t TO c1@'127.0.0.0/8';
SELECT User, Host FROM mysql.tables_priv WHERE User='c1';
REVOKE SELECT ON cidr_db.t FROM c1@'127.0.0.0/255.0.0.0';
SET PASSWORD FOR c1@'127.0.0.0/8' = PASSWORD('pw2');
ALTER USER c1@'127.0.0.0/255.0.0.0' IDENTIFIED BY 'pw';
RENAME USER c1@'127.0.0.0/8' TO c1@'127.0.0.0/16';
SELECT User, Host FROM mysql.global_priv WHERE User='c1';
SELECT User, Host FROM mysql.db WHERE User='c1';
RENAME USER c1@'127.0.0.0/255.255.0.0' TO c1@'127.0.0.0/8';
REVOKE ALL PRIVILEGES, GRANT OPTION FROM c1@'127.0.0.0/255.0.0.0';
SHOW GRANTS FOR c1@'127.0.0.0/8';
DROP TABLE cidr_db.t;
DROP DATABASE cidr_db;
--echo # GRANT normalises the host of an account it auto-creates
CREATE TABLE test.t (a INT);
CREATE PROCEDURE test.p1() SELECT 1;
CREATE ROLE cidr_role;
GRANT SELECT ON test.* TO g1@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT SELECT ON test.t TO g2@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT EXECUTE ON PROCEDURE test.p1 TO g3@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT cidr_role TO g4@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT PROXY ON ''@'%' TO g5@'10.0.0.0/24' IDENTIFIED BY 'pw';
SELECT User, Host FROM mysql.global_priv WHERE User LIKE 'g_' ORDER BY User;
SELECT User, Host FROM mysql.db WHERE User='g1';
SELECT User, Host FROM mysql.tables_priv WHERE User='g2';
SELECT User, Host FROM mysql.procs_priv WHERE User='g3';
SELECT User, Host FROM mysql.roles_mapping WHERE User='g4';
SELECT User, Host FROM mysql.proxies_priv WHERE User='g5';
DROP USER g1@'10.0.0.0/255.255.255.0', g2@'10.0.0.0/255.255.255.0',
g3@'10.0.0.0/255.255.255.0', g4@'10.0.0.0/255.255.255.0',
g5@'10.0.0.0/255.255.255.0';
DROP ROLE cidr_role;
DROP PROCEDURE test.p1;
DROP TABLE test.t;
--echo # DEFINER reaches the same grammar rule
CREATE USER def@'10.0.0.0/24' IDENTIFIED BY 'pw';
GRANT ALL ON test.* TO def@'10.0.0.0/255.255.255.0';
CREATE DEFINER=def@'10.0.0.0/24' VIEW test.v1 AS SELECT 1;
CREATE DEFINER=def@'10.0.0.0/24' PROCEDURE test.p2() SELECT 1;
SELECT definer FROM information_schema.views
WHERE table_schema='test' AND table_name='v1';
SELECT definer FROM information_schema.routines
WHERE routine_schema='test' AND routine_name='p2';
DROP VIEW test.v1;
DROP PROCEDURE test.p2;
DROP USER def@'10.0.0.0/255.255.255.0';
--echo # A host without '/' is not a masked host and is untouched
CREATE USER v1@'%', v2@'10.0.0.%', v3@'127.0.0._', v4@'localhost',
v5@'127.0.0.1', v6@'::1';
SELECT User, Host FROM mysql.global_priv WHERE User LIKE 'v_' ORDER BY User;
DROP USER v1@'%', v2@'10.0.0.%', v3@'127.0.0._', v4@'localhost',
v5@'127.0.0.1', v6@'::1';
--echo # A malformed mask is rejected, quoting the host as it was written
--error ER_INVALID_HOST_NETMASK
CREATE USER e01@'10.0.0.0/33';
--error ER_INVALID_HOST_NETMASK
CREATE USER e02@'10.0.0.0/0';
--error ER_INVALID_HOST_NETMASK
CREATE USER e03@'10.0.0.0/-1';
--error ER_INVALID_HOST_NETMASK
CREATE USER e04@'10.0.0.0/0.0.0.0';
--error ER_INVALID_HOST_NETMASK
CREATE USER e05@'10.0.0.0/255.0.255.0';
--error ER_INVALID_HOST_NETMASK
CREATE USER e06@'10.0.0.0/255.255.0.255';
--error ER_INVALID_HOST_NETMASK
CREATE USER e07@'10.1.2.3/24';
--error ER_INVALID_HOST_NETMASK
CREATE USER e08@'10.1.2.3/255.255.255.0';
--error ER_INVALID_HOST_NETMASK
CREATE USER e09@'127.0.0.0/1';
--error ER_INVALID_HOST_NETMASK
CREATE USER e10@'2001:db8::/32';
--error ER_INVALID_HOST_NETMASK
CREATE USER e11@'10.0.0.%/24';
--error ER_INVALID_HOST_NETMASK
CREATE USER e12@'10.0.0.0/abc';
--error ER_INVALID_HOST_NETMASK
CREATE USER e13@'10.0.0.0/';
--error ER_INVALID_HOST_NETMASK
CREATE USER e14@'10.0.0.0/24x';
--error ER_INVALID_HOST_NETMASK
GRANT SELECT ON test.* TO e20@'10.0.0.0/33' IDENTIFIED BY 'pw';
--error ER_INVALID_HOST_NETMASK
GRANT SELECT ON test.* TO e21@'10.1.2.3/24' IDENTIFIED BY 'pw';
SELECT User, Host FROM mysql.global_priv WHERE User LIKE 'e__';
--error ER_CANNOT_USER
ALTER USER e30@'10.0.0.0/33' IDENTIFIED BY 'pw';
--error ER_NONEXISTING_GRANT
REVOKE SELECT ON test.* FROM e30@'10.0.0.0/33';
--echo # RENAME USER validates the target only
CREATE USER r1@'127.0.0.0/8' IDENTIFIED BY 'pw';
--error ER_CANNOT_USER
RENAME USER r1@'127.0.0.0/8' TO r1@'10.1.2.3/24';
--error ER_CANNOT_USER
RENAME USER r1@'127.0.0.0/8' TO r1@'10.0.0.0/255.0.255.0';
SHOW WARNINGS;
RENAME USER r1@'127.0.0.0/8' TO r1@'10.0.0.0/16';
SELECT User, Host FROM mysql.global_priv WHERE User='r1';
DROP USER r1@'10.0.0.0/255.255.0.0';
--echo # acl_load() skips a stored row whose mask it cannot honour
CREATE USER leg@'10.0.0.0/24' IDENTIFIED BY 'pw';
UPDATE mysql.global_priv SET Host='10.0.0.0/255.0.255.0' WHERE User='leg';
FLUSH PRIVILEGES;
--error ER_NONEXISTING_GRANT
SHOW GRANTS FOR leg@'10.0.0.0/255.0.255.0';
RENAME USER leg@'10.0.0.0/255.0.255.0' TO leg@'10.0.0.0/16';
FLUSH PRIVILEGES;
SHOW GRANTS FOR leg@'10.0.0.0/255.255.0.0';
DROP USER leg@'10.0.0.0/255.255.0.0';
CREATE USER leg2@'10.0.0.0/24' IDENTIFIED BY 'pw';
UPDATE mysql.global_priv SET Host='10.1.2.3/255.255.255.0' WHERE User='leg2';
FLUSH PRIVILEGES;
DROP USER leg2@'10.1.2.3/255.255.255.0';
SELECT User, Host FROM mysql.global_priv WHERE User='leg2';
CREATE USER leg3@'10.0.0.0/24' IDENTIFIED BY 'pw';
UPDATE mysql.global_priv SET Host='10.0.0.0/24' WHERE User='leg3';
FLUSH PRIVILEGES;
--error ER_NONEXISTING_GRANT
SHOW GRANTS FOR leg3@'10.0.0.0/24';
--error ER_CANNOT_USER
DROP USER leg3@'10.0.0.0/24';
DELETE FROM mysql.global_priv WHERE User='leg3';
FLUSH PRIVILEGES;
--echo # ... in tables_priv and procs_priv too
CREATE DATABASE legdb;
CREATE TABLE legdb.t (a INT);
CREATE PROCEDURE legdb.p() SELECT 1;
CREATE USER legt@'%' IDENTIFIED BY 'pw';
GRANT SELECT ON legdb.t TO legt@'%';
GRANT EXECUTE ON PROCEDURE legdb.p TO legt@'%';
connect (con1,127.0.0.1,legt,pw,,$MASTER_MYPORT,);
connection con1;
SELECT * FROM legdb.t;
CALL legdb.p();
disconnect con1;
connection default;
UPDATE mysql.tables_priv SET Host='127.0.0.0/255.0.255.0' WHERE User='legt';
UPDATE mysql.procs_priv SET Host='127.0.0.0/255.0.255.0' WHERE User='legt';
FLUSH PRIVILEGES;
connect (con1,127.0.0.1,legt,pw,,$MASTER_MYPORT,);
connection con1;
SELECT CURRENT_USER();
--error ER_TABLEACCESS_DENIED_ERROR
SELECT * FROM legdb.t;
--error ER_PROCACCESS_DENIED_ERROR
CALL legdb.p();
disconnect con1;
connection default;
DELETE FROM mysql.tables_priv WHERE User='legt';
DELETE FROM mysql.procs_priv WHERE User='legt';
DROP USER legt@'%';
DROP PROCEDURE legdb.p;
DROP TABLE legdb.t;
DROP DATABASE legdb;
DROP USER c1@'127.0.0.0/8', c2@'127.0.0.0/24', c3@'127.0.0.1/32',
c4@'127.0.0.0/31', c5@'0.0.0.0/1',
n1@'127.0.0.0/255.0.0.0', n2@'127.0.0.0/255.255.255.0';
FLUSH PRIVILEGES;
--echo #
--echo # End of MDEV-25515 tests
--echo #

View File

@@ -12423,3 +12423,7 @@ ER_RESULT_CUT_BY_LIMIT
eng "Result was cut by %s) because of %s limit"
ER_CUT_VALUES_WHILE_PROCESSING
eng "Some values were cut while processing %s). Increase %s if you want to avoid the cut"
ER_INVALID_HOST_NETMASK
eng "Invalid masked host '%s' for user '%s'"
ER_ACL_ENTRY_IGNORED_BAD_HOST_MASK
eng "'%s' entry '%s' ignored, the host is not a valid ip/netmask"

View File

@@ -339,6 +339,7 @@ ulong role_global_merges= 0, role_db_merges= 0, role_table_merges= 0,
#ifndef NO_EMBEDDED_ACCESS_CHECKS
static bool ignore_max_password_errors(const ACL_USER *acl_user);
static void update_hostname(acl_host_and_ip *host, const char *hostname);
static bool valid_host_mask(const char *hostname);
static bool show_proxy_grants (THD *, const char *, const char *,
char *, size_t);
static bool show_role_grants(THD *, const char *,
@@ -443,6 +444,17 @@ public:
safe_str(host.hostname));
return TRUE;
}
if (!valid_host_mask(host.hostname) ||
!valid_host_mask(proxied_host.hostname))
{
char entry[2 * (USERNAME_LENGTH + HOSTNAME_LENGTH) + 4];
my_snprintf(entry, sizeof(entry), "%s@%s %s@%s", proxied_user,
safe_str(proxied_host.hostname), user,
safe_str(host.hostname));
sql_print_warning(ER_DEFAULT(ER_ACL_ENTRY_IGNORED_BAD_HOST_MASK),
"proxies_priv", entry);
return TRUE;
}
return FALSE;
}
@@ -3437,6 +3449,15 @@ static bool acl_load(THD *thd, const Grant_tables& tables)
host.host.hostname, host.db);
continue;
}
if (!valid_host_mask(host.host.hostname))
{
char entry[HOSTNAME_LENGTH + NAME_LEN + 2];
my_snprintf(entry, sizeof(entry), "%s|%s", host.host.hostname,
safe_str(host.db));
sql_print_warning(ER_DEFAULT(ER_ACL_ENTRY_IGNORED_BAD_HOST_MASK),
"host", entry);
continue;
}
#ifndef TO_BE_REMOVED
if (host_table.num_fields() == 8)
{ // Without grant
@@ -3518,6 +3539,17 @@ static bool acl_load(THD *thd, const Grant_tables& tables)
continue;
}
/* Skipped rows stay in the table, so DROP and RENAME USER still work */
if (!valid_host_mask(user.host.hostname))
{
char entry[USERNAME_LENGTH + HOSTNAME_LENGTH + 2];
my_snprintf(entry, sizeof(entry), "%s@%s", user.user.str,
safe_str(user.host.hostname));
sql_print_warning(ER_DEFAULT(ER_ACL_ENTRY_IGNORED_BAD_HOST_MASK),
"user", entry);
continue;
}
if (user_table.get_auth(thd, &acl_memroot, &user))
continue;
for (uint i= 0; i < user.nauth; i++)
@@ -3592,6 +3624,15 @@ static bool acl_load(THD *thd, const Grant_tables& tables)
db.db, db.user, safe_str(db.host.hostname));
continue;
}
if (!valid_host_mask(db.host.hostname))
{
char entry[NAME_LEN + USERNAME_LENGTH + HOSTNAME_LENGTH + 3];
my_snprintf(entry, sizeof(entry), "%s %s@%s", db.db,
db.user, safe_str(db.host.hostname));
sql_print_warning(ER_DEFAULT(ER_ACL_ENTRY_IGNORED_BAD_HOST_MASK),
"db", entry);
continue;
}
db.access= access_t(fix_rights_for_db(db_table.get_access()));
db.initial_access= db.access;
db.sort=get_magic_sort("hdu", db.host.hostname, db.db, db.user);
@@ -5407,6 +5448,101 @@ static const char *calc_ip(const char *ip, long *val, char end)
return ip;
}
/* Convert CIDR prefix length (1-32) to a 32-bit IPv4 subnet mask. */
static const char *calc_cidr(const char *prefix_str, long *val)
{
uchar prefix;
if (!(prefix_str=str2int(prefix_str, INT16_MAX, 10, &prefix)) ||
*prefix_str != '\0' || !prefix || prefix > 32)
return 0;
*val= (long) (uint32) (0xFFFFFFFFU << (32 - prefix));
return prefix_str;
}
/*
compare_hostname() tests (client_ip & ip_mask) == ip, so a zero or
non-contiguous mask, or an ip with host bits set, can never match.
*/
static bool valid_masked_ip(long ip, long mask)
{
ulong inv, m= (ulong) (uint32) mask;
if (!m)
return false; /* a mask of 0.0.0.0 */
inv= (~m) & 0xFFFFFFFFUL;
if (inv & (inv + 1))
return false; /* not contiguous */
return !(((ulong) (uint32) ip) & inv);
}
/*
Rewrite a masked host into the canonical netmask spelling:
10.0.0.0/8 -> 10.0.0.0/255.0.0.0
010.0.0.0/+8 -> 10.0.0.0/255.0.0.0
10.0.0.0/255.0.0.0 -> unchanged, already canonical
Anything that is not a valid masked host is returned unchanged and no error
is raised, so DROP USER and RENAME USER can still name a malformed host
stored by a version that did not validate it.
The exception is a stored literal CIDR host such as 10.0.0.0/24: it is
converted here, so it can no longer be named and has to be removed with a
direct DELETE from the privilege tables.
*/
LEX_CSTRING normalize_masked_host(THD *thd, const LEX_CSTRING &host)
{
long ip, mask;
const char *p;
char buf[32]; /* 255.255.255.255/255.255.255.255 and NUL */
uint32 a, m;
size_t len;
if (!host.str || !memchr(host.str, '/', host.length))
return host;
if (!(p= calc_ip(host.str, &ip, '/')))
return host;
if (!calc_ip(p + 1, &mask, '\0') && !calc_cidr(p + 1, &mask))
return host;
if (!valid_masked_ip(ip, mask))
return host;
a= (uint32) ip;
m= (uint32) mask;
len= my_snprintf(buf, sizeof(buf), "%u.%u.%u.%u/%u.%u.%u.%u",
a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF,
m >> 24, (m >> 16) & 0xFF, (m >> 8) & 0xFF, m & 0xFF);
return thd->strmake_lex_cstring(buf, len);
}
/*
Check that a host given in masked form is well formed:
a.b.c.d/255.255.255.0
A CIDR prefix is rejected here on purpose: normalize_masked_host() has
already rewritten every valid one, so a prefix reaching this point means
the host never went through the parser.
*/
static bool valid_host_mask(const char *hostname)
{
long ip, mask;
const char *p;
if (!hostname || !strchr(hostname, '/'))
return true; /* not a masked host */
return (p= calc_ip(hostname, &ip, '/')) &&
calc_ip(p + 1, &mask, '\0') && valid_masked_ip(ip, mask);
}
static void update_hostname(acl_host_and_ip *host, const char *hostname)
{
@@ -5666,6 +5802,14 @@ static int replace_user_table(THD *thd, const User_table &user_table,
if (!combo->auth)
combo->auth= &auth_no_password;
/* Only rows about to be created, so an older bad row stays droppable */
if (!valid_host_mask(combo->host.str))
{
my_error(ER_INVALID_HOST_NETMASK, MYF(0), combo->host.str,
combo->user.str);
goto end;
}
old_row_exists = 0;
restore_record(table, s->default_values);
user_table.set_host(combo->host.str, combo->host.length);
@@ -9680,6 +9824,17 @@ static bool grant_load(THD *thd,
}
}
if (!valid_host_mask(mem_check->host.hostname))
{
char entry[NAME_LEN + USERNAME_LENGTH + HOSTNAME_LENGTH + 3];
my_snprintf(entry, sizeof(entry), "%s %s@%s", safe_str(mem_check->tname),
mem_check->user, safe_str(mem_check->host.hostname));
sql_print_warning(ER_DEFAULT(ER_ACL_ENTRY_IGNORED_BAD_HOST_MASK),
"tables_priv", entry);
delete mem_check;
continue;
}
if (! mem_check->ok())
delete mem_check;
else if (column_priv_insert(mem_check))
@@ -9723,6 +9878,18 @@ static bool grant_load(THD *thd,
continue;
}
}
if (!valid_host_mask(mem_check->host.hostname))
{
char entry[NAME_LEN + USERNAME_LENGTH + HOSTNAME_LENGTH + 3];
my_snprintf(entry, sizeof(entry), "%s %s@%s",
safe_str(mem_check->tname), mem_check->user,
safe_str(mem_check->host.hostname));
sql_print_warning(ER_DEFAULT(ER_ACL_ENTRY_IGNORED_BAD_HOST_MASK),
"procs_priv", entry);
delete mem_check;
continue;
}
enum_sp_type type= (enum_sp_type)procs_priv.routine_type()->val_int();
const Sp_handler *sph= Sp_handler::handler(type);
if (!sph || !(hash= sph->get_priv_hash()))
@@ -13332,6 +13499,18 @@ bool mysql_rename_user(THD *thd, List <LEX_USER> &list)
DBUG_ASSERT(!user_from->is_role());
DBUG_ASSERT(!user_to->is_role());
/* Target only, so a bad host can be repaired by renaming it */
if (!valid_host_mask(user_to->host.str))
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_INVALID_HOST_NETMASK,
ER_THD(thd, ER_INVALID_HOST_NETMASK),
user_to->host.str, user_to->user.str);
append_user(thd, &wrong_users, user_to);
result= TRUE;
continue;
}
/*
Search all in-memory structures and grant tables
for a mention of the new user name.
@@ -14234,6 +14413,8 @@ access_t get_column_grant(THD *, GRANT_INFO *, const char *, const char *,
{ return access_t(ALL_KNOWN_ACL); }
int acl_check_setrole(THD *, const LEX_CSTRING &, access_t *) { return 0; }
int acl_setrole(THD *, const LEX_CSTRING &, const access_t&) { return 0; }
LEX_CSTRING normalize_masked_host(THD *, const LEX_CSTRING &host)
{ return host; }
#endif /*NO_EMBEDDED_ACCESS_CHECKS */
static int set_privs_on_login(THD *thd, const ACL_USER *acl_user)

View File

@@ -148,6 +148,7 @@ bool mysql_drop_user(THD *thd, List <LEX_USER> &list, bool handle_as_role);
bool mysql_rename_user(THD *thd, List <LEX_USER> &list);
int mysql_alter_user(THD *thd, List <LEX_USER> &list);
bool mysql_revoke_all(THD *thd, List <LEX_USER> &list);
LEX_CSTRING normalize_masked_host(THD *thd, const LEX_CSTRING &host);
void fill_effective_table_privileges(THD *thd, GRANT_INFO *grant,
const char *db, const char *table);
bool sp_revoke_privileges(THD *thd,

View File

@@ -16824,7 +16824,12 @@ user_name:
check_host_name(&$$->host))
MYSQL_YYABORT;
if ($$->host.str[0])
{
$$->host= thd->make_ident_casedn($$->host);
$$->host= normalize_masked_host(thd, $$->host);
if (unlikely(!$$->host.str))
MYSQL_YYABORT;
}
else
$$->host= host_not_specified;
}