Most cyber-attacks are financially motivated, so attackers constantly come up with new ways to breach data. While the amount and sophistication of such attacks are constantly increasing, most of them are based on memory-corruption vulnerabilities—a problem that has been persisting over the last four decades. To better fight against cyber-attackers, administrators who understand memory corruption can leverage this knowledge to proactively defend infrastructure. This guide will provide administrators with information to help them better understand memory corruption and the aftermath should an attacker exploit the vulnerability.
- Linux Kernel Vulnerabilities:
- Buffer Overflow
- Integer Overflow
- Memory Corruption
- Denial of Service
- Missing Pointer Check
- Missing Permission Check
- Uninitialized Data
- What are the Most Hazardous Memory Vulnerabilities
- How are Memory Corruptions Issues Detected?
- How to Mitigate Memory Corruption Vulnerabilities?
- How to Protect Your Infrastructure from Memory Corruption Vulnerabilities?
Linux Kernel Vulnerabilities
The Linux kernel can be vulnerable to a variety of memory issues. The following memory-based vulnerabilities are where attackers focus efforts and are the foundation for many of the security patches released every month. If you’ve ever wondered why a specific vulnerability requires a critical patch, the following issues can lead to critical data breaches.
A buffer overflow is a condition where developers do not perform validation checks on variables before allowing assignment of data. If the data is too large for the variable definition, an “overflow” occurs that allows an attacker to inject their own code in adjacent memory space. If the code design does not validate data, a buffer overflow could give an attacker root access or perform remote code execution.
Some languages are not vulnerable to buffer overflows (e.g. Java and Python), but Linux is written in C, which is vulnerable to overflow attacks. Here is a small example of vulnerable C code:
In the above code, a user is prompted to enter their password. If the user enters “correct_password” as their password, the code validates that input is correct and performs kernel actions. The password variable is set to an 8-character array limit, but what happens when a user enters 10 characters? Since there is no validation that the password entered isn’t more than 8 characters, the extra characters overwrite the password_validated variable information in memory and allow the kernel actions to execute.
Linux is open-source, so an attacker who reads this code in Linux source code (or any software code for that matter) can write exploit code to take advantage of the flaw and execute kernel actions without necessary privileges.
CVE-2019-10126 is an example of a Linux buffer overflow vulnerability. In this CVE, you can see that an attacker could exploit the vulnerability to modify system files or other stored information.
When developers create an integer variable, they define the maximum value that can be stored using the data type. For instance, a 32-bit unsigned integer variable can store a value from 0 and 4,294,967,295 or a signed integer between −2,147,483,648 and 2,147,483,647.
What happens when code adds 1 to a variable storing the value 2,147,483,647? A statement executing 2,147,483,647 + 1 will likely return a negative value of -2,147,483,648. In other words, a positive integer flips to a negative value and a negative integer overflow will result in a positive number. For unsigned integers, the results are dependent on language, but for the C language, results are mainly unpredictable.
Integer overflows have two risks:
- If developers calculate the length of a buffer and assign the result to a variable without enough space, a buffer overflow occurs and the application is vulnerable to buffer overflow attacks.
- In financial applications, account funds could be incorrect and the organization could lose or gain money incorrectly.
CVE-2018-14634 is an example of an integer overflow vulnerability in Linux that allows an attacker to escalate privileges on the system.
Memory corruption in the Linux kernel can be frustrating for developers and server administrators experiencing issues. Errors caused by memory corruption can result in random behavior and sometimes throw no errors at all. Memory corruption happens when developers inadvertently modify the wrong data in memory or alter pointers that point to specific memory locations where data is stored.
Programming errors causing memory corruption come in large varieties, but they can be broken down into two categories:
- Heap memory errors
- Stack memory errors
To summarize these two categories, these errors occur when code attempts to read or write memory that was freed or read and write to an out-of-bounds array.
CVE-2018-16884 is an example of a kernel memory corruption error that caused a system panic and possible privilege escalation if exploited.
Denial of Service
Most people have heard of distributed denial-of-service (DDoS) where attackers use several hijacked devices to flood a target with enough traffic to crash the server, but a denial-of-service (DoS) is the term used for any attack that disrupts any service. A memory-based DoS is referred to as a resource exhaustion attack, because they are often caused by improper memory management. If these vulnerabilities exist on servers, an attacker could crash critical services used by thousands of users.
CVE-2018-5390 is an example of a denial-of-service created from the way Linux handles TCP sessions.
Missing Pointer Check
A pointer in C is a variable that points to a memory location that stores a value as opposed to a variable that stores a value in memory. This distinction is important in C programming as developers can change a value in memory for a specific variable even if that variable is out of scope.
An example of a NULL pointer reference is below:
In the above code, the memory address of the variable x isn’t assigned to the pointer until after it’s called in the printf() function. The printf() function therefore calls an invalid pointer reference.
CVE-2018-10323 details a bug in the Linux kernel where a NULL pointer dereference caused a denial-of-service.
Missing Permission Check
Memory is allocated for every program that runs on a computer, and this data must be protected from other programs. If a developer attempts to read from or write to memory space allocated to another program, a memory permission error is thrown.
When a function uses a variable to modify or create data, the variable itself must first be initialized with a value. If the variable is not assigned a value, it’s considered uninitialized and causes undefined behavior if referenced in the program.
Undefined behavior from uninitialized data is difficult to detect as C compilers are not required to diagnose or throw an error. Most developers work with testing or fuzzing tools to identify uninitialized data that could cause undefined error issues.
CVE-2019-16921 is an example of a Linux kernel vulnerability where a system file does not initialize a data structure, which could allow attackers to obtain sensitive data.
What are the Most Hazardous Memory Vulnerabilities?
Every year, MITRE publishes its Top 25 Most Dangerous Software Weaknesses report. Note that this report includes web-based attacks, but the three most common and hazardous memory vulnerabilities are:
- Buffer overflows
- Integer overflows that lead to a buffer overflow
- Pointer dereference (missing pointer check)
All three vulnerabilities can lead to severe data breaches. Some of the biggest data breaches to date are from memory corruption vulnerabilities. In a recent intelligence report, a key finding to note is that nearly 55% of attacks are application-specific. With open-source applications often a part of dependency code or as the main application itself, a savvy attacker can identify issues in code and craft an exploit against the vulnerability.
Another finding of interest from the report is that 31% of attacks target the technology industry. The technology industry is responsible for storage, networking, safeguarding, and transferring sensitive information. By targeting this industry, attackers can get access to the infrastructure used to store data and exploit it silently leaving the business breached for potentially months.
How are Memory Corruption Issues Detected?
Memory corruption stems from developer errors, but when you have multiple applications running on one machine, how do you know if any of these applications cause issues? Generally, you can identify errors in Linux system logs when the operating system detects issues. For instance, the first 64KB of memory is reserved for the BIOS and Linux will scan this memory for any changes. If Linux detects any modification of data in this memory space, it will log it as a warning for administrators.
Linux is an open-source operating system, so many of the memory-based vulnerabilities are reported by researchers. An example of a recent finding is 'BleedingTooth'. The vulnerability allows an attacker with the bd address of a target machine to crash it, cause a denial-of-service (DoS), elevate privileges, and possibly gain encryption keys.
Security researchers find bugs in software using a variety of tools and through careful review of the code. Scanning tools (e.g. fuzzing) can be used to trigger errors that can then be reported to developers. Developers patch the vulnerability and a security update is released.
How to Mitigate Memory Corruption Vulnerabilities?
Luckily for users, Linux has a number of security features to help combat memory corruption. Kernel Address Space Layout Randomization, Control Flow Integrity, and Kernel Page Table Isolation are just a few features implemented in the kernel to help mitigate attacks. Even with these mitigation features built into Linux, the operating system and dependencies are found to suffer from vulnerabilities.
Mitigation techniques depend on the vulnerability found, but many of the temporary ways memory bugs can be mitigated involve disabling a critical component. For instance, when Heartbleed made the news, administrators could disable the heartbeat used in OpenSSL to communicate between client and server. While this solved the issue temporarily as administrators waited for a fix, any application that required a heartbeat signal from the server would have unexpected results (e.g. crashes or denial-of-service).
The temporary mitigation techniques are usually not permanent and patches must be applied. Security patches are necessary to update the code and stop exploiting the vulnerability permanently, but this requires a reboot. Because of the downtime, system administrators often leave the vulnerability unpatched until the maintenance window is available. Delaying patches has led to some of the biggest data breaches to date.
How to Protect Your Infrastructure from Memory Corruption Vulnerabilities?
Since administrators are left to the mercy of developers, it’s essential that they take the right steps to stop attackers. You might not control the code, but administrators can take the right steps to minimize the network’s attack surface and avoid being the next target. Here are a few ways administrators can stop memory-based attacks:
- Run vulnerability scans regularly. Vulnerability scans will identify issues including those already reported publicly. When a vulnerability is found, a patch can be installed.
- Perform thorough testing of any third-party applications before promoting them to a production server.
- Use KernelCare’s live patching to quickly apply the latest security patches without the need for a reboot.
Memory-based vulnerabilities are difficult to detect due to the code causing them, but administrators that patch systems immediately when these updates become available provide the best protection against errors. With KernelCare, administrators don’t need to delay patching. Our live patching service is a rebootless system that patches several flavors of Linux automatically so that administrators do not leave their critical infrastructure open to vulnerabilities while waiting for a maintenance window.