Skip to content

Commit

Permalink
Merge branch 'master' into fire
Browse files Browse the repository at this point in the history
  • Loading branch information
crazycs520 authored Jun 17, 2021
2 parents e7e61a4 + ed686d1 commit cff70d7
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 33 deletions.
2 changes: 1 addition & 1 deletion executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,7 @@ func filterTemporaryTableKeys(vars *variable.SessionVars, keys []kv.Key) []kv.Ke
return keys
}

newKeys := keys[:]
newKeys := keys[:0:len(keys)]
for _, key := range keys {
tblID := tablecodec.DecodeTableID(key)
if _, ok := txnCtx.GlobalTemporaryTables[tblID]; !ok {
Expand Down
14 changes: 14 additions & 0 deletions executor/executor_pkg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ import (
"github.com/pingcap/tidb/config"
"github.com/pingcap/tidb/executor/aggfuncs"
"github.com/pingcap/tidb/expression"
"github.com/pingcap/tidb/kv"
plannerutil "github.com/pingcap/tidb/planner/util"
txninfo "github.com/pingcap/tidb/session/txninfo"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/tablecodec"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util"
"github.com/pingcap/tidb/util/chunk"
"github.com/pingcap/tidb/util/memory"
"github.com/pingcap/tidb/util/mock"
"github.com/pingcap/tidb/util/ranger"
"github.com/pingcap/tidb/util/tableutil"
)

var _ = Suite(&testExecSuite{})
Expand Down Expand Up @@ -549,3 +552,14 @@ func getGrowing(m aggPartialResultMapper) bool {
value := *point
return value.oldbuckets != nil
}

func (s *pkgTestSuite) TestFilterTemporaryTableKeys(c *C) {
vars := variable.NewSessionVars()
const tableID int64 = 3
vars.TxnCtx = &variable.TransactionContext{
GlobalTemporaryTables: map[int64]tableutil.TempTable{tableID: nil},
}

res := filterTemporaryTableKeys(vars, []kv.Key{tablecodec.EncodeTablePrefix(tableID), tablecodec.EncodeTablePrefix(42)})
c.Assert(res, HasLen, 1)
}
1 change: 1 addition & 0 deletions executor/executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ func (s *testSuiteP1) TestShow(c *C) {
"Usage Server Admin No privileges - allow connect only",
"BACKUP_ADMIN Server Admin ",
"RESTORE_ADMIN Server Admin ",
"SYSTEM_USER Server Admin ",
"SYSTEM_VARIABLES_ADMIN Server Admin ",
"ROLE_ADMIN Server Admin ",
"CONNECTION_ADMIN Server Admin ",
Expand Down
84 changes: 64 additions & 20 deletions executor/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/metrics"
"github.com/pingcap/tidb/planner/core"
plannercore "github.com/pingcap/tidb/planner/core"
"github.com/pingcap/tidb/plugin"
"github.com/pingcap/tidb/privilege"
"github.com/pingcap/tidb/sessionctx"
Expand All @@ -44,6 +45,7 @@ import (
"github.com/pingcap/tidb/util/collate"
"github.com/pingcap/tidb/util/hack"
"github.com/pingcap/tidb/util/logutil"
"github.com/pingcap/tidb/util/sem"
"github.com/pingcap/tidb/util/sqlexec"
"github.com/pingcap/tidb/util/timeutil"
"github.com/pingcap/tipb/go-tipb"
Expand Down Expand Up @@ -851,16 +853,47 @@ func (e *SimpleExec) executeAlterUser(s *ast.AlterUserStmt) error {
}

failedUsers := make([]string, 0, len(s.Specs))
checker := privilege.GetPrivilegeManager(e.ctx)
if checker == nil {
return errors.New("could not load privilege checker")
}
activeRoles := e.ctx.GetSessionVars().ActiveRoles
hasCreateUserPriv := checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv)
hasSystemUserPriv := checker.RequestDynamicVerification(activeRoles, "SYSTEM_USER", false)
hasRestrictedUserPriv := checker.RequestDynamicVerification(activeRoles, "RESTRICTED_USER_ADMIN", false)
hasSystemSchemaPriv := checker.RequestVerification(activeRoles, mysql.SystemDB, mysql.UserTable, "", mysql.UpdatePriv)

for _, spec := range s.Specs {
user := e.ctx.GetSessionVars().User
if spec.User.CurrentUser || ((user != nil) && (user.Username == spec.User.Username) && (user.AuthHostname == spec.User.Hostname)) {
spec.User.Username = user.Username
spec.User.Hostname = user.AuthHostname
} else {
checker := privilege.GetPrivilegeManager(e.ctx)
activeRoles := e.ctx.GetSessionVars().ActiveRoles
if checker != nil && !checker.RequestVerification(activeRoles, "", "", "", mysql.SuperPriv) {
return ErrDBaccessDenied.GenWithStackByArgs(spec.User.Username, spec.User.Hostname, "mysql")

// The user executing the query (user) does not match the user specified (spec.User)
// The MySQL manual states:
// "In most cases, ALTER USER requires the global CREATE USER privilege, or the UPDATE privilege for the mysql system schema"
//
// This is true unless the user being modified has the SYSTEM_USER dynamic privilege.
// See: https://mysqlserverteam.com/the-system_user-dynamic-privilege/
//
// In the current implementation of DYNAMIC privileges, SUPER can be used as a substitute for any DYNAMIC privilege
// (unless SEM is enabled; in which case RESTRICTED_* privileges will not use SUPER as a substitute). This is intentional
// because visitInfo can not accept OR conditions for permissions and in many cases MySQL permits SUPER instead.

// Thus, any user with SUPER can effectively ALTER/DROP a SYSTEM_USER, and
// any user with only CREATE USER can not modify the properties of users with SUPER privilege.
// We extend this in TiDB with SEM, where SUPER users can not modify users with RESTRICTED_USER_ADMIN.
// For simplicity: RESTRICTED_USER_ADMIN also counts for SYSTEM_USER here.

if !(hasCreateUserPriv || hasSystemSchemaPriv) {
return plannercore.ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER")
}
if checker.RequestDynamicVerificationWithUser("SYSTEM_USER", false, spec.User) && !(hasSystemUserPriv || hasRestrictedUserPriv) {
return plannercore.ErrSpecificAccessDenied.GenWithStackByArgs("SYSTEM_USER or SUPER")
}
if sem.IsEnabled() && checker.RequestDynamicVerificationWithUser("RESTRICTED_USER_ADMIN", false, spec.User) && !hasRestrictedUserPriv {
return plannercore.ErrSpecificAccessDenied.GenWithStackByArgs("RESTRICTED_USER_ADMIN")
}
}

Expand Down Expand Up @@ -1104,25 +1137,24 @@ func renameUserHostInSystemTable(sqlExecutor sqlexec.SQLExecutor, tableName, use
func (e *SimpleExec) executeDropUser(s *ast.DropUserStmt) error {
// Check privileges.
// Check `CREATE USER` privilege.
if !config.GetGlobalConfig().Security.SkipGrantTable {
checker := privilege.GetPrivilegeManager(e.ctx)
if checker == nil {
return errors.New("miss privilege checker")
}
activeRoles := e.ctx.GetSessionVars().ActiveRoles
if !checker.RequestVerification(activeRoles, mysql.SystemDB, mysql.UserTable, "", mysql.DeletePriv) {
if s.IsDropRole {
if !checker.RequestVerification(activeRoles, "", "", "", mysql.DropRolePriv) &&
!checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) {
return core.ErrSpecificAccessDenied.GenWithStackByArgs("DROP ROLE or CREATE USER")
}
}
if !s.IsDropRole && !checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) {
return core.ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER")
checker := privilege.GetPrivilegeManager(e.ctx)
if checker == nil {
return errors.New("miss privilege checker")
}
activeRoles := e.ctx.GetSessionVars().ActiveRoles
if !checker.RequestVerification(activeRoles, mysql.SystemDB, mysql.UserTable, "", mysql.DeletePriv) {
if s.IsDropRole {
if !checker.RequestVerification(activeRoles, "", "", "", mysql.DropRolePriv) &&
!checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) {
return core.ErrSpecificAccessDenied.GenWithStackByArgs("DROP ROLE or CREATE USER")
}
}
if !s.IsDropRole && !checker.RequestVerification(activeRoles, "", "", "", mysql.CreateUserPriv) {
return core.ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER")
}
}

hasSystemUserPriv := checker.RequestDynamicVerification(activeRoles, "SYSTEM_USER", false)
hasRestrictedUserPriv := checker.RequestDynamicVerification(activeRoles, "RESTRICTED_USER_ADMIN", false)
failedUsers := make([]string, 0, len(s.UserList))
sysSession, err := e.getSysSession()
defer e.releaseSysSession(sysSession)
Expand Down Expand Up @@ -1150,6 +1182,18 @@ func (e *SimpleExec) executeDropUser(s *ast.DropUserStmt) error {
}
}

// Certain users require additional privileges in order to be modified.
// If this is the case, we need to rollback all changes and return a privilege error.
// Because in TiDB SUPER can be used as a substitute for any dynamic privilege, this effectively means that
// any user with SUPER requires a user with SUPER to be able to DROP the user.
// We also allow RESTRICTED_USER_ADMIN to count for simplicity.
if checker.RequestDynamicVerificationWithUser("SYSTEM_USER", false, user) && !(hasSystemUserPriv || hasRestrictedUserPriv) {
if _, err := sqlExecutor.ExecuteInternal(context.TODO(), "rollback"); err != nil {
return err
}
return plannercore.ErrSpecificAccessDenied.GenWithStackByArgs("SYSTEM_USER or SUPER")
}

// begin a transaction to delete a user.
sql.Reset()
sqlexec.MustFormatSQL(sql, `DELETE FROM %n.%n WHERE Host = %? and User = %?;`, mysql.SystemDB, mysql.UserTable, user.Hostname, user.Username)
Expand Down
2 changes: 1 addition & 1 deletion planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2380,7 +2380,7 @@ func (b *PlanBuilder) buildSimple(ctx context.Context, node ast.StmtNode) (Plan,
case *ast.AlterInstanceStmt:
err := ErrSpecificAccessDenied.GenWithStack("SUPER")
b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SuperPriv, "", "", "", err)
case *ast.AlterUserStmt, *ast.RenameUserStmt:
case *ast.RenameUserStmt:
err := ErrSpecificAccessDenied.GenWithStackByArgs("CREATE USER")
b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreateUserPriv, "", "", "", err)
case *ast.GrantStmt:
Expand Down
1 change: 1 addition & 0 deletions privilege/privileges/privileges.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var _ privilege.Manager = (*UserPrivileges)(nil)
var dynamicPrivs = []string{
"BACKUP_ADMIN",
"RESTORE_ADMIN",
"SYSTEM_USER",
"SYSTEM_VARIABLES_ADMIN",
"ROLE_ADMIN",
"CONNECTION_ADMIN",
Expand Down
99 changes: 88 additions & 11 deletions privilege/privileges/privileges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,22 +472,99 @@ func (s *testPrivilegeSuite) TestAlterUserStmt(c *C) {
se := newSession(c, s.store, s.dbName)

// high privileged user setting password for other user (passes)
mustExec(c, se, "CREATE USER 'superuser2'")
mustExec(c, se, "CREATE USER 'nobodyuser2'")
mustExec(c, se, "CREATE USER 'nobodyuser3'")
mustExec(c, se, "GRANT ALL ON *.* TO 'superuser2'")
mustExec(c, se, "GRANT CREATE USER ON *.* TO 'nobodyuser2'")

c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser2", Hostname: "localhost", AuthUsername: "superuser2", AuthHostname: "%"}, nil, nil), IsTrue)
mustExec(c, se, "CREATE USER superuser2, nobodyuser2, nobodyuser3, nobodyuser4, nobodyuser5, semuser1, semuser2, semuser3, semuser4")
mustExec(c, se, "GRANT ALL ON *.* TO superuser2")
mustExec(c, se, "GRANT CREATE USER ON *.* TO nobodyuser2")
mustExec(c, se, "GRANT SYSTEM_USER ON *.* TO nobodyuser4")
mustExec(c, se, "GRANT UPDATE ON mysql.user TO nobodyuser5, semuser1")
mustExec(c, se, "GRANT RESTRICTED_TABLES_ADMIN ON *.* TO semuser1")
mustExec(c, se, "GRANT RESTRICTED_USER_ADMIN ON *.* TO semuser1, semuser2, semuser3")
mustExec(c, se, "GRANT SYSTEM_USER ON *.* to semuser3") // user is both restricted + has SYSTEM_USER (or super)

c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser2", Hostname: "localhost"}, nil, nil), IsTrue)
mustExec(c, se, "ALTER USER 'nobodyuser2' IDENTIFIED BY 'newpassword'")
mustExec(c, se, "ALTER USER 'nobodyuser2' IDENTIFIED BY ''")

// low privileged user trying to set password for other user (fails)
c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser2", Hostname: "localhost", AuthUsername: "nobodyuser2", AuthHostname: "%"}, nil, nil), IsTrue)
// low privileged user trying to set password for others
// nobodyuser3 = SUCCESS (not a SYSTEM_USER)
// nobodyuser4 = FAIL (has SYSTEM_USER)
// superuser2 = FAIL (has SYSTEM_USER privilege implied by SUPER)

c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser2", Hostname: "localhost"}, nil, nil), IsTrue)
mustExec(c, se, "ALTER USER 'nobodyuser2' IDENTIFIED BY 'newpassword'")
mustExec(c, se, "ALTER USER 'nobodyuser2' IDENTIFIED BY ''")
_, err := se.ExecuteInternal(context.Background(), "ALTER USER 'superuser2' IDENTIFIED BY 'newpassword'")
c.Assert(err, NotNil)
mustExec(c, se, "ALTER USER 'nobodyuser3' IDENTIFIED BY ''")
_, err := se.ExecuteInternal(context.Background(), "ALTER USER 'nobodyuser4' IDENTIFIED BY 'newpassword'")
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SYSTEM_USER or SUPER privilege(s) for this operation")
_, err = se.ExecuteInternal(context.Background(), "ALTER USER 'superuser2' IDENTIFIED BY 'newpassword'")
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SYSTEM_USER or SUPER privilege(s) for this operation")

// Nobody3 has no privileges at all, but they can still alter their own password.
// Any other user fails.
c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser3", Hostname: "localhost"}, nil, nil), IsTrue)
mustExec(c, se, "ALTER USER 'nobodyuser3' IDENTIFIED BY ''")
_, err = se.ExecuteInternal(context.Background(), "ALTER USER 'nobodyuser4' IDENTIFIED BY 'newpassword'")
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation")
_, err = se.ExecuteInternal(context.Background(), "ALTER USER 'superuser2' IDENTIFIED BY 'newpassword'") // it checks create user before SYSTEM_USER
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation")

// Nobody5 doesn't explicitly have CREATE USER, but mysql also accepts UDPATE on mysql.user
// as a substitute so it can modify nobody2 and nobody3 but not nobody4

c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser5", Hostname: "localhost"}, nil, nil), IsTrue)
mustExec(c, se, "ALTER USER 'nobodyuser2' IDENTIFIED BY ''")
mustExec(c, se, "ALTER USER 'nobodyuser3' IDENTIFIED BY ''")
_, err = se.ExecuteInternal(context.Background(), "ALTER USER 'nobodyuser4' IDENTIFIED BY 'newpassword'")
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the SYSTEM_USER or SUPER privilege(s) for this operation")

c.Assert(se.Auth(&auth.UserIdentity{Username: "semuser1", Hostname: "localhost"}, nil, nil), IsTrue)
mustExec(c, se, "ALTER USER 'semuser1' IDENTIFIED BY ''")
mustExec(c, se, "ALTER USER 'semuser2' IDENTIFIED BY ''")
mustExec(c, se, "ALTER USER 'semuser3' IDENTIFIED BY ''")

sem.Enable()
defer sem.Disable()

// When SEM is enabled, even though we have UPDATE privilege on mysql.user, it explicitly
// denies writeable privileges to system schemas unless RESTRICTED_TABLES_ADMIN is granted.
// so the previous method of granting to the table instead of CREATE USER will fail now.
// This is intentional because SEM plugs directly into the privilege manager to DENY
// any request for UpdatePriv on mysql.user even if the privilege exists in the internal mysql.user table.

// UpdatePriv on mysql.user
c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser5", Hostname: "localhost"}, nil, nil), IsTrue)
_, err = se.ExecuteInternal(context.Background(), "ALTER USER 'nobodyuser2' IDENTIFIED BY 'newpassword'")
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation")

// actual CreateUserPriv
c.Assert(se.Auth(&auth.UserIdentity{Username: "nobodyuser2", Hostname: "localhost"}, nil, nil), IsTrue)
mustExec(c, se, "ALTER USER 'nobodyuser2' IDENTIFIED BY ''")
mustExec(c, se, "ALTER USER 'nobodyuser3' IDENTIFIED BY ''")

// UpdatePriv on mysql.user but also has RESTRICTED_TABLES_ADMIN
c.Assert(se.Auth(&auth.UserIdentity{Username: "semuser1", Hostname: "localhost"}, nil, nil), IsTrue)
mustExec(c, se, "ALTER USER 'nobodyuser2' IDENTIFIED BY ''")
mustExec(c, se, "ALTER USER 'nobodyuser3' IDENTIFIED BY ''")

// As it has (RESTRICTED_TABLES_ADMIN + UpdatePriv on mysql.user) + RESTRICTED_USER_ADMIN it can modify other restricted_user_admins like semuser2
// and it can modify semuser3 because RESTRICTED_USER_ADMIN does not also need SYSTEM_USER
mustExec(c, se, "ALTER USER 'semuser1' IDENTIFIED BY ''")
mustExec(c, se, "ALTER USER 'semuser2' IDENTIFIED BY ''")
mustExec(c, se, "ALTER USER 'semuser3' IDENTIFIED BY ''")

c.Assert(se.Auth(&auth.UserIdentity{Username: "superuser2", Hostname: "localhost"}, nil, nil), IsTrue)
_, err = se.ExecuteInternal(context.Background(), "ALTER USER 'semuser1' IDENTIFIED BY 'newpassword'")
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the RESTRICTED_USER_ADMIN privilege(s) for this operation")

c.Assert(se.Auth(&auth.UserIdentity{Username: "semuser4", Hostname: "localhost"}, nil, nil), IsTrue)
// has restricted_user_admin but not CREATE USER or (update on mysql.user + RESTRICTED_TABLES_ADMIN)
mustExec(c, se, "ALTER USER 'semuser4' IDENTIFIED BY ''") // can modify self
_, err = se.ExecuteInternal(context.Background(), "ALTER USER 'nobodyuser3' IDENTIFIED BY 'newpassword'")
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation")
_, err = se.ExecuteInternal(context.Background(), "ALTER USER 'semuser1' IDENTIFIED BY 'newpassword'")
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation")
_, err = se.ExecuteInternal(context.Background(), "ALTER USER 'semuser3' IDENTIFIED BY 'newpassword'")
c.Assert(err.Error(), Equals, "[planner:1227]Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation")
}

func (s *testPrivilegeSuite) TestSelectViewSecurity(c *C) {
Expand Down

0 comments on commit cff70d7

Please sign in to comment.