infect3d - Borrower can grief/DoS Refinancer Bots by setting a ERC1155 callback that reverts when executed on-chain but pass when simulated off-chain #249
Borrower can grief/DoS Refinancer Bots by setting a ERC1155 callback that reverts when executed on-chain but pass when simulated off-chain
Summary
Off-chain simulation are never perfect and always differ from on-chain execution. This might be because of some global state chain variable always being the same when using simulation tools, simulated blockchain state not entirely accurate with actual state, or past tx modifying the chain state in an unpredictable way.
An attacker can leverage this to create an refinanciable loan that would always revert when called by the Refinancer bot, but pass when simulated.
This could lock the Refinancer bot into a DoS state for a period of time, trying to refinance an always reverting loan.
Root Cause
PredictDotLoan.sol:500: entire refinance(Refinancing[] calldata refinancings) call reverts if at least one _refinance execution reverts during the loop execution
PredictDotLoan.sol:1100: _transferExcessCollateralIfAny calls CTF.safeTransferFrom(address(this), receiver, positionId, excessCollateral, ""), where receiver can be controlled by the attacker, and reverts on onERC1155Received callback execution.
Internal pre-conditions
attacker has a "refinanciable" active loan
External pre-conditions
borrower contract implement onERC1155Received callback that will revert
Attack Path
attacker create a contract at address 0xbad that will act as a borrower
that contract will implement onERC1555Received that will revert only when executed on-chain, but not during simulations
attacker create a first loan offer with another address and accept it with 0xbad, making 0xbad the borrower of the loan
attacker create a second loan offer that would be chosen by the bot as the best loan to refinance the malicious first loan (the bot follows an algorithm and therefore will always have a predictable outcome)
the Refinancer bot detect the loan as refinanciable by the second one, and simulate the execution which will pass
the Refinancer bot include it in its Refinancing[] input array and execute refinance
Revert
Depending on how the bot simulate the call:
if it is by simulating each loan refinancing separately, onERC1155Received could simply call toggleAutoRefinancingEnabled, this will make the loop revert L501 once the _refinance execution is over
if it is by simulating the refinance loop, onERC1155Received could implement a logic that revert when executed on-chain, but pass when executed off-chain (based on chain state information that is not accessible to the simulated call)
Impact
Refinancer bot DoS/grief as long as the "attack loans" are deployed, as the bot will naively try to refinance that loan again as it is the best fitting one based on its selection algorithm
PoC
N/A
Mitigation
Use a try/catch statement in the loop, to not suffer from reverting refinancing.
As _refinance is an internal function this is not possible as try/catch only works for external calls, but sponsors could either make it external (and role protected), or create an external wrapper function
infect3d
Medium
Borrower can grief/DoS Refinancer Bots by setting a ERC1155 callback that reverts when executed on-chain but pass when simulated off-chain
Summary
Off-chain simulation are never perfect and always differ from on-chain execution. This might be because of some global state chain variable always being the same when using simulation tools, simulated blockchain state not entirely accurate with actual state, or past tx modifying the chain state in an unpredictable way.
An attacker can leverage this to create an refinanciable loan that would always revert when called by the Refinancer bot, but pass when simulated.
This could lock the Refinancer bot into a DoS state for a period of time, trying to refinance an always reverting loan.
Root Cause
PredictDotLoan.sol:500
: entirerefinance(Refinancing[] calldata refinancings)
call reverts if at least one_refinance
execution reverts during the loop executionPredictDotLoan.sol:1100
:_transferExcessCollateralIfAny
callsCTF.safeTransferFrom(address(this), receiver, positionId, excessCollateral, "")
, where receiver can be controlled by the attacker, and reverts ononERC1155Received
callback execution.Internal pre-conditions
External pre-conditions
onERC1155Received
callback that will revertAttack Path
0xbad
that will act as a borroweronERC1555Received
that will revert only when executed on-chain, but not during simulations0xbad
, making0xbad
the borrower of the loanRefinancing[]
input array and executerefinance
Depending on how the bot simulate the call:
onERC1155Received
could simply calltoggleAutoRefinancingEnabled
, this will make the loop revertL501
once the_refinance
execution is overonERC1155Received
could implement a logic that revert when executed on-chain, but pass when executed off-chain (based on chain state information that is not accessible to the simulated call)Impact
PoC
N/A
Mitigation
Use a
try/catch
statement in the loop, to not suffer from reverting refinancing. As_refinance
is an internal function this is not possible as try/catch only works for external calls, but sponsors could either make it external (and role protected), or create an external wrapper function