From 723e2bc6d8513ea8c466f3857c11af34ea71502f Mon Sep 17 00:00:00 2001 From: Morgan Tocker Date: Fri, 16 Jul 2021 05:53:33 -0600 Subject: [PATCH] executor, privileges: fix infoschema.user_privileges privilege requirements (#26070) --- executor/infoschema_reader.go | 3 +- executor/infoschema_reader_test.go | 12 ++++-- privilege/privilege.go | 2 +- privilege/privileges/cache.go | 18 +++++--- privilege/privileges/privileges.go | 4 +- privilege/privileges/privileges_test.go | 56 +++++++++++++++++++++++++ 6 files changed, 83 insertions(+), 12 deletions(-) diff --git a/executor/infoschema_reader.go b/executor/infoschema_reader.go index c381f934af211..abbfcd2b17ef1 100644 --- a/executor/infoschema_reader.go +++ b/executor/infoschema_reader.go @@ -1269,7 +1269,8 @@ func (e *memtableRetriever) setDataForProcessList(ctx sessionctx.Context) { func (e *memtableRetriever) setDataFromUserPrivileges(ctx sessionctx.Context) { pm := privilege.GetPrivilegeManager(ctx) - e.rows = pm.UserPrivilegesTable() + // The results depend on the user querying the information. + e.rows = pm.UserPrivilegesTable(ctx.GetSessionVars().ActiveRoles, ctx.GetSessionVars().User.Username, ctx.GetSessionVars().User.Hostname) } func (e *memtableRetriever) setDataForMetricTables(ctx sessionctx.Context) { diff --git a/executor/infoschema_reader_test.go b/executor/infoschema_reader_test.go index 23a92313aa220..673f8d73ea91d 100644 --- a/executor/infoschema_reader_test.go +++ b/executor/infoschema_reader_test.go @@ -395,17 +395,23 @@ func (s *testInfoschemaTableSuite) TestUserPrivileges(c *C) { func (s *testInfoschemaTableSuite) TestUserPrivilegesTable(c *C) { tk := testkit.NewTestKit(c, s.store) + tk1 := testkit.NewTestKit(c, s.store) + // test the privilege of new user for information_schema.user_privileges tk.MustExec("create user usageuser") + c.Assert(tk.Se.Auth(&auth.UserIdentity{ + Username: "usageuser", + Hostname: "127.0.0.1", + }, nil, nil), IsTrue) tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee="'usageuser'@'%'"`).Check(testkit.Rows("'usageuser'@'%' def USAGE NO")) // the usage row disappears when there is a non-dynamic privilege added - tk.MustExec("GRANT SELECT ON *.* to usageuser") + tk1.MustExec("GRANT SELECT ON *.* to usageuser") tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee="'usageuser'@'%'"`).Check(testkit.Rows("'usageuser'@'%' def Select NO")) // test grant privilege - tk.MustExec("GRANT SELECT ON *.* to usageuser WITH GRANT OPTION") + tk1.MustExec("GRANT SELECT ON *.* to usageuser WITH GRANT OPTION") tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee="'usageuser'@'%'"`).Check(testkit.Rows("'usageuser'@'%' def Select YES")) // test DYNAMIC privs - tk.MustExec("GRANT BACKUP_ADMIN ON *.* to usageuser") + tk1.MustExec("GRANT BACKUP_ADMIN ON *.* to usageuser") tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee="'usageuser'@'%'" ORDER BY privilege_type`).Check(testkit.Rows("'usageuser'@'%' def BACKUP_ADMIN NO", "'usageuser'@'%' def Select YES")) } diff --git a/privilege/privilege.go b/privilege/privilege.go index 14bafce9b3860..c4051e91c6375 100644 --- a/privilege/privilege.go +++ b/privilege/privilege.go @@ -67,7 +67,7 @@ type Manager interface { DBIsVisible(activeRole []*auth.RoleIdentity, db string) bool // UserPrivilegesTable provide data for INFORMATION_SCHEMA.USER_PRIVILEGES table. - UserPrivilegesTable() [][]types.Datum + UserPrivilegesTable(activeRoles []*auth.RoleIdentity, user, host string) [][]types.Datum // ActiveRoles active roles for current session. // The first illegal role will be returned. diff --git a/privilege/privileges/cache.go b/privilege/privileges/cache.go index 33501c1f9b143..95ec707ad6226 100644 --- a/privilege/privileges/cache.go +++ b/privilege/privileges/cache.go @@ -1430,15 +1430,23 @@ func privToString(priv mysql.PrivilegeType, allPrivs []mysql.PrivilegeType, allP return strings.Join(pstrs, ",") } -// UserPrivilegesTable provide data for INFORMATION_SCHEMA.USERS_PRIVILEGE table. -func (p *MySQLPrivilege) UserPrivilegesTable() [][]types.Datum { +// UserPrivilegesTable provide data for INFORMATION_SCHEMA.USERS_PRIVILEGES table. +func (p *MySQLPrivilege) UserPrivilegesTable(activeRoles []*auth.RoleIdentity, user, host string) [][]types.Datum { + // Seeing all users requires SELECT ON * FROM mysql.* + // The SUPER privilege (or any other dynamic privilege) doesn't help here. + // This is verified against MySQL. + showOtherUsers := p.RequestVerification(activeRoles, user, host, mysql.SystemDB, "", "", mysql.SelectPriv) var rows [][]types.Datum - for _, user := range p.User { - rows = appendUserPrivilegesTableRow(rows, user) + for _, u := range p.User { + if showOtherUsers || u.match(user, host) { + rows = appendUserPrivilegesTableRow(rows, u) + } } for _, dynamicPrivs := range p.Dynamic { for _, dynamicPriv := range dynamicPrivs { - rows = appendDynamicPrivRecord(rows, dynamicPriv) + if showOtherUsers || dynamicPriv.match(user, host) { + rows = appendDynamicPrivRecord(rows, dynamicPriv) + } } } return rows diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 9f46334211544..1e8aaf882fcba 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -522,9 +522,9 @@ func (p *UserPrivileges) DBIsVisible(activeRoles []*auth.RoleIdentity, db string } // UserPrivilegesTable implements the Manager interface. -func (p *UserPrivileges) UserPrivilegesTable() [][]types.Datum { +func (p *UserPrivileges) UserPrivilegesTable(activeRoles []*auth.RoleIdentity, user, host string) [][]types.Datum { mysqlPriv := p.Handle.Get() - return mysqlPriv.UserPrivilegesTable() + return mysqlPriv.UserPrivilegesTable(activeRoles, user, host) } // ShowGrants implements privilege.Manager ShowGrants interface. diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index f4fa0cc8c8349..ef4ffd2233bd7 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -1805,3 +1805,59 @@ func (s *testPrivilegeSuite) TestDynamicPrivsRegistration(c *C) { tk.MustExec(sqlGrant) } } + +func (s *testPrivilegeSuite) TestInfoschemaUserPrivileges(c *C) { + // Being able to read all privileges from information_schema.user_privileges requires a very specific set of permissions. + // SUPER user is not sufficient. It was observed in MySQL to require SELECT on mysql.* + tk := testkit.NewTestKit(c, s.store) + tk.MustExec("CREATE USER isnobody, isroot, isselectonmysqluser, isselectonmysql") + tk.MustExec("GRANT SUPER ON *.* TO isroot") + tk.MustExec("GRANT SELECT ON mysql.user TO isselectonmysqluser") + tk.MustExec("GRANT SELECT ON mysql.* TO isselectonmysql") + + // First as Nobody + tk.Se.Auth(&auth.UserIdentity{ + Username: "isnobody", + Hostname: "localhost", + }, nil, nil) + + // I can see myself, but I can not see other users + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isnobody'@'%'"`).Check(testkit.Rows("'isnobody'@'%' def USAGE NO")) + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isroot'@'%'"`).Check(testkit.Rows()) + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isselectonmysqluser'@'%'"`).Check(testkit.Rows()) + + // Basically the same result as as isselectonmysqluser + tk.Se.Auth(&auth.UserIdentity{ + Username: "isselectonmysqluser", + Hostname: "localhost", + }, nil, nil) + + // Now as isselectonmysqluser + // Tests discovered issue that SELECT on mysql.user is not sufficient. It must be on mysql.* + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isnobody'@'%'"`).Check(testkit.Rows()) + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isroot'@'%'"`).Check(testkit.Rows()) + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isselectonmysqluser'@'%'"`).Check(testkit.Rows("'isselectonmysqluser'@'%' def USAGE NO")) + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isselectonmysql'@'%'"`).Check(testkit.Rows()) + + // Now as root + tk.Se.Auth(&auth.UserIdentity{ + Username: "isroot", + Hostname: "localhost", + }, nil, nil) + + // I can see myself, but I can not see other users + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isnobody'@'%'"`).Check(testkit.Rows()) + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isroot'@'%'"`).Check(testkit.Rows("'isroot'@'%' def Super NO")) + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isselectonmysqluser'@'%'"`).Check(testkit.Rows()) + + // Now as isselectonmysqluser + tk.Se.Auth(&auth.UserIdentity{ + Username: "isselectonmysql", + Hostname: "localhost", + }, nil, nil) + + // Now as isselectonmysqluser + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isnobody'@'%'"`).Check(testkit.Rows("'isnobody'@'%' def USAGE NO")) + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isroot'@'%'"`).Check(testkit.Rows("'isroot'@'%' def Super NO")) + tk.MustQuery(`SELECT * FROM information_schema.user_privileges WHERE grantee = "'isselectonmysqluser'@'%'"`).Check(testkit.Rows("'isselectonmysqluser'@'%' def USAGE NO")) +}