Kernel module control
Blacklist what you never load.
Your kitchen has a drawer of attachments for the stand mixer. Dough hook, whisk, pasta roller, and a couple of odd tools you have never once used. The mixer will snap in whichever attachment a recipe asks for, right away, no questions asked. The Linux kernel (the core program that talks straight to your hardware) keeps a drawer like this too. Its attachments are called modules: drivers and features it can click into a running system the moment something needs one. Convenient. Also a problem, because a few attachments in that drawer are old, rusty, and sharp.
A kernel module runs with full kernel power. Once it loads, its code is part of the most privileged program on the machine. Most modules are fine and you want them there. But a handful cover strange filesystems and forgotten network protocols your server will never touch, and some of those have shipped with real security holes. Give an attacker a way to make the kernel load one of those buggy modules and you have handed them a bug to attack at the highest privilege level on the box. The hardening move is one plain idea: tape over the drawer slots you will never use, so the kernel refuses to fetch those attachments at all.
How a module loads when you never asked
Here is the part that surprises people. You do not type anything to load a module. The kernel autoloads on demand. Plug in a USB webcam and the kernel reads the device's ID, looks up which module claims that ID, and loads it for you. Ask the network stack for a socket of an unusual type and it does the same for the protocol code behind it. The lookup runs on aliases, small 'if you see X, load module Y' labels baked into each module. When the kernel decides it needs one, it hands the job to a tiny user-space helper called modprobe, which finds the file and inserts it.
That on-demand path is the attack surface. An ordinary user with no special powers cannot load a module by hand, but they can open a socket, and opening a socket for an odd protocol is enough to make the kernel go fetch the code behind it, running the loader with full root power on the user's behalf. DCCP (Datagram Congestion Control Protocol, a transport protocol almost nobody uses) is the textbook case. In 2017 a use-after-free bug in the kernel's DCCP code, tracked as CVE-2017-6074 (a CVE is a public ID for a known security flaw), let a local user climb all the way to root, and the trigger was creating one DCCP socket. On a server that never speaks DCCP, that code had no business being in memory in the first place.
Blacklist the modules you will never use
The fix lives in /etc/modprobe.d, a directory of rules modprobe reads every time it runs. Create one file for your hardening rules. The protocols below are DCCP, SCTP (Stream Control Transmission Protocol), RDS (Reliable Datagram Sockets), and TIPC (Transparent Inter-Process Communication), all niche transports with a CVE history, alongside a few old filesystem formats a normal server never mounts, like cramfs (a tiny compressed read-only filesystem) and udf (the format used on DVDs).
# --- Old, niche filesystems this host will never mount ---install cramfs /bin/falseinstall freevxfs /bin/falseinstall jffs2 /bin/falseinstall udf /bin/false# --- Obscure network protocols with a history of kernel bugs ---install dccp /bin/falseinstall sctp /bin/falseinstall rds /bin/falseinstall tipc /bin/false# Also strip each module's own autoload aliases (belt and suspenders)blacklist dccpblacklist sctpblacklist rdsblacklist tipc
Read 'install cramfs /bin/false' as a swap. It tells modprobe that 'loading' cramfs now means running /bin/false, a tiny program that does nothing and exits with a failure code. Every attempt to load that module through modprobe, whether the kernel triggered it automatically or someone typed it by hand, runs /bin/false and gives up. The real module never enters the kernel. The older version of this recipe pointed at /bin/true (does nothing, exits success), and that blocks the load equally well. /bin/false is the better choice because anything that scripted the load now sees a clear non-zero failure instead of a quiet, misleading 'success'.
The 'blacklist' lines do something narrower, and the difference matters. 'blacklist dccp' tells modprobe to ignore that module's built-in autoload aliases. It shuts the automatic door, the one the DCCP socket trick walked through. But a direct 'modprobe dccp' by name still loads the module, because blacklist never touched that path. That gap is the whole reason the 'install' line is the real lock, and why the CIS benchmarks (CIS is the Center for Internet Security, which publishes widely used hardening checklists) pair the two directives together.
New rules take effect for the next load, not retroactively. A module already sitting in memory stays until you remove it with 'sudo modprobe -r dccp' or reboot, so check 'lsmod' after you write the file. One more step people forget: some filesystem modules get baked into the initramfs (the small temporary filesystem the kernel unpacks early in boot), so run 'sudo update-initramfs -u' to carry your blacklist into that early copy too.
Turn module loading off entirely
Blacklisting handles modules you can name ahead of time. For a locked-down, single-purpose host you can go further and weld the drawer shut once everything you need is already snapped in. The switch is a kernel tunable named kernel.modules_disabled, set through sysctl (the interface for reading and writing kernel settings on a running system). Flip it to 1 and the running kernel refuses every new module load, by any path, until the next reboot.
Notice the last line. This knob is a one-way latch: it takes only the value 1, so you cannot set it back to 0, and the kernel rejects the attempt as an invalid argument. A reboot is the only reset, which is the whole point. An attacker who lands root on this host still cannot load a kernel module, so the usual route to planting a kernel rootkit is shut for the rest of this boot. It also covers a gap that blacklists leave open. A modprobe.d rule binds only modprobe, and a root user can walk around modprobe and shove a module file straight in with 'insmod /path/to/module.ko' (insmod is the bare-bones insert-a-module command: no alias lookup, no install rules, just this file into the kernel). Because modules_disabled blocks the underlying load syscall itself, it stops insmod too. The other control for that same gap is enforcing signed modules, so the kernel loads only modules signed by a trusted key (Secure Boot and kernel lockdown can require this).
What a defender watches for
Controls are only half the job. You also want to see the day one of these lines gets crossed. Start with a baseline: 'lsmod' on a healthy host is your known-good list, and anything new showing up later is a question to answer. The kernel also keeps a running 'tainted' flag that records whether anything unusual has been loaded.
Zero means a clean, in-tree, properly signed set of modules. The value is a bitmask, and loading an out-of-tree or unsigned module flips specific bits (an unsigned module, for instance, sets the 8192 bit). A box that should only ever run stock, signed modules suddenly reporting a non-zero taint is worth a hard look. For a real record of who loaded what and when, put the module syscalls under the audit daemon (auditd), the built-in Linux service that logs security-relevant kernel events.
## Log every kernel module load and unload, tagged "modules"-a always,exit -F arch=b64 -S init_module,finit_module,delete_module -k modules-a always,exit -F arch=b32 -S init_module,finit_module,delete_module -k modules
init_module and finit_module are the syscalls that insert a module; delete_module removes one. With those rules loaded ('sudo augenrules --load'), every load and unload lands in the audit log stamped with the user, the timestamp, and the command, and 'sudo ausearch -k modules' replays them during an investigation. An unexpected entry in 'lsmod', matched to a load event whose parent process has no business touching the kernel, is one of the cleaner signals that someone pushed a kernel rootkit onto the host.
One hard truth sits under all of this. Asking a compromised kernel what modules are loaded is like asking a burglar whether anyone broke in. Once a malicious module is inside the kernel, it owns the very code your tools lean on for answers. It can hide its own process, files, and network connections from ps, ls, ss, and even lsmod, because every one of those tools asks the kernel, and the kernel is now lying to them. That is why the strongest signals come from outside the running system: an audit log shipped off the box before anyone can edit it, a taint flag you noticed before the rootkit learned to fake it, a file baseline checked from a trusted boot. Treat a loaded module you cannot explain as an incident, not a curiosity.
Try this
Work through “What a defender watches for” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.
Takeaway
The trap worth remembering here: modules_disabled is easy to fire into your own foot. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.