Attackathon _ Fuel Network 33170 - [Smart Contract - Medium] UFP Exp In Sway-lib Logic Vulnerability

Submitted on Sat Jul 13 2024 10:02:26 GMT-0400 (Atlantic Standard Time) by @Minato7namikazi for Attackathon | Fuel Network

Report ID: #33170

Report type: Smart Contract

Report severity: Medium

Target: https://github.com/FuelLabs/sway-libs/tree/0f47d33d6e5da25f782fc117d4be15b7b12d291b

Impacts:

  • Permanent freezing of funds

Description

Brief/Intro

During my audit of the UFP (unsigned fixed-point number) implementations in the sway-libs , this logic vulnerability were identified. This could lead to incorrect calculations in any smart contracts or scripts relying on this function and cause massive losses.

Vulnerability Details

impl Exponent for UFP128 {
    fn exp(exponent: Self) -> Self {
        let one = UFP128::from((1, 0));
        let p2 = one / UFP128::from((2, 0));
        let p3 = one / UFP128::from((6, 0));
        let p4 = one / UFP128::from((24, 0));
        let p5 = one / UFP128::from((120, 0));
        let p6 = one / UFP128::from((720, 0));
        let p7 = one / UFP128::from((5040, 0));

        // common technique to counter losing sugnifucant numbers in usual approximation
        let _res_minus_1 = exponent + exponent * exponent * (p2 + exponent * (p3 + exponent * (p4 + exponent * (p5 + exponent * (p6 + exponent * p7)))));
        let res = one;
        res
    }
}

The function calculates _res_minus_1 correctly .. but then it ignores this calculation and simply returns one. This means the exponential function will always return 1, regardless of the input.

The impact is Loss of funds because of

  • Incorrect Financial Calculations : using the vulnerable exponential function for compound interest, option pricing, or yield calculations. With this bug, all such calculations would return 1, regardless of the input.

  • Some token distribution models or staking rewards systems use exponential decay or growth. This bug would make such systems non-functional, potentially: Distributing incorrect amounts of tokens

Recommendation

we should use the calculated result like


impl Exponent for UFP128 {
    fn exp(exponent: Self) -> Self {
        let one = UFP128::from((1, 0));
        let p2 = one / UFP128::from((2, 0));
        let p3 = one / UFP128::from((6, 0));
        let p4 = one / UFP128::from((24, 0));
        let p5 = one / UFP128::from((120, 0));
        let p6 = one / UFP128::from((720, 0));
        let p7 = one / UFP128::from((5040, 0));

        // Common technique to counter losing significant numbers in usual approximation
        let res_minus_1 = exponent + exponent * exponent * (p2 + 
                         exponent * (p3 + exponent * (p4 + 
                         exponent * (p5 + exponent * (p6 + 
                         exponent * p7)))));

        one + res_minus_1
    }
}
  • Now it's calculates the exponential function using a Taylor series approximation and should return the correct result. the function now adds 1 to the calculated res_minus_1 to get the final result, as the original calculation was for (e^x - 1).

Proof of concept

Add this PoC test in the end of the ufp128.sw


#[test]
fn PoC_exp() {


    let one = UFP128::from_uint(1);
    let res = UFP128::exp(one);
    assert(res == UFP128::from((1, 0)));


    let two = UFP128::from_uint(2);
    let res = UFP128::exp(two);
    assert(res == UFP128::from((1, 0)));


    let three = UFP128::from_uint(3);
    let res = UFP128::exp(three);
    assert(res == UFP128::from((1, 0)));


    let four = UFP128::from_uint(4);
    let res = UFP128::exp(four);
    assert(res == UFP128::from((1, 0)));


    let five = UFP128::from_uint(5);
    let res = UFP128::exp(five);
    assert(res == UFP128::from((1, 0)));


    let six = UFP128::from_uint(6);
    let res = UFP128::exp(six);
    assert(res == UFP128::from((1, 0)));


    let seven = UFP128::from_uint(7);
    let res = UFP128::exp(seven);
    assert(res == UFP128::from((1, 0)));


    let eight = UFP128::from_uint(8);
    let res = UFP128::exp(eight);
    assert(res == UFP128::from((1, 0)));


    let nine = UFP128::from_uint(9);
    let res = UFP128::exp(nine);
    assert(res == UFP128::from((1, 0)));


}

Last updated