Linux kernel Hardening
- Michaël Grand
- Jan 5
- 15 min read

Why hardening the Linux kernel is important?
The Linux kernel is a quite BIG thing which grows since the beginning of 90's with more than a million of commits and thousands of different contributors. It is built as a monolithic component that can be extended using either statically or dynamically linked modules. Its portability (x86, ARM, MIPS, RISC-V...) and its scalability allow its usage on various hardware platforms from the simplest embedded system to the most powerful server. Its usage is not limited to general purpose computations indeed, its PREEMPT_RT extension allows its usage in (soft) real time systems.
Being an open-source piece of software and considering its versatility and extended usage it is not surprising that the Linux kernel has been targeted by numerous hackers since its beginning. At the time of writing, CVE details counts more than 8,000 vulnerabilities and the policy change in how the Linux Foundation manages the kernel's CVE will lead to a dramatic increase in the number of CVEs impacting it. In fact, as a new CVE Naming Authority, the kernel.org organization is now free to declare and manages the kernel's CVEs and they are willing to streamline the process to make it as efficient and transparent as possible.
Given the vital role of the kernel, its large attack surface, and the considerable number of vulnerabilities identified, vendors of embedded products have no choice but to secure their kernel instances.
Preliminary setup
In the remainder of this article, we will base our work on Yocto using the branch scarthgap of the distro Poky with machine qemuarm64. We use the default configuration with debug-tweaks and ssh-server-dropbear enabled:
EXTRA_IMAGE_FEATURES:append = " allow-empty-password allow-root-login empty-root-password ssh-server-dropbear"Structure of the kernel and hardening strategies
The Linux kernel implements several specialized subsystems:
Processus management
Memory management
I/O management and file systems
Drivers
Network stacks
System calls
Security
Obviously, considering the considerable number of features and interfaces that are integrated and exposed by the kernel, the developer of a distribution can enable or disable most of them. There are (at least) five mechanisms that allow to enable, disable, or configure features in the kernel:
Kconfig: This is the natural evolution of the C macros. Kconfig and the related config files tackle most of the concerns coming with C macros. Kconfig allows to define dependencies as well as default values for the macros. It also separates their declarations from their definitions which allows to set their values without modifying the C headers. Kconfig was initially introduced with the Linux kernel and then used with various software including [Zephyr OS][4].
Device Tree: The device tree is another concept introduced by the developers of the Linux kernel. It is a data structure for describing hardware and tells the Linux kernel about the presence, configuration, and address map of hardware devices, especially in systems where hardware cannot be discovered automatically (e.g., ARM-based SoCs). The Device Tree is developed a plain text source file that is compiled in a Device Tree Blob (DTB) that can be read by the kernel. DTBs are not exclusively used to describe the hardware but are also used as container storing programs (including the kernel itself) and configurations. An example of this usage is the FIT (Flattened Image Tree) file format used by U-Boot to store, the kernel, the DTB, the initramfs and sometimes additional data useful to boot a Linux system.
Command line: As any Linux program, the Linux kernel is launched using parameters that can be set at boot to configure the way the Linux kernel should behave. This command line is used to define configuration parameters of which the value cannot be known at compile time and / or do not necessarily depend on the available hardware. It can be used to set the console's baud rate, define the path to the root drive, etc.
Dynamic modules: Extension modules can be dynamically (if the option is enabled) mounted into the kernel to extend its features or interfaces. New hardware drivers or low-level services can be implemented this way. Obviously, kernel modules have the same level of privileges (EL1) as the kernel itself.
Sysctl: This is a command that can be used to set some kernel parameters at runtime (e.g. ASLR activation or access to dmesg by non-privileged users etc.)
As you can see, securing the Linux kernel means configuring each of these mechanisms “correctly” and ensuring that this configuration cannot be changed by a malicious user using Secure Boot.
Enabling security features & removing weaknesses
As explained previously, the Linux kernel is configured using Kconfig and each configuration parameter receives a default value during its definition. At build time, the user passes to the build system a configuration file that will override the default configuration defined by Kconfig.
It turns out that configuration files provided in the kernel source code or by the SoC vendors are quite large. In the case of the ARM64 architecture the basic configuration file provided by the kernel source code sets the value of more than 1800 parameters so manually checking its proper configuration is not possible. In addition, assessing the impact of a given kernel parameter over its security is not an easy task as the role of a large number of these parameters is obscure. Finally, some parameters will have a negative impact on the kernel’s performance. Some of them will only impact the compilation time while others increase the boot time or the processor load which is far more problematic.
Keeping in mind these constraints, we can improve the kernel configuration in three separate ways:
Enabling security features contributing to the defense in depth or protecting against a specific exploit.
CONFIG_RANDOMIZE_BASE enables the randomization of the kernel base address.
CONFIG_RETPOLINE prevents the exploitation of the branch prediction mechanism by an attacker.
Disabling features that are considered vulnerable.
CONFIG_DEVMEM allows R/W access to raw memory.
Disabling features that are not used by the target.
CONFIG_ETHERNET allows to completely disable Ethernet support if it is not required.
The two first points might be hard to address due to high number of configuration parameters available. Even if kernel developers tried to group some of the interesting hardening features in a unique Kconfig file, a large number of interesting parameters are still defined elsewhere. Aware of this problem, the Linux Kernel Self-Protection Project (KSPP) proposes a selection of parameters that should be enabled or disabled to enhance the security of the kernel.
This web site also references a very handy tool, namely kernel-hardening-checker that can be used to automatically check the kernel configuration against the recommended settings. At the time of writing these lines, there are 222 recommended settings for ARM64, let us check out Linux configuration file against these recommended settings using the following command:
$ ./bin/kernel-hardening-checker -c .config
Hereafter we give an extract of the command output where you can see that there are both OK and FAIL statuses meaning our kernel configuration might be improved.
[+] Kconfig file to check: .config
[+] Detected kernel version: (6, 6, 96)
[+] Detected architecture: ARM64
[+] Detected compiler: GCC 130400
======================================================================================================================
option_name | type | reason | decision |desired_val | check_result
======================================================================================================================
CONFIG_BUG |kconfig| self_protection |defconfig | y | OK
CONFIG_SLUB_DEBUG |kconfig| self_protection |defconfig | y | OK
CONFIG_THREAD_INFO_IN_TASK |kconfig| self_protection |defconfig | y | OK
CONFIG_IOMMU_DEFAULT_PASSTHROUGH |kconfig| self_protection |defconfig | is not set | OK
CONFIG_IOMMU_SUPPORT |kconfig| self_protection |defconfig | y | OK
[...]
CONFIG_SHUFFLE_PAGE_ALLOCATOR |kconfig| self_protection | kspp | y | FAIL: "is not set"
CONFIG_FORTIFY_SOURCE |kconfig| self_protection | kspp | y | FAIL: "is not set"
CONFIG_DEBUG_VIRTUAL |kconfig| self_protection | kspp | y | FAIL: "is not set"
[]...]
[+] Config check is finished: 'OK' - 126 / 'FAIL' - 96 Considering the number of failures and the uncertainty in how a given parameter will affect the entire system security, the best approach seems to strictly follow the tool recommendation. You can do that by generating a kernel configuration fragment using this command:
./bin/kernel-hardening-checker -g ARM64 > kernel_fragment.cfgHowever, blindly applying the fixes recommended by the tool might strongly impact the performance (otherwise the kernel developers would have enabled everything by default). Kernel documentation and resources available on internet can help us to identify the most performance impacting / less useful security features in the context of embedded systems.
Option | Suitability | Remark |
CONFIG_SLUB_DEBUG | Low | Increase kernel size |
CONFIG_KFENCE | Low | Add memory error detection, increase CPU usage |
CONFIG_GCC_PLUGIN_LATENT_ENTROPY | Low | Use SW to "generate" entropy, increase compilation time and CPU usage. A TRNG + DRBG should be used instead |
CONFIG_STACKPROTECTOR_PER_TASK | Low | Use one canary per stack instead of one for the whole kernel. Increase code size and CPU usage |
CONFIG_DEBUG_CREDENTIALS | Low | Perform checks on credentials handled by the kernel |
CONFIG_DEBUG_NOTIFIERS | Low | Mainly useful for kernel developers |
CONFIG_DEBUG_SG | Low | Mainly useful for driver developers |
CONFIG_UBSAN_* (bounds, local_bounds, etc.) | Low | Looks for undefined behaviors (UB), increase CPU usage, must be supported by the ARCH |
CONFIG_UBSAN_TRAP | Low | Triggers reboot in case of detected UB, impact the system stability |
CONFIG_KASAN_HW_TAGS | Low | Only available on ARMv8.5, may increase the CPU usage |
CONFIG_SECURITY_SELINUX | Low | Prefer Apparmor over SELinux on embedded devices |
CONFIG_RANDSTRUCT_FULL | Low | Randomize kernel's struct during compilation, increase RAM footprint and CPU usage |
There are others security features that may affect the performance of a machine:
Option | Suitability | Why |
CONFIG_SHUFFLE_PAGE_ALLOCATOR | Medium | Randomizes page allocation to harden memory layout, but may disrupt CPU cache efficiency. |
CONFIG_RANDOM_KMALLOC_CACHES | Medium | Makes kmalloc caches non-deterministic, increase memory fragmentation |
CONFIG_INIT_STACK_ALL_ZERO | Medium | Initializes the stack at zero when entering a function, increase CPU usage |
CONFIG_INIT_ON_ALLOC_DEFAULT_ON | Medium | Zeroize memory pages at allocation, increase CPU usage |
CONFIG_INIT_ON_FREE_DEFAULT_ON | Medium | Zeroize memory pages on free, increase CPU usage |
CONFIG_CFI_CLANG | Medium | Verifies integrity of indirect function calls (Control-Flow Integrity). Increase compilation time, limited to CLANG |
CONFIG_SHADOW_CALL_STACK | Medium | Maintains a separate stack for return addresses. Depends on CLANG, GCC, increase CPU usage. |
CONFIG_SCHED_CORE | Medium | Enables scheduling strategies for security, may impact real-time behavior. |
CONFIG_PAGE_TABLE_CHECK | Medium | Validates page tables at runtime. Heavy in debug, useful for hardening. |
You should try to keep everything enabled and only discard the less suitable configurations if you experience performance issues.
Obviously, you should not enable configuration that have been disabled by the tool. However, you may experience some trouble considering that, for example, the tool recommends to disable dynamic loading of modules by unsetting CONFIG_MODULES. Be careful when enabling these configuration parameters.
NOTE: CONFIG_INIT_ON_ALLOC_DEFAULT_ON and CONFIG_INIT_ON_FREE_DEFAULT_ON seem to be redundant however this is not the case. Indeed, you cannot be completely sure that freed memory area is really zeroized (e.g., memory corruption, program crash, etc.).
Ultimately, considering your use case, you can disable useless features such as IPv6, Bluetooth, etc.
Protocols that could be disabled:
Option | Purpose | Why Disable |
CONFIG_IPV6 | IPv6 support | Not needed if using IPv4-only; often targeted due to complexity |
CONFIG_BRIDGE | Ethernet bridging | Only needed on network switches/routers |
CONFIG_VLAN_8021Q | VLAN tagging | Unused in basic setups |
CONFIG_BT | Bluetooth | Disable to prevent Bluetooth-based attacks (e.g. BlueBorne) |
CONFIG_WIRELESS | Wireless stack (802.11) | Remove if no Wi-Fi device |
CONFIG_CFG80211 | Wi-Fi configuration backend | Unnecessary if wireless disabled |
CONFIG_XFRM | IPsec framework | Remove if no IPsec/VPN is used |
CONFIG_INET_DIAG | Netlink socket diagnostics | May expose sensitive info to userspace |
Hardware interfaces that could be disabled:
Option | Purpose |
CONFIG_USB_SUPPORT | USB stack |
CONFIG_USB_STORAGE | USB mass storage support |
CONFIG_USB_HID | USB keyboard/mouse support |
CONFIG_FIREWIRE | IEEE1394 (obsolete) |
CONFIG_THUNDERBOLT | Thunderbolt interface |
CONFIG_MMC / CONFIG_SDIO | SD card support |
CONFIG_SERIO | PS/2 mouse/keyboard |
CONFIG_INPUT_MOUSE / KEYBOARD | Disable if headless system |
CONFIG_I2C, CONFIG_SPI, CONFIG_CAN | Disable if not using those buses |
CONFIG_PARPORT | Parallel port support |
Filesystems that could be disabled:
Option | Purpose |
CONFIG_VFAT_FS | FAT/FAT32 |
CONFIG_NFS_FS | Network file system |
CONFIG_CIFS | SMB/Windows shares |
CONFIG_ISO9660_FS | CD-ROM filesystem |
CONFIG_UDF_FS | DVD filesystem |
CONFIG_FUSE_FS | Userspace file systems (can be used for privilege escalation) |
Linux kernel command line hardening
When launching Linux, the bootloader passes a command line to the kernel binary. This command line can be read from the command line interface:
$ cat /proc/cmdline > kernel_cmdline
The kernel-hardening-checker tool also has an option allowing to check the security of the command line. By passing the tool on our default poky distribution with the recommended configuration (except for settings that are less suitable) we obtain the following list:
$ ~/kernel-hardening-checker/bin/kernel-hardening-checker -c tmp/work/qemuarm64-poky-linux/linux-yocto/6.6.96+git/linux-qemuarm64-standard-build/.config -l kernel_cmdline[...]
nosmep |cmdline| self_protection |defconfig | is not set | OK: is not found
nosmap |cmdline| self_protection |defconfig | is not set | OK: is not found
nokaslr |cmdline| self_protection |defconfig | is not set | OK: is not found
nopti |cmdline| self_protection |defconfig | is not set | OK: is not found
no_hash_pointers |cmdline| self_protection |defconfig | is not set | OK: is not found
nospectre_v1 |cmdline| self_protection |defconfig | is not set | OK: is not found
nospectre_v2 |cmdline| self_protection |defconfig | is not set | OK: is not found
nospectre_bhb |cmdline| self_protection |defconfig | is not set | OK: is not found
nospec_store_bypass_disable |cmdline| self_protection |defconfig | is not set | OK: is not found
dis_ucode_ldr |cmdline| self_protection |defconfig | is not set | OK: is not found
arm64.nobti |cmdline| self_protection |defconfig | is not set | OK: is not found
arm64.nopauth |cmdline| self_protection |defconfig | is not set | OK: is not found
arm64.nomte |cmdline| self_protection |defconfig | is not set | OK: is not found
iommu.passthrough |cmdline| self_protection |defconfig | 0 | OK: CONFIG_IOMMU_DEFAULT_PASSTHROUGH is "is not set"
iommu.strict |cmdline| self_protection |defconfig | 1 | OK: CONFIG_IOMMU_DEFAULT_DMA_STRICT is "y"
mitigations |cmdline| self_protection |defconfig | auto | OK: mitigations is not found
kpti |cmdline| self_protection |defconfig | is not off | OK: mitigations is not found
ssbd |cmdline| self_protection |defconfig | kernel | OK: mitigations is not found
rodata |cmdline| self_protection |defconfig | full | OK: CONFIG_RODATA_FULL_DEFAULT_ENABLED is "y"
slab_merge |cmdline| self_protection | kspp | is not set | OK: is not found
slub_merge |cmdline| self_protection | kspp | is not set | OK: is not found
page_alloc.shuffle |cmdline| self_protection | kspp | 1 | FAIL: is not found
slab_nomerge |cmdline| self_protection | kspp | is present | OK: CONFIG_SLAB_MERGE_DEFAULT is "is not set"
init_on_alloc |cmdline| self_protection | kspp | 1 | OK: CONFIG_INIT_ON_ALLOC_DEFAULT_ON is "y"
init_on_free |cmdline| self_protection | kspp | 1 | OK: CONFIG_INIT_ON_FREE_DEFAULT_ON is "y"
hardened_usercopy |cmdline| self_protection | kspp | 1 | OK: CONFIG_HARDENED_USERCOPY is "y"
slab_common.usercopy_fallback |cmdline| self_protection | kspp | is not set | OK: is not found
kfence.sample_interval |cmdline| self_protection | kspp | 100 | OK: CONFIG_KFENCE_SAMPLE_INTERVAL is "100"
lockdown |cmdline| self_protection | kspp |confidentiality| OK: CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY is "y"
module.sig_enforce |cmdline| self_protection | kspp | 1 | OK: CONFIG_MODULES is "is not set"
efi |cmdline| self_protection | kspp |*disable_early_pci_dma*| OK: CONFIG_EFI_DISABLE_PCI_DMA is "y"
randomize_kstack_offset |cmdline| self_protection | kspp | 1 | OK: CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT is "y"
nosmt |cmdline|cut_attack_surface| kspp | is present | FAIL: is not present
debugfs |cmdline|cut_attack_surface| grsec | off | OK: CONFIG_DEBUG_FS is "is not set"
sysrq_always_enabled |cmdline|cut_attack_surface|grapheneos| is not set | OK: is not found
bdev_allow_write_mounted |cmdline|cut_attack_surface|a13xp0p0v | 0 | OK: CONFIG_BLK_DEV_WRITE_MOUNTED is not found
norandmaps |cmdline| harden_userspace |defconfig | is not set | OK: is not found
[+] Config check is finished: 'OK' - 230 / 'FAIL' - 29Two failures are identified:
Enabling CONFIG_SHUFFLE_PAGE_ALLOCATOR is not sufficient, page_alloc.shuffle must also be set to 1 to enable randomization of page allocation.
The parameter nosmt can be used to disable multi-threading (e.g., to prevent side channel attack where an attacker targets a process owned by somebody else and executed on another core / thread) looks like overkill in most use case so you can safely ignore this warning.
In a nutshell, securing the command line consists merely in not explicitly disabling security features (except for debugging purposes) !
Hardening kernel's runtime configuration
Properly set kernel's runtime configuration
Linux kernel has a substantial number of parameters that can be configured at runtime using sysctl. The list of the available parameters as well as their current values can be obtained using the following command :
sysctl -aExecuting this command on Poky Scarthgap returns 1033 parameters... Hopefully, the kernel-hardening-checker tool will help us once again to check the proper configuration of our runtime configuration. First, you need to retrieve the current configuration of your kernel (you need to run the command inside qemu and retrieve the dump using scp for example):
sysctl -a > sysctl.dumpYou can then analyze the dump :
~/kernel-hardening-checker/bin/kernel-hardening-checker -c tmp/work/qemuarm64-poky-linux/linux-yocto/6.6.96+git/linux-qemuarm64-standard-build/.config -l kernel_cmdline -s sysctl.dump
net.core.bpf_jit_harden |sysctl | self_protection | kspp | 2 | OK: CONFIG_BPF_JIT is not found
kernel.oops_limit |sysctl | self_protection |a13xp0p0v | 100 | FAIL: "10000"
kernel.warn_limit |sysctl | self_protection |a13xp0p0v | 100 | FAIL: "0"
vm.mmap_min_addr |sysctl | self_protection | kspp | 65536 | FAIL: "4096"
kernel.dmesg_restrict |sysctl |cut_attack_surface| kspp | 1 | FAIL: "0"
kernel.perf_event_paranoid |sysctl |cut_attack_surface| kspp | 3 | FAIL: "2"
dev.tty.ldisc_autoload |sysctl |cut_attack_surface| kspp | 0 | FAIL: "1"
kernel.kptr_restrict |sysctl |cut_attack_surface| kspp | 2 | FAIL: "0"
dev.tty.legacy_tiocsti |sysctl |cut_attack_surface| kspp | 0 | FAIL: "1"
user.max_user_namespaces |sysctl |cut_attack_surface| kspp | 0 | FAIL: "897"
kernel.kexec_load_disabled |sysctl |cut_attack_surface| kspp | 1 | OK: CONFIG_KEXEC_CORE is not found
kernel.unprivileged_bpf_disabled |sysctl |cut_attack_surface| kspp | 1 | OK: CONFIG_BPF_SYSCALL is "is not set"
vm.unprivileged_userfaultfd |sysctl |cut_attack_surface| kspp | 0 | OK: CONFIG_USERFAULTFD is "is not set"
kernel.modules_disabled |sysctl |cut_attack_surface| kspp | 1 | OK: CONFIG_MODULES is "is not set"
kernel.io_uring_disabled |sysctl |cut_attack_surface| grsec | 2 | FAIL: "0"
kernel.sysrq |sysctl |cut_attack_surface|a13xp0p0v | 0 | OK: CONFIG_MAGIC_SYSRQ is "is not set"
fs.protected_symlinks |sysctl | harden_userspace | kspp | 1 | FAIL: "0"
fs.protected_hardlinks |sysctl | harden_userspace | kspp | 1 | FAIL: "0"
fs.protected_fifos |sysctl | harden_userspace | kspp | 2 | FAIL: "0"
fs.protected_regular |sysctl | harden_userspace | kspp | 2 | FAIL: "0"
fs.suid_dumpable |sysctl | harden_userspace | kspp | 0 | OK
kernel.randomize_va_space |sysctl | harden_userspace | kspp | 2 | FAIL: "1"
kernel.yama.ptrace_scope |sysctl | harden_userspace | kspp | 3 | FAIL: is not found
vm.mmap_rnd_bits |sysctl | harden_userspace |a13xp0p0v | 24 | FAIL: "18"
vm.mmap_rnd_compat_bits |sysctl | harden_userspace |a13xp0p0v | 16 | FAIL: "11"Now let us explain each setting :
Kernel Parameter | Description | Recommendation (embedded system context) |
kernel.oops_limit | Maximum number of kernel oops (serious but non-fatal errors) before the system panics. | Should be set to a number > to 0. Too many errors can be a sign of an on-going attack. 100 seems fine, you should fine tune this value if your system is unstable. |
kernel.warn_limit | Limits the number of kernel warnings that can be issued. Prevents log flooding from repeated warnings. | Should be set to a number > to 0. Too many errors can be a sign of an on-going attack. 100 seems fine, you should fine tune this value if your system is unstable. |
vm.mmap_min_addr | Minimum virtual memory address a user process can mmap. Protects against NULL pointer dereference exploits by reserving low memory addresses. | 65536 is the default value used by most Linux distributions. I do not think it is much more secure than using 4096 but it does not arm. |
kernel.dmesg_restrict | Restricts access to the kernel log buffer (dmesg). When enabled, only root (or processes with CAP_SYSLOG) can read it. | This restriction should be enabled as I doubt dmesg is used in production. |
kernel.perf_event_paranoid | Controls access to performance monitoring features (perf). Higher values restrict usage to root only, mitigating potential side-channel attacks. | Don't care, side-channel attacks are unlikely. |
dev.tty.ldisc_autoload | Controls whether the kernel automatically loads line disciplines. | Must be set to 0 to prevent, for example, CVE-2017-2636. |
kernel.kptr_restrict | Controls visibility of kernel pointers in /proc and logs. Restricts exposure of addresses that could aid kernel exploits. | Must be set to 0 as kernel addresses should never leak (in particular when KASLR is enabled...). |
dev.tty.legacy_tiocsti | Controls whether unprivileged processes can inject keystrokes into TTYs using the TIOCSTI ioctl. Disabling mitigates privilege escalation risks. | Must be set to 0. |
user.max_user_namespaces | Sets the maximum number of user namespaces that can be created. Restricting reduces attack surface of user namespace-based privilege escalations. | Should be set to 0 if you do not use sandboxing mechanisms otherwise leave the default value. You can also use Apparmor to restrict the creation of user namespaces. |
kernel.kexec_load_disabled | Disables the ability to load a new kernel with kexec. Prevents unprivileged or malicious kernel replacement. | Should be set to 1 even if CONFIG_KEXEC_CORE is not set. |
kernel.unprivileged_bpf_disabled | Disables unprivileged use of BPF (Berkeley Packet Filter). Only root or processes with CAP_BPF can use it. | eBPF can be used to mount privilege escalation attacks so it is better to set it to 1 |
vm.unprivileged_userfaultfd | Controls whether unprivileged processes can use userfaultfd. Disabling prevents certain DoS and privilege escalation attacks. | Must be set to 0 to prevent attacks. |
kernel.modules_disabled | Permanently disables kernel module loading once set. Prevents attackers from inserting malicious modules after boot. | Should be set to 0 if you do not use dynamic modules even if dynamic modules were disabled at build time. |
kernel.io_uring_disabled | Disables io_uring for unprivileged users. Mitigates risks from bugs in high-performance asynchronous I/O. | There is a consensus in disabling this feature so you must disable it by setting this value to 2. |
kernel.sysrq | Controls the SysRq key (Magic SysRq). Allows low-level system commands (reboot, memory dump, etc.). Can be restricted for security. | You should set this value to 0 as it is pointless in embedded systems environments, you should also disable CONFIG_MAGIC_SYSRQ. |
fs.protected_symlinks | Prevents exploitation of symlink-following vulnerabilities (e.g., TOCTOU attacks) by enforcing safe symlink resolution. | Must be set to 1. |
fs.protected_hardlinks | Prevents unprivileged users from creating hard links to files they do not own unless they have read/write access. | Must be set to 1. |
fs.protected_fifos | Restricts writing to FIFOs (named pipes) to prevent data spoofing by unprivileged users. | Must be set to 2. |
fs.protected_regular | Similar to protected_fifos, but for regular files opened in special ways that could lead to security issues. | Must be set to 2. |
fs.suid_dumpable | Controls whether core dumps are created for setuid programs. Typically disabled to prevent leaking sensitive data. | Set this value to 0 even to prevent dumping sensitive information that could be processed by a stuid program. |
kernel.randomize_va_space | Controls Address Space Layout Randomization (ASLR). 0 = off, 1 = conservative randomization, 2 = full randomization. Mitigates memory corruption attacks. | Better to set this value to 2 if your system can support this mode (read more). |
kernel.yama.ptrace_scope | Restricts ptrace usage. Higher values prevent processes from being debugged unless explicitly allowed. | Should be set as high as possible considering that you will not use debug features on production devices. |
vm.mmap_rnd_bits | Number of bits of randomness added to mmap base address for ASLR. Higher = stronger randomization. | While higher is better and ARM64 theoretically supports vm.mmap_rnd_bits=33, this value is limited to 24 at compilation time for performance reasons. |
vm.mmap_rnd_compat_bits | Same as mmap_rnd_bits, but for 32-bit compatibility processes on 64-bit kernels. | Recommended value is 16. |
NOTE: Some sysctl settings are redundant with kernel configuration however, from a general point of view, you should both set kernel configuration AND sysctl to reduce the risk of misconfiguration. In the same way, do not trust default value and override them.
Making the configuration permanent
SysVInit
By default, our Poky distribution uses SysVInit. In this case, you can automatically load a sysctl configuration by creating and filling the file /etc/sysctl.conf with, for example, these values :
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
vm.mmap_min_addr = 65536
fs.protected_symlinks = 1
fs.protected_hardlinks = 1
You can trigger the immediate loading of the configuration using the following command :
sysctl -p /etc/sysctl.conf
Systemd
Now, Systemd is often used in place of SysVInit. In this case you can automatically load a sysctl configuration by creating and filling the file /etc/sysctl.d/99-hardening.conf. The 99- prefix ensures that this configuration file will be the last applied config file (therefore it will override any other conf files).
sysctl -w vm.mmap_rnd_bits=24 >> /etc/sysctl.d/99-hardening.confConclusion
The Linux kernel exposes a tremendous number of (obscure) parameters and securing its configuration is not a trivial task. Hopefully, recommendations from the Linux Kernel Self-Protection Project or the kernel-hardening-checker tool can help developers in this task even if there is still some work to do to obtain the best security without impacting too much the performances.
To go further
TrustnGo is a company that provides software products, consulting, development, and training services in embedded cyber-security. Do not hesitate to contact us whether you have any questions about our offers or just want to discuss this blog post.