Secret Vault on Aptos

First Flight #46
Beginner FriendlyWallet
100 EXP
View results
Submission Details
Impact: low
Likelihood: medium
Invalid

[M-1]: Lack of Resource Management Causes Transaction Failures

[M-1]: Lack of Resource Management Causes Transaction Failures

Description

A user should be able to query the contract to see if a secret exists without causing an error. However, the get_secret function directly attempts to borrow_global without first checking if a Vault resource actually exists. If no vault has been created, this operation will fail and cause the entire transaction or view call to abort.

#[view]
public fun get_secret(caller: address):String acquires Vault{
assert!(caller == @owner, NOT_OWNER);
@>let vault = borrow_global<Vault>(@owner); // ABORTS if no resource exists at @owner.
vault.secret
}

Risk

Likelihood: Medium

  • It is a common and expected behavior for a user or front-end application to check for a value before one has been set. This will always fail.

Impact: Low

  • This flaw does not lead to data loss or theft, but it represents poor design and creates a brittle contract. It forces front-ends to build complex, off-chain logic to avoid calling a function that should be safe to call.

Proof of Concept

The issue is a direct result of the borrow_global function's defined behavior in Move.

  1. The contract is deployed. No one has called set_secret yet, so no Vault resource exists at the @owner address.

  2. A front-end or user calls the get_secret view function to check the current secret.

  3. The function executes borrow_global<Vault>(@owner).

  4. The Move VM requires that the resource must exist for borrow_global to succeed. Since it doesn't, the VM aborts the execution with an ERESOURCE_NOT_FOUND error.

Recommended Mitigation

The function should first check for the resource's existence and return an Option type, which is the standard way to handle optional values in Move. This makes the function robust and easier to integrate with.

- #[view]
- public fun get_secret(caller: address):String acquires Vault{
- assert!(caller == @owner, NOT_OWNER);
- let vault = borrow_global<Vault>(@owner);
- vault.secret
- }
+ #[view]
+ public fun get_secret(caller: address): option::Option<String> acquires Vault {
+ assert!(caller == @owner, NOT_OWNER);
+ // First, check if the vault exists.
+ if (exists<Vault>(@owner)) {
+ // If it exists, borrow it and return Some(secret).
+ option::some(borrow_global<Vault>(@owner).secret)
+ } else {
+ // If not, return None.
+ option::none<String>()
+ }
+ }
Updates

Lead Judging Commences

bube Lead Judge 17 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Lack of `Vault` existence check in `get_secret`

There is no security impact on the protocol, therefore this is an Informational finding. Also, it is a user mistake, if the user calls `get_secret` without first calling `set_secret`.

Support

FAQs

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