Matt Hillman, Principal Security Researcher
5 mins read
This is particularly dangerous in situations where code has already been subject to code review and been assessed to be free from exploitable vulnerabilities in a 32-bit environment: it could immediately become vulnerable when compiled on a 64-bit system. It should be noted that these classes of vulnerability are not new and similar issues have been found and exploited before. However, the migration to 64-bit technology is regularly leaving organizations exposed to risk, particularly when there is a reliance on security reviews and assurance activities performed previously on a different architecture.
On 32-bit systems, the amount of possible input to an application is naturally limited by the available address space. For example, on Microsoft Windows systems, memory allocations in user-mode are usually less than 2 gigabytes in size. In reality, however, the space available for memory allocations on 32-bit systems will be much less, as space will be reserved for binaries, stacks, and heaps. Nevertheless, this can still be more than 2 gigabytes when the /3GB switch is used during booting, although this is not the default setting.
However, on 64-bit systems, these limits are greatly increased, and the allocation of much larger memory blocks may be possible, particularly with the large amounts of RAM now available on 64-bit systems. Whilst good practice dictates that the size of any data passed to a function is checked, it is often the case that developers make assumptions about the maximum possible size of that data – and these assumptions could be based on the upper limit for a memory allocation on the platform itself. When transferred to a 64-bit system, these deviations from best practice can become exploitable if an attacker can introduce large amounts of data into the application.
While providing large amounts of data to an application may not seem practical as an attack in some situations, it should be remembered that on a 20Mbit line it will only take about half an hour to send 4 gigabytes of data. As many applications will happily sit there unattended and unmonitored accepting input, this is a perfectly viable attack. Similarly, local application or kernel vulnerabilities that require large amounts of memory are even more likely to be exploited, as allocating and filling 4 gigabytes of memory will only take seconds on modern systems.
Here follow some examples of vulnerabilities that can occur on 64-bit systems that would not be exploitable on 32-bit systems.
Where the size of the input is obtained and added to (e.g. incremented to make space for a terminating character) and that size is represented as an unsigned integer, the integer could overflow if more data were introduced than the maximum value of that unsigned integer. On 32-bit systems, there would not be enough memory to hold 0xFFFFFFFF bytes of data along with program code and the operating system, so the size could never be enough to trigger the overflow. However, on 64-bit systems, this becomes a real possibility.
On 32-bit systems, the value types ‘unsigned int’, ‘long’ and ‘size_t’ can be used interchangeably. However, on 64-bit systems, these value types are not equivalent. In situations where these have not been used in the correct manner, exploitable conditions can exist.
As the previous examples show, the migration of software from 32-bit to 64-bit systems can introduce new vulnerabilities, or make previously unexploitable vulnerabilities exploitable. Consequently, it is recommended that the migration process should always include a code review during which the focus should be placed on security. As we have seen, the assumptions made by programmers and used in previous code reviews may not hold true.
The following recommendations provide general guidance on identifying and resolving the types of issues which could be encountered:
Are there any size limits on incoming data? If not, it is very likely that the code handling the incoming data is flawed, or that the functions using the input afterwards will not be coded so as to handle the data in a safe manner. Reallocation operations in network applications have proven to be particularly vulnerable. In many scenarios, limiting the input data to prevent excessive amounts of memory from being allocated is a reasonable control to enforce.
Review any usage of ‘int’ types for length, offset, and size values. Any use of a 32-bit integer for these kinds of values should be investigated as the code is likely to be flawed in the great majority of cases. If code is found to be affected by this issue, then each instance will need to be evaluated to determine the impact. Developers may wish to review the use of ‘int’ in their application as a whole and use safer types such as ‘long’ or preferably ‘size_t’.
When code is first compiled for a 64-bit platform, it is important that attention is paid to any compiler warnings, especially those concerning truncation and casting of integer types. These can often indicate bugs that might be exploitable.