Every kernel module you load is code running with full ring-0 privilege. An out-of-tree module with a backdoor, a legitimate module with a known CVE, or an unwanted filesystem driver loaded automatically when the wrong USB stick appears โ each is a path to root that bypasses every userspace defence you have. This guide covers the security-focused side of modprobe: signature verification, automatic load auditing, hard blacklisting, and post-boot lockdown.
What is loaded right now
lsmod # name, size, refcount, used-by
lsmod | wc -l # total module count
lsmod | awk '$3==0 {print $1}' # loaded but unused (candidates for removal)
modinfo nf_tables | head # description, parameters, signature, license
The "license" line in modinfo tells you whether the module taints the kernel. License: GPL is clean; License: Proprietary taints, which limits the support you can expect from upstream.
Signed modules and Secure Boot
Distribution kernels with Secure Boot enforce signature verification โ only modules signed by a trusted key can load. Verify your host:
mokutil --sb-state # Secure Boot enabled?
cat /sys/kernel/security/lockdown # current lockdown mode
dmesg | grep -E 'taint|signature|loaded kernel'
modinfo MODULE | grep -E '^sig_'
If lockdown is integrity or confidentiality, unsigned modules cannot be loaded at all โ even by root. This is the recommended baseline for internet-facing servers.
Loading and unloading safely
sudo modprobe -v dummy # verbose; shows dependencies
sudo modprobe --dump-modversions /lib/modules/$(uname -r)/.../mod.ko
sudo modprobe -r dummy # unload, fails if in use
sudo dmesg -T | tail # kernel feedback after load
Always use modprobe rather than insmod โ only modprobe resolves dependencies and respects modprobe.d configuration.
Persisting load and parameters
echo dummy | sudo tee /etc/modules-load.d/dummy.conf
echo 'options dummy numdummies=4' | sudo tee /etc/modprobe.d/dummy.conf
sudo update-initramfs -u # Debian/Ubuntu
sudo dracut --force # RHEL family
Hard-deny: blacklist plus install-false
Blacklist alone only stops automatic loading; an attacker can still modprobe the module manually. The combination of blacklist and install-redirect closes both:
# /etc/modprobe.d/CIS.conf
install cramfs /bin/true
install freevxfs /bin/true
install jffs2 /bin/true
install hfs /bin/true
install hfsplus /bin/true
install squashfs /bin/true
install udf /bin/true
install usb-storage /bin/true
install dccp /bin/true
install sctp /bin/true
install rds /bin/true
install tipc /bin/true
install firewire-core /bin/true
blacklist cramfs
blacklist freevxfs
blacklist jffs2
blacklist hfs
blacklist hfsplus
blacklist squashfs
blacklist udf
blacklist usb-storage
blacklist dccp
blacklist sctp
blacklist rds
blacklist tipc
blacklist firewire-core
Each entry closes a specific attack vector: rare filesystem drivers used by USB exploits, obscure protocols used in CVEs, and DMA-attack interfaces. The list is the CIS Linux Benchmark recommendation; apply, rebuild initramfs, reboot.
Post-boot lockdown
For appliances or any host where module loading should stop after boot:
echo 1 | sudo tee /proc/sys/kernel/modules_disabled
# or persistent:
echo 'kernel.modules_disabled = 1' | sudo tee /etc/sysctl.d/99-modules.conf
Once set, no module can be loaded or unloaded until reboot. Apply only after all expected modules are loaded; mistime it and you cannot load nf_conntrack when iptables decides it needs it.
Auditing module load events
# /etc/audit/rules.d/modules.rules
-w /sbin/insmod -p x -k modules
-w /sbin/modprobe -p x -k modules
-w /sbin/rmmod -p x -k modules
-a always,exit -F arch=b64 -S init_module,delete_module,finit_module -k modules
sudo augenrules --load
sudo ausearch -k modules -ts today | head
Now every module load attempt โ successful or not, automatic or manual โ is recorded with the originating UID, parent process, and exact syscall arguments. This is the single most useful audit rule for catching kernel-rootkit installation.
Module integrity baseline
An attacker may replace a legitimate .ko file on disk and wait for next reboot. Defend by snapshotting hashes:
find /lib/modules/$(uname -r) -name '*.ko*' \
-exec sha256sum {} + | sort > /var/lib/modules-baseline.sha256
# later:
diff /var/lib/modules-baseline.sha256 \
<(find /lib/modules/$(uname -r) -name '*.ko*' -exec sha256sum {} + | sort)
Wrap into a daily cron and ship the diff to your log collector. Any non-empty diff outside an apt/dnf upgrade window is suspicious.
The audit script
#!/bin/bash
echo "== Loaded modules count =="
lsmod | tail -n +2 | wc -l
echo "== Tainted? =="
cat /proc/sys/kernel/tainted
echo "== Lockdown =="
cat /sys/kernel/security/lockdown 2>/dev/null || echo "n/a"
echo "== Modules-disabled =="
cat /proc/sys/kernel/modules_disabled
echo "== Recently loaded (audit) =="
ausearch -k modules -ts today 2>/dev/null | grep -c 'type=SYSCALL'
echo "== Unsigned loaded modules =="
for m in /sys/module/*/sections; do
d=$(dirname "$m"); n=$(basename "$d")
modinfo "$n" 2>/dev/null | grep -q '^sig_id' || echo " $n"
done | head -10
Common pitfalls
- Blacklisting
usb-storageon a server, then needing it for an emergency backup; document an "emergency unblock" runbook before locking down. - Setting
kernel.modules_disabled=1in sysctl too early; if a service later requires a not-yet-loaded module the host hangs. - Forgetting initramfs rebuild after blacklist changes โ the unwanted module loads anyway from the early ramdisk.
- Using
install MODULE /bin/falseinstead of/bin/true;/bin/falseexits non-zero and pollutes logs.
Module security is configured once at provisioning, validated weekly, and forgotten the rest of the year โ until it saves you. Apply the CIS deny list, sign in audit rules, snapshot hashes, and your kernel attack surface shrinks dramatically without losing a single feature you actually use.