System Hardening

Boot & Kernel Hardening

Secure boot, kernel lockdown, and hardening sysctls.

The kernel is the most privileged code on the machine, and the boot chain is the sequence that decides which kernel runs. An attacker who can influence either has effectively unlimited power: they can disable every other control you configured. Boot and kernel hardening is about making that influence expensive. You verify that the code which boots is the code you intended, and you close off the kernel interfaces that turn a limited foothold into full root.

This is the layer most likely to break a system, so it is also where snapshots earn their keep. Restore the lab VM freely; a hardening sysctl that locks you out is a learning experience, not a disaster.

The boot chain and Secure Boot

Boot proceeds through a chain where each stage hands control to the next: firmware (UEFI) loads the shim, the shim loads GRUB, GRUB loads the kernel and initramfs, and the kernel starts init. Secure Boot inserts a cryptographic check at each hand-off. Firmware verifies the shim’s signature, the shim verifies GRUB, and the kernel is verified before it runs. A tampered bootloader fails the check and refuses to load.

mokutil --sb-state

Expected output:

SecureBoot enabled

Secure Boot defends the pre-boot environment, but it does not stop an attacker who already has root on the running system. It raises the cost of persistence via a malicious bootloader or an unsigned kernel module. Pair it with a GRUB password so the boot menu cannot be edited to pass init=/bin/bash and drop to a root shell.

grep -c 'password_pbkdf2' /etc/grub.d/40_custom

Expected output:

1

High-value hardening sysctls

The kernel exposes hundreds of tunables under /proc/sys, and a handful materially raise the bar for local privilege escalation and information leaks. These are cheap, reversible, and low risk, which makes them the first thing to apply. Set them in a file under /etc/sysctl.d/ so they persist across reboots.

sysctlDefaultHarden toWhat it stops
kernel.kptr_restrict12Leaking kernel pointers that defeat KASLR
kernel.dmesg_restrict0 or 11Unprivileged reads of the kernel ring buffer
kernel.unprivileged_userns_clone10User namespaces as a privilege-escalation surface
kernel.yama.ptrace_scope11 or 2One process attaching to and reading another
net.ipv4.conf.all.rp_filter11Spoofed source addresses (reverse path filtering)

Address Space Layout Randomization (ASLR) is on by default at level 2; the kptr_restrict and dmesg_restrict settings above protect the information an attacker would use to defeat it. Apply a hardening set like this:

sudo tee /etc/sysctl.d/99-hardening.conf > /dev/null <<'EOF'
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.unprivileged_userns_clone = 0
kernel.yama.ptrace_scope = 2
EOF
sudo sysctl --system 2>&1 | grep hardening

Expected output:

* Applying /etc/sysctl.d/99-hardening.conf ...
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.unprivileged_userns_clone = 0
kernel.yama.ptrace_scope = 2

Verify a specific value took effect:

sysctl kernel.kptr_restrict

Expected output:

kernel.kptr_restrict = 2

A word of caution on unprivileged_userns_clone: some sandboxed applications (certain browsers, container runtimes in rootless mode) rely on unprivileged user namespaces. Disabling it improves security but can break those tools, so test on the workload before rolling out.

Restricting kernel module loading

Loadable kernel modules run with full kernel privileges. Attackers abuse module autoloading to load an obscure, buggy module on demand and exploit it. Two controls help. First, once boot is complete and every needed module is loaded, you can freeze module loading entirely for the rest of the uptime.

sudo sysctl kernel.modules_disabled=1
sysctl kernel.modules_disabled

Expected output:

kernel.modules_disabled = 1

This is a one-way switch: it cannot be undone without a reboot, which is exactly the point. Second, you can blacklist rarely used, historically buggy modules so autoloading never touches them.

sudo tee /etc/modprobe.d/blacklist-rare.conf > /dev/null <<'EOF'
install dccp /bin/false
install sctp /bin/false
install rds /bin/false
install tipc /bin/false
EOF
sudo modprobe dccp

Expected output:

modprobe: ERROR: could not insert 'dccp': Operation not permitted

The install ... /bin/false form is stronger than blacklist because it prevents both autoloading and explicit modprobe. This overlaps with the CIS Benchmark, which flags exactly these filesystem and network modules; see CIS Benchmark for the full list and how a scanner reports them.

Kernel lockdown

When Secure Boot is on, Ubuntu can enable kernel lockdown mode, which severs the paths root would use to modify the running kernel (writing to /dev/mem, loading unsigned modules, using kexec). It closes the gap between “attacker has root” and “attacker owns the kernel”.

cat /sys/kernel/security/lockdown

Expected output:

none [integrity] confidentiality

The value in brackets is active. integrity blocks modification of the running kernel; confidentiality additionally blocks reading kernel memory. Lockdown is automatically integrity when Secure Boot is enabled, which is another reason the two features belong together.

Practical Guidance

  1. Enable Secure Boot and set a GRUB password so the boot menu cannot be edited to bypass authentication with init=/bin/bash.
  2. Drop a /etc/sysctl.d/99-hardening.conf with kptr_restrict, dmesg_restrict, and yama.ptrace_scope on every host; these are cheap and reversible.
  3. Test unprivileged_userns_clone = 0 against your workload before rolling out, since rootless containers and sandboxed browsers may depend on it.
  4. Blacklist rare network and filesystem modules with the install ... /bin/false form, and freeze module loading with modules_disabled=1 on stable, unchanging hosts.
  5. Snapshot the lab VM before every boot or kernel change so a lockout costs a restore, not a rebuild.
  6. Confirm updated kernels still boot after enabling Secure Boot (see Patch Management), since an unsigned or unexpected kernel will be rejected.