Assessing the Tux Strength: Part 2 - Into the Kernel
The previous article in this series on Linux security described different userspace protection mechanisms that can be applied to protect binaries on a Linux system. Unsurprisingly, without additional kernel settings and protections most of the previously described mechanisms cannot be utilised to their full extent. This article will therefore focus on kernel features that have a direct impact on security of running binaries. Specific security frameworks such as SELinux, Grsecurity RBAC, AppArmor and others will not be discussed here although they may feature in future articles.
Most of the kernel features described here will be related to the addition of protection within userspace; however, a few of them will also have a direct impact on the security of the kernel itself. The security of the kernel is also very important as once an attacker is able to execute their own code in the kernel space there’s very little that can be done to maintain the security of the system. The Linux kernel is subject to rapid development with many new features being added or existing functionality being amended and support for new devices is added on a regular basis. It should be noted that the direct and indirect impact of these issues on the security of the kernel can be easily overlooked. It is also not uncommon for a security feature that is enabled in the kernel to have an impact on its performance and the overall performance of the system as well.
The testing that was conducted to provide the information presented in this article was performed against the same distributions as in the previous research. Output from the following settings and tools were reviewed during this assessment:
- ‘mmap_min_addr’ (/proc/sys/vm/mmap_min_addr)
- ‘randomize_va_space’ (/proc/sys/kernel/randomize_va_space)
The first setting is responsible for setting the minimal virtual address that can be allocated in userspace. This has certain security implications; for example, if an attacker can trigger a NULL pointer dereference kernel vulnerability. If a page at an address of 0 is mapped and filled with data of their choice the kernel would access the memory page prepared by an attacker. Unsurprisingly, this can quite easily result in arbitrary code execution and the techniques for exploiting systems in this manner have previously been published. The use of this kernel setting should prevent this types of exploitation by enforcing the lowest possible address to a safer value (usually 64KB).
The second setting specifies which process memory sections will have their addresses randomised upon start up. If it’s set to 0 randomisation is completely disabled. If it’s set to 1 the base addresses of the stack and VDSO pages and mmap requests are all randomised. This also means that shared libraries will be loaded at random addresses as well as the binaries that are compiled as Position Independent Executable (of a type ET_DYN rather than ET_EXEC). When the value is set to two, in addition to all the aforementioned protections, the base address of the heap is also randomised. It should be noted that for PaX enabled kernels (http://pax.grsecurity.net/), the heap is randomised regardless whether the setting is set to 1 or 2.
Paxtest is a test suite initially developed by Peter Busser from the Adamantix project and further developed by Brad Spengler, the author of the Grsecurity patch. The paxtest suite tests various memory protection mechanisms, more details on its workings can be found in its README file. In summary, it performs a number of checks to determine whether it is possible to execute arbitrary code using different memory regions and attack techniques; it also assesses the amount of randomisation of different memory areas. The results are then provided back to the user in a consistent manner allowing comparison between and benchmarking of different systems.
The results of assessing these settings on the target platforms are included here: -
The results that were identified are described below.
All tested distributions have this parameter set to a non-zero value with the two most common defaults being 4096 and 65536. The latter value will prevent user programs from accessing the first 64 kilobytes (the first segment) of memory. It should be noted that even when set at 4 kilobytes it effectively protects a user from mapping page 0 thus making the exploitation of NULL pointer dereferences significantly harder but not impossible. It should also be noted that some programs will not work correctly if they cannot map a page below the first 64 kilobytes of memory.
All of the tested distributions except one had this setting set to 2. This is the highest level of randomisation that the Linux kernel can provide without external patches such as Grsecurity. The only distribution that had this value set to 1 was OpenSuse and it is assumed that this is due to the CONFIG_COMPAT_BRK setting being enabled in the default kernel. This option allows the running of legacy binaries (ie. linked against libc.so.5) that would not otherwise be possible. The result of having this value set to 1 is that the base address of the heap is not randomised for binaries not compiled as Position Independent Executables (ET_EXEC instead of ET_DYN).
It was also discovered that on Debian, although the value was set to 2, the heap base addresses were not randomised. This behaviour is the same as if the value was set to 1! This has been reported as a bug and can now be tracked here. All other distributions behaved as expected, with the value set to 2 which provided the best available randomisation on a default kernel.
Paxtest results – arbitrary code execution
Paxtest runs a number of checks to identify if certain memory regions are (or could be made) executable. This checks whether an attacker is able to execute their code if they are able to inject it into a running process. It also checks the randomisation of various memory regions. A detailed description for the tests is included in the README file within the paxtest archive. The results of running this tool on the target platforms are included below.
|Executable anonymous mapping||Vulnerable||Killed||Killed||Vulnerable||Killed|
|Executable shared library bss||Vulnerable||Vulnerable||Killed||Vulnerable||Vulnerable|
|Executable shared library data||Vulnerable||Vulnerable||Killed||Vulnerable||Vulnerable|
|Executable anonymous mapping (mprotect)||Vulnerable||Vulnerable||Killed||Vulnerable||Vulnerable|
|Executable bss (mprotect)||Vulnerable||Killed||Killed||Vulnerable||Vulnerable|
|Executable data (mprotect)||Vulnerable||Killed||Killed||Vulnerable||Vulnerable|
|Executable heap (mprotect)||Vulnerable||Killed||Killed||Vulnerable||Vulnerable|
|Executable stack (mprotect)||Vulnerable||Vulnerable||Killed||Vulnerable||Vulnerable|
|Executable shared library bss (mprotect)||Vulnerable||Vulnerable||Killed||Vulnerable||Vulnerable|
|Executable shared library data (mprotect)||Vulnerable||Vulnerable||Killed||Vulnerable||Vulnerable|
|Writable text segments||Vulnerable||Vulnerable||Killed||Vulnerable||Vulnerable|
|Return to function (strcpy)||Vulnerable||Vulnerable||Vulnerable||Vulnerable||Vulnerable|
|Return to function (memcpy)||Vulnerable||Vulnerable||Vulnerable||Vulnerable||Vulnerable|
|Return to function (strcpy, PIE)||Vulnerable||Vulnerable||Vulnerable||Vulnerable||Vulnerable|
|Return to function (memcpy, PIE)||Vulnerable||Vulnerable||Vulnerable||Vulnerable||Vulnerable|
As can be observed from the results above, all of the distributions were vulnerable to ‘return-to-function’ classes of attack. These attacks involve an attacker calling legitimately loaded functions (or pieces of code) in order to achieve their goal. This is an extremely difficult attack scenario to protect against (as programs need to call their own functions, including those included in other libraries) and at the moment there’s no “on or off” protection against them. The highest protection can be achieved using a high level of randomisation, which would require a 64 bit address space rather than 32 bit. According to this article a 32 bit address space would allow an attacker to bruteforce the location of certain memory regions thus providing them with a greater chance of successful exploitation.
Another conclusion is that other than the Gentoo Grsecurity enabled kernel, all other distributions are vulnerable, at least to some extent, from known exploitation techniques. In particular Debian and OpenSuse do not offer any additional protection and as a result fail on every paxtest check.
The notable exceptions in the results are Fedora and Ubuntu. Both distributions do not allow the ability to write code to a certain memory region and then execute it. This can be observed from the results of the first five tests. Fedora goes one step further and also prevents the bss, data and heap sections from being marked as executable using the ’mprotect’ system call. It should be noted that there would still be numerous other memory regions where an attacker could upload their code and then use the ‘mprotect’ function to mark it as executable.
It must also be noted that all four distributions – Debian, Fedora, OpenSuse and Ubuntu provide a writable text section. The text section is the section of the binary that holds the program code that is executed. If an attacker can write to this region, they can obviously gain control over the execution flow, by overwriting existing code with that of their choice. It is advisable to have the text segment as read-only; however, it should be noted that this could prevent some the binaries from running when text relocation is required. Text relocation happens when not all of the code (usually a shared library) is compiled as position independent code (PIC) which then forces the linker to write the location of a specific symbols into the text segment at run time.
Another example where a writable memory section is required (in this case the stack) are gcc trampolines. Trampolines are created for nested functions which are functions that are defined inside another function body. More information about nested functions and trampolines can be found here and here.
Paxtest results – randomisation
The paxtest suite checks the level of randomisation for different process sections as well as process types – whether compiled as a standard executable or as a Position Independent Executable.
The more memory that is randomised (and as such has a higher level of entropy), the more difficult it is for an attacker to guess the correct value and thus successfully exploit the vulnerability. It should be noted that 16 bit randomisation gives 2^16 = 65536 possibilities, which means that on average 32,768 tries would be needed to guess the correct value. It is also worth noting that for 32 bit systems, the 32 bit addressing scheme would be an upper theoretical limit of entropy. However, in reality this is further limiter by reserved memory areas (such as the kernel space), the need to align memory mappings to page boundaries (for effective memory management) and the need to map large consecutive memory regions. These factors will further limit the overall entropy associated with the randomisation of different memory regions. It should be noted that the values included in the following table for results are only estimates and not strict results in a scientific sense. That means that running the test multiple times may result in slightly different results being obtained.
|Anonymous mapping randomisation test||9 bits||12 bits||17 bits||12 bits||12 bits|
|Heap randomisation test (ET_EXEC)||No randomisation||14 bits||23 bits||No randomisation||13 bits|
|Heap randomisation test (PIE)||12 bits||18 bits||23 bits||12 bits||14 bits|
|Main executable randomisation (ET_EXEC)||No randomisation||No randomisation||15 bits||No randomisation||No randomisation|
|Main executable randomisation (PIE)||12 bits||12 bits||15 bits||12 bits||12 bits|
|Shared library randomisation test||10 bits||3 bits||17 bits||10 bits||12 bits|
|Stack randomisation test (SEGMEXEC)||10 bits||19 bits||23 bits||19 bits||19 bits|
|Stack randomisation test (PAGEEXEC)||10 bits||19 bits||23 bits||19 bits||19 bits|
As can be seen in the table above, Debian, Fedora, OpenSuse and Ubuntu provide a similar level of randomisation for most of the tests. Debian does not provide randomisation for the heap due to the previously mentioned bug. The same result for OpenSuse is a result of the ‘randomize_va_space’ value being set to 1. It should also be noted that in general, the results indicate that the Debian kernel provides less entropy than other three previously mentioned distributions. An exceptionally low value of randomisation is produced by Fedora for shared libraries. This would make it easier for an attacker to successfully exploit a vulnerability using ‘return-to-libc’ or ‘return-to-code’ attack as the addresses would be easily guessable or possible to bruteforce. The results for Gentoo with its PaX kernel shows that it is possible to achieve better randomisation as every result shows more entropy that of any of the other tested kernels. It should also be noted that the higher values for Gentoo are also the result of the gcc settings which in this case are configured to create Position Independent Executables (ET_DYN) by default.
As described in this article, kernel security settings have a critical impact on the overall security of processes that are executed on Linux systems. Also, a default Linux kernel facilitates the success of a number of exploitation techniques which are because of low randomisation entropy, a vulnerability is therefore not as difficult for an attacker to exploit. A more positive conclusion from this testing is that with patches like Grsecurity, it is possible to significantly improve the security of the kernel and thus the security of the whole system. The installation of this component is therefore something that is highly recommended as part of any Linux deployment. The conclusion can also be reached that whichever distribution you choose to run there are tools available to improve the security over that of the default install as well as those that can help you to measure the robustness of your systems.
The next article in this series will describe different attack strategies that could be used to achieve successful exploitation if control over execution has been gained by an attacker. The assessed software will include the popular OpenSSH daemon and a known vulnerable version of Firefox. To make sure you get to see the results as soon as they are published be sure to subscribe to our RSS feed, or follow us on Twitter.