Nexix Security Labs
Linux Kernel Bug Found and Fixed
The majority of reported Linux "security" problems aren't truly Linux bugs. CrowdStrike's research on the top Linux-based malware families, for example, was really about system management security errors with telnet, SSH, and Docker, not Linux at all. However, this does not rule out the possibility of security flaws in Linux.
GRIMM, a cybersecurity firm, has revealed an interesting trio of Linux kernel defects they discovered in code that had been hanging around unnoticed for 15 years.
No one else appears to have looked at the code in that period, at least not diligently enough to identify the problems, thus they've been patched, and the three CVEs they discovered have been fixed:
CVE-2021-27365. Exploitable heap buffer overflow due to the use of sprintf()
CVE-2021-27363. Kernel address leak due to pointer used as a unique ID
CVE-2021-27364. Buffer overread leading to data leakage or denial of service (kernel panic)
The flaws were discovered in the kernel code that handles iSCSI, a component that allows you to communicate with SCSI devices such as tape and disc drives that aren't directly attached to your computer via the network.
Of course, if one doesn't use SCSI or iSCSI on the network any longer, they're probably saying to themselves, "No worries for me, I don't have any of the iSCSI kernel drivers installed because I'm just not utilizing them."
After all, flawed kernel code can't be abused if it's just lying on disc; it needs to be loaded into memory and being used before it can create problems.
Except that most (or at least many) Linux systems not only come with hundreds (if not thousands) of kernel modules in the /lib/modules directory tree, ready to use if ever needed but also come configured to allow adequately authorized programs to initiate module loading on demand.
GRIMM took a second look at the above-mentioned problems because of the possible risk posed by unloved, underutilized, and mainly ignored drivers.
The researchers were able to locate software that an unprivileged attacker might use to activate the defective driver code they had discovered, and they were able to create workable exploits that could do a variety of things, including:
Perform privilege escalation to give an ordinary user, kernel-level abilities.
Extract kernel memory addresses to aid other assaults that require the location of kernel code loaded in memory.
Crash the kernel and the whole system with it.
Read snippets of data out of kernel memory that was supposed to be off-limits.
The data that an unprivileged user would be able to peek at could include pieces of data being transported during actual iSCSI device visits, as uncertain and limited as the final exploit sounds.
If that's the case, a thief with an unprivileged account on a server that uses iSCSI might theoretically execute an innocent-looking software in the background, sniffing out a random selection of privileged data from memory.
Even a fragmented and unstructured stream of confidential data grabbed occasionally from a privileged process (remember the infamous Heartbleed bug?) might let deadly secrets slip through the cracks.
Remember how easy it is for computer software to recognize and "scrape up" data patterns like credit card numbers and email addresses as they pass through RAM.
The first problem in this group was caused by the "use of sprintf()," as we described earlier.
That's a C function that stands for formatted print into the string and allows you to save a text message in a block of memory for later use.
For example, consider this code:
char buf; /* Reserve a 64-byte block of bytes */ char *str = "42"; /* Actually has 3 bytes, thus: '4' '2' NUL */ /* Trailing zero auto-added: 0x34 0x32 0x00 */ sprintf(buf,"Answer is %s",str)
would leave the 12 characters "Answer is 42" in the memory block buf, followed by a zero byte terminator (ASCII NUL), and 51 undisturbed bytes at the end of the 64-byte buffer.
Sprintf(), on the other hand, is always unsafe and should never be used because it doesn't check if the printed data will fit in the final memory block.
If the string in the variable str is longer than 54 bytes, including the zero byte at the end, it will not fit into buf with the added text "Answer is ", as seen above.
Even worse, if the text data str doesn't have a zero byte at the end, which is how C indicates when to finish copying a string, you may go up copying hundreds or even millions of bytes in memory until you reach a zero byte, at which point the kernel would almost likely crash.
Instead of using C procedures that can conduct memory copies of any length, modern code should utilize snprintf() and its colleagues, which format and print at most N bytes into a string.
The second bug arose from the use of memory addresses as IDs.
That seems like a reasonable idea: if you need to identify a data object in your kernel code with an ID number that won't conflict with any other object in your code, you can simply use the numbers 1, 2, 3, and so on, adding one each time, and solve the problem.
"Why not use the memory location where my object is kept, because it's obviously unique, given that two objects can't be at the same place in kernel RAM at the same time?" you might think if you want a unique identifier that won't clash with any other numbered object in the kernel. (Not unless there's already a memory-usage crisis.)
The problem is that if your object ID is ever visible outside of the kernel, for example so that untrusted programs in "userland" can refer to it, you've just given away knowledge about the internal layout of kernel memory, which isn't meant to happen.
KASLR, or kernel address space layout randomization, is a feature in modern kernels that prevents unprivileged users from finding out the kernel's exact internal structure.
If you've ever tried lock-picking (a popular and surprisingly calming activity among hackers and cybersecurity experts – you can even buy transparent locks for educational purposes), you'll know that knowing how the lock's mechanism is laid out internally makes it a lot easier.
Similarly, knowing exactly what's been loaded where inside the kernel almost always makes other bugs such as buffer overflows much easier to exploit.
WHAT TO DO?
Update your kernel. If you rely on your distribution's author for new kernels, make sure you have the most recent version. The versions in which these problems were fixed are listed above.
Don't use C programming functions that are known to be troublesome. Any memory accessing function that does not keep track of the maximum quantity of data that can be used should be avoided. Keep note of your operating system's or programming environment's officially documented "safe C string functions" and use them wherever possible. This increases your chances of avoiding memory overruns.
Don't use memory addresses as handles or "unique" IDs. If you can't use a counter that increments by one every time, use a random number with at least 128 bits. Universally unique identifiers, or UUIDs, are another name for this. On Linux and macOS, use /dev/urandom, and on Windows, use BCryptGenRandom().
Consider locking down kernel module loading to prevent surprises. If you set the kernel.modules disable=1 system variable in Linux, No more modules can be loaded after your server has booted up and is functioning correctly, whether by accident or by design, and this configuration can only be switched off by rebooting. Use sysctl -w kernel.modules disable=1 or echo 1 > /proc/sys/kernel/modules disable to disable kernel modules.
Consider identifying and keeping only the kernel modules you need. You can either develop a static kernel with only the necessary modules or create a kernel package for your servers that removes any extraneous components. If you use a static kernel, you can disable module loading entirely.
For more information visit us on: www.nexixsecuritylabs.com
To schedule an audit you can contact us at: firstname.lastname@example.org
Your Security | Our Concern