Post-Mortem 11/1/23 - Rebond from unbonded could lead to extra rewards and fees

This is a post-mortem document describing a critical severity [1] bug that was fixed on 11/1/23.

Summary

A vulnerability in the BondingManager contract used to manage the LPT stake and ETH fees of orchestrators and delegators was reported to the core team on 10/31/23 by a whitehat hacker, via Livepeer’s Immunefi bug bounty program. This vulnerability, if exploited, would have allowed a fully unbonded (need to previously have been bonded) attacker to rebond to an orchestrator in a particular state and earn extra rewards and fees. While there were a few addresses that inadvertently earned small amounts of extra rewards and fees as a result of this vulnerability, no address’ funds were at risk and there was/is sufficient LPT and ETH within the protocol to cover withdrawals.

Vulnerability

Description

An attacker would have been able to earn extra rewards and fees by doing the following:

  • Fully unbonding a previously bonded delegator address.
  • Calling rebondFromUnbonded() or rebondFromUnbondedWithHint() with the _to address set to an orchestrator that fulfills any of the following conditions:
    • Has called reward before the current round and has not called reward in the current round. If this condition is fulfilled, the attacker would earn extra rewards starting in the next round.
    • Has redeemed a ticket before the current round and has not redeemed a ticket in the current round. If this condition is fulfilled, the attacker would earn extra fees starting in the next round.

The amount of extra rewards and fees that an attacker could extract depended on (i.e. as these values increase, the extra rewards and fees increase as well):

  • The amount of rewards and fees earned by the orchestrator since first becoming active through the round of the rebond.
  • The amount of stake of the attacker.

In this scenario, the BondingManager did not ensure that the orchestrator’ cumulativeRewardFactor and cumulativeFeeFactor values tracked for the current round would be properly initialized. These factor values are used to calculate the rewards and fees of a staked delegator based on the algorithm described in LIP-36. If these factor values were left uninitialized then the rewards and fees calculated for the delegator would be higher than expected.

Potential Impact

The potential impact on the protocol from this attack was that an attacker could earn extra rewards and fees, and under certain circumstances even prevent other addresses’ from accessing the funds that they should be entitled to due to insufficient funds being available for withdrawal. Note: The latter scenario did not occur and is addressed later in this section.

This attack is repeatable so an attacker would have been able to continuously earn extra rewards and fees prior to detection as long as there existed an orchestrator that fulfilled the attack conditions that the attacker could rebond to.

Based on a post-mortem analysis, there were 20 addresses that earned a total of ~123 LPT of extra rewards and ~1.5 ETH of extra fees. However, there was always sufficient LPT and ETH in the protocol to cover withdrawals for all addresses because there are amounts of unclaimed LPT rewards and ETH fees within the protocol that exceed the extra rewards and fees from the vulnerability and that can be used for withdrawals. Unclaimed LPT and ETH accrue when delegators claim earnings for a round before their orchestrator calls reward or redeems winning tickets for that round. When the orchestrator calls reward or redeems a winning ticket, any rewards and fees that would have been credited to the delegator are instead left unclaimed because the delegator already claimed earnings for the round.

Likelihood of Exploitation

The primary motivation of the attacker would be to earn extra rewards and fees.

At the technical level, an attacker would need to selectively target orchestrators for rebonding that fulfill one of the following conditions:

  • Has called reward before the current round and has not called reward in the current round. If this condition is fulfilled, the attacker would earn extra rewards starting in the next round.
  • Has redeemed a ticket before the current round and has not redeemed a ticket in the current round. If this condition is fulfilled, the attacker would earn extra fees starting in the next round.

At the capital cost level, the attacker would need to put up stake in order to earn extra rewards and fees.

Mitigation

Description

The fix for the vulnerability was the addition of logic in the rebondFromUnbonded() and rebondFromUnbondedWithHint() functions that ensures that the orchestrator’s cumulativeRewardFactor and cumulativeFeeFactor values for the round in which these functions are called are properly initialized.

Takeaways

The vulnerability could have been prevented with better test coverage to ensure that anytime that a delegator’s delegate changes that the delegate’s (i.e. the orchestrator) cumulativeRewardFactor and cumulativeFeeFactor are properly initialized. This logic already existed for other functions that changed a delegator’s delegate, but was missing for rebondFromUnbonded() and rebondFromUnbondedWithHint().

In the future, it is important to clearly define protocol invariants such as “whenever a transaction updates an address’ delegate, the delegate’s cumulative factor values should be non-zero at the end of the transaction if the delegate has called reward or redeemed a ticket in the past” and then ensure in tests that the invariant is upheld after any function call. This type of protocol invariant testing will help with avoiding situations where tests for logic are added for certain functions, but not for others.

Conclusion

The core team thanks the Immunefi whitehat hacker for their responsible disclosure of this vulnerability which helped safeguard the users of the Livepeer network. They have been awarded a $40k bug bounty for the disclosure based on the guidelines for reward amounts of critical vulnerabilities in the bug bounty program.

[1] As classified based on the severity system used for the Immunifi bug bounty program.