Downcast of Number to Address
Overview
- Severity: Low
- Confidence: Medium
- Affected Versions: < 0.8.0
What is the Downcast of Number to Address vulnerability?
Solidity, like many other programming languages, supports explicit type casts made by the developer. In earlier versions of Solidity, it was possible to make a direct narrowing cast to an address type from a wider numeric type, which could potentially result in incorrect address values due to truncated bits and faulty assumptions about the data being cast.
Further reading: Solidity Documentation: Explicit Type Conversions
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.0;
contract CalldataHandler {
// Function that directly processes calldata to perform an action
function processCalldata() external payable {
// Load calldata into temporary variables
uint256 dataSlice1;
uint256 dataSlice2;
assembly {
dataSlice1 := calldataload(0) // Assuming specific calldata structure
dataSlice2 := calldataload(32) // for demonstration purposes
}
// Perform some action with these data, e.g. transfer ether
address targetAddress = address(dataSlice1);
uint256 amount = dataSlice2;
(bool success, ) = payable(targetAddress).call{value: amount}("");
require(success, "Failed to send value");
}
// Ensure the contract can receive Ether
receive() external payable {}
}
In the above example, contract CalldataHandler
has functionality for directly accessing calldata associated with function calls to processCalldata
rather than using parameter passing. N.B. that for simplifying purposes, the calldata are assumed to start with the two variables of interest, in this case an address and an amount of ether to send to it. However, the address here is extracted from data stored as a uint256
variable, which may truncate important bits and cause ether to be sent to an incorrect and possible irrecoverable address.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.7.0;
contract CalldataHandlerUpdated {
// Function that directly processes calldata to perform an action
function processCalldata() external payable {
// Initialize a variable to store the extracted address
address targetAddress;
// Load calldata into temporary variable for the amount
uint256 amount;
assembly {
// Load the first 20 bytes of calldata into a uint160 variable
// Solidity automatically pads this on the right, fitting the address format
targetAddress := and(calldataload(0), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
amount := calldataload(32)
}
// Perform the action with the extracted data, e.g., transfer Ether
(bool success, ) = payable(targetAddress).call{value: amount}("");
require(success, "Failed to send value");
}
// Ensure the contract can receive Ether
receive() external payable {}
}
In the revised example above, contract CalldataHandlerUpdated
now loads only 20 bytes of data into an address
variable directly, rather than converting from a uint256
and then making a narrowing downcast. N.B. the same simplifying assumption regarding the positioning of the variables in the calldata applies in this example as well.