Attackathon _ Fuel Network 33175 - [Smart Contract - High] Sway-lib Subtract i Logic Vulnerability
Submitted on Sat Jul 13 2024 12:16:30 GMT-0400 (Atlantic Standard Time) by @Minato7namikazi for Attackathon | Fuel Network
Report ID: #33175
Report type: Smart Contract
Report severity: High
Target: https://github.com/FuelLabs/sway-libs/tree/0f47d33d6e5da25f782fc117d4be15b7b12d291b
Impacts:
Permanent freezing of funds
Description
Vulnerability Details
within sway-libs the subtract
method of the Subtract
trait implementation for I8
impl core::ops::Subtract for I8 {
/// Subtract a I8 from a I8. Panics of overflow.
fn subtract(self, other: Self) -> Self {
let mut res = Self::new();
if self.underlying >= Self::indent()
&& other.underlying >= Self::indent()
{
if self.underlying > other.underlying {
res = Self::from_uint(self.underlying - other.underlying + Self::indent());
} else {
res = Self::from_uint(self.underlying - (other.underlying - Self::indent()));
}
} else if self.underlying >= Self::indent()
&& other.underlying < Self::indent()
{
res = Self::from_uint(self.underlying - Self::indent() + other.underlying);
} else if self.underlying < Self::indent()
&& other.underlying >= Self::indent()
{
res = Self::from_uint(self.underlying - (other.underlying - Self::indent()));
} else if self.underlying < Self::indent()
&& other.underlying < Self::indent()
{
if self.underlying < other.underlying {
res = Self::from_uint(other.underlying - self.underlying + Self::indent());
} else {
res = Self::from_uint(self.underlying + other.underlying - Self::indent());
}
}
res
}
}
The issue is in the last branch of the conditional statement:
else if self.underlying < Self::indent()
&& other.underlying < Self::indent()
{
if self.underlying < other.underlying {
res = Self::from_uint(other.underlying - self.underlying + Self::indent());
} else {
res = Self::from_uint(self.underlying + other.underlying - Self::indent());
}
}
The problem is in the case where self.underlying < other.underlying
. The current implementation is adding Self::indent()
instead of subtracting it. This will result in incorrect subtraction for certain negative numbers.
As example to trigger the bug
1- let's creates two
I8
values:a
representing -2 andb
representing -3.2- let's performs the subtraction
a - b
, which should equal 1. because -2 - (-3) = 1and while
a (representing -2) has an underlying value of 126 .. b (representing -3) has an underlying value of 125 .. and Self::indent() is 128
These values fall into the last branch of the Vulnerable if-else statement
else if self.underlying < Self::indent()
&& other.underlying < Self::indent()
{
if self.underlying < other.underlying {
res = Self::from_uint(other.underlying - self.underlying + Self::indent());
} else {
res = Self::from_uint(self.underlying + other.underlying - Self::indent());
}
}
Since 126 (self.underlying) > 125 (other.underlying)
, it will use the second calculation:
res = Self::from_uint(self.underlying + other.underlying - Self::indent());
Plugging in the values:
res = Self::from_uint(126 + 125 - 128);
res = Self::from_uint(123);
Therefore, the incorrect result will have an underlying value of 123.
Check the PoC for full runnable example
Impact is loss of funds because of
Incorrect Financial Calculations: Any function that uses subtraction with I8
types, especially when dealing with negative numbers, could produce incorrect results. This could lead to logical errors in tokens & funds distribution or any computation that relies on accurate integer arithmetic.
Recommendation
else if self.underlying < Self::indent()
&& other.underlying < Self::indent()
{
if self.underlying < other.underlying {
res = Self::from_uint(Self::indent() - (other.underlying - self.underlying));
} else {
res = Self::from_uint(self.underlying - other.underlying + Self::indent());
}
}
This change ensures that when subtracting a larger negative number from a smaller negative number, the result is correctly calculated as a positive number.
Proof of concept
Add this PoC test in the end of the i8.sw
#[test]
fn i8_Subtraction_PoC() {
// -2 - (-3) should equal 1
// but that isn't true here
let a = I8::neg_from(2u8); // -2
let b = I8::neg_from(3u8); // -3
let result = a - b;
// The expected underlying value for 1 is 129 (128 + 1)
let expected = I8::from_uint(129u8);
// Assert that the underlying values are correct (this should pass)
assert(a.underlying() == 126u8);
assert(b.underlying() == 125u8);
// Assert that the result is not equal to the expected value which prove the bug
assert(!(result == expected));
// the result value will incorrectly calculated as 123u8 instead of 129u8 in this implementation (this should pass)
assert(result.underlying() == 123u8);
}
Last updated
Was this helpful?