A vulnerability has been identified in the FjordAuction.sol contract, allowing a user to place a bid after the auction has ended. This exploit enables a malicious bidder to claim 100% of the auctioned prize, effectively locking all other participants’ FJO tokens within the contract and preventing them from claiming their rightful tokens.
The vulnerability arises due to a timing overlap between the bidding and auction ending phases. The FjordAuction::bid function reverts if timestamp > auctionEndTime, while the FjordAuction::auctionEnd function reverts if timestamp < auctionEndTime. As a result, both functions can be called when timestamp == auctionEndTime.
This timing discrepancy allows an attacker to:
Call auctionEnd() to finalize the auction.
Immediately call bid(totalAmount) to place a bid equivalent to the total amount of all bids in the auction.
Call claimTokens() to withdraw 100% of the auctioned tokens.
Since the contract will have no auction tokens remaining, legitimate bidders will be unable to claim their tokens. Additionally, these users cannot retrieve their FJO tokens, as they are burned when FjordAuction::auctionEnd() is invoked.
This vulnerability can result in the total loss of auction tokens for all participants except the attacker. It also locks the participants’ FJO in the contract, making the FjordAuction.sol contract useless.
The impact can be verified with the proof of concept below, which can be added to the auctions test suite:
Manual code review.
There are different alternatives to fixing this issue, each with nuances that the development team can consider:
Remove the timing overlap: Adjust the logic either in the bid() or auctionEnd() function to ensure that both cannot be called when timestamp == auctionEndTime. This adjustment means that no bids can be placed on the block where the auction ends
Implement an ended safeguard: Introduce a check in the bid() function to ensure it cannot be called after the auction has ended, regardless of the timestamp. This approach allows users to place bids until the last block of the auction, but only transactions processed before the transaction that calls auctionEnd() will be accepted.
The protocol doesn't properly treat the `block.timestamp == auctionEndTime` case. Impact: High - There are at least two possible impacts here: 1. By chance, user bids could land in a block after the `auctionEnd()` is called, not including them in the multiplier calculation, leading to a situation where there are insufficient funds to pay everyone's claim; 2. By malice, where someone can use a script to call `auctionEnd()` + `bid(totalBids)` + `claimTokens()`, effectively depriving all good faith bidders from tokens. Likelihood: Low – The chances of getting a `block.timestamp == auctionEndTime` are pretty slim, but it’s definitely possible.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.