Root + Impact
Description
The set_secret()
function is restricted to the contract owner and is intended to allow the owner to initialize or update the stored secret. However, the function calls move_to(caller, secret_vault)
without checking if a Vault resource already exists at the owner’s address.
public entry fun set_secret(caller:&signer, secret:vector<u8>){
let secret_vault = Vault{secret: string::utf8(secret)};
@> move_to(caller, secret_vault);
event::emit(SetNewSecret {});
}
In Move semantics, move_to
aborts if the resource already exists. This means that once the owner sets the secret for the first time, any subsequent attempt to update it will always fail, effectively locking the secret permanently unless the resource is manually removed through other means.
Risk
Likelihood: High
Impact: High
Proof of Concept
The following PoC, which can be added to the secret_vault::vault module
, demonstrates that the contract owner becomes permanently unable to update the stored secret after the initial creation due to the lack of a resource existence check in set_secret()
.
#[test(owner = @0xcc, user = @0x123)]
#[expected_failure]
fun test_dos_set_secret(owner: &signer, user: &signer) {
use aptos_framework::account;
use std::string::utf8;
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(user));
let secret = b"i'm a secret";
set_secret(owner, secret);
debug::print(&utf8(b"[SUCCESS] First secret set successfully"));
debug::print(&utf8(b"[WARNING] Attempting to update secret - this will fail..."));
let new_secret = b"i'm a new secret";
set_secret(owner, new_secret);
}
Execution Output:
#[test(owner = @0xcc, user = @0x123)]
#[expected_failure]
fun test_dos_set_secret(owner: &signer, user: &signer) {
use aptos_framework::account;
use std::string::utf8;
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(user));
let secret = b"i'm a secret";
set_secret(owner, secret);
debug::print(&utf8(b"[SUCCESS] First secret set successfully"));
debug::print(&utf8(b"[WARNING] Attempting to update secret - this will fail..."));
let new_secret = b"i'm a new secret";
set_secret(owner, new_secret);
}
Recommended Mitigation
public entry fun set_secret(caller: &signer, secret: vector<u8>) {
+ if (exists<Vault>(signer::address_of(caller))) {
+ let vault = borrow_global_mut<Vault>(signer::address_of(caller));
+ vault.secret = string::utf8(secret);
+ event::emit(SetNewSecret {});
+ return;
+ }
let secret_vault = Vault{secret: string::utf8(secret)};
move_to(caller, secret_vault);
event::emit(SetNewSecret {});
}