Attackathon _ Fuel Network 32275 - [Smart Contract - Medium] Various Sway Libs Bugs
Submitted on Mon Jun 17 2024 08:57:19 GMT-0400 (Atlantic Standard Time) by @anatomist for Attackathon | Fuel Network
Report ID: #32275
Report type: Smart Contract
Report severity: Medium
Target: https://github.com/FuelLabs/sway-libs/tree/0f47d33d6e5da25f782fc117d4be15b7b12d291b
Impacts:
- Incorrect library function behaviors
Description
Brief/Intro
sway-libs provides a lot of utility functions to help contract development. Thus its correctness is important to ensure developers don't suffer from hidden vulnerabilities. We've identified a large amount of bugs within the libraries, and will go through each bug class in this report.
Vulnerability Details
Incorrect bit width for signed_integers and fixed_point
- The
i256::bitsis incorrectly set to 128. - Strictly speaking,
ifp64has ~33 bits and not 64 bits. Similarlyifp128has ~64 bits andifp256has ~129 bits
Incorrect two's complement implementation for signed_integers
- The twos complement currently doesn't make sense. For instance, two's complement for -1i8 should be 0xff, but the implementation returns 0x81
Incorrect indent for i128 and i256
i128indent should be1<<127, andi256indent should be1<<255
Unnecessary subtract In UFP32::fract
- The subtraction of
u32::maxinUFP32::fractwill lead to underflow andpanic
Incorrect usage of IFP::from
IFP::fromtreats input as raw underlying instead of integers (different fromsigned_integer). It is misused in several places
IFP::ceil overflow on values close to min
IFP::ceil takesceilof underlying and then adjusts the values. Thus values near tomin` might suffer from incorrect overflow
Incorrect panic of fixed_point::round
fixed_point::roundtakes bothceil/floorand use them to derive the rounded values. Thus even if the final result falls within the valid range, function would stillpanicif eitherceiloffloordoes.
Incorrect denominator in UFP128::sqrt and UFP32::sqrt
- The denominator for
UFP128::sqrtshould be1<<32instead of2<<32 - The denominator for
UFP32::sqrtshould be1<<8instead of1<<16
UFP::sqrt precision loss
- The implementation of
UFP::sqrtloses 1/4 of precision in terms of its size.
UFP::pow` premature overflow leads to revert
UFP::powdirectly callspowon the underlying value, which will easily overflow for values >= 1.
fixed_point::exp precision loss
UFP::expis wildly inaccurate. We haven't properly estimated to amount of precision lost. But the PoC attached should demonstrate the idea.
UFP32::exp and IFP256::exp uses incorrect taylor series
UFP32::expuses the taylor series forUFP64::expIFP256::expuses the taylor series forIFP128::exp
Incorrect min for IFP
minforIFPshould usetype::MAXas underlying value
Lack of consideration of negative zero for IFP compare functions
IFP::gtandIFP::ltdoesn't consider negative zeros. This might lead to incorrect results.IFP::non_negativedoesn't consider negative zeroes. And might returnfalsefor those.
Incorrect adjustment for IFP::ceil
IFP::ceilincreasesunderlyingwhen the value is negative, while the correct implementation should be to decreaseunderlying.
Impact Details
It is hard to provide a concrete impact for library functions. But let's just say users may not be able to predict its behavior and might be caught of guard when those functions do unexpected stuff. And given the abundance of bugs, it seems extremely likely that users would run into those.
References
Incorrect bit width for signed_integers and fixed_point
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/signed_integers/i256.sw#L88https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L46https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L46https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L46
Incorrect two's complement implementation for signed_integers
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/signed_integers/i8.sw#L398https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/signed_integers/i16.sw#L399https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/signed_integers/i32.sw#L398https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/signed_integers/i64.sw#L399https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/signed_integers/i128.sw#L423https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/signed_integers/i256.sw#L419
Incorrect indent for i128 and i256
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/signed_integers/i128.sw#L38https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/signed_integers/i256.sw#L39
Unnecessary subtract In UFP32::fract
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp32.sw#L391
Incorrect usage of IFP::from
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L416https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L475https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L476https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L416https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L475https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L476https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L554
IFP::ceil overflow on values close to min
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L473https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L473https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L473
Incorrect panic of fixed_point::round
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp32.sw#L443https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp64.sw#L437https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp128.sw#L383https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L515https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L515https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L515
Incorrect denominator in UFP128::sqrt and UFP32::sqrt
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp128.sw#L443https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp32.sw#L462
UFP::sqrt precision loss
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp32.sw#L462https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp64.sw#L456https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp128.sw#L442
UFP::pow` premature overflow leads to revert
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp32.sw#L492https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp32.sw#L497https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp64.sw#L487https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp128.sw#L450https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp128.sw#L452
fixed_point::exp precision loss
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp32.sw#L471https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp64.sw#L465https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp128.sw#L469https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L533https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L533https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L533
UFP32::exp and IFP256::exp uses incorrect taylor series
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ufp32.sw#L475https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L537
Incorrect min for IFP
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L87https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L87https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L87
Lack of consideration of negative zero for IFP compare functions
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L208https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L220https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L208https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L220https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L208https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L220https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L193https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L193https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L193
Incorrect adjustment for IFP::ceil
https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp64.sw#L475https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp128.sw#L475https://github.com/FuelLabs/sway-libs/blob/0f47d33d6e5da25f782fc117d4be15b7b12d291b/libs/src/fixed_point/ifp256.sw#L475
Proof of concept
Proof of Concept
Incorrect bit width for signed_integers and fixed_point
#[test]
fn i256_bits() -> () {
assert(I256::bits() == 256);
()
}
Incorrect two's complement implementation for signed_integers
#[test]
fn i8_twos_compliment() -> () {
let val = I8::neg_from(1);
let complement = val.twos_complement();
assert(complement.underlying() == 255);
}
Incorrect indent for i128 and i256
#[test]
fn i128_indent() -> () {
// -2^65 should be in range
let u128_val = U128::from((2, 0));
let i128_val = I128::neg_from(u128_val);
()
}
Unnecessary subtract In UFP32::fract
#[test]
fn ufp32_fract_incorrect_underflow() -> () {
let zero = UFP32::from_uint(0);
let one = UFP32::from_uint(1);
assert(one.fract() == zero);
()
}
Incorrect usage of IFP::from
#[test]
fn ifp64_incorrect_usage_of_from() -> () {
let ifp64_zero = IFP64::zero();
let ifp64_val = ifp64_zero - IFP64::from(UFP32::from(65537u32)); //-0x1.0001
assert((ifp64_zero - IFP64::from_uint(2u32)) == ifp64_val.floor());
()
}
IFP::ceil overflow on values close to min
#[test]
fn ifp128_ceil_incorrect_overflow() -> () {
let ifp128_zero = IFP128::zero();
let ifp128_val = ifp128_zero - IFP128::from(UFP64::from(18446744069414584321)); //-0xffffffff.00000001
let expected = ifp128_zero - IFP128::from(UFP64::from(18446744069414584320)); //-0xffffffff.00000000
assert(expected == ifp64_val.ceil());
()
}
Incorrect panic of fixed_point::round
#[test]
fn ufp64_round_incorrect_overflow() -> () {
let val = UFP64::from(18446744069414584321); //0xffffffff.00000001
assert(UFP64::from(18446744069414584320) == val.round());
()
}
Incorrect denominator in UFP128::sqrt and UFP32::sqrt
#[test]
fn ufp128_sqrt_incorrect_denom() -> () {
let val = UFP128::from((0, 1)); //0x0000000000000000.0000000000000001
assert(UFP128::from((0, 4294967296)) == val.sqrt()); //0x0000000000000000.0000000100000000
()
}
UFP::sqrt precision loss
#[test]
fn ufp64_sqrt_precision_loss() -> () {
let val = UFP64::from(3); //0x00000000.00000002
assert(UFP64::from(92681) == val.sqrt()); //0x00000000.00016a09
()
}
UFP::pow` premature overflow leads to revert
#[test]
fn ufp64_pow_premature_overflow() -> () {
let val = UFP64::from_uint(1); //0x00000001.00000000
assert(val == val.pow(2u32));
()
}
fixed_point::exp precision loss
#[test]
fn u64_imprecise_exponent() -> () {
let val = UFP64::from_uint(1);
let val = UFP64::exp(val);
assert(val == UFP64::from(11674931554)); //0x2.b7e15162
()
}
UFP32::exp and IFP256::exp uses incorrect taylor series
Incorrect min for IFP
#[test]
fn ifp128_incorrect_min() -> () {
let ifp128_zero = IFP128::zero();
let ifp128_min = IFP128::min();
let ifp128_max = IFP128::max();
assert(ifp128_zero - ifp128_max == ifp128_min);
()
}
Lack of consideration of negative zero for IFP compare functions
#[test]
fn ifp128_negative_zero() -> () {
let val = IFP128::from(UFP64::from(1));
let zero = IFP128::zero();
let neg_zero = (zero - val).fract();
assert((zero == neg_zero) != (zero > neg_zero));
assert(neg_zero.non_negative());
()
}
Incorrect adjustment for IFP::ceil
#[test]
fn ifp128_incorrect_ceil() -> () {
let ifp128_zero = IFP128::zero();
let ifp128_val = ifp128_zero - IFP128::from(UFP64::from(1));
assert(ifp128_val.ceil() > ifp128_val);
()
}