The Kernel Self-Protection project aims to make Linux more secure
Kernel Keeper
Security vulnerabilities in the kernel often remain undetected. The kernel hacker initiative, Kernel Self-Protection, promotes safe programming techniques to keep attackers off the network, and, if they do slip through the net, mitigate the consequences.
Any Black Hat who finds a previously unknown vulnerability in the Linux kernel has hit the jackpot. Potentially millions of servers and embedded devices are suddenly open to attack, and the attacker can usually gain root privileges. Users clearly don't want this to happen, and kernel makers try to prevent such events.
Based on pure theory, strict coding standards and a sophisticated software quality management system ensure that loopholes are found immediately and eliminated before the release. A shining and rare example is OpenBSD, which in 20 years' time has only had two significant security breaches [1]. Although I am certainly a supporter of the vigilant approach followed by OpenBSD, we have to be realistic: The Linux kernel contains mountains of code that no one can review with the required depth; dependencies vary, and thus so do the possible attack vectors. (See the box entitled "Harmless Start" for a complex example.)
Harmless Start
Some security breaches are well hidden. CVE-2015-7547 started with an innocuous-sounding bug report on Glibc 2.20 [2]: A programming error that had probably existed since Glibc 2.9 caused a program crash. Six months later, it turned out that a skillful combination of access turned the error into an attack.
The Glibc programmers had provided a buffer of 2048 bytes on the stack for a DNS response, which they wanted to put back on heap if the response was larger. But this sometimes went wrong, because the function queried an IPv4 and an IPv6 address and tried to use the rest of the buffer in the second answer. Only if that didn't work was the bigger buffer requested on heap. However, the variable that held the pointer for this buffer was not updated, so that access to the stack continued. A stack overflow was made possible by the fact that the size check no longer fit the situation.
Taking advantage of this (now remedied) error situation involves some complications for the attacker: For example, they would have to send DNS packets larger than 2KB. According to the vulnerability investigators, the attacker therefore either needs control over a DNS server or at least the ability to spoof DNS packets as the man-in-the-middle [3]. Alternatively, an attack could also be triggered by clever timing. To do this, the second response has to arrive after the time out and the first response needs to be larger than 2KB. For the second response, Glibc then incorrectly sets the buffer size to "large," while it only allocates a small buffer to the stack. This allows a stack overflow.
Because the attack requires an unusual combination of parameters and complex dependencies, it is difficult to detect. One measure that could help detect such an attack is a canary (described later in this article). If a check reveals the canary value has changed, a buffer overflow has occurred. Other measures include address space layout randomization. A non-executable stack would have made the attack more difficult and thus reduced the risk.
The complexity of the Linux kernel means that it is likely to carry legacy ballast and bugs for an indefinite period of time. At the end of 2010 [4], Jonathan Corbet checked how long the safety-relevant bugs eliminated in that year had existed until discovered: 22 of the 80 loopholes examined had been in the code for more than five years!
Practical experience leads to an approach that simultaneously makes attacks more difficult and reduces the consequences of exploitable code weaknesses. This two-pronged approach is the goal of the Kernel Self-Protection [5] project.
Break-In Technology for Everyone
Viewed through the looking glass with sufficient hindsight, most attacks on programs work in a similar way. An attacker tries to add new program code to a running process, which the hijacked process then executes with its privileges. The added code can be SQL or shell commands, or typically, binary code in kernel attacks. In order to inject this code, attackers exploit programming errors that allow them to determine memory contents and manipulate the program counter.
The program counter is a CPU register that points to the next instruction to be executed. For Intel processors, the 16-bit programs had the Instruction Pointer (IP), the 32-bit world had the Extended IP (EIP), and the 64-bit world has a somewhat-morbid Relative Instruction Pointer (RIP). Almost all attacks aim to change the contents of this register in such a way that it points to one of the attacker's commands instead of the next command intended by the programmer.
Direct write access to this register is virtually impossible, which makes a small detour necessary. When a program calls a function, the program copies the return address (i.e., the point at which the program will continue running after calling the function) to the stack. Since the stack also contains local function variables that the attacker may be able to manipulate through input, this return address is a popular destination.
A Leap Back to Ruin
The attacker has several options for changing the address. A buffer overflow is a bit rough: The attacker simply overfills a variable with excessive amounts of data and floods the return address (Figure 1). Slightly more subtle attacks rely on format string vulnerabilities, which make it possible to both read and manipulate the stack.
Integer overflows are another possibility; for example, an attacker might exploit the fact that signed and unsigned integers have different value ranges. The unsigned number 128 could become a -128. If the program only checks whether a certain upper limit is complied with, for example, because there is a maximum of 100 values on the stack, -128 is technically OK. Instead of occupying memory locations 0 to 100 as the programmer intended, the range now extends from -128 to 100, which can trigger an overflow situation.
Run for Cover
An attacker can attempt to manipulate the stack or heap. Both are storage areas in which data is normally stored. Therefore, a popular avoidance strategy, which Kernel Self-Protection also advocates, is to mark memory areas as either executable or non-executable (in other words, to declare some areas as code and others as data). Newer CPUs have a bit for this in the page descriptor. AMD refers to this as the No Execute (NX) and Intel as the Execute Disable (XD) bit. Linux kernel supports write protection in this context.
Although these access rights do not provide 100 percent protection, they at least make the attack more difficult. For example, advanced attackers could still gain control of the system with a Return to Libc attack, which writes the entry address of execve()
to the stack along with suitable parameters [6] [7].
Successful attackers first only manage to influence the memory of a hijacked process. It's a good thing that modern processors contain functions that separate the kernel memory from application memory. In this case, a kernel function must not execute any commands that have been injected into the user space. This protection also makes attacks more difficult.
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.
-
Fedora 41 Released with New Features
If you're a Fedora fan or just looking for a Linux distribution to help you migrate from Windows, Fedora 41 might be just the ticket.
-
AlmaLinux OS Kitten 10 Gives Power Users a Sneak Preview
If you're looking to kick the tires of AlmaLinux's upstream version, the developers have a purrfect solution.
-
Gnome 47.1 Released with a Few Fixes
The latest release of the Gnome desktop is all about fixing a few nagging issues and not about bringing new features into the mix.
-
System76 Unveils an Ampere-Powered Thelio Desktop
If you're looking for a new desktop system for developing autonomous driving and software-defined vehicle solutions. System76 has you covered.
-
VirtualBox 7.1.4 Includes Initial Support for Linux kernel 6.12
The latest version of VirtualBox has arrived and it not only adds initial support for kernel 6.12 but another feature that will make using the virtual machine tool much easier.