This is a first of a series of articles describing different security mechanisms and exploitation mitigation techniques available in Linux environments and their use across various Linux distributions. This article focuses solely on userspace protections. Subsequent articles will focus on specific software such as web browsers or network daemons and their security exposure as well as additional kernel security mechanisms and frameworks that are available.
Memory corruption attacks are still a very common way to compromise a modern computer system. However, the once basic techniques of buffer overflows have evolved into more sophisticated memory corruption attacks and at the same time the mechanisms to protect the integrity of processes and system memory has also improved.
Probably every attack mitigation technique that has been developed over time to mitigate memory corruption exploits can be implemented and used in one form or another in a Linux operating system. Many people argue that some of the protection mechanisms are more effective than others, for example by mitigating against a larger number of different attacks. Furthermore, a number of them arguably have a performance impact on the system and could also produce some challenges in terms of compilation of the specific software as well as in ensuring compatibility with the rest of the system.
It is probably safe to say that these two factors (the impact on performance and the maintenance overhead) are the biggest obstacles to have all of these mechanisms implemented and enabled by default in any given Linux distribution. As with any complex software and development effort (particularly those that are open source), it comes with no surprise that it is possible to find some political or at least ideological issues at play as well (e.g. the wide ranging discussions between Grsecurity and the main kernel development teams). Therefore, as we will see within this document different distributions have different mechanisms and protection enabled by default. A comparison has been completed between the default stable releases of five popular Linux distributions – Debian, Fedora, Ubuntu, OpenSUSE and Gentoo Hardened relating to key protection mechanisms which can contribute to the overall security of the system. The author believes that the first four named distributions would be most prevalent across enterprise environments; Gentoo Hardened has been chosen as a comparison point, as this distribution aims to provide the user with the ability to have all of the considered security mechanism enforced system wide with relative ease, although with much greater time requirements due to the need of compilation of the software.
The exact versions of Linux distribution that have been assessed are as follows:
Stable x86 32-bit releases were used and all systems were tested within virtualised environments using KVM virtualisation. All systems were tested on an Intel processor with native support for the NX bit and virtualisation.
The following security mechanisms were considered:
The following tools have been used for comparison:
All sample data was taken with a user logged into an X session with a running sshd daemon and Mozilla Firefox browser. As the distributions varied in the number of processes that were started by default, in some case a single process had a bigger impact on the overall percentage score as for instance Ubuntu was by default running 96 processes versus 34 processes on Gentoo!
According to its Wikipedia entry, the Linux kernel has NX bit support since the 2.6.8 release in October 2004. It is enabled for both 32-bit and 64-bit kernels for hardware that has the NX bit. In order to take advantage of non-executable memory mappings on hardware that does not support the non-execute bit, it is necessary to use the grsecurity patch or alternatively the Exec Shield patch from Red Hat. It should also be noted, that without additional protections an attacker could easily bypass the NX bit for instance by manually adjusting the permissions that are set on a given memory page. Nevertheless, as this is a realm of the Linux kernel and will therefore be discussed in greater detail in future articles.
It should also be noted that apart from the NX bit support, all of the protection mechanisms discussed here are to be implemented in the userspace and not at the kernel level. It is important to acknowledge that a vulnerability within the Linux kernel could be exploited regardless of the protection mechanisms that are enabled for the user space processes. It is possible to compile modern kernel with Stack Smashing Support (SSP) (using the gcc -fstack-proctector option enabled in kernel configuration), although additional kernel security enhancements would often require applying external patches such as the grsecurity patch. Some distributions tend to maintain their own set of kernel patches that include some additional security features. Nevertheless, as mentioned earlier kernel security issues are excluded from this article and will be included in a future part.
A closer comparison of all of the five distributions revealed differences in approaches to security mechanisms that are enabled by default. The detailed results of the testing are presented and described below (all graphs are shown as a percentage of all running binaries).
RELRO protects internal data structures of an ELF file from being used by an attacker to gain control of the execution flow. This is achieved by modifying the PLT (Procedure Linking Table) or GOT (Global Offset Table) sections of the ELF file. When full RELRO is used, the whole GOT is marked as read-only when the process is running and therefore cannot be modified by an attacker. For partial RELRO, only PLT-GOT is marked as read-only. Both options also reorder internal ELF data structures, such that internal structures precede other sections and as such are protected should an attacker be able to overflow other ELF sections. It should be noted that full RELRO requires all symbols to be resolved during the start-up of a binary (so the GOT can be marked as read only) and as such could result in a longer start-up time for big applications that would need to load large number of symbols for all shared libraries.
As can be noted from the diagram above, Debian and Fedora do not take full advantage of this mechanism, having it enabled in partial mode only for a small percentage of binaries. In our test case, Fedora and Debian did not support full RELRO for any of the binaries that were run by default. On the other hand, OpenSUSE and Ubuntu supported at least partial RELRO for all processes and full RELRO for small number of them (with Ubuntu reaching about 14% of all tested binaries). Gentoo Hardened had the full RELRO option enabled for all processes but one, which had partial RELRO enabled (Xorg). On the other hand, the only running binary in OpenSUSE during our test which had full RELRO enabled was pulseaudio. Given previous security issues in pulseaudio this is a good thing; however, it is worth noting that OpenSUSE does not use the setuid bit on this binary. The only binary on Debian that had partial RELRO support was the ‘dbus-daemon’ process.
Stack Smashing Protector makes it more difficult to exploit buffer overflow vulnerabilities by implementing additional security checks on the process stack. The SSP extension has been available in the GCC compiler since the 4.1 release.
As can be observed on the graph above, it is quite prevalent in Fedora, OpenSUSE and Ubuntu, with all of these distributions varying around 80% of all processes that have this feature enabled. Remarkable differences can be observed in Debian and Gentoo, where about 10% of checked processes have it enabled. In case of Gentoo Hardened this is due to internal issues which needed to be addressed before this functionality could be made available globally. This has been recently addressed and is currently available in a testing release of the gcc compiler and is expected to be moved into stable tree after a 30 days of testing period.
The FORTIFY_SOURCE is a protection mechanism that attempts to prevent buffer overflows by replacing insecure functions with their secure equivalents as well as terminating execution of a process if an overflow has been detected. It should be noted that where buffer sizes cannot be determined during compilation or runtime, some exploits might be undetected by this mechanism. It also adds additional protections against format string vulnerabilities. More information about FORTIFY_SOURCE can be find here and here
It should be noted that the script used to assess the presence of the FORTIFY_SOURCE only checked if a symbol for an ‘armoured’ version of a function is present in a given binary. In theory, it is possible for a binary not to use any ‘fortified’ function, or a condition which cannot be fortified (unknown buffer sizes prior to execution) which could result in a false positive result. Also, if the sizes of all buffers are known before compilation, there is no need to use fortified function and as such it will not be replaced. This again could result in a false positive. For the FORTIFY_SOURCE option to be used, the binary needs to be compiled with the optimisation flag set to at least value of two. This is done by passing the ‘-O2’ option to gcc during compilation.
As can be observed from the graph above, all compared distributions except for Debian make good use of this protection with the amount of enabled binaries reaching around 80% percent. Gentoo reaches around 90% which could indicate that other distributions could potentially enable it for few more binaries, although it should be appreciated that Gentoo runs far fewer processes by default than its counterparts. In this test, Debian had this feature enabled on two processes (3%) of all those that were tested, both of which belonged to the kerneloops package.
The ability to predict memory addresses of a process and its sections is a great aid for an attacker attempting to exploit a system. Although in recent years more memory randomisation support has been incorporated into operating systems, most of the binaries will still be loaded under the same address in any given Linux system. Unless they have been compiled as a Position Independent Executable (PIE) that is. Having a binary compiled as PIE allows the operating system to load and map all of the process sections at random addresses during runtime. This makes exploitation much more difficult provided that randomisation provides an acceptable level of entropy; a 64 bit system will be able to provide much better randomisation than a 32 bit system due to bigger address range available to the operating system.
All modern kernels have the ability to randomise certain memory segments of a process address space even if it has not been compiled as a position independent executable (PIE). This is controlled via the randomise_va_space setting in the proc filesytem. When set to 2, Linux will randomise stack, heap and mmap() requests, when set to 1, only stack space will be randomised. Nevertheless, as mentioned earlier, there could be a number of other memory sections of a process that would be mapped at the same memory addresses. Hence compilation of code as PIE/PIC allows the loader to run an entire binary from different memory regions each time, thus forcing an attacker to guess or bruteforce the correct memory mappings. It should be noted that Position Independent Code can have performance hit on x86 architectures, which is assessed by different sources to be usually between 1% and 5%. This can especially occur if a compiled binary needs to use non-PIC code using so called text relocation mechanism (TEXTREL). Moreover, some software, specifically if using machine specific assembly code, might refuse to compile as PIE all together.
As can be seen on the graph above, most of the compared distributions do not take advantage of this mechanism on the large scale. Values differ from 8% (Debian) to OpenSUSE (21%) while Gentoo Hardened achieves 100%. It should be noted that other distributions have this feature enabled for ‘high risk’ processes such as network enabled services like SSH. However, apart from Gentoo Hardened, only Ubuntu enables PIE by default for the Mozilla Firefox browser. Given the track record for vulnerabilities that have been discovered in a number of different browsers and attackers interest in them, one could expect that the binary is better armoured to withstand such attacks. Experience shows that the end user will very likely rely on and never change the default security measures that are enabled by a vendor. It would therefore be a good practice to embed in a rollout process of at least most critical binaries, an assessment of security measures that are enabled during package creation by distribution maintainers.
As has been discussed within this paper a number of effective and well tested security measures exist in a Linux environment; however, with minor exceptions they are not widely used or deployed by default. Also, different Linux vendors tend to have different approaches to using specific mechanism in their distribution. Although in some case more exposed binaries (like network daemons) have additional protection enabled (mostly PIE), others, like web browsers, mostly didn’t. Where they were enabled, apart from instances mentioned earlier, it seemed that this was more of an accidental use or a mainstream developer decision rather than a consistent distribution policy.
As proved many times before there are no silver bullets to all security woes for Linux users. Different distributions will have different security mechanisms enabled by default and there seems to be no clear winner in this contest. While the Gentoo Hardened project can provide you with the highest use of the available userspace memory protection mechanisms, it is definitely not for faint hearted and you probably need to get your hands dirty to get the most of it. Maybe it is time to start asking your favourite distribution about their policy for security mechanisms described above?
The next article will focus on default and additional protections within the kernel itself which are enabled in the Linux distributions described above. This will be then followed by closer view on the practical side of exploitation of Mozilla Firefox and OpenSSH binaries running on different distributions with different protection mechanisms enabled.