top of page

Linux kernel Hardening


How to properly configure the Linux kernel

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.cfg

However, 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' - 29

Two 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 -a

Executing 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.dump

You 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.conf

Conclusion


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.

bottom of page