Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theft of ETH that was not used for successful execution of orders in non-atomic execution #275

Closed
code423n4 opened this issue Nov 13, 2022 · 3 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue duplicate-241 satisfactory satisfies C4 submission criteria; eligible for awards

Comments

@code423n4
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2022-11-looksrare/blob/f4c90ca149f4aeeac125605a56166297b717201a/contracts/LooksRareAggregator.sol#L51
https://github.com/code-423n4/2022-11-looksrare/blob/f4c90ca149f4aeeac125605a56166297b717201a/contracts/LooksRareAggregator.sol#L109
https://github.com/code-423n4/2022-11-looksrare/blob/f4c90ca149f4aeeac125605a56166297b717201a/contracts/lowLevelCallers/LowLevelETH.sol#L43
https://github.com/code-423n4/2022-11-looksrare/blob/f4c90ca149f4aeeac125605a56166297b717201a/contracts/lowLevelCallers/LowLevelETH.sol#L46

Vulnerability details

Description

There is an execute function in LooksRareAggregator contract. It refunds any ETH that was unused (for example that left due to the unsuccessful execution of an order) at the end of its execution flow:

_returnETHIfAny(originator);

_returnETHIfAny function is the following:

/**
 * @notice Return ETH back to the designated sender if any ETH is left in the payable call.
 */
function _returnETHIfAny(address recipient) internal {
    assembly {
        if gt(selfbalance(), 0) {
            let status := call(gas(), recipient, selfbalance(), 0, 0, 0, 0)
        }
    }
}

Let's remember the EIP-150 63/64 rule. The 63/64 rule says that call opcode can consume at most 63/64 gas of parent call, in other words, the child call can consume "all but one 64th" of the gas, and parent calls will have at least 1/64 of gas to continue the execution.

So, _returnETHIfAny can make a call with gas that is not enough to successfully end the execution of the transfer, but the parent call will still have some gas and can successfully end the execution. As result, the remaining ETH will not be refunded but the execution will succeed.

After that, the attacker can withdraw the remaining ETH by calling execute function with any valid input and receiving the remaining ETH in the same way that the fair user (actually he is the attacker's target) expected to receive it.

The same is applicable for the _returnETHIfAny() and _returnETHIfAnyWithOneWeiLeft() functions, but they are not used in places of the code where it can lead to some malicious actions.

Attack scenario

A fair user calls execute function with a nonzero ETH value and isAtomic parameter equals false.

Some of the orders fail and a situation described in Description section of this document happens ("according to EIP-150 rule ETH that should be refunded to the user will remain on the aggregator contract but the execution of the overall call will succeed"). That can happen if the attacker (for example using MEV logic) front-runs such a transaction and changes the state in an appropriate way (changes some storage slots/executes some orders by himself/makes some of the orders available/etc.). Also, it is possible if an originator is a smart contract and the call of a fallback function fails due to its internal logic.

In the next transaction, the attacker calls execute function with any valid input (for example buying a cheap, or even fake one, nft) and receives funds that correspond to the fair user, according to the logic of the _returnETHIfAny function.

Impact

Theft of ETH that was not used for successful execution of orders in non-atomic execution, with the possibility of forcing a fair user to this scenario.

Please note that eth_estimateGas from web3 json-rpc API, returns gas that isn't enough to finish the returning funds to the recipient. Because of that, many users will use this standard method will suffer from loss of funds.

Recommended Mitigation Steps

Add an additional check that the refund ETH call was succeeded:

/**
 * @notice Return ETH back to the designated sender if any ETH is left in the payable call.
 */
function _returnETHIfAny(address recipient) internal {
    assembly {
        if gt(selfbalance(), 0) {
            let status := call(gas(), recipient, selfbalance(), 0, 0, 0, 0)
        }
    }
    if (!status) revert ETHTransferFail();
}
@code423n4 code423n4 added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Nov 13, 2022
code423n4 added a commit that referenced this issue Nov 13, 2022
@c4-judge
Copy link
Contributor

Picodes marked the issue as duplicate of #241

@c4-judge c4-judge added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value downgraded by judge Judge downgraded the risk level of this issue and removed 3 (High Risk) Assets can be stolen/lost/compromised directly labels Dec 16, 2022
@c4-judge
Copy link
Contributor

Picodes changed the severity to 2 (Med Risk)

@c4-judge c4-judge added the satisfactory satisfies C4 submission criteria; eligible for awards label Dec 16, 2022
@c4-judge
Copy link
Contributor

Picodes marked the issue as satisfactory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue duplicate-241 satisfactory satisfies C4 submission criteria; eligible for awards
Projects
None yet
Development

No branches or pull requests

2 participants