29026 - [SC - High] Hackers can steal the unclaimed yield to get th...

Hackers can steal the unclaimed yield to get the double reward from the Poolvoter contract by calling distributeEx() 2 times in the same block

Submitted on Mar 5th 2024 at 00:43:27 UTC by @perseverance for Boost | ZeroLend

Report ID: #29026

Report type: Smart Contract

Report severity: High

Target: https://github.com/zerolend/governance

Impacts:

  • Theft of unclaimed yield

Description

Description

Brief/Intro

Hackers can steal the unclaimed yield from the Poolvoter contract using the function distributeEx() 2 times in the same block. By doing this, the hacker can get double the reward from the protocol. Thus, steal the yield from other users.

Vulnerability Details

Background Information

The Zerolend provide the Incentive program as described in the Documentation of the protocol: https://docs.zerolend.xyz/zeronomics/token-overview

According to the Zerolend Boost Technical Walkthrough, this is done via the Poolvoter contract https://github.com/zerolend/governance/blob/main/contracts/voter/PoolVoter.sol

The contract will receive the reward via the function notifyRewardAmount

https://github.com/zerolend/governance/blob/main/contracts/voter/PoolVoter.sol#L154-L158

So the program will give the rewards to participants who perform actions such as providing liquidity. The contract will provide the rewards for all the pools in the list:

https://github.com/zerolend/governance/blob/main/contracts/voter/PoolVoter.sol#L22

The reward is distributed based on the weights[pool]/totalWeight. The formula

this can be seen in the function distributeEx

https://github.com/zerolend/governance/blob/main/contracts/voter/PoolVoter.sol#L214-L235

The vulnerability

So the token is distributed to all the pools in the list based on the portion weights[_pools[pool]]) / _totalWeight. But the bug in the function distributeEx allows a pool to receive the reward of other pools by calling distributeEx repeatedly at least 2 times. How many times depend on the supply and borrow gauge contract. I analyzed with the current code, it is possible to call 2 times.

If a hacker deposit or borrow into a pool (POOL_A) and can get 50% of reward of POOL_A. According to the design of the protocol, if POOL_A have weights is 10% of the total reward then the reward should be

REWARD_AMOUNT * 0.1 * 0.5 = 0.05 * REWARD_AMOUNT

But by calling distributeEx 2 times, the hacker can get

REWARD_AMOUT * 0.2 * 0.5 = 0.1 * REWARD_AMOUT = 2 times

So the profit can be 2 times

How:

Assume that there is 5 pools in the list of "_pools" and the POOL_A has index of 2

the Hacker call

Balance reward_token of this contract is REWARD_AMOUNT. Since POOL_A has weight of 10% then _reward = 0.1 * REWARD_AMOUNT

The pool is the contract LendingPoolGauge https://github.com/zerolend/governance/blob/main/contracts/voter/gauge/LendingPoolGauge.sol#L20-L35

So the reward is sent to supplyGauge and borrowGauge.

I analyzed the LendingPoolGaugeFactory , so the BorrowGauge and SupplyGauge is GaugeIncentiveController contract

https://github.com/zerolend/governance/blob/main/contracts/voter/gauge/RewardBase.sol#L104-L131

On the first call to this function, the transaction call will go to branch

When the hacker call distributeEx() the second time, the the transaction call will go to the second branch

So we can see that this bug allow the hackers to double the reward get from the protocol.

Impacts

About the severity assessment

This bug allow the hackers to double the reward get from the protocol and thus steal yield from other users.

This bug is high category "Theft of unclaimed yield"

Proof of concept

Proof of concept

Step 1: Wait the reward token is distributed to the PoolVoter contract Step 2: Deploy a contract. In this contract have the attack function

Step 3: Call getReward to take the reward https://github.com/zerolend/governance/blob/main/contracts/voter/gauge/RewardBase.sol#L79

It seems that the contract PoolVoter not yet deployed, so I provide the above POC to demonstrate the bug. Please let me know if you need more clarification.

Last updated

Was this helpful?