Capability Fixes (#7918)

* add cap and name checks

* Update x/capability/keeper/keeper.go

* address reviews

* fix missing trim

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
Aditya 2020-11-13 11:13:59 +00:00 committed by GitHub
parent 15103b63a1
commit 4b529a41cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 14 deletions

View File

@ -65,6 +65,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
* `server/types.AppExporter` requires extra argument: `AppOptions`.
* `server.AddCommands` requires extra argument: `addStartFlags types.ModuleInitFlags`
* `x/crisis.NewAppModule` has a new attribute: `skipGenesisInvariants`. [PR](https://github.com/cosmos/cosmos-sdk/pull/7764)
* [#7918](https://github.com/cosmos/cosmos-sdk/pull/7918) Add x/capability safety checks:
* All outward facing APIs will now check that capability is not nil and name is not empty before performing any state-machine changes
* `SetIndex` has been renamed to `InitializeIndex`
### Features

View File

@ -9,7 +9,7 @@ import (
// InitGenesis initializes the capability module's state from a provided genesis
// state.
func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) {
if err := k.SetIndex(ctx, genState.Index); err != nil {
if err := k.InitializeIndex(ctx, genState.Index); err != nil {
panic(err)
}

View File

@ -2,6 +2,7 @@ package keeper
import (
"fmt"
"strings"
"github.com/tendermint/tendermint/libs/log"
@ -49,6 +50,8 @@ type (
}
)
// NewKeeper constructs a new CapabilityKeeper instance and initializes maps
// for capability map and scopedModules map.
func NewKeeper(cdc codec.BinaryMarshaler, storeKey, memKey sdk.StoreKey) *Keeper {
return &Keeper{
cdc: cdc,
@ -67,6 +70,9 @@ func (k *Keeper) ScopeToModule(moduleName string) ScopedKeeper {
if k.sealed {
panic("cannot scope to module via a sealed capability keeper")
}
if strings.TrimSpace(moduleName) == "" {
panic("cannot scope to an empty module name")
}
if _, ok := k.scopedModules[moduleName]; ok {
panic(fmt.Sprintf("cannot create multiple scoped keepers for the same module name: %s", moduleName))
@ -117,10 +123,10 @@ func (k *Keeper) InitializeAndSeal(ctx sdk.Context) {
k.sealed = true
}
// SetIndex sets the index to one (or greater) in InitChain according
// InitializeIndex sets the index to one (or greater) in InitChain according
// to the GenesisState. It must only be called once.
// It will panic if the provided index is 0, or if the index is already set.
func (k Keeper) SetIndex(ctx sdk.Context, index uint64) error {
func (k Keeper) InitializeIndex(ctx sdk.Context, index uint64) error {
if index == 0 {
panic("SetIndex requires index > 0")
}
@ -200,6 +206,9 @@ func (k Keeper) InitializeCapability(ctx sdk.Context, index uint64, owners types
// Note, namespacing is completely local, which is safe since records are prefixed
// with the module name and no two ScopedKeeper can have the same module name.
func (sk ScopedKeeper) NewCapability(ctx sdk.Context, name string) (*types.Capability, error) {
if strings.TrimSpace(name) == "" {
return nil, sdkerrors.Wrap(types.ErrInvalidCapabilityName, "capability name cannot be empty")
}
store := ctx.KVStore(sk.storeKey)
if _, ok := sk.GetCapability(ctx, name); ok {
@ -247,6 +256,9 @@ func (sk ScopedKeeper) NewCapability(ctx sdk.Context, name string) (*types.Capab
// Note, the capability's forward mapping is indexed by a string which should
// contain its unique memory reference.
func (sk ScopedKeeper) AuthenticateCapability(ctx sdk.Context, cap *types.Capability, name string) bool {
if strings.TrimSpace(name) == "" || cap == nil {
return false
}
return sk.GetCapabilityName(ctx, cap) == name
}
@ -256,6 +268,12 @@ func (sk ScopedKeeper) AuthenticateCapability(ctx sdk.Context, cap *types.Capabi
// index. If the owner already exists, it will return an error. Otherwise, it will
// also set a forward and reverse index for the capability and capability name.
func (sk ScopedKeeper) ClaimCapability(ctx sdk.Context, cap *types.Capability, name string) error {
if cap == nil {
return sdkerrors.Wrap(types.ErrNilCapability, "cannot claim nil capability")
}
if strings.TrimSpace(name) == "" {
return sdkerrors.Wrap(types.ErrInvalidCapabilityName, "capability name cannot be empty")
}
// update capability owner set
if err := sk.addOwner(ctx, cap, name); err != nil {
return err
@ -282,6 +300,9 @@ func (sk ScopedKeeper) ClaimCapability(ctx sdk.Context, cap *types.Capability, n
// previously claimed or created. After releasing the capability, if no more
// owners exist, the capability will be globally removed.
func (sk ScopedKeeper) ReleaseCapability(ctx sdk.Context, cap *types.Capability) error {
if cap == nil {
return sdkerrors.Wrap(types.ErrNilCapability, "cannot release nil capability")
}
name := sk.GetCapabilityName(ctx, cap)
if len(name) == 0 {
return sdkerrors.Wrap(types.ErrCapabilityNotOwned, sk.module)
@ -321,6 +342,9 @@ func (sk ScopedKeeper) ReleaseCapability(ctx sdk.Context, cap *types.Capability)
// by name. The module is not allowed to retrieve capabilities which it does not
// own.
func (sk ScopedKeeper) GetCapability(ctx sdk.Context, name string) (*types.Capability, bool) {
if strings.TrimSpace(name) == "" {
return nil, false
}
memStore := ctx.KVStore(sk.memKey)
key := types.RevCapabilityKey(sk.module, name)
@ -332,15 +356,14 @@ func (sk ScopedKeeper) GetCapability(ctx sdk.Context, name string) (*types.Capab
// to still have the capability in the go map since changes to
// go map do not automatically get reverted on tx failure,
// so we delete here to remove unnecessary values in map
delete(sk.capMap, index)
// TODO: Delete index correctly from capMap by storing some reverse lookup
// in-memory map. Issue: https://github.com/cosmos/cosmos-sdk/issues/7805
return nil, false
}
cap := sk.capMap[index]
if cap == nil {
// delete key from store to remove unnecessary mapping
memStore.Delete(key)
return nil, false
panic("capability found in memstore is missing from map")
}
return cap, true
@ -349,14 +372,20 @@ func (sk ScopedKeeper) GetCapability(ctx sdk.Context, name string) (*types.Capab
// GetCapabilityName allows a module to retrieve the name under which it stored a given
// capability given the capability
func (sk ScopedKeeper) GetCapabilityName(ctx sdk.Context, cap *types.Capability) string {
if cap == nil {
return ""
}
memStore := ctx.KVStore(sk.memKey)
return string(memStore.Get(types.FwdCapabilityKey(sk.module, cap)))
}
// Get all the Owners that own the capability associated with the name this ScopedKeeper uses
// GetOwners all the Owners that own the capability associated with the name this ScopedKeeper uses
// to refer to the capability
func (sk ScopedKeeper) GetOwners(ctx sdk.Context, name string) (*types.CapabilityOwners, bool) {
if strings.TrimSpace(name) == "" {
return nil, false
}
cap, ok := sk.GetCapability(ctx, name)
if !ok {
return nil, false
@ -382,6 +411,9 @@ func (sk ScopedKeeper) GetOwners(ctx sdk.Context, name string) (*types.Capabilit
// The method returns an error if either the capability or the owners cannot be
// retreived from the memstore.
func (sk ScopedKeeper) LookupModules(ctx sdk.Context, name string) ([]string, *types.Capability, error) {
if strings.TrimSpace(name) == "" {
return nil, nil, sdkerrors.Wrap(types.ErrInvalidCapabilityName, "cannot lookup modules with empty capability name")
}
cap, ok := sk.GetCapability(ctx, name)
if !ok {
return nil, nil, sdkerrors.Wrap(types.ErrCapabilityNotFound, name)

View File

@ -38,6 +38,9 @@ func (suite *KeeperTestSuite) SetupTest() {
func (suite *KeeperTestSuite) TestInitializeAndSeal() {
sk := suite.keeper.ScopeToModule(banktypes.ModuleName)
suite.Require().Panics(func() {
suite.keeper.ScopeToModule(" ")
})
caps := make([]*types.Capability, 5)
// Get Latest Index before creating new ones to sychronize indices correctly
@ -105,6 +108,10 @@ func (suite *KeeperTestSuite) TestNewCapability() {
suite.Require().True(ok)
suite.Require().Equal(cap, got)
suite.Require().True(cap == got, "expected memory addresses to be equal")
cap, err = sk.NewCapability(suite.ctx, " ")
suite.Require().Error(err)
suite.Require().Nil(cap)
}
func (suite *KeeperTestSuite) TestOriginalCapabilityKeeper() {
@ -151,11 +158,15 @@ func (suite *KeeperTestSuite) TestAuthenticateCapability() {
badCap := types.NewCapability(100)
suite.Require().False(sk1.AuthenticateCapability(suite.ctx, badCap, "transfer"))
suite.Require().False(sk2.AuthenticateCapability(suite.ctx, badCap, "bond"))
suite.Require().False(sk1.AuthenticateCapability(suite.ctx, cap1, " "))
suite.Require().False(sk1.AuthenticateCapability(suite.ctx, nil, "transfer"))
}
func (suite *KeeperTestSuite) TestClaimCapability() {
sk1 := suite.keeper.ScopeToModule(banktypes.ModuleName)
sk2 := suite.keeper.ScopeToModule(stakingtypes.ModuleName)
sk3 := suite.keeper.ScopeToModule("foo")
cap, err := sk1.NewCapability(suite.ctx, "transfer")
suite.Require().NoError(err)
@ -171,6 +182,9 @@ func (suite *KeeperTestSuite) TestClaimCapability() {
got, ok = sk2.GetCapability(suite.ctx, "transfer")
suite.Require().True(ok)
suite.Require().Equal(cap, got)
suite.Require().Error(sk3.ClaimCapability(suite.ctx, cap, " "))
suite.Require().Error(sk3.ClaimCapability(suite.ctx, nil, "transfer"))
}
func (suite *KeeperTestSuite) TestGetOwners() {
@ -237,6 +251,8 @@ func (suite *KeeperTestSuite) TestGetOwners() {
}
}
_, ok := sk1.GetOwners(suite.ctx, " ")
suite.Require().False(ok, "got owners from empty capability name")
}
func (suite *KeeperTestSuite) TestReleaseCapability() {
@ -264,6 +280,8 @@ func (suite *KeeperTestSuite) TestReleaseCapability() {
got, ok = sk1.GetCapability(suite.ctx, "transfer")
suite.Require().False(ok)
suite.Require().Nil(got)
suite.Require().Error(sk1.ReleaseCapability(suite.ctx, nil))
}
func (suite KeeperTestSuite) TestRevertCapability() {

View File

@ -8,9 +8,11 @@ import (
// x/capability module sentinel errors
var (
ErrCapabilityTaken = sdkerrors.Register(ModuleName, 2, "capability name already taken")
ErrOwnerClaimed = sdkerrors.Register(ModuleName, 3, "given owner already claimed capability")
ErrCapabilityNotOwned = sdkerrors.Register(ModuleName, 4, "capability not owned by module")
ErrCapabilityNotFound = sdkerrors.Register(ModuleName, 5, "capability not found")
ErrCapabilityOwnersNotFound = sdkerrors.Register(ModuleName, 6, "owners not found for capability")
ErrInvalidCapabilityName = sdkerrors.Register(ModuleName, 2, "capability name not valid")
ErrNilCapability = sdkerrors.Register(ModuleName, 3, "provided capability is nil")
ErrCapabilityTaken = sdkerrors.Register(ModuleName, 4, "capability name already taken")
ErrOwnerClaimed = sdkerrors.Register(ModuleName, 5, "given owner already claimed capability")
ErrCapabilityNotOwned = sdkerrors.Register(ModuleName, 6, "capability not owned by module")
ErrCapabilityNotFound = sdkerrors.Register(ModuleName, 7, "capability not found")
ErrCapabilityOwnersNotFound = sdkerrors.Register(ModuleName, 8, "owners not found for capability")
)

View File

@ -108,7 +108,7 @@ func checkMisbehaviourHeader(
chainID, _ = clienttypes.SetVersionNumber(chainID, header.GetHeight().GetVersionNumber())
}
// - ValidatorSet must have 2/3 similarity with trusted FromValidatorSet
// - ValidatorSet must have TrustLevel similarity with trusted FromValidatorSet
// - ValidatorSets on both headers are valid given the last trusted ValidatorSet
if err := tmTrustedValset.VerifyCommitLightTrusting(
chainID, tmCommit, clientState.TrustLevel.ToTendermint(),