Skip to content

Latest commit

 

History

History
208 lines (153 loc) · 9.14 KB

042.md

File metadata and controls

208 lines (153 loc) · 9.14 KB

Magic Eggshell Stallion

High

Oracle Staleness/Paused Defaulting to Full Earning Power Vulnerability

Summary

https://github.com/withtally/staker/blob/90802966475239c3eb8aafc3dbf18edd5a0b6b1b/src/BinaryEligibilityOracleEarningPowerCalculator.sol#L130-L137 The Oracle Staleness and Pause Exploit vulnerability exists within the BinaryEligibilityOracleEarningPowerCalculator contract of the Tally ARB Staker protocol. This flaw allows an attacker to manipulate the system's earning power calculations by either pausing the oracle or causing it to become stale. When triggered, the contract defaults all delegatees to receive 100% earning power, irrespective of their actual eligibility scores. This undermines the protocol's fundamental mechanism of rewarding only eligible participants, enabling unauthorized entities to earn rewards disproportionately or indefinitely. If left unaddressed, this vulnerability can lead to significant financial losses, governance manipulation, and erosion of user trust.

Vulnerability Detail

a. Detailed Explanation

The BinaryEligibilityOracleEarningPowerCalculator contract determines a staker's earning power based on their delegatee's eligibility score. The core logic is as follows:

if (_isOracleStale() || isOraclePaused) return _amountStaked;
return _isDelegateeEligible(_delegatee) ? _amountStaked : 0;

Mechanism Breakdown:

  1. Oracle Staleness Check (_isOracleStale()):

    • Evaluates whether the oracle has failed to update delegatee scores within a predefined window (STALE_ORACLE_WINDOW).
    • If stale, the system defaults to granting full earning power (_amountStaked) to all delegatees, bypassing actual eligibility checks.
  2. Oracle Pause Flag (isOraclePaused):

    • A boolean flag controlled by the oraclePauseGuardian.
    • When set to true, the system similarly defaults to full earning power for all delegatees.

Vulnerability Path:

  • Exploitation via Oracle Pause:

    • An attacker gains control over the oraclePauseGuardian role.
    • They set isOraclePaused to true, forcing the contract to grant full earning power universally.
  • Exploitation via Oracle Staleness:

    • The attacker disrupts the oracle's functionality, preventing it from updating delegatee scores.
    • Once the STALE_ORACLE_WINDOW elapses without updates, the system automatically grants full earning power to all delegatees.

b. Proof-of-Concept Code and Results

Test Code:

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {Test} from "forge-std/Test.sol";
import {BinaryEligibilityOracleEarningPowerCalculator} from "../src/BinaryEligibilityOracleEarningPowerCalculator.sol";
import {ERC20VotesMock} from "./mocks/MockERC20Votes.sol";

contract OracleVulnerabilityTest is Test {
    BinaryEligibilityOracleEarningPowerCalculator calculator;
    address owner;
    address scoreOracle;
    address pauseGuardian;
    address attacker;
    uint256 staleWindow;
    uint256 threshold;
    ERC20VotesMock mockToken;

    function setUp() public {
        owner = makeAddr("owner");
        scoreOracle = makeAddr("oracle");
        pauseGuardian = makeAddr("guardian");
        attacker = makeAddr("attacker");
        
        staleWindow = 1 days;
        threshold = 50; // Minimum score needed for eligibility

        vm.startPrank(owner);
        calculator = new BinaryEligibilityOracleEarningPowerCalculator(
            owner,
            scoreOracle,
            staleWindow,
            pauseGuardian,
            threshold,
            1 hours
        );
        mockToken = new ERC20VotesMock();
        vm.stopPrank();
    }

    function testExploit_StalenessGrantsFullPower() public {
        // Setup: Set initial score below threshold
        vm.startPrank(scoreOracle);
        calculator.updateDelegateeScore(attacker, 10); // Score well below threshold
        vm.stopPrank();

        // Initial check - should have zero earning power
        assertEq(
            calculator.getEarningPower(100, attacker, attacker),
            0,
            "Should have zero earning power when score below threshold"
        );

        // Simulate oracle becoming stale
        vm.warp(block.timestamp + staleWindow + 1);

        // Check after staleness - should now have full earning power
        uint256 stakeAmount = 100;
        assertEq(
            calculator.getEarningPower(stakeAmount, attacker, attacker),
            stakeAmount,
            "Should have full earning power when oracle is stale"
        );
    }

    function testExploit_PausedOracleGrantsFullPower() public {
        // Setup: Set initial score below threshold
        vm.startPrank(scoreOracle);
        calculator.updateDelegateeScore(attacker, 10);
        vm.stopPrank();

        // Initial check
        assertEq(
            calculator.getEarningPower(100, attacker, attacker),
            0,
            "Should have zero earning power when score below threshold"
        );

        // Pause oracle
        vm.prank(pauseGuardian);
        calculator.setOracleState(true);

        // Check after pause - should have full earning power
        uint256 stakeAmount = 100;
        assertEq(
            calculator.getEarningPower(stakeAmount, attacker, attacker),
            stakeAmount,
            "Should have full earning power when oracle is paused"
        );
    }
}

Test Results:

Ran 2 tests for test/OracleVulnerability.t.sol:OracleVulnerabilityTest [PASS] testExploit_PausedOracleGrantsFullPower() (gas: 60889) [PASS] testExploit_StalenessGrantsFullPower() (gas: 55424) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 38.21ms (8.45ms CPU time)

Ran 1 test suite in 87.07ms (38.21ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

Interpretation:

  1. testExploit_StalenessGrantsFullPower() Passed:

    • Verified that after the oracle becomes stale (i.e., no updates within STALE_ORACLE_WINDOW), a delegatee with a score below the threshold (10 < 50) is erroneously granted full earning power (100 instead of 0).
  2. testExploit_PausedOracleGrantsFullPower() Passed:

    • Confirmed that when the oracle is explicitly paused by the oraclePauseGuardian, a delegatee with an insufficient score similarly receives full earning power.

These results conclusively demonstrate that both exploitation vectors—oracle staleness and oracle pausing—effectively bypass the intended eligibility checks, validating the presence of the vulnerability.

c. Key Insights Demonstrating Vulnerability Validity

  • Bypassing Eligibility Checks:

    • The contract's fallback mechanism prioritizes system availability over security, defaulting to full earning power when the oracle's state is compromised or inactive.
  • Role Exploitation:

    • Control over the oraclePauseGuardian role or the ability to induce oracle staleness provides an attacker with the means to exploit the system's fallback logic.
  • System Invariant Violation:

    • The protocol's invariant that only eligible delegatees should receive rewards is violated when earning power defaults to full, irrespective of actual delegatee scores.
  • Impact on Core Functionality:

    • The vulnerability directly affects the reward distribution mechanism, a cornerstone of the staking protocol, leading to potential economic abuse and governance manipulation.

Impact

The Oracle Staleness and Pause Exploit has profound implications for the Tally ARB Staker protocol:

  1. Financial Exploitation:

    • Unlimited Reward Accumulation: Attackers can continuously earn rewards by maintaining the oracle in a stale or paused state, disregarding actual delegatee eligibility.
    • Resource Drain: Legitimate stakers with low or zero eligibility scores can unjustly siphon rewards meant for active, eligible participants.
  2. Governance Manipulation:

    • Distorted Voting Power: Full earning power may inadvertently or deliberately grant disproportionate governance influence to ineligible or malicious delegates.
    • Erosion of Trust: Users may lose confidence in the protocol's fairness and security, leading to reduced participation and potential exit.
  3. System Integrity Compromise:

    • Economic Imbalance: The protocol's reward distribution becomes skewed, undermining the economic incentives designed to promote active governance participation.
    • Potential for Repeated Exploits: Persistent toggling of the oracle state can create an ongoing vulnerability window, exacerbating the protocol's exposure.

Tool used

Manual Review and Foundry

Recommendation

To mitigate the Oracle Staleness and Pause Exploit, the following actionable steps are recommended:

a. Adjust Fallback Logic

  • Default to Zero Earning Power:

    • Modify the getEarningPower function to return zero earning power when the oracle is stale or paused, rather than granting full earning power.
    if (_isOracleStale() || isOraclePaused) return 0;
    return _isDelegateeEligible(_delegatee) ? _amountStaked : 0;
  • Conditional Reward Accrual:

    • Implement a mechanism to halt reward distribution entirely if the oracle is stale or paused, preventing any earning regardless of delegatee scores.