Loading and Unloading Kernel Modules on Linux – free linux kernel development course

Loading and Unloading Linux Kernel Modules: rmmod, modprobe, Security | Free Linux Kernel Programming Course

EmbeddedPathashalaLinux Kernel Programming › Loading and Unloading Modules

Loading and Unloading Kernel Modules on Linux 6.x
rmmod · modprobe · delete_module() · Security · init/exit best practices
Level
Beginner–Intermediate
Kernel
Linux 6.x
Part
2 of 2
This series: PREV_LEC NEXT_LEC
What you will learn in this tutorial
rmmod and its error codes delete_module() syscall modprobe dependency resolution CAP_SYS_MODULE capability module signing in kernel 6.x kernel lockdown and Secure Boot init/exit function best practices permanent modules (no exit)

1. Unloading a module with rmmod

When you want to remove a kernel module from memory, you use rmmod. You pass it the module’s name — not a file path.

# This works
$ sudo rmmod bluetooth

# This does NOT work — do not use a .ko path
$ sudo rmmod /lib/modules/6.8.0/kernel/net/bluetooth/bluetooth.ko.xz
Why root? Removing a kernel module is a privileged operation. It requires the CAP_SYS_MODULE Linux capability. In practice this means you must run as root or with sudo. More on this in the security section.

What rmmod does under the hood

rmmod is just a thin shell. All the real work happens in a Linux system call called delete_module(2). When you run rmmod bluetooth, the flow looks like this:

What Happens When You Run rmmod
User types: sudo rmmod bluetooth
kmod binary (rmmod symlink) calls delete_module(“bluetooth”, flags)
▼ crosses into kernel space ▼
Kernel: delete_module() in kernel/module/main.c
① Check CAP_SYS_MODULE capability → EPERM if missing
② Check modules_disabled sysctl → EPERM if set to 1
③ Check if module is in LIVE state → EBUSY if not
④ Check if any other module depends on this one
⑤ Check reference count → EWOULDBLOCK if > 0
⑥ Set state to GOING, call exit function, free memory
▼ success ▼
Module removed from /proc/modules and /sys/module/

2. Why rmmod fails — the three error codes

Understanding why rmmod fails is one of the most common struggles for kernel developers. There are three distinct reasons, each with its own error code.

rmmod / delete_module() Error Decision Flow
Does the caller have CAP_SYS_MODULE?
NO → EPERM (Operation not permitted)
YES → continue
Is kernel.modules_disabled == 0?
NO (it’s 1) → EPERM
YES (it’s 0) → continue
Is module state == LIVE and does it have an exit function?
NO → EBUSY (Device or resource busy)
YES → continue
Is reference count == 0? (no one using it, no dependents)
NO → EWOULDBLOCK (module is in use)
YES → unload succeeds ✓
ErrorReasonHow to fix
EPERM Missing CAP_SYS_MODULE capability, or kernel.modules_disabled is set to 1 Run with sudo. If modules_disabled=1, a reboot is required — it cannot be undone at runtime.
EBUSY Module is not in LIVE state (still loading or already going), OR it has no exit function Wait for load to finish. If no exit function, the module is permanent and cannot be unloaded without a reboot.
EWOULDBLOCK Reference count is nonzero — something is actively using the module Find and close/stop whatever is using the module (open files, processes, active connections), then retry.

Permanent modules — no exit function

A module without a module_exit() registration is considered permanent. The kernel refuses to unload it because there is no cleanup function to call — unloading it would leave the kernel in an inconsistent state.

Such a module appears in lsmod with a in the dependency column and cannot be removed with rmmod (you get EBUSY). The only way to remove it is to reboot. This is intentional — some kernel subsystems should never be unloaded once active.

What about rmmod -f (force)? The -f flag exists only if the kernel was compiled with CONFIG_MODULE_FORCE_UNLOAD=y. Without that config option, the flag is silently ignored. Even with it enabled, forcing an unload of a module with an active reference count is dangerous and can cause kernel panics or data corruption. It also permanently taints the kernel (TAINT_FORCED_RMMOD), which means kernel developers may refuse to help diagnose crashes afterward.

3. modprobe — the smart way to load and unload

While insmod and rmmod are “dumb” (they handle exactly one module and do zero dependency resolution), modprobe is intelligent. It is the tool you should use in almost every situation.

How modprobe resolves dependencies

When you install a new kernel, a utility called depmod scans every .ko file under /lib/modules/$(uname -r)/, looks at the symbols each module exports and imports, and builds a dependency map. This map is saved as /lib/modules/$(uname -r)/modules.dep.

When you run modprobe bluetooth, it reads modules.dep, sees that bluetooth needs a module called rfkill, loads rfkill first, then loads bluetooth.

modprobe Dependency Resolution — Example: bluetooth
$ modprobe bluetooth
▼ reads
/lib/modules/6.x.x/modules.dep
▼ discovers dependency chain
Step 1
Load rfkill
Step 2
Load bluetooth
▼ both now live in kernel
Module: bluetooth (LIVE) — depends on: rfkill (LIVE)

Common modprobe commands

# Load a module (and all its dependencies)
$ sudo modprobe bluetooth

# Remove a module (and remove unused dependencies)
$ sudo modprobe -r bluetooth

# Show what would be loaded WITHOUT actually loading
$ modprobe --show-depends bluetooth

# Regenerate modules.dep (run after adding out-of-tree modules)
$ sudo depmod -a

Setting permanent module options

If you want a module to always load with specific parameters, create a .conf file in /etc/modprobe.d/:

# File: /etc/modprobe.d/my-module.conf
options usbcore autosuspend=2

# Prevent a module from loading automatically
blacklist nouveau
insmod vs modprobe — when to use each:
Use insmod only when you are actively developing a module and loading it by path from your build directory. Use modprobe for everything else — it handles dependencies and reads your configuration files.

4. Security in Linux 6.x — three layers that protect the kernel

On older Linux systems, anyone with root access could insert any kernel module. Modern Linux 6.x systems have three independent security layers that raise the bar significantly. Understanding these layers is essential because they are the most common reason a correctly-built module refuses to load on a modern system.

Module Security Layers in Linux 6.x — Three Independent Guards
Layer 1 — Capability
CAP_SYS_MODULE required for load/unload.
kernel.modules_disabled sysctl (one-way latch) can lock the system permanently until reboot.
+
Layer 2 — Module Signing
Each .ko can carry a PKCS#7 cryptographic signature. With sig_enforce=1 or CONFIG_MODULE_SIG_FORCE, unsigned modules are rejected.
+
Layer 3 — Lockdown LSM
Kernel lockdown mode (engaged automatically under UEFI Secure Boot) blocks unsigned module loading even if layers 1 and 2 permit it.
All three layers are independent. Passing one does not bypass the others.

Layer 1: CAP_SYS_MODULE and modules_disabled

The CAP_SYS_MODULE Linux capability is required for both init_module() (load) and delete_module() (unload). Running as root grants this capability by default.

The kernel.modules_disabled sysctl adds a second check. When set to 1, all module loading and unloading is blocked regardless of capability.

# Check current value
$ cat /proc/sys/kernel/modules_disabled
0

# Lock module loading permanently until reboot (cannot be undone!)
$ echo 1 | sudo tee /proc/sys/kernel/modules_disabled
modules_disabled is a one-way ratchet. Once set to 1, it cannot be set back to 0 at runtime. The only way to re-enable module loading is to reboot. Use this only in production hardening scenarios where all required modules are already loaded.

Layer 2: Module signing

The kernel can require every .ko file to carry a cryptographic signature. This signature is checked against a set of trusted public keys that are baked into the kernel at build time.

Kernel config / boot paramEffect
CONFIG_MODULE_SIG=yEnable signature checking. Unsigned modules load but taint the kernel.
CONFIG_MODULE_SIG_FORCE=yCompile-time enforcement. Unsigned or invalid modules are rejected (ENOKEY).
module.sig_enforce=1 (boot param)Runtime enforcement equivalent to SIG_FORCE, without recompiling the kernel.

For custom or out-of-tree modules on a system with module signing, you need to sign your module with a key whose certificate is trusted by the running kernel. On systems with UEFI Secure Boot, this means enrolling your certificate as a Machine Owner Key (MOK).

# Generate a key pair for signing (one-time setup)
$ openssl req -new -x509 -newkey rsa:2048 -keyout my_key.pem \
  -out my_cert.pem -days 365 -subj "/CN=My Kernel Module Key/"

# Sign a module
$ /usr/src/linux-headers-$(uname -r)/scripts/sign-file \
  sha256 my_key.pem my_cert.pem my_module.ko

# Enroll the certificate for Secure Boot (requires reboot to confirm)
$ sudo mokutil --import my_cert.pem

Layer 3: Kernel lockdown and Secure Boot

The lockdown LSM (Linux Security Module) was mainlined in Linux 5.4 and is active in all 6.x kernels. It adds a security level above and beyond capabilities and module signing.

# Check current lockdown level
$ cat /sys/kernel/security/lockdown
none [integrity] confidentiality

The value in square brackets is the currently active level. There are three levels:

  • none — no additional restrictions from lockdown
  • integrity — unsigned modules are blocked, /dev/mem access restricted, kexec of unsigned images blocked
  • confidentiality — stricter: also blocks reading kernel memory, hibernation, and more
UEFI Secure Boot automatically activates lockdown=integrity. If your machine boots with Secure Boot enabled, the kernel logs this at startup and enforces lockdown regardless of whether CONFIG_MODULE_SIG_FORCE is set. This is the most common reason unsigned modules fail to load on modern Ubuntu, Fedora, or RHEL systems even when you are running as root.

5. Writing good init and exit functions

Every kernel module has two optional but strongly recommended functions: an init function that runs when the module is loaded, and an exit function that runs when it is unloaded. Understanding how to write them correctly avoids kernel crashes, memory leaks, and resource deadlocks.

The basic structure

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A short description of what this module does");

static int __init my_module_init(void)
{
    /* Initialization code goes here */
    /* Return 0 on success, negative errno on failure */
    return 0;
}

static void __exit my_module_exit(void)
{
    /* Cleanup code goes here */
    /* No return value — this must always succeed */
}

module_init(my_module_init);
module_exit(my_module_exit);

The __init and __exit markers — what they do

__init and __exit — Memory Behavior After Module Load
Loadable Module (.ko)
__init section
Contains: my_module_init()
core section (always kept)
Contains: rest of module code
__exit section
Contains: my_module_exit()
→ after load →
In Kernel Memory (LIVE)
__init section
FREED — kernel reclaims this memory
core section
KEPT — module lives here
__exit section
KEPT (loadable) / FREED (built-in)

The __init marker tells the compiler to place the init function in a special section. After the module loads successfully and the init function returns, the kernel frees that section’s memory. This is an optimization — you do not need the init code after it has run once.

The __exit marker places the exit function in its own section. When a module is compiled directly into the kernel (built-in), the exit function can never run — so the compiler discards it entirely, saving space.

The error-path unwind pattern

Your init function may acquire multiple resources in sequence. If step 3 fails after steps 1 and 2 succeeded, you must undo steps 1 and 2 in reverse order before returning the error. The standard pattern uses goto labels:

static int __init my_module_init(void)
{
    int ret;

    ret = register_thing_a();
    if (ret)
        return ret;          /* Nothing to undo yet */

    ret = register_thing_b();
    if (ret)
        goto err_b;          /* Undo step A */

    ret = register_thing_c();
    if (ret)
        goto err_c;          /* Undo steps B and A */

    return 0;                /* All good */

err_c:
    unregister_thing_b();
err_b:
    unregister_thing_a();
    return ret;
}

static void __exit my_module_exit(void)
{
    /* Undo everything in reverse order of init */
    unregister_thing_c();
    unregister_thing_b();
    unregister_thing_a();
}

The six rules for safe init/exit code

Rule 1 — Return 0 on success, negative errno on failure

The init function must return 0 for success. For failure, return a negative error code like -ENOMEM, -ENODEV, or -EINVAL. Never return a positive value — the kernel may warn about it. A nonzero return aborts the load and the module never becomes LIVE.

Rule 2 — Exit must always succeed

The exit function has no return value and is expected to always clean up completely. If your exit function can fail, you have a design problem. Whatever you register or allocate in init, you must unregister or free in exit.

Rule 3 — Always declare MODULE_LICENSE()

MODULE_LICENSE() is mandatory. If it is missing or contains a non-GPL-compatible value, the kernel marks itself tainted and blocks access to symbols exported with EXPORT_SYMBOL_GPL(), which includes a large portion of the modern kernel API.

Rule 4 — Always provide an exit function

Without module_exit(), the module becomes permanent — it cannot be unloaded without a reboot. Only omit the exit function intentionally when the module truly must never be removed (rare in practice).

Rule 5 — Unregister everything before exit returns

Before exit returns, cancel all pending work queue items, cancel all timers, wait for any RCU callbacks, and deregister all callbacks and interrupt handlers. If any callback fires after your module memory is freed, the kernel will crash.

Rule 6 — Use THIS_MODULE and try_module_get()

When you register a struct file_operations or similar structure, set its .owner = THIS_MODULE. The kernel uses this to automatically increment your module’s reference count when the device is opened, preventing unload while it is in use. Never rely on manual reference counting unless absolutely necessary.

Interview Questions & Answers

Q1. What system call does rmmod use internally?
rmmod (via libkmod) calls the delete_module(2) Linux system call, passing the module name and a flags argument. The kernel’s implementation in kernel/module/main.c performs capability checks, reference count checks, and state checks before calling the module’s exit function and freeing its memory.
Q2. Why does rmmod fail with “Operation not permitted” even when I am root?
There are two possible reasons both resulting in EPERM: either the caller lacks CAP_SYS_MODULE (unusual for true root, but possible in containers or restricted environments), or the kernel.modules_disabled sysctl is set to 1. Check with cat /proc/sys/kernel/modules_disabled — if it shows 1, module loading and unloading are completely locked until the next reboot.
Q3. What happens to __init code after the module loads?
The kernel frees the memory occupied by the __init section immediately after the init function returns successfully. This is a deliberate memory optimization — setup code is only needed once, so there is no point keeping it resident in kernel RAM for the life of the module.
Q4. Can a module be loaded on a system with Secure Boot even without a signed .ko?
On most modern distributions with UEFI Secure Boot enabled, the kernel automatically activates lockdown=integrity. Under this lockdown level, unsigned modules are rejected regardless of whether CONFIG_MODULE_SIG_FORCE is set in the kernel config. To load an unsigned or custom-signed module, you must either disable Secure Boot in UEFI firmware settings, or generate a key pair, sign your module, and enroll the certificate as a Machine Owner Key (MOK) using mokutil.
Q5. What is the difference between EBUSY and EWOULDBLOCK from rmmod?
EBUSY means the module is in the wrong state or has no exit function — the problem is structural and cannot be resolved by waiting. EWOULDBLOCK means the module has a nonzero reference count — something is currently using it. EWOULDBLOCK is potentially resolvable by closing whatever is using the module and retrying.
Q6. What does MODULE_LICENSE() affect beyond documentation?
It determines which kernel symbols your module can use. The Linux kernel exports certain symbols only to GPL-compatible modules using EXPORT_SYMBOL_GPL(). If your MODULE_LICENSE() is not GPL-compatible (e.g. “Proprietary”), attempts to use those symbols will fail at load time with “Unknown symbol” errors. Additionally, a non-GPL-compatible or missing license taints the kernel, which signals to kernel developers that the system may not be fully open and supportable.
Q7. What is the purpose of the goto error-unwind pattern in module init?
It ensures that if initialization fails partway through, all resources acquired so far are released in reverse order before returning the error. Without this pattern, a failed init could leave kernel resources (IRQs, memory, device registrations) in a partially allocated state, causing resource leaks or kernel instability. The goto pattern is the idiomatic and most readable way to implement this in C without nested conditionals.

Free Linux Kernel Programming Course

This tutorial is part of a completely free Linux kernel programming course at EmbeddedPathashala. No registration required. Learn at your own pace, from fundamentals to advanced device driver development.

← Part 1: Module Inspection Back to Course Index

Leave a Reply

Your email address will not be published. Required fields are marked *