# Stale owner index lets attackers create "ghost" programs and exhaust resources

Solaxy's owner_index is never updated on account mutation/removal, letting attackers create ghost programs and grow the index unboundedly to exhaust memory/CPU.

- Payout: Contest reward
- Severity: high
- Program: Solaxy
- Credited: 0xnija
- Disclosed: 2025-09-01
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_solaxy.pdf#3.1.1
- Library page: https://bugbountylibrary.com/r/cantina%3Asolaxy%3A3.1.1
- Tags: domain:web3 impact:dos impact:integrity kind:contest platform:cantina sev:high tech:solana technique:source-review vuln:business-logic vuln:dos

**Target:** SVMStorage account/owner_index in Solana VM program cache
**Root cause:** owner_index is only populated when the key is absent, so set_account owner changes and remove_account deletions leave stale loader-id→pubkey entries that are never reconciled.
**Steps:**
1. Observe set_account only calls set_owner_index when get_account(pubkey) is None, so owner changes leave the old loader entry.
2. Observe remove_account deletes from accounts but not owner_index, leaving dangling keys.
3. Repeatedly create, close, or reassign accounts to accumulate thousands of orphaned pubkeys in owner_index.
4. Engine::fill_cache() iterates the whole vector on each rebuild, so memory and CPU scale with orphan count.
5. Legitimate programs that changed owner are silently skipped by get_program_accounts, breaking cache and transactions.
**How found:** Code review of the two data structures (accounts map and owner_index reverse index) and the invariant that mutations must keep them in sync; noticed the 'missing key' check instead of 'owner changed' and that remove_account never touches the index.
**Exploitation:** Attacker repeatedly creates, closes, or reassigns accounts to inject dead entries into owner_index; each cache rebuild iterates the growing vector, causing memory/CPU exhaustion and dropping legitimately re-owned programs from the cache.
**Insight:** When a contract keeps a reverse index alongside a primary map, audit every mutation path (overwrite, delete, owner change) for index sync, not just insertion; a 'key absent' guard is a tell that updates are missed.
