Denial-of-Service Attack caused by nonReentrant modifier

From WEB3 Vulnerapedia
Jump to navigation Jump to search

Denial-of-Service Attack caused by nonReentrant modifier can occur in smart contracts, especially those deployed on Ethereum or similar blockchain platforms. The nonReentrant modifier is typically used to prevent reentrancy attacks, where a contract's function is recursively called before the previous call completes, potentially leading to unexpected behavior or manipulation of the contract's state.

Details

The following functions contain the nonReentrant modifier:

- HighStreetPoolBase.stake()

- HighStreetPoolBase.unstake()

- HighStreetPoolBase.updateStakeLock()

- HighStreetPoolBase.processRewards()

- HighStreetCorePool.transferHighToken()

- HighStreetCorePool.transferHighTokenFrom()

When receiveVaultRewards() function is called the pendingVaultRewards of the stakers will be always different from 0. In this case, when HighStreetPoolBase.unstake() is called, the flow below will be executed:

1. HighStreetPoolBase.unstake() (nonReentrant)
2. - -  HighStreetCorePool._unstake()
3. - - - - HighStreetPoolBase._unstake()
4. - - - - - - HighStreetPoolBase._sync()
5. - - - - - - HighStreetCorePool._processRewards()
6. - - - - - - - - HighStreetCorePool._processVaultRewards()
7. - - - - - - - - - - HighStreetCorePool.transferHighToken()(nonReentrant again)

In this case, HighStreetCorePool.transferHighToken() will be called by HighStreetPoolBase.unstake(), which is not possible as both functions got the nonReentrant modifier. This only happens when pendingVaultRewards > 0 as per the code below:

function _processVaultRewards ( address _staker ) private {
   User storage user = users [ _staker ];
  
   uint256 pendingVaultClaim = pendingVaultRewards ( _staker );
   if ( pendingVaultClaim == 0) return ;
   
   // read HIGH token balance of the pool via standard ERC20 interface
   uint256 highBalance = IERC20 ( HIGH ). balanceOf ( address ( this ));
   require ( highBalance >= pendingVaultClaim , " contract HIGH balance too low ");
  
   // update ` poolTokenReserve ` only if this is a HIGH Core Pool
   if ( poolToken == HIGH ) {
   // protects against rounding errors
   poolTokenReserve -= pendingVaultClaim > poolTokenReserve ?
    poolTokenReserve : pendingVaultClaim ;
   }
  
   user.subVaultRewards = weightToReward(user.totalWeight, vaultRewardsPerWeight);
  
   // transfer fails if pool HIGH balance is not enough - which is a desired behavior
   transferHighToken ( _staker, pendingVaultClaim );
  
   emit VaultRewardsClaimed(msg.sender, _staker, pendingVaultClaim);
 }

This would cause every single call to HighStreetPoolBase.unstake() to revert with the following error message: ReentrancyGuard: reentrant call

Recommendation

It is recommended to remove the nonReentrant modifier from the HighStreetCorePool.transferHighToken() function.

Sources

https://medium.com/@bloqarl/uncovering-real-life-examples-of-denial-of-service-attacks-on-smart-contracts-8bc220c2cdd0