Security testing is becoming essential for every business. Undetected bugs and security vulnerabilities can lead to expensive consequences or even losses that businesses can’t recover from.
Security issues are usually considered when developing web and SaaS solutions. However, security matters for each and every product, no matter what goals it serves and what technologies it uses. At Apriorit, we always include security testing in our quality assurance strategies.
In this article, we focus on some crucial aspects of Linux security testing. When developing a system for protecting Linux virtual machines with the help of a Linux kernel module, we pay attention to the safety of the most essential component — the kernel module. Security issues when loading and running a kernel module can make virtual machines vulnerable and prevent the entire system from functioning properly.
Below, we share our experience testing the security of Linux kernel modules. This post will be useful for developers and quality assurance engineers who are working with Linux kernel development and care about Linux kernel module testing.
Security testing of a Linux kernel module has to start at the earliest stage — the moment the kernel loads when the system boots. The reason is that hackers can find vulnerabilities at this very stage and use them to alter or compromise the module.
This is why it’s best to start Linux kernel module tests with examining the initialization system and identifying weaknesses in it.
When a system boots, the kernel itself loads the majority of kernel modules. Additional modules are loaded later if they’re mentioned in etc/modules. Modules are loaded together with their dependencies by the modprobe daemon. Also, kernel modules have options that can be set using the configuration files in /etc/modprobe.d/> or /lib/modprobe.d/.
Thus, our first potential vulnerability hides in configuration files in modprobe.d. Using configuration files, cybercriminals can stop a module from loading when the system starts.
There are several ways to stop a module from loading. Let’s explore the three most common scenarios in detail.
Adding a module to the blacklist
One possible way to stop a kernel module from loading is by blacklisting it in a configuration file. There are two ways to do that.
1. Create a *.conf file> in /etc/modprobe.d with the command "blacklist <module name>".
Here is an example for Ubuntu and the r8169 module for a network card driver:
When the system boots, the r8169 module won’t be loaded.
However, you can load it manually using this command:
Note that you can add "blacklist <module name>" to any existing *.conf in the /etc/modprobe.d folder.
2. Create a *.conf file in /lib/modprobe.d with "blacklist <module name>".
Example for Ubuntu:
The result is the same: the network card driver won’t load when the system boots.
Disabling a kernel module
Apart from adding modules to a blacklist, there are other ways to disable them so they can't be loaded.
To disable a module, you can redirect it using the
install command. If you do so, modprobe will try to load the connected file and define the module incorrectly (e.g. as /bin/true or /bin/false), so it won’t be loaded.
1. Create a *.conf file in /etc/modprobe.d with the command "install <module name> /bin/false" or "install <module name> /bin/true":
Once again, the r8169 module won’t be loaded when the system boots.
Note that you can add "install <module name> bin/true" to any existing *.conf file in the /etc/modprobe.d folder.
2. Create a *.conf file in /lib/modprobe.d with "install <module name> /bin/false" or "install <module name> /bin/true", as shown in the example below:
When the system boots, the r8169 module won’t be loaded.
Blacklisting a module through the GRUB bootloader settings
Apart from configuration files in /etc/modprobe.d/ and /lib/modprobe.d/, another potential vulnerability lies in the GRUB bootloader settings, which also allow you to blacklist kernel modules.
For example, you can add this code to /etc/default/grub:
In this case, the module specified won’t be loaded when the system boots.
When performing Linux kernel module security testing, it’s critical to check major vulnerabilities that may allow for a specific kernel module to be prevented from loading. The system under test has to check the kernel module and protect it from failing to load.
Apart from loading a kernel module using modprobe, you can also try to load it automatically in the init system. Although this is an unusual way to load a kernel module, it can be used in a system under test.
Let’s explore how automatic kernel loading works in the init system.
When a kernel module loads, the init system mounts a root filesystem. Then the init program launches and initializes the system under test.
Initialization settings are described in the etc/inittab configuration file. One of the essential settings is runlevel — a preset operating state for a Unix-like operating system.
There are seven runlevels for Linux initialization systems:
- 0 — System halt
- 1 — Single-user mode
- 2 — Multi-user mode without networking
- 3 — Multi-user mode with networking
- 4 — Not used/user-definable
- 5 — Multi-user mode with networking, display manager, and X11 support
- 6 — System reboot
Once the runlevel is determined, init runs symbolic links from the /etc/rc.d/rcN.d catalog, where N is the runlevel number.
Links are named according to the format S<num><name>, so both the name and the order number of the script are included. Scripts, which are referenced by links, are stored in the /etc/rc.d/init.d folder.
If you store a kernel module startup script in the /etc/rc.d/init.d folder and put links to this script in the etc/rc.d/rcN.d folders, the kernel module will load after the system starts without using modprobe.
This means there are other potential vulnerabilities in the kernel module loading process. If a kernel module loads through a script in /etc/rc.d/init.d, hackers can prevent it from loading by:
- Changing or removing the startup script in /etc/rc.d/init.d
- Changing or removing symbolic links in the /etc/rc.d/rcN.d folders
When testing a system, it’s critical to check whether it can detect and react to any changes in automatic script loading and symbolic links to required runlevels.
Kernel modules are responsible for various processes. Thus, you have to make sure that the module you’re going to load into the Linux kernel is the one you planned to load.
Such a simple mistake as loading the wrong module can lead to serious consequences:
- Data leakage
- Data disruption
- Forced data encryption
- Corruption of kernel module functionality
- Kernel panic in the Linux kernel
A kernel panic is a safety measure taken by an operating system’s kernel upon detecting a fatal internal error from which the kernel is either unable to safely recover or after which the system cannot continue to run without an elevated risk of major data loss.
To prevent negative consequences, always validate a kernel module before loading it into the Linux kernel. This can be done in several ways.
Let’s take a closer look at the two most popular methods for validating a kernel module.
Validating a kernel module hash
Any modification to a module leads to a change in its hash. To validate the correctness of a modification, you can use the Secure Hash Algorithm 1 (SHA-1).
Verifying files with SHA-1 is done by producing a checksum before the file has been transmitted, then again once it reaches its destination. The transmitted file can be considered genuine only if the checksums are identical.
If any unauthorized modifications appear or malicious module is disguised as yours, the checksum will uncover the attempt to compromise your hash. Chances that hackers will manage to come up with the correct checksum are more than low.
Verifying a kernel module signature
When a module is compiled, it’s signed with a unique company certificate that can’t be fabricated or emulated. If the module is signed with any other certificate, this will be identified when validating the module's signature.
This means that even if cybercriminals manage to replace the kernel module and keep the checksum of an original file, there’s no chance they can sign the module with a unique company certificate.
Whether there’s no signature or the certificate is wrong, this issue will be identified during the validation stage before loading the module to the Linux kernel.
Using Kali Linux for Penetration Testing
Once you’ve finished security testing for the kernel module during loading, it’s time to move to the next step — protecting the initialized module. After a module is initialized, hackers still have opportunities to remove or replace it.
First of all, you need to secure the module file and control manipulations with modprobe that affect module loading.
Also, pay attention to any PCI devices connected to the module. Check for possible device manipulations, as these may lead to loss of kernel module efficiency.
Finally, if the module loads automatically in the init system, you should check and manage possible manipulations with scripts for automatic loading or with symbolic links at required runlevels.
Let’s explore each of the potential vulnerabilities mentioned above in detail.
Controlling operations with the module file
It’s crucial to make sure the kernel module performs correctly and is not unloaded, modified, or removed.
Therefore, you have to check the primary file operations with the module file:
- Transfer to another folder
- Modify the module
- Modify attributes
- Replace the module file
Also, check these operations for module unloading:
- Modprobe -r
All of these operations have to be blocked for both regular users and root accounts.
Controlling operations with a PCI device
Since a large part of a running kernel is drivers, and drivers are installed on devices, it’s also important to check operations related to PCI devices.
The operations you have to check are:
- Unbind driver from device
- Disable device
- Remove device
- Power off device
All of these operations can prevent the interoperability between a module (driver) and a PCI device, which can halt the module. To be more specific, these operations may lead to tampered access, data leakage, or data loss.
Controlling operations with the init system
As we’ve mentioned, there are two potential vulnerabilities in the init system you should be worried about:
- Startup script in /etc/rc.d/init.d
- Symbolic links in /etc/rc.d/rcN.d. folders
After module initialization, you have to check the essential file operations for those scripts and links. In this way, you can make sure that module settings can’t be modified to prevent the kernel module from loading.
Check the following operations with startup scripts and symbolic links:
- Modify content
- Transfer to another folder
- Modify attributes
All of these operations should be blocked for both regular users and root accounts.
We’ve already shown how important the functioning of the kernel module is and why you should protect its integrity. Now it’s time to think about kernel module memory.
It’s not enough to protect the module by validating its hash and signature. You also need to control the module memory.
The kernel module operates the section of Read-Write operations. By creating a record in this section, hackers may cause issues such as:
- Module or system crashes
- Running code recorded to the kernel module memory in the protected system
- Breakdown or modification of module functionality
The process of checking and securing the kernel module memory can be divided into two steps.
Step 1. Check the list of imported functions in the module right after it’s loaded into the Linux kernel
Pay attention to the functions the module uses. The readelf utility can help you by displaying a list of all functions a module uses. Note that this list will be long – no fewer than 100 functions for sure.
You have to confirm that the list of functions and all accompanying attributes are what you expected. Only by verifying this list is it possible to ensure that kernel module performance won’t be compromised.
Step 2. Secure the kernel module memory loaded to the Linux kernel in real time
This step is a bit more complicated than the previous.
To control the virtual system’s memory (including the memory of the kernel module), you have to ensure the possibility to manage the memory at the hardware or hypervisor level. You can do this using the QEMU hypervisor, for example.
You can ensure memory security in the following way:
- Set read-only permissions for all memory sections with Read-Write permissions so that the memory can’t be modified.
- Limit access to memory sections that must have Read-Write permissions so that only kernel module code and imported functions can write to them.
Kernel modules are vulnerable during all stages of their performance, from system launch to running processes in the Linux kernel.
You can’t ignore the risks related to kernel modules, as they can cause serious damage to any project and lead to significant losses.
To ensure the safety of users’ data and the reliability of your system, you need to take a complex and consistent approach to kernel module security.
In this article, we described several weak spots in Linux kernel module security and suggested ways you can address these weaknesses when running security tests.
At Apriorit, we leverage the experience of our professional QA team that never stops exploring potential new threats and ways to prevent them. Contact us if you’re looking for robust security testing services. We are always ready to help!