BuyerAgent Batch Purchase Failure Due to Asset Transfer or Approval Revocation
The vulnerability allows a malicious seller to disrupt the purchase process of the BuyerAgent contract. This occurs when an asset that is supposed to be bought by the BuyerAgent is either transferred away or has its approval revoked, causing the entire batch purchase transaction to fail. As a result, the BuyerAgent becomes unable to complete the intended purchase of other assets, leading to a Denial of Service (DoS) scenario.
The vulnerability stems from the flow involving the BuyerAgent's interaction with the Swan contract for purchasing assets. The BuyerAgent first sends an oraclePurchaseRequest
to gather the list of assets to be purchased, which is determined by an external oracle. Once the purchase list is available, the user calls BuyerAgent::purchase
to buy the assets. During this phase, the BuyerAgent invokes Swan::purchase
for each asset in the purchase list.
https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/swan/BuyerAgent.sol?plain=1#L237-L252
Each asset listed for purchase creates an ERC721 token, where the seller is the owner of the respective tokenId. https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/swan/SwanAsset.sol?plain=1#L20-L43
The vulnerability is exposed when one of the ERC721 assets in the purchase list is transferred away from the seller to another user, or when the seller revokes the approval for Swan. Since the Swan contract requires the ownership of the asset to remain unchanged and the asset's approval to remain valid, the transfer or revocation leads to the failure of the Swan::purchase
call. https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/swan/Swan.sol?plain=1#L294
Consequently, this failure causes BuyerAgent::purchase
to revert, preventing the purchase of other assets listed in the transaction.
A malicious seller lists an asset on Swan for purchase.
The malicious seller either transfers the listed asset to another address or revokes Swan's approval before the BuyerAgent::purchase
call is made by directly interact with asset contract.
The BuyerAgent includes the listed asset for purchase based on the oracle's recommendation. The BuyerAgent, oracle, or Swan contract are unaware of any transfer of ownership or revocation of approval events that might occur after the listing.
The BuyerAgent attempts to purchase the asset using Swan::purchase
. However, due to the changed state (transfer or revoked approval), the call reverts.
This reversion causes the entire BuyerAgent::purchase
transaction to fail, leading to a denial of service for other assets that were meant to be purchased.
The impact of this vulnerability is significant as it allows a malicious seller to manipulate the behavior of the BuyerAgent contract, ultimately preventing it from purchasing other legitimate assets. This attack can be used to deceive buyers by causing failed transactions, even if those transactions involve assets from honest sellers. This vulnerability can be exploited repeatedly to create a denial of service, disrupting normal operations of the BuyerAgent and affecting market participants.
Manual Review
Hardhat for testing
Since BuyerAgent::purchase
needs to get the list of assets from the oracle output and it's a bit complex to simulate that directly, I added a wrapper function to BuyerAgent to call Swan::purchase
from BuyerAgent directly (and avoid signer issues from hardhat):
This allows direct testing of individual asset purchases. you should add this function to BuyerAgent
contract.
create new file in test directory:
A seller lists two assets on the Swan platform. The BuyerAgent can buy the second asset successfully, which proves the correct functionality of the purchase mechanism. However, the first asset, which is transferred or has approval revoked, will fail. Before the BuyerAgent::purchase
function is called, the seller transfers one of the assets or revokes the approval for Swan. The BuyerAgent::purchase
call attempts to execute but fails due to the changed status of the asset, resulting in a reversion of the entire transaction.
output:
The solution to this issue may depend on design choices. One possible recommendation is to modify the purchase logic to check the result of each individual Swan::purchase
call and ignore reverts for specific assets. This way, if one asset cannot be purchased due to a transfer or approval revocation, the other assets can still be successfully purchased. This approach would prevent the entire batch transaction from failing due to issues with a single asset.
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.