👓Examples
Understanding the Examples: In the "Examples" section, we delve into practical instances of casting vulnerabilities unearthed from actual bug bounty reports, Code Arena analyses, and more. By studying these real-life cases, you'll gain a deeper, more tangible understanding of unsafe casting and its potential impact, enhancing your auditing skills in a practical and impactful way.
Example 1: Unsafe Cast On Oracle Return Value
Real life Cod4rena Bug Find With Payout
Consider a system where oracle.pcvStats
returns newProtocolEquity
as int256
, which is then cast to uint256
in recalculate
. The issue lies in the possibility of newProtocolEquity
being negative, leading to incorrect conversions. This can be resolved using the SafeCast library, which safely converts signed integers to unsigned integers, and vice versa, preventing possible overflows or underflows.
Example 2: Unstafe cast in vesting
Real life Cod4rena Bug Find With Payout
In the function claim()
, the variable vestedAmount
is subtracted from vester.amount
and then the result is being casted to uint192. In Solidity, a uint256 variable can hold a much larger value than a uint192. The value of uint256 can be up to 2^256 - 1, while the value of uint192 can only be up to 2^192 - 1.
The line of code of interest here is:
vester.amount -= uint192(vestedAmount);
This statement does two things:
It subtracts
vestedAmount
fromvester.amount
(which reduces the amount of tokens still to be vested for the user).It casts the result to a uint192 type.
If vester.amount
were to hold a value larger than what can be held by a uint192 after subtracting vestedAmount
, the behavior would be problematic. This is because casting a larger value to a smaller data type, like uint192, in Solidity does not revert or throw an error. Instead, it will silently overflow, i.e., lose the data exceeding its maximum storage capacity, which may lead to incorrect computations or even allow for potential exploits.
To visualize this, let's say we have a uint256 value which is 2^200, which is too large to fit into a uint192. If we were to cast this uint256 to a uint192, the resulting value would not be 2^200, but 2^200 mod 2^192. This is because the cast operation in Solidity does not revert on overflow; instead, it applies the modulo operation.
This problem can be mitigated by using the SafeCast library from OpenZeppelin. SafeCast provides functions to safely convert unsigned integers to a smaller size. Instead of the cast operation silently overflowing, the SafeCast functions will revert the transaction if an overflow occurs, thus protecting against potential exploits or bugs.
So, instead of the line:
You could use:
The SafeMath.sub
method reverts on underflow (if vestedAmount
is larger than vester.amount
), and the SafeCast.toUint192
method reverts on overflow (if the result of the subtraction operation is larger than what a uint192 can hold).
Using these libraries can help you to ensure that arithmetic and casting operations in your smart contracts are safe from overflow and underflow bugs.
Example 3: Unsafe uint128 casting may overflow
Real life Cod4rena Bug Find With Payout
In the given code, the function _calcRewardIntegral
is calculating rewards for users based on some business logic. It makes use of various types of integer values, mainly uint256
and uint128
. This function performs several casts from uint256
to uint128
and vice versa.
Let's take a look at these lines:
Here, rewardIntegral
is a uint256
value. The result of the expression ((bal - rewardRemaining) * 1e20) / _supply
is also uint256
. The code then casts both rewardIntegral
and the result of the expression to uint128
before performing the addition. This could potentially lead to overflow if the uint256
value exceeds the maximum value that can be stored in a uint128
variable.
In Solidity, an overflow would not result in an error or "revert". Instead, the value is calculated using the modulo operation, which would result in a much smaller number than expected. This could lead to a user receiving significantly less rewards than they should have.
To mitigate this, it is recommended to use the SafeCast library from OpenZeppelin. This library provides functions for safely casting between different numeric types without the risk of overflow. The above code snippet would look something like this with SafeCast:
This code performs the same operation as before but now uses the SafeCast library to ensure the operation will not overflow. If the value is too large to fit in a uint128
, these functions will revert the transaction, preventing any unexpected behavior.
By using SafeCast, you're ensuring that values that are too large to be stored as a uint128
will cause a transaction to revert, rather than silently overflowing and possibly leading to unexpected behavior in your smart contract.
Example 4: Implicit Under-flows
High Vulnerability
Real life Cod4rena Bug Find With Payout
In the examples provided, there are potential underflows happening due to operations on uint
values. An underflow occurs when a variable that has an unsigned integer type is decremented below its minimum value, which wraps around to its maximum value. For example, in the case of uint
, the minimum value is 0
, and if we try to decrement 0
by 1
, the result is 2^256 - 1
, the maximum uint
value, due to underflow.
The examples show conversions to int256
after subtracting uint
values or negating uint
values. If the intermediate result is less than 0
, it will be represented as a large uint
value (close to 2^256 - 1
). When this uint
value is then cast to int256
, the value could become negative.
In Solidity 0.8.x
, underflows and overflows are automatically checked and will cause a revert. However, this check is not applied on typecasts, which can lead to unexpected results. Therefore, it's important to ensure underflows don't occur by casting each operand before the operation.
The original problems occur when a subtraction operation is performed between uint
values or a uint
value is negated, and the result is then cast to int256
. This may lead to incorrect values due to underflow.
The mitigation steps in a more generalized form are:
For subtraction operations, ensure that each operand is cast to
int256
before performing the operation. So replaceint256(a-b)
withint256(a)-int256(b)
.For negation operations, first cast the
uint
value toint256
before applying the negation. So replaceint256(-x)
with-int256(x)
.
Doing this ensures that the operations are performed on int256
values, thereby preventing any unexpected underflow situations that could arise from the operation being performed on uint
values.
Last updated