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

Execution may be stuck in destination chain as operators estimate gas consumption #433

Open
code423n4 opened this issue Oct 25, 2022 · 13 comments
Labels
bug Something isn't working disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) grade-b QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax responded The Holograph team has reviewed and responded sponsor acknowledged Technically the issue is correct, but we're not going to resolve it for XYZ reasons

Comments

@code423n4
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2022-10-holograph/blob/f8c2eae866280a1acfdc8a8352401ed031be1373/contracts/HolographOperator.sol#L567

Vulnerability details

Description

The Operator CLI uses jobEstimator() static function to calculate the required gas cost to perform executeJob(). The problem is that it overestimates used gas. The gas() opcode is called at the end of the assembly block, but needs to be calculated right after the call and correct for the memory storage of "result" variable:

assembly {
  calldatacopy(0, bridgeInRequestPayload.offset, sub(bridgeInRequestPayload.length, 0x40))
  /**
   * @dev bridgeInRequest doNotRevert is purposefully set to false so a rever would happen
   */
  mstore8(0xE3, 0x00)
  let result := call(gas(), sload(_bridgeSlot), callvalue(), 0, sub(bridgeInRequestPayload.length, 0x40), 0, 0)
  /**
   * @dev if for some reason the call does not revert, it is force reverted
   */
  if eq(result, 1) {
    returndatacopy(0, 0, returndatasize())
    revert(0, returndatasize())
  }
  /**
   * @dev remaining gas is set as the return value
   */
  mstore(0x00, gas())
  return(0x00, 0x20)
}

The impact of this overestimation of gas is that honest operators may reject a job to be executed, as they calculate higher gas consumption which means it will not be executed successfully. Therefore, executions may be stuck on the destination chain indefinitely.

Impact

Executions may be stuck in destination chain.

Tools Used

Manual audit

Recommended Mitigation Steps

Gas needs to be calculated right after the call and correct for the memory storage of "result" variable:

@code423n4 code423n4 added 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 labels Oct 25, 2022
code423n4 added a commit that referenced this issue Oct 25, 2022
@gzeoneth
Copy link
Member

Minor difference, execution won't be stuck as operation only need to supply gas as specified regardless of the estimation result.

@gzeoneth gzeoneth added the disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) label Oct 31, 2022
@ACC01ADE
Copy link

ACC01ADE commented Nov 9, 2022

The estimation is accurate, caller of function knows gasIn, function returns gasLeft, caller then knows that gasLimit = gasIn - gasLeft. Whether or not an operator trusts the formula, follows it, or refuses to operate is entirely out of scope.

@ACC01ADE
Copy link

ACC01ADE commented Nov 9, 2022

To clarify, the estimation is accurate in the sense that it will have more gas limit available than is actually used. Meaning that there will be a bit of dust wei leftover still. An operator should never be in the negative if executing a job with this gas limit being set.

@ACC01ADE ACC01ADE added sponsor acknowledged Technically the issue is correct, but we're not going to resolve it for XYZ reasons responded The Holograph team has reviewed and responded labels Nov 9, 2022
@trust1995
Copy link

The issue highlights that the gas which sender pays for, which is just the bridgeIn request, is less than what the operator estimates it will cost, because of the surrounding overhead. Therefore, it may be the case that operator decides the gas cost is too expensive and refuse to execute it, although user was honest and specified gas correctly. Does that make sense?

@gzeoneth
Copy link
Member

Agree there would be an edge case that if the user submit the exact amount of gas will be rejected by operator. The risk seems to be low because at worst this should only cause some delay.

@gzeoneth gzeoneth added QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value labels Nov 19, 2022
@trust1995
Copy link

I think it would cause more than delay, because if operator would refuse to executeJob this operation, the NFT would not be available in either chain.
The current code doesn't support recovery of the NFT in this scenario

@gzeoneth
Copy link
Member

I think it would cause more than delay, because if operator would refuse to executeJob this operation, the NFT would not be available in either chain. The current code doesn't support recovery of the NFT in this scenario

I think executeJob is permissionless after certain delay?

@trust1995
Copy link

Sure, but why would any executor take up an execution that is calculated to be too expensive?

@gzeoneth
Copy link
Member

Sure, but why would any executor take up an execution that is calculated to be too expensive?

The user himself can execute the job, my point being the nft would not be lost. And I believe in most of the case the flow should prompt the user to include sufficient buffer to avoid this situation.

@trust1995
Copy link

Agree that user can always self execute, if it wasn't the case it would be HIGH risk as there is actually no way to rescue the NFT. However we are still talking about:

  1. temporary freeze of asset until user self rescues. That itself is M/H level impact in most bug bounties I've come across (Latest immunefi bug bounty here)
  2. user is forced to become an operator, stake funds, run CLI client etc. Definitely not what they signed up for. M is applicable when assets are not at direct risk, but some basic functionality of the protocol is impaired, like in our case.

@gzeoneth
Copy link
Member

The fact that this require the user to intentionally submit the exact gas limit manually (which can be reasonably assumed to be prevented by UI) and an attacker cannot force a user into such situation (delay attack is not possible) make this issue low risk.

@trust1995
Copy link

trust1995 commented Nov 19, 2022

Don't agree that this could only happen when user manually submits. We have no information ahead of time about how the UI works and whether it passes a spare gas limit over the actual gas amount. If it passes gas limit without buffers, the overestimation would lead gas limit to be too high and therefore not attractive for honest operators.
I've made my case. If judge's opinion is still Low I accept it.

@gzeoneth
Copy link
Member

Judging this as low risk and as a QA report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) grade-b QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax responded The Holograph team has reviewed and responded sponsor acknowledged Technically the issue is correct, but we're not going to resolve it for XYZ reasons
Projects
None yet
Development

No branches or pull requests

5 participants