Massive Crimson Cougar
Medium
Impact: Incorrect use of bitwise operations masks and shifts can lead to misconfigurations, overwriting unrelated fields in the ReserveConfiguration bitmap, and potentially destabilizing the protocol.
The ReserveConfiguration library uses bitwise operations to pack multiple configuration parameters into a single uint256 value self.data. Each parameter is represented by a specific range of bits, isolated using predefined masks and shift positions. Improper use of these masks and positions can result in:
- Overwriting unrelated bits, corrupting reserve configurations.
- Reading invalid data due to an incorrect mask or shift.
- Logical errors during parameter updates, leading to unexpected behaviour.
Bitmask manipulation errors could:
- Cause the contract to operate with incorrect reserve parameters, affecting liquidation thresholds, caps, or flags.
- Create unintended borrowing or freezing behaviour, potentially locking user funds or enabling unauthorized actions.
- Lead to cascading failures in other parts of the protocol reliant on these configurations.
Example of Bitmask Misuse:
uint256 incorrectMask = ReserveConfiguration.LTV_MASK << 4; // Incorrect shift
self.data = (self.data & ~incorrectMask) | (newLtv << 20);
In this example:
1. An incorrect shift (<< 4) creates a misaligned mask.
2. This overwrites unrelated fields in self.data, corrupting the reserve's configuration.
Impact in a Live Scenario: If the setLtv function is misused:
- It could inadvertently overwrite the Liquidation Threshold or Borrowing Enabled bits, making the reserve unusable.
- A misconfiguration may allow users to borrow more than allowed, leading to insolvency risk.
Assertions for Mask Validation:
- Include runtime checks to validate masks and shifts during development. For instance:
require((mask & ~mask) == 0, "Invalid mask"); require((shiftedMask & LTV_MASK) == 0, "Overlapping masks");
function setLtv(DataTypes.ReserveConfigurationMap memory self, uint256 ltv) internal pure {
require(ltv <= MAX_VALID_LTV, Errors.INVALID_LTV);
uint256 clearedData = self.data & ~LTV_MASK; // Clear LTV bits
uint256 updatedData = clearedData | ltv; // Set new LTV
require((updatedData & ~LTV_MASK) == (self.data & ~LTV_MASK), "Unrelated fields overwritten");
self.data = updatedData;
}
function createMask(uint256 startBit, uint256 bitLength) internal pure returns (uint256) {
return ((1 << bitLength) - 1) << startBit;
}