Table of Contents
Why Kernel Hardening Matters
The kernel is the most privileged part of the system. If someone can run arbitrary code in kernel mode, every other defense (file permissions, containers, user separation, SELinux, etc.) can be bypassed.
Kernel hardening focuses on:
- Reducing attack surface
- Making exploitation of bugs harder or impossible
- Adding runtime checks and protections
- Enforcing stricter separation between kernel and user space
This chapter assumes general hardening ideas are known and focuses specifically on kernel-related techniques and configuration.
Threats Targeting the Kernel
Kernel hardening addresses several specific classes of attacks:
- Privilege escalation
Exploiting a kernel bug (e.g., in system calls, drivers, filesystems) to go from an unprivileged user toroot. - Information leaks
Bugs that let user space read kernel memory or other processes’ data (e.g., uninitialized memory, side channels). - Arbitrary code execution in kernel space
Overwriting function pointers, vtables, or return addresses to run attacker-controlled code in the kernel. - Abuse of powerful interfaces
Misconfigured or unnecessary features (/dev/mem, unneeded filesystems, debugfs, eBPF) that expose too much power.
Kernel hardening tries both to remove opportunities and add safety belts so that if a vulnerability exists, exploitation is less likely.
Kernel Configuration for Hardening
Many protections are controlled at kernel build time via CONFIG_* options. On major distributions you usually consume these as prebuilt kernels, but the options still matter:
- To check current options (if supported):
zgrep CONFIG_HARDENED /proc/config.gz
or inspect /boot/config-$(uname -r).
Below is an overview of key categories.
Compile-Time Hardening Options
Stack Protection
- Stack canaries (
CONFIG_STACKPROTECTOR,CONFIG_STACKPROTECTOR_STRONG)
Insert a secret “canary” value before return addresses. If overwritten (typical in stack buffer overflows), the kernel detects it and panics instead of continuing with corrupted control flow. - Ensure at least
CONFIG_STACKPROTECTOR_STRONG=yis enabled.
Read-Only and No-Execute Data
- Read-only kernel data (
CONFIG_RODATA,CONFIG_DEBUG_RODATAon older kernels; typically enabled implicitly in newer ones)
Mark certain sections of kernel memory as read-only so they can’t be modified at runtime. CONFIG_STRICT_KERNEL_RWX(or equivalent)
Enforces that kernel memory regions are either writable or executable, but not both. That limits code injection.
Fortified Functions
CONFIG_FORTIFY_SOURCE
Adds bounds-checks to some common memory and string functions (e.g.,strcpy,memcpy) when the compiler can infer sizes. Helps catch overflows at runtime.
Control-Flow Integrity (CFI) and Related
Some architectures and toolchains support extra protection:
- CFI (Control-Flow Integrity)
Ensures indirect calls (through function pointers) only target valid destinations. Implementation and availability vary (e.g., LLVM CFI on some Android/Google kernels). - Shadow call stack / return address protection
Keeps return addresses in a separate protected stack to stop ROP-like attacks.
On general-purpose distro kernels, these may not yet be widely available, but are worth enabling on custom or embedded builds when supported.
Other Miscellaneous Hardening Configs
Examples you might encounter:
CONFIG_REFCOUNT_FULL– hardened reference counting to prevent refcount overflows/underflows.CONFIG_DEBUG_LISTand similar sanity checks – add consistency checks to common kernel data structures (can catch exploits but have runtime cost).CONFIG_GCC_PLUGIN_RANDSTRUCT(on some kernels) – randomizes the layout of certain sensitive kernel structures to frustrate exploits that depend on field offsets.
Disabling Unnecessary Features
The most impactful hardening step is often removing what you don’t need:
- Unneeded filesystems (e.g., obscure network or legacy filesystems)
- Unused network protocols
- Legacy driver stacks, development/debug features, etc.
In a custom kernel configuration tool (e.g., make menuconfig), you would:
- Set unused items to
n(not built at all) - Avoid building dangerous things as modules if they may get loaded unintentionally
For distribution kernels you can’t easily change CONFIG_*, but you can reduce attack surface at runtime (e.g., blacklisting modules), discussed below.
Runtime Kernel Hardening Settings (sysctl and boot parameters)
Hardening does not require compiling your own kernel. Many protections are controlled via:
- sysctl interface:
/proc/sys/or/etc/sysctl.confand/etc/sysctl.d/.conf - Kernel command line: via bootloader (GRUB, etc.)
Virtual Memory and Address Space Protections
Kernel ASLR and Memory Randomization
On modern kernels, Address Space Layout Randomization (ASLR) is usually on by default.
kernel.randomize_va_space0– disabled1– conservative randomization2– full ASLR (recommended)
Check and enable:
sysctl kernel.randomize_va_space
sysctl -w kernel.randomize_va_space=2
Persist via /etc/sysctl.d/99-hardening.conf:
kernel.randomize_va_space = 2Restricting /dev/mem and /dev/kmem
These devices give low-level memory access and are rarely needed on production systems.
Relevant options (varies by kernel):
CONFIG_STRICT_DEVMEM
Restricts/dev/memto I/O memory ranges; blocks raw access to RAM.
Check sysctl parameter:
kernel.kptr_restrict
Controls exposure of kernel pointers via/proc,dmesg, etc.:0– no restrictions1– restricts pointer display to privileged processes2– hide kernel pointers even from root (except very specific cases)
Recommended:
sysctl -w kernel.kptr_restrict=2Persist:
kernel.kptr_restrict = 2Protecting System Call and Debug Interfaces
seccomp and Unprivileged System Calls
Some kernels provide toggles to prevent unprivileged processes from using potentially dangerous interfaces:
- Unprivileged BPF:
kernel.unprivileged_bpf_disabled 0– allowed1– disallowed except forrootwithCAP_BPFetc.
Recommended on general-purpose systems:
sysctl -w kernel.unprivileged_bpf_disabled=1Persist:
kernel.unprivileged_bpf_disabled = 1- Some distros also have:
kernel.unprivileged_userns_clone(Debian/Ubuntu) – control whether unprivileged user namespaces can be created.
User namespaces improve container isolation but also expand the kernel attack surface. Decision depends on your container model and risk appetite. If you don’t use unprivileged containers, consider:
sysctl -w kernel.unprivileged_userns_clone=0ptrace Restrictions
ptrace allows one process to debug or control another, which can be powerful and dangerous.
kernel.yama.ptrace_scope(on kernels with Yama LSM):0– classic ptrace behavior1– restrict debugging to child processes only (recommended default)2– onlyrootcan use ptrace3– no ptrace (even for root, except withCAP_SYS_PTRACEin some setups)
Recommended baseline:
sysctl -w kernel.yama.ptrace_scope=1
Hardened environments may choose 2 or 3, but that can break debuggers and some monitoring tools.
dmesg Restrictions
dmesg can leak sensitive kernel information.
kernel.dmesg_restrict:0– anyone can readdmesg1– onlyroot(or CAP_SYSLOG) may view it
Recommended:
sysctl -w kernel.dmesg_restrict=1Persist:
kernel.dmesg_restrict = 1Namespaces and User Isolation
Namespaces (user, PID, mount, etc.) are a core part of containerization but also increase kernel complexity and attack surface. Beyond kernel.unprivileged_userns_clone, some hardened kernels and distros provide extra tweaks:
- Disable or restrict certain namespaces if you don’t rely on unprivileged containers.
- Use LSMs (SELinux/AppArmor) to confine processes that use namespaces.
Exact options are distro-specific, but the principle is: only allow the namespaces and features you actually use.
Hardening Boot Parameters (GRUB / Kernel CMDLINE)
Kernel parameters can enforce security-related behaviors from the earliest boot phase. You add them to your bootloader config, for example on GRUB-based systems:
- Edit
/etc/default/grub - Modify
GRUB_CMDLINE_LINUX_DEFAULT - Regenerate config (e.g.,
update-gruborgrub2-mkconfigdepending on distro) - Reboot
Examples of hardening parameters:
slab_nomerge
Prevents merging of slabs of similar objects, making some heap exploitation techniques harder.page_alloc.shuffle=1
Randomizes the order of pages given to allocations; complicates some memory corruption exploits.vsyscall=none
Disables the oldvsyscallmechanism (legacy compatibility feature); reduces attack surface.debugfs=off
Prevents debugfs from being automatically mounted; reduces access to internals.pti=on(if not automatic)
Force kernel page table isolation (KPTI) to mitigate certain CPU side-channel attacks (Meltdown-like).
A hardened command line might look like:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash slab_nomerge page_alloc.shuffle=1 vsyscall=none debugfs=off"Always test changes carefully, as some options carry performance costs or can break specific workloads.
Limiting and Hardening Kernel Modules
Kernel modules are loadable pieces of kernel code. Reducing and controlling them is a major hardening step.
Disabling Module Loading at Runtime
Some hardened setups disable module loading once boot is complete.
- sysctl:
kernel.modules_disabled: 0– allow loading/unloading modules1– permanently disable loading new modules until reboot
To disable further loading after boot:
sysctl -w kernel.modules_disabled=1
Note: once set to 1, it cannot be reverted without a reboot. This is typically used in highly controlled, immutable systems where all needed modules are built-in.
Blacklisting Unneeded or Risky Modules
Instead of disabling all modules, you can prevent specific ones from loading.
Create a file, for example /etc/modprobe.d/blacklist-hardening.conf:
blacklist firewire-core
blacklist usb-storage
blacklist cramfs
blacklist udfThis ensures these modules won’t load automatically. You can blacklist:
- Legacy filesystems you never use (e.g.,
cramfs,freevxfs,jfs,hfs, etc.) - Unneeded network protocols or hardware interfaces
- USB storage drivers on servers where removable drives are forbidden
Use lsmod, lspci, lsusb, and logs to see which modules are actually in use before blacklisting.
Locking Module Options
Some risky behaviors can be disabled via module parameters. For example, some network modules have debugging or promiscuous-mode settings that you might want to lock down. These are very module-specific; consult modinfo <module> and module documentation.
You can enforce parameters via /etc/modprobe.d/*.conf. Example:
options usbcore autorun=0LSM-Based Kernel Hardening Layers
Linux Security Modules (LSMs) add policy-driven access control and other hardening. A full explanation of SELinux/AppArmor is handled elsewhere; here we focus on kernel-perspective aspects.
Enabling and Enforcing LSMs
At boot, the kernel chooses which LSMs to enable and in what order (varies by distro and kernel version).
Commonly:
- SELinux: controlling process and object labels with mandatory access control.
- AppArmor: path-based profiles with similar goals.
- Yama: provides ptrace and other extra protections.
- Landlock, Lockdown, Integrity frameworks on some kernels.
Typical kernel parameters:
selinux=1 enforcing=1– ensure SELinux is enabled and enforcing.apparmor=1 security=apparmor– ensure AppArmor is primary LSM.
On modern kernels, lsm= parameter can set LSM ordering, e.g.:
lsm=lockdown,yama,integrity,apparmor,bpfConsult your distro documentation; some handle this implicitly.
Kernel Lockdown Mode
Kernel Lockdown restricts some operations even from root, to protect against tampering with the running kernel (especially in secure boot scenarios).
Modes:
none– no lockdownintegrity– prevent modifications of the kernel (e.g., loading unsigned modules, writing to kernel memory).confidentiality– additionally restrict actions that could leak kernel data.
To enable via kernel cmdline:
lockdown=integrityor
lockdown=confidentialityLockdown interacts with Secure Boot on many distros (it may be enabled automatically when Secure Boot is active).
Hardening eBPF and Tracing Facilities
eBPF is powerful for observability and networking, but it’s also complex and has had security issues.
Restricting eBPF
We already mentioned:
kernel.unprivileged_bpf_disabled=1
Additional steps may include:
- Ensure only trusted users or services can use BPF-based tooling.
- Use cgroup-bpf or LSM hooks to confine BPF program usage (where supported).
On some hardened kernels you can:
- Compile out certain BPF helpers or features not used in your environment.
- Rely on distro-provided “secure” profiles.
Debugfs, Tracing, and Perf
Debugging and tracing feature sets (debugfs, ftrace, perf_event_open) expose a lot of kernel internals.
- Ensure debugfs is not auto-mounted on production systems:
- Boot parameter:
debugfs=off - Or do not mount
/sys/kernel/debugat all, or restrict it heavily. perf_event_paranoidsysctl controls who can use performance counters:
sysctl kernel.perf_event_paranoidTypical recommended values:
2or3to restrict unprivileged access (depending on your needs).
Example in /etc/sysctl.d/99-hardening.conf:
kernel.perf_event_paranoid = 3Hardened Kernel Projects and Distro Variants
If you manage security-sensitive environments, it can be worth using kernels with additional hardening patches:
- Grsecurity / PaX (historical reference; currently non-free for most use cases)
Provided many advanced hardening features like superior ASLR, memory protections, and exploit mitigations. - Linux-hardened (Arch)
A kernel with a set of hardened configs and patches, used in security-focused projects. - Distribution-hardening efforts
Some distros (e.g., Fedora, some enterprise distros) steadily add hardened defaults—SELinux enforcing, betterCONFIG_*, more strict sysctls, restrictive LSMs.
When evaluating such kernels, consider:
- Performance impact
- Hardware/driver compatibility
- User-space compatibility (some anti-exploitation measures can break assumptions of certain applications)
Practical Hardening Workflow
A realistic kernel hardening approach on a general-purpose server might look like:
- Inventory and remove what you don’t need
- Identify unused subsystems, filesystems, network stacks.
- Blacklist unneeded modules.
- Avoid auto-mounting debugfs.
- Tighten sysctl settings
kernel.randomize_va_space=2kernel.kptr_restrict=2kernel.dmesg_restrict=1kernel.yama.ptrace_scope=1kernel.unprivileged_bpf_disabled=1kernel.perf_event_paranoid=3(if you don’t need perf for unprivileged users)- Disable unprivileged user namespaces if safe for your use case.
- Harden boot parameters
- Add
slab_nomerge,page_alloc.shuffle=1,vsyscall=none,debugfs=offwhere supported. - Ensure LSMs are enabled and enforcing (e.g., SELinux/AppArmor).
- Consider module policies
- On static, controlled systems, disable further module loading with
kernel.modules_disabled=1after boot. - Otherwise, at least constrain who can insert modules (e.g., ensure only trusted admins, restrict
CAP_SYS_MODULE). - Monitor and iterate
- Watch logs for warnings or failures tied to new settings.
- Adjust where necessary (for example, debugging tools might need relaxed ptrace or perf settings in staging environments).
Balancing Security, Stability, and Performance
Kernel hardening is always a trade-off:
- Security: every additional check or restriction may block potential exploits.
- Stability: overly aggressive or misconfigured measures can break legitimate software.
- Performance: some mitigations (KPTI, extra integrity checks, CFI) incur overhead.
Good practice:
- Start with distribution-recommended secure defaults.
- Apply stricter settings in stages, testing each change:
- First on development or staging systems.
- Then in production with careful monitoring.
- Use different profiles for:
- Desktop vs. server
- High-performance vs. security-critical workloads
Kernel hardening is most effective when combined with other layers: application hardening, network controls, strong authentication, and good operational practices.