Porting software to 64-bit compatibility can have unexpected security implications.
64-bit architecture is well and truly here, but 32-bit software is still in wide use. However, any porting of software to 64-bit compatibility can have unexpected security implications, even without any code changes in the programs, drivers or operating systems. 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.
With the wide availability of x64 CPUs, many organisations are now switching to 64-bit operating systems and applications. This is driven by the increasing memory requirements of applications and servers, the decreasing cost of the new hardware and the widely-available support for applications and operating systems.
When code reviews are conducted of C/C++ applications that were developed on 32-bit systems and then ported to 64-bit, certain classes of security vulnerability are commonly identified.
This article gives a brief overview of these types of vulnerability and what to do about them.
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 organisations 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 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 which 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.
There 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 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: