TrustnGo
Retour au blog

Linux Kernel Hardening

5 janvier 2025 · 15 min de lecture

Cet article n'a pas encore été traduit en français, voici la version anglaise.

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 CVEs 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 manage 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:

  • Process 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 defining dependencies as well as default values for the macros. It also separates their declarations from their definitions, which allows setting their values without modifying the C headers. Kconfig was initially introduced with the Linux kernel and then used with various software including Zephyr OS.
  • 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 as a plain text source file that is compiled into 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 containers 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 whose 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 on 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 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 completely disabling Ethernet support if it is not required.

The two first points might be hard to address due to the 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 our 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 be to strictly follow the tool's 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 the internet can help us 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 has been disabled by the tool. However, you may experience some trouble considering that, for example, the tool recommends disabling dynamic loading of modules by unsetting CONFIG_MODULES. Be careful when enabling these configuration parameters.

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 it to check the security of the command line. By running 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 attacks where an attacker targets a process owned by somebody else and executed on another core/thread) — this looks like overkill in most use cases, so you can safely ignore this warning.

In a nutshell, securing the command line consists merely of 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 greater than 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 greater than 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 harm.
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 the 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 on 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 to prevent dumping sensitive information that could be processed by a setuid 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.
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 the 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.

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 the performance too much.

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.