# Tombstoned observer can maliciously add a duplicate observer address resulting in forfeiting voting rewards of targeted observers

Tombstoned observer can set NewObserverAddress to an existing observer, creating duplicates in ObserverList that offset the victim's voting rewards with missed-vote penalties.

- Payout: Contest reward
- Severity: high
- Program: ZetaChain
- Credited: berndartmueller
- Disclosed: 2024-09-06
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-11-zetachain-findings/issues/411
- Library page: https://bugbountylibrary.com/r/c4%3A2023-11-zetachain%3AH-08
- Tags: domain:web3 impact:funds-loss impact:integrity kind:contest platform:code4rena sev:high tech:go technique:source-review vuln:access-control vuln:business-logic vuln:reward-distribution

**Target:** Cosmos SDK observer module (MsgUpdateObserver / UpdateObserverAddress)
**Root cause:** UpdateObserverAddress replaces old address with new without checking whether newObserverAddress already exists in ObserverList, allowing duplicates; removal only deletes the first occurrence.
**Steps:**
1. Tombstone a validator (or be a tombstoned observer) whose acc address is in ObserverList.
2. Send `MsgUpdateObserver{Creator: self, OldObserverAddress: self, NewObserverAddress: victimAddr, UpdateReason: Tombstoned}`.
3. UpdateObserverList replaces self with victimAddr, producing duplicate victimAddr entries in ObserverList.
4. New ballots copy duplicates into VoterList; victim's legitimate vote reward is offset by missed-vote penalty for the duplicate entry.
5. Removing the observer later only deletes the first occurrence, leaving the duplicate able to post block headers.
**How found:** Code review of UpdateObserverAddress/UpdateObserverList noticed no duplicate check, then traced how ObserverList feeds VoterList in ballots and how removal deletes only the first match in hooks.go.
**Exploitation:** A tombstoned observer submits MsgUpdateObserver pointing NewObserverAddress at a targeted observer already in the list, duplicating that address across all chain ObserverMappers; subsequent ballots penalize the victim for the phantom duplicate vote, forfeiting their rewards.
**Escalation:** Duplicates also survive observer removal, letting the address keep posting light-client block headers after being removed.
**Insight:** When an update function does a blind in-place replace over a list, check whether the replacement value can already exist in that list; duplicates then propagate into downstream per-entry accounting (ballots, rewards, penalties) and single-occurrence removal logic.
