Beginner FriendlyGameFi
100 EXP
View results
Submission Details
Impact: high
Likelihood: medium
Invalid

Unprotected Resource Re-initialization

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

    Answer: Initialization functions (e.g., creating a user profile, vault, or token registry) should only be called once per account. After the resource exists, repeated calls should be rejected to avoid overwriting or duplicating sensitive state.


  • Explain the specific issue or problem in one or more sentences

    Answer: The initialize_vault function does not check whether a Vault already exists. A malicious or accidental re-call of initialize_vault will overwrite the victim’s existing vault, erasing their balance or data.

module vault_init::vault {
struct Vault has key {
balance: u64,
}
/// @> Problem: No check if a Vault already exists
public entry fun initialize_vault(caller: &signer) acquires Vault {
let addr = signer::address_of(caller);
// Always overwrites, even if one already exists
move_to(caller, Vault { balance: 0 });
}
}

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

    Answer: This occurs whenever a user (or attacker) calls initialize_vault again.


  • Reason 2

    Answer: No guard condition prevents repeated initialization, so the risk is inherent.

Impact:

  • Impact 1

    Answer: Victim loses existing vault state (funds, balance, or private data).


  • Impact 2

    Answer: Could be abused by an attacker who tricks victims into re-initializing their account, leading to denial of service or permanent loss.


Proof of Concept

Step 1: The user legitimately stores a vault with balance = 500.

Step 2: initialize_vault is called again, overwriting the vault without warning.

Step 3: The balance resets to 0, showing how the lack of existence checks causes destructive overwrites.

#[test_only]
module vault_init::poc_exploit {
use vault_init::vault;
use std::debug;
#[test(user = @0xAAA)]
fun test_reinitialize(user: &signer) acquires vault::Vault {
// Step 1: User creates their vault with funds
move_to(user, vault::Vault { balance: 500 });
// Step 2: User (or attacker tricking them) calls initialize_vault again
vault::initialize_vault(user);
// Step 3: Check the vault’s balance (should be 0, overwriting original funds)
let new_vault = borrow_global<vault::Vault>(@0xAAA);
debug::print(&new_vault.balance); // prints 0 — funds wiped out
}
}

Recommended Mitigation

The exists(addr) check ensures only first-time initialization succeeds.

Repeated calls will fail with an assertion error, protecting existing state.

This aligns with Move’s principle of resource safety — resources should be unique and not overwritten without explicit destruction.

Optional enhancement: add a destroy_vault function controlled by the user, allowing re-initialization only after explicit deletion.

- remove this code
- public entry fun initialize_vault(caller: &signer) acquires Vault {
- let addr = signer::address_of(caller);
- move_to(caller, Vault { balance: 0 });
- }
+ add this code
+ /// Secure initialization: only create if Vault does not already exist
+ public entry fun initialize_vault(caller: &signer) acquires Vault {
+ let addr = signer::address_of(caller);
+ assert!(!exists<Vault>(addr), 1); // Error code 1: Vault already exists
+ move_to(caller, Vault { balance: 0 });
+ }
Updates

Appeal created

bube Lead Judge 12 days ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.