Root + Impact
set_secret
unconditionally does move_to
even if Vault already exists. A second call aborts with RESOURCE_ALREADY_EXISTS
, preventing update of the secret.
Description
The owner should be able to rotate or update their secret. A second set_secret to the same address aborts and there is no function that specifically updates the secret in case the Vault already exists.
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 {});
}
Risk
Owner cannot update the secret and there is no function that updates a Vault if it already exists.
Proof of Concept
Consider running the following test :
#[test(owner = @0xcc)]
#[expected_failure(major_status = 4004, location = Self)]
fun test_set_secret_again(owner: &signer) {
use aptos_framework::account;
account::create_account_for_test(signer::address_of(owner));
let bytes = b"i'm a secret";
set_secret(owner, bytes);
set_secret(owner, bytes);
}
Recommended Mitigation
- 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 {});
- }
+ public entry fun set_secret(caller: &signer, secret: vector<u8>) acquires Vault {
+ let addr = signer::address_of(caller);
+ if (!exists<Vault>(addr)) {
+ move_to(caller, Vault { secret: string::utf8(secret) });
+ } else {
+ let v = borrow_global_mut<Vault>(addr);
+ v.secret = string::utf8(secret);
+ };
+ event::emit(SetNewSecret {});
+ }