Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Valid

Missing Input Validation in `Snow::buySnow`

Root + Impact

Zero Amount Bypass and Unlimited Minting Attack Vector

Description

The `buySnow()` function contains a critical input validation vulnerability that allows users to bypass payment requirements and potentially execute denial-of-service attacks. The function fails to validate that the `amount` parameter is greater than zero, creating multiple attack vectors:
1. Zero Amount Bypass: Users can call `buySnow(0)` with zero ETH to trigger timer manipulation without payment
2. Free Timer Reset: Attackers can reset `s_earnTimer` without purchasing any tokens
3. Gas-Efficient DoS: Enables low-cost denial-of-service attacks against `earnSnow()` functionality
4. Unlimited Function Calls: No rate limiting or amount restrictions allow spam attacks
The vulnerability stems from:
- Missing `require(amount > 0)` validation
- No maximum limit on the `amount` parameter
- Logic flaw allowing zero-value operations to succeed
- Lack of input sanitization before critical operations
//@audit Missing Input Validation
// No maximum limit on amount parameter in buySnow()
// No validation that amount > 0
// Could lead to free minting or DOS attacks
function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount); // state change after external call
}
s_earnTimer = block.timestamp;
emit SnowBought(msg.sender, amount);
}

Risk

Likelihood:

Anyone can call `buySnow(0)` repeatedly since there's no restriction on `amount > 0`.
The function still:
- Updates `s_earnTimer`
- Emits an event
- Consumes gas
- While it doesn't mint tokens or drain funds, it contaminates the earn timer, clutters logs, and wastes computation, which could:
- Block or grief legitimate users using earnSnow()
- Be spammed by bots
- Very cheap to execute. Easy to automate. Likely to be exploited by a malicious actor or even a curious user.

Impact:

This vulnerability creates several severe attack vectors:
1. Zero-Cost Timer Manipulation:
- Attackers can call `buySnow(0)` with `msg.value = 0` to reset `s_earnTimer`
- Bypasses the need to pay fees for timer contamination attacks
2. Enhanced Denial-of-Service:
- Combines with Timer Contamination vulnerability for more efficient attacks
- Attackers can spam `buySnow(0)` calls to maintain permanent DoS
- Significantly reduces the economic barrier to sustained attacks

Proof of Concept

run
forge `test --match-test testFreeMintingWithZeroAmount -vvv`
and `forge test --match-test testWETHPaymentBypassesETHPath -vvv` with test in `TestSnow.t.sol`
function testFreeMintingWithZeroAmount() public {
vm.startPrank(jerry);
// Approve max amount to avoid allowance error
weth.approve(address(snow), type(uint256).max);
// Call buySnow with zero amount, expecting free mint
uint256 balanceBefore = snow.balanceOf(jerry);
snow.buySnow(0); // Should revert in secure contracts, but will succeed here
uint256 balanceAfter = snow.balanceOf(jerry);
assertEq(balanceAfter - balanceBefore, 0); // Free call went through (no cost, no mint)
vm.stopPrank();
}
function testWETHPaymentBypassesETHPath() public {
vm.startPrank(jerry);
// Mint excessive amount of WETH to jerry
weth.mint(jerry, FEE * 1_000);
// Approve WETH transfer
weth.approve(address(snow), type(uint256).max);
// Call buySnow() with no ETH (msg.value = 0), using WETH path
uint256 snowBefore = snow.balanceOf(jerry);
snow.buySnow(1); // No msg.value sent, only WETH path taken
uint256 snowAfter = snow.balanceOf(jerry);
assertEq(snowAfter - snowBefore, 1); // Minted successfully with WETH
vm.stopPrank();
}

Recommended Mitigation

While not directly stealing funds, this vulnerability significantly enhances the effectiveness of other attacks (particularly Timer Contamination) by reducing attack costs by over 90%. It transforms expensive attacks into nearly free operations, making protocol disruption accessible to any malicious actor with minimal resources
i so recommed Input Validation implementation
function buySnow(uint256 amount) external payable canFarmSnow {
+ if (amount == 0) {revert S__ZeroValue();}
// Rest of function logic...
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

buying of snow resets global timer thus affecting earning of free snow

When buySnow is successfully called, the global timer is reset. This inadvertently affects the earning of snow as that particular action also depends on the global timer.

Support

FAQs

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