From ca63e2c6276e5ea121d456524446f4f22b5b7e56 Mon Sep 17 00:00:00 2001 From: Rucha Deodhar Date: Thu, 4 Dec 2025 18:35:50 +0530 Subject: [PATCH] MDEV-37950: INSERT ... RETURNING exposes columns for which the user lacks SELECT privilege Analysis: When setup_fields() is called, the want_privilege is set to NO_ACL, so correct priveleges are not checked. Fix: Since RETURNING requires SELECT_ACL privelige, when we are setting up the returning fields for the given query, set want_privilege to SELECT_ACL. Reset to original value of want_privilege once done. --- mysql-test/main/insert_notembedded.result | 43 +++++++++++++++++ mysql-test/main/insert_notembedded.test | 57 +++++++++++++++++++++++ sql/sql_base.cc | 19 ++++++-- 3 files changed, 115 insertions(+), 4 deletions(-) diff --git a/mysql-test/main/insert_notembedded.result b/mysql-test/main/insert_notembedded.result index 5df69cc8ffe..6c5e8057500 100644 --- a/mysql-test/main/insert_notembedded.result +++ b/mysql-test/main/insert_notembedded.result @@ -121,3 +121,46 @@ connection default; DROP DATABASE meow; set local sql_mode=default; set global sql_mode=default; +# +# MDEV-37950: INSERT ... RETURNING exposes columns for which +# the user lacks SELECT privilege +# +CREATE USER regular; +GRANT INSERT ON *.* TO regular; +GRANT DELETE ON *.* TO regular; +CREATE DATABASE test1; +DROP TABLE IF EXISTS test1.t_trigger_test1; +Warnings: +Note 1051 Unknown table 'test1.t_trigger_test1' +CREATE TABLE test1.t_trigger_test1 ( +id INT AUTO_INCREMENT PRIMARY KEY, +name VARCHAR(50), +note VARCHAR(100) +); +CREATE TRIGGER test1.trg_before_insert +BEFORE INSERT ON test1.t_trigger_test1 +FOR EACH ROW +BEGIN +SET NEW.name = CONCAT('BEFORE_', NEW.name); +END | +INSERT INTO test1.t_trigger_test1 (name) VALUES ('Alice') RETURNING *; +ERROR 42000: SELECT command denied to user 'regular'@'localhost' for column 'id' in table 't_trigger_test1' +INSERT INTO test1.t_trigger_test1 (name) VALUES ('Alice') RETURNING id, name, note; +ERROR 42000: SELECT command denied to user 'regular'@'localhost' for column 'id' in table 't_trigger_test1' +# same for DELETE because delete with "WHERE" still requires to +# read from the table, which basically means having select privileges +DELETE FROM test1.t_trigger_test1 WHERE id=1; +ERROR 42000: SELECT command denied to user 'regular'@'localhost' for column 'id' in table 't_trigger_test1' +DELETE FROM test1.t_trigger_test1 WHERE id=1 RETURNING id; +ERROR 42000: SELECT command denied to user 'regular'@'localhost' for column 'id' in table 't_trigger_test1' +DELETE FROM test1.t_trigger_test1 WHERE id=1 RETURNING *; +ERROR 42000: SELECT command denied to user 'regular'@'localhost' for column 'id' in table 't_trigger_test1' +DELETE FROM test1.t_trigger_test1 RETURNING *; +ERROR 42000: SELECT command denied to user 'regular'@'localhost' for column 'id' in table 't_trigger_test1' +DELETE FROM test1.t_trigger_test1 RETURNING id; +ERROR 42000: SELECT command denied to user 'regular'@'localhost' for column 'id' in table 't_trigger_test1' +DELETE FROM test1.t_trigger_test1; +DROP TRIGGER test1.trg_before_insert; +DROP TABLE test1.t_trigger_test1; +DROP USER regular; +DROP DATABASE test1; diff --git a/mysql-test/main/insert_notembedded.test b/mysql-test/main/insert_notembedded.test index 2769aee8d8a..09f5b18531d 100644 --- a/mysql-test/main/insert_notembedded.test +++ b/mysql-test/main/insert_notembedded.test @@ -158,3 +158,60 @@ DROP DATABASE meow; set local sql_mode=default; set global sql_mode=default; + +--echo # +--echo # MDEV-37950: INSERT ... RETURNING exposes columns for which +--echo # the user lacks SELECT privilege +--echo # +CREATE USER regular; +GRANT INSERT ON *.* TO regular; +GRANT DELETE ON *.* TO regular; + +CREATE DATABASE test1; +DROP TABLE IF EXISTS test1.t_trigger_test1; +CREATE TABLE test1.t_trigger_test1 ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50), + note VARCHAR(100) +); +DELIMITER |; +CREATE TRIGGER test1.trg_before_insert +BEFORE INSERT ON test1.t_trigger_test1 +FOR EACH ROW +BEGIN + SET NEW.name = CONCAT('BEFORE_', NEW.name); +END | +DELIMITER ;| + +change_user regular; + +--error ER_COLUMNACCESS_DENIED_ERROR +INSERT INTO test1.t_trigger_test1 (name) VALUES ('Alice') RETURNING *; +--error ER_COLUMNACCESS_DENIED_ERROR +INSERT INTO test1.t_trigger_test1 (name) VALUES ('Alice') RETURNING id, name, note; + +--echo # same for DELETE because delete with "WHERE" still requires to +--echo # read from the table, which basically means having select privileges + +--error ER_COLUMNACCESS_DENIED_ERROR +DELETE FROM test1.t_trigger_test1 WHERE id=1; + +--error ER_COLUMNACCESS_DENIED_ERROR +DELETE FROM test1.t_trigger_test1 WHERE id=1 RETURNING id; + +--error ER_COLUMNACCESS_DENIED_ERROR +DELETE FROM test1.t_trigger_test1 WHERE id=1 RETURNING *; + +--error ER_COLUMNACCESS_DENIED_ERROR +DELETE FROM test1.t_trigger_test1 RETURNING *; + +--error ER_COLUMNACCESS_DENIED_ERROR +DELETE FROM test1.t_trigger_test1 RETURNING id; + +DELETE FROM test1.t_trigger_test1; + +change_user root; +DROP TRIGGER test1.trg_before_insert; +DROP TABLE test1.t_trigger_test1; +DROP USER regular; +DROP DATABASE test1; diff --git a/sql/sql_base.cc b/sql/sql_base.cc index aeb829c60e5..00a2fb90c58 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -7909,12 +7909,23 @@ bool setup_fields(THD *thd, Ref_ptr_array ref_pointer_array, int setup_returning_fields(THD* thd, TABLE_LIST* table_list) { + GRANT_INFO *saved_grant; + int res= 0; + if (!thd->lex->has_returning()) return 0; - return setup_wild(thd, table_list, thd->lex->returning()->item_list, NULL, - thd->lex->returning(), true) - || setup_fields(thd, Ref_ptr_array(), thd->lex->returning()->item_list, - MARK_COLUMNS_READ, NULL, NULL, 0, THD_WHERE::RETURNING); + + saved_grant= &table_list->table->grant; + table_list->table->grant.want_privilege|= SELECT_ACL; + + res= setup_wild(thd, table_list, thd->lex->returning()->item_list, NULL, + thd->lex->returning(), true) + || setup_fields(thd, Ref_ptr_array(), thd->lex->returning()->item_list, + MARK_COLUMNS_READ, NULL, NULL, 0, THD_WHERE::RETURNING); + + table_list->table->grant= *saved_grant; + + return res; }