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

Fixed#safeMulDiv rounds incorrect when rounding mode is set to ROUND #195

Open
c4-bot-8 opened this issue Aug 19, 2024 · 0 comments
Open
Labels
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 🤖_primary AI based primary recommendation sufficient quality report This report is of sufficient quality

Comments

@c4-bot-8
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/libraries/Fixed.sol#L561-L607

Vulnerability details

Impact

Fixed#safeMulDiv rounds incorrect when rounding mode is set to ROUND and functions closer to CEIL

Proof of Concept

function safeMulDiv(
    uint192 a,
    uint192 b,
    uint192 c,
    RoundingMode rounding
) internal pure returns (uint192 result) {
    if (a == 0 || b == 0) return 0;
    if (a == FIX_MAX || b == FIX_MAX || c == 0) return FIX_MAX;

    uint256 result_256;
    unchecked {
        (uint256 hi, uint256 lo) = fullMul(a, b);
        if (hi >= c) return FIX_MAX;
        uint256 mm = mulmod(a, b, c);
        if (mm > lo) hi -= 1;
        lo -= mm;
        uint256 pow2 = c & (0 - c);

        uint256 c_256 = uint256(c);
        // Warning: Should not access c below this line

        c_256 /= pow2; <- @audit c_256 is modified
        lo /= pow2;
        lo += hi * ((0 - pow2) / pow2 + 1);
        uint256 r = 1;
        r *= 2 - c_256 * r;
        r *= 2 - c_256 * r;
        r *= 2 - c_256 * r;
        r *= 2 - c_256 * r;
        r *= 2 - c_256 * r;
        r *= 2 - c_256 * r;
        r *= 2 - c_256 * r;
        r *= 2 - c_256 * r;
        result_256 = lo * r;

        // Apply rounding
        if (rounding == CEIL) {
            if (mm != 0) result_256 += 1;
        } else if (rounding == ROUND) {
            if (mm > ((c_256 - 1) / 2)) result_256 += 1; <- @audit modified c_256 used to check rounding
        }
    }
    if (result_256 >= FIX_MAX) return FIX_MAX;
    return uint192(result_256);
}

For rounding to be accurate the unmodified divisor must be used to check the remainder of the division. Above we see that c_256 is divided by pow2 before being used to check mm (the remainder). Take the following example of c = 1e18:

pow2 = c & (0 - c) = 1000000000000000000 & (0 - 1000000000000000000) = 524288

c_256 = c_256/524288 = 19073486328125

As we can see the final c_256 is significantly lower than it should be. In this scenario, values greater than 499999999999999999 should be rounded up however, due to the error in implementation, values greater than 9536743164062 will be rounded up. To put that in decimal terms, that is 0.499999999999999999 vs 0.000009536743164062 which is a staggering difference. This effectively turns ROUND into CEIL.

Although safeMulDiv is not used with the ROUND rounding in the in-scope contracts, this math library is used across the entire protocol and will presumably be used during future upgrades as well.

We all know that rounding error, although they seems insignificant have lead to some very nasty exploits in defi. Any future update which utilizes this function to round for distribution of asset, pricing or share evaluation could cause loss of funds. In such situation, the incorrect rounding direction could cause incremental advantage to an attacker who could repeatedly exploit the rounding error to drain funds from the protocol. This risk is exponentially greater due to these contracts being deployed on low cost chains like base and OP.

Tools Used

Forge fuzz testing

Recommended Mitigation Steps

Use c rather than c_256 for checking the round:

        if (rounding == CEIL) {
            if (mm != 0) result_256 += 1;
        } else if (rounding == ROUND) {
--          if (mm > ((c_256 - 1) / 2)) result_256 += 1;
++          if (mm > ((c - 1) / 2)) result_256 += 1;
        }

Assessed type

Error

@c4-bot-8 c4-bot-8 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 Aug 19, 2024
c4-bot-2 added a commit that referenced this issue Aug 19, 2024
@c4-bot-12 c4-bot-12 added the 🤖_primary AI based primary recommendation label Aug 19, 2024
howlbot-integration bot added a commit that referenced this issue Aug 20, 2024
@howlbot-integration howlbot-integration bot added the sufficient quality report This report is of sufficient quality label Aug 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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 🤖_primary AI based primary recommendation sufficient quality report This report is of sufficient quality
Projects
None yet
Development

No branches or pull requests

2 participants