Skip to content

Commit 2106e94

Browse files
committed
Bug #31427410: MISSING CHECKS ON INFORMATION_SCHEMA.USER_ATTRIBUTES
RB#24996 1. Implemented a CAN_ACCESS_USER() internal function that returns true if: - if it's the slave thread - if the ACL system hasn't been initialized - if the user checked is the currently authenticated account - if the currently authenticated account has UPDATE on mysql.user - if the currently authenticated account has SELECT on mysql.user - if the currently authenticated account has CREATE USER and same SYSTEM_USER level 2. Made the I_S.USSER_ATTRIBUTES view use a WHERE clause with the new internal function 3. updated the test suite
1 parent d4f8ce5 commit 2106e94

18 files changed

+151
-10
lines changed

mysql-test/r/information_schema_ci.result

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,8 @@ SELECT CAN_ACCESS_TABLE("test", "t1") AS f1, COLUMN_NAME AS F2 FROM INFORMATION
22572257
ERROR HY000: Access to native function 'CAN_ACCESS_TABLE' is rejected.
22582258
PREPARE stmt FROM 'SELECT CAN_ACCESS_TABLE("test", "t1") AS f1, COLUMN_NAME AS F2 FROM INFORMATION_SCHEMA.COLUMNS';
22592259
ERROR HY000: Access to native function 'CAN_ACCESS_TABLE' is rejected.
2260+
SELECT CAN_ACCESS_USER(NULL, NULL);
2261+
ERROR HY000: Access to native function 'CAN_ACCESS_USER' is rejected.
22602262
# Case 2: Invoking I_S native methods should be allowed through I_S queries.
22612263
SELECT * FROM INFORMATION_SCHEMA.TABLES where table_name='t1';
22622264
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE ENGINE VERSION ROW_FORMAT TABLE_ROWS AVG_ROW_LENGTH DATA_LENGTH MAX_DATA_LENGTH INDEX_LENGTH DATA_FREE AUTO_INCREMENT CREATE_TIME UPDATE_TIME CHECK_TIME TABLE_COLLATION CHECKSUM CREATE_OPTIONS TABLE_COMMENT

mysql-test/r/information_schema_cs.result

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,8 @@ SELECT CAN_ACCESS_TABLE("test", "t1") AS f1, COLUMN_NAME AS F2 FROM INFORMATION
22572257
ERROR HY000: Access to native function 'CAN_ACCESS_TABLE' is rejected.
22582258
PREPARE stmt FROM 'SELECT CAN_ACCESS_TABLE("test", "t1") AS f1, COLUMN_NAME AS F2 FROM INFORMATION_SCHEMA.COLUMNS';
22592259
ERROR HY000: Access to native function 'CAN_ACCESS_TABLE' is rejected.
2260+
SELECT CAN_ACCESS_USER(NULL, NULL);
2261+
ERROR HY000: Access to native function 'CAN_ACCESS_USER' is rejected.
22602262
# Case 2: Invoking I_S native methods should be allowed through I_S queries.
22612263
SELECT * FROM INFORMATION_SCHEMA.TABLES where table_name='t1';
22622264
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE ENGINE VERSION ROW_FORMAT TABLE_ROWS AVG_ROW_LENGTH DATA_LENGTH MAX_DATA_LENGTH INDEX_LENGTH DATA_FREE AUTO_INCREMENT CREATE_TIME UPDATE_TIME CHECK_TIME TABLE_COLLATION CHECKSUM CREATE_OPTIONS TABLE_COMMENT

mysql-test/suite/information_schema/r/i_s_schema_definition_debug.result

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,13 +1631,15 @@ CREATE OR REPLACE DEFINER=`mysql.infoschema`@`localhost` VIEW information_schema
16311631
user AS USER,
16321632
host AS HOST,
16331633
user_attributes->>"$.metadata" AS `ATTRIBUTE` FROM
1634-
mysql.user
1634+
mysql.user WHERE
1635+
CAN_ACCESS_USER(mysql.user.user,mysql.user.host)
16351636

16361637
INSERT INTO I_S_check_table(t) VALUES ('CREATE OR REPLACE DEFINER=`mysql.infoschema`@`localhost` VIEW information_schema.USER_ATTRIBUTES AS SELECT
16371638
user AS USER,
16381639
host AS HOST,
16391640
user_attributes->>"$.metadata" AS `ATTRIBUTE` FROM
1640-
mysql.user
1641+
mysql.user WHERE
1642+
CAN_ACCESS_USER(mysql.user.user,mysql.user.host)
16411643
');
16421644
SET debug = '-d,fetch_system_view_definition';
16431645
########################################################################

mysql-test/suite/information_schema/t/i_s_schema_definition_debug.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,10 @@ INSERT INTO I_S_published_schema
286286
'b1359fbef2feecf8dfc688dc0716cea8686905aeee15a0115bcbfdbc0dc8f456');
287287
INSERT INTO I_S_published_schema
288288
VALUES ('80022', '80022', 0,
289-
'62fc3151c2e890ecc3d7b4b93b4660f2a5723235fa2d78b24d3c4c8c0a38ac51');
289+
'53555f72c6938d7a343d6b344900d52acf4ebe228bb0d70b30eb1ad1ab693a8d');
290290
INSERT INTO I_S_published_schema
291291
VALUES ('80022', '80022', 1,
292-
'f50b558841a43fa9cc085a72f8df2bb62b8af4c6904b7f0d44fc133e69b23368');
292+
'6702af4a1d352b7c8a27280358b7fa95667c5b3e27d1a7f1e4ffbe791d00dffe');
293293

294294
LET $checksum_version = `SELECT IF(ISNULL(mysqld_version), "0", i_s_version)
295295
FROM I_S_published_schema i RIGHT OUTER JOIN whole_schema w

mysql-test/suite/rpl/t/rpl_alter_user.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,15 +233,15 @@ DROP USER u1@localhost;
233233
CREATE USER u1@localhost IDENTIFIED BY 'foo' ATTRIBUTE '{"trackingId": "12345"}';
234234
CREATE USER u2@localhost COMMENT 'This is account is used by my private LAMP project';
235235
--source include/sync_slave_sql_with_master.inc
236-
--connect(slave_con1, localhost, u1, foo, test, $SLAVE_MYPORT,,SSL)
236+
--connect(slave_con1, localhost, root,, test, $SLAVE_MYPORT,,SSL)
237237
--echo [connection slave]
238238
SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE USER = 'u1' OR USER = 'u2';
239239
--connection master
240240
--echo [connection master]
241241
--disconnect slave_con1
242242
ALTER USER u1@localhost COMMENT 'TODO: Delete this user';
243243
--source include/sync_slave_sql_with_master.inc
244-
--connect(slave_con1, localhost, u1, foo, test, $SLAVE_MYPORT,,SSL)
244+
--connect(slave_con1, localhost, root,, test, $SLAVE_MYPORT,,SSL)
245245
--echo [connection slave]
246246
SELECT * FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE USER = 'u1';
247247
--let $comment = `SELECT JSON_EXTRACT(ATTRIBUTE, "$.comment") FROM INFORMATION_SCHEMA.USER_ATTRIBUTES WHERE USER= 'u1'`

mysql-test/t/information_schema_cs.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2080,6 +2080,9 @@ SELECT CAN_ACCESS_TABLE("test", "t1") AS f1, COLUMN_NAME AS F2 FROM INFORMATION
20802080
--error ER_NO_ACCESS_TO_NATIVE_FCT
20812081
PREPARE stmt FROM 'SELECT CAN_ACCESS_TABLE("test", "t1") AS f1, COLUMN_NAME AS F2 FROM INFORMATION_SCHEMA.COLUMNS';
20822082

2083+
--error ER_NO_ACCESS_TO_NATIVE_FCT
2084+
SELECT CAN_ACCESS_USER(NULL, NULL);
2085+
20832086
--echo # Case 2: Invoking I_S native methods should be allowed through I_S queries.
20842087
--replace_column 6 # 7 # 8 # 9 # 10 # 11 # 12 # 13 # 14 # 15 # 16 # 17 # 18 # 19 # 20 # 21 #
20852088
SELECT * FROM INFORMATION_SCHEMA.TABLES where table_name='t1';

sql/auth/auth_common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ bool mysql_alter_user(THD *thd, List<LEX_USER> &list, bool if_exists);
737737
bool mysql_drop_user(THD *thd, List<LEX_USER> &list, bool if_exists,
738738
bool drop_role);
739739
bool mysql_rename_user(THD *thd, List<LEX_USER> &list);
740+
bool acl_can_access_user(THD *thd, LEX_USER *user);
740741

741742
/* sql_auth_cache */
742743
void init_acl_memory();

sql/auth/sql_security_ctx.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,8 @@ std::pair<bool, bool> Security_context::has_global_grant(
713713
auth_id.
714714
true - consider as privilege exists
715715
false - consider as privilege do not exist
716+
@param [in] throw_error Flag to decide if error needs to be thrown
717+
or not.
716718
717719
@retval true auth_id has the privilege but the current_auth does not, also
718720
throws error.
@@ -721,7 +723,8 @@ std::pair<bool, bool> Security_context::has_global_grant(
721723
bool Security_context::can_operate_with(const Auth_id &auth_id,
722724
const std::string &privilege,
723725
bool cumulative /*= false */,
724-
bool ignore_if_nonextant /*= true */) {
726+
bool ignore_if_nonextant /*= true */,
727+
bool throw_error /* = true */) {
725728
DBUG_TRACE;
726729
Acl_cache_lock_guard acl_cache_lock(current_thd,
727730
Acl_cache_lock_mode::READ_MODE);
@@ -736,7 +739,8 @@ bool Security_context::can_operate_with(const Auth_id &auth_id,
736739
if (ignore_if_nonextant)
737740
return false;
738741
else {
739-
my_error(ER_USER_DOES_NOT_EXIST, MYF(0), auth_id.auth_str().c_str());
742+
if (throw_error)
743+
my_error(ER_USER_DOES_NOT_EXIST, MYF(0), auth_id.auth_str().c_str());
740744
return true;
741745
}
742746
}
@@ -748,7 +752,8 @@ bool Security_context::can_operate_with(const Auth_id &auth_id,
748752
: true;
749753
}
750754
if (is_mismatch) {
751-
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), privilege.c_str());
755+
if (throw_error)
756+
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), privilege.c_str());
752757
}
753758
return is_mismatch;
754759
}

sql/auth/sql_security_ctx.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ class Security_context {
8282
bool cumulative = false);
8383
bool can_operate_with(const Auth_id &auth_id, const std::string &privilege,
8484
bool cumulative = false,
85-
bool ignore_if_nonextant = true);
85+
bool ignore_if_nonextant = true,
86+
bool throw_error = true);
8687
int activate_role(LEX_CSTRING user, LEX_CSTRING host,
8788
bool validate_access = false);
8889
void clear_active_roles(void);

sql/auth/sql_user.cc

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,57 @@ bool check_change_password(THD *thd, const char *host, const char *user,
180180
return false;
181181
}
182182

183+
/**
184+
Auxilary function for the CAN_ACCESS_USER internal function
185+
used to check if a row from mysql.user can be accessed or not
186+
by the current user
187+
188+
@arg thd the current thread
189+
@arg user_arg the user account to check
190+
@retval true the current user can access the user
191+
@retval false the current user can't access the user
192+
193+
@sa @ref Item_func_can_access_user, @ref dd::system_views::User_attributes
194+
*/
195+
bool acl_can_access_user(THD *thd, LEX_USER *user_arg) {
196+
/* if ACL is not initalized show everything */
197+
if (!initialized) return true;
198+
199+
/* show everything if slave thread */
200+
if (thd->slave_thread) return true;
201+
202+
LEX_USER *user = get_current_user(thd, user_arg);
203+
204+
/* hide the rows whose user can't be inited */
205+
if (!user) return false;
206+
207+
Security_context *sctx = thd->security_context();
208+
209+
/* show if it's the same user */
210+
if (!strcmp(sctx->priv_user().str, user->user.str) &&
211+
!my_strcasecmp(system_charset_info, user->host.str,
212+
sctx->priv_host().str))
213+
return true;
214+
215+
/* show if the current user has UPDATE on mysql.* */
216+
if (!check_access(thd, UPDATE_ACL, consts::mysql.c_str(), nullptr, nullptr,
217+
true, true))
218+
return true;
219+
220+
/* show if the current user has SELECT on mysql.* */
221+
if (!check_access(thd, SELECT_ACL, consts::mysql.c_str(), nullptr, nullptr,
222+
true, true))
223+
return true;
224+
225+
/* disable if the current user doesn't have SYSTEM_USER and the user account
226+
* checked does */
227+
if (sctx->can_operate_with(user, consts::system_user, false, true, false))
228+
return false;
229+
230+
/* show if the current user has CREATE ACL, otherwise hide */
231+
return sctx->check_access(CREATE_USER_ACL, consts::mysql);
232+
}
233+
183234
/**
184235
Auxiliary function for constructing CREATE USER sql for a given user.
185236

sql/dd/impl/system_views/user_attributes.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ User_attributes::User_attributes() {
3838
m_target_def.add_field(FIELD_METADATA, "`ATTRIBUTE`",
3939
"user_attributes->>\"$.metadata\"");
4040
m_target_def.add_from("mysql.user");
41+
m_target_def.add_where("CAN_ACCESS_USER(mysql.user.user,mysql.user.host)");
4142
}
4243

4344
} // namespace system_views

sql/dd/info_schema/metadata.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ namespace info_schema {
192192
Changes from version 80021:
193193
194194
- WL#13369: Added new I_S view 'SCHEMATA_EXTENSIONS'.
195+
- Bug #31427410: Added a WHERE clause to I_S.USER_ATTRIBUTES
195196
196197
197198
80023: Next IS version number after the previous is public.

sql/item_create.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,6 +1638,7 @@ static const std::pair<const char *, Create_func *> func_array[] = {
16381638
{"CAN_ACCESS_ROUTINE",
16391639
SQL_FN_LIST_INTERNAL(Item_func_can_access_routine, 5)},
16401640
{"CAN_ACCESS_EVENT", SQL_FN_INTERNAL(Item_func_can_access_event, 1)},
1641+
{"CAN_ACCESS_USER", SQL_FN_INTERNAL(Item_func_can_access_user, 2)},
16411642
{"ICU_VERSION", SQL_FN(Item_func_icu_version, 0)},
16421643
{"CAN_ACCESS_RESOURCE_GROUP",
16431644
SQL_FN_INTERNAL(Item_func_can_access_resource_group, 1)},

sql/item_func.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8417,6 +8417,49 @@ longlong Item_func_can_access_table::val_int() {
84178417
return 0;
84188418
}
84198419

8420+
/**
8421+
@brief
8422+
INFORMATION_SCHEMA picks metadata from new DD using system views.
8423+
In order for INFORMATION_SCHEMA to skip listing user accpounts for which
8424+
the user does not have rights, the following SQL function is used.
8425+
8426+
Syntax:
8427+
int CAN_ACCCESS_USER(user_part, host_part);
8428+
8429+
@returns,
8430+
1 - If current user has access.
8431+
0 - If not.
8432+
8433+
@sa @ref acl_can_access_user
8434+
*/
8435+
longlong Item_func_can_access_user::val_int() {
8436+
DBUG_TRACE;
8437+
8438+
THD *thd = current_thd;
8439+
// Read user, host
8440+
String user_name;
8441+
String *user_name_ptr = args[0]->val_str(&user_name);
8442+
String host_name;
8443+
String *host_name_ptr = args[1]->val_str(&host_name);
8444+
if (host_name_ptr == nullptr || user_name_ptr == nullptr) {
8445+
null_value = true;
8446+
return 0;
8447+
}
8448+
8449+
// Make sure we have safe string to access.
8450+
host_name_ptr->c_ptr_safe();
8451+
user_name_ptr->c_ptr_safe();
8452+
MYSQL_LEX_STRING user_str = {const_cast<char *>(user_name_ptr->ptr()),
8453+
user_name_ptr->length()};
8454+
MYSQL_LEX_STRING
8455+
host_str = {const_cast<char *>(host_name_ptr->ptr()),
8456+
host_name_ptr->length()};
8457+
LEX_USER user;
8458+
if (!LEX_USER::init(&user, thd, &user_str, &host_str)) return 0;
8459+
8460+
return acl_can_access_user(thd, &user) ? 1 : 0;
8461+
}
8462+
84208463
/**
84218464
@brief
84228465
INFORMATION_SCHEMA picks metadata from new DD using system views. In

sql/item_func.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2462,6 +2462,18 @@ class Item_func_can_access_table : public Item_int_func {
24622462
}
24632463
};
24642464

2465+
class Item_func_can_access_user : public Item_int_func {
2466+
public:
2467+
Item_func_can_access_user(const POS &pos, Item *a, Item *b)
2468+
: Item_int_func(pos, a, b) {}
2469+
longlong val_int() override;
2470+
const char *func_name() const override { return "can_access_user"; }
2471+
bool resolve_type(THD *) override {
2472+
maybe_null = true;
2473+
return false;
2474+
}
2475+
};
2476+
24652477
class Item_func_can_access_trigger : public Item_int_func {
24662478
public:
24672479
Item_func_can_access_trigger(const POS &pos, Item *a, Item *b)

sql/table.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7277,6 +7277,11 @@ LEX_USER *LEX_USER::alloc(THD *thd, LEX_STRING *user_arg,
72777277
LEX_STRING *host_arg) {
72787278
LEX_USER *ret = static_cast<LEX_USER *>(thd->alloc(sizeof(LEX_USER)));
72797279
if (ret == nullptr) return nullptr;
7280+
return LEX_USER::init(ret, thd, user_arg, host_arg);
7281+
}
7282+
7283+
LEX_USER *LEX_USER::init(LEX_USER *ret, THD *thd, LEX_STRING *user_arg,
7284+
LEX_STRING *host_arg) {
72807285
/*
72817286
Trim whitespace as the values will go to a CHAR field
72827287
when stored.

sql/table.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2456,6 +2456,12 @@ struct LEX_USER {
24562456
explicitly.
24572457
*/
24582458
static LEX_USER *alloc(THD *thd, LEX_STRING *user, LEX_STRING *host);
2459+
/*
2460+
Initialize the members of this struct. It is preferable to use this method
2461+
to initialize a LEX_USER rather initializing the members explicitly.
2462+
*/
2463+
static LEX_USER *init(LEX_USER *to_init, THD *thd, LEX_STRING *user,
2464+
LEX_STRING *host);
24592465
};
24602466

24612467
/**

unittest/gunit/dd_info_schema_native_func-t.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,5 +285,10 @@ TEST_F(ISNativeFuncTest, AllNullArguments) {
285285
CREATE_ITEM(Item_func_internal_is_enabled_role, TWO_NULL_ARGS);
286286
item->val_int();
287287
EXPECT_EQ(1, item->null_value);
288+
289+
// CAN_ACCESS_USER(NULL, NULL, NULL)
290+
CREATE_ITEM(Item_func_can_access_user, TWO_NULL_ARGS);
291+
item->val_int();
292+
EXPECT_EQ(1, item->null_value);
288293
}
289294
} // namespace dd_info_schema_native_func

0 commit comments

Comments
 (0)