Advanced OS Customizations & Bootloaders

Persistent LKMs on Android: Techniques for Autoloading Kernel Modules at Boot

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction: The Elusive LKM Persistence on Android

Linux Kernel Modules (LKMs) offer powerful capabilities for extending kernel functionality without recompiling the entire kernel. On desktop Linux systems, autoloading LKMs at boot is a well-understood process, typically involving `modprobe.d` configurations or `systemd` units. However, Android, with its highly customized Linux kernel, unique boot process, and robust security posture (especially SELinux), presents significant challenges for achieving persistent LKM autoloading.

This expert-level guide delves into advanced techniques for ensuring your custom LKMs are loaded automatically during the Android boot sequence. We’ll explore modifications to the ramdisk, custom `init` services, and crucial SELinux policy adjustments, enabling developers and researchers to maintain kernel-level modifications across reboots on rooted devices.

Understanding Android’s Boot Process for LKM Autoloading

The Android boot process is a intricate dance, starting from the bootloader, loading the kernel, and then handing off to `init`, the first user-space process. `init` is responsible for setting up the environment, mounting filesystems, and launching essential services based on its configuration files, primarily `init.rc` and various `init..rc` scripts located within the ramdisk.

The ramdisk, embedded within the `boot.img` alongside the kernel, contains the root filesystem (`/`) during early boot. This is where `init` resides, along with crucial configuration files, SELinux policies, and basic utilities. For an LKM to be persistently loaded, its `insmod` command must be executed within this early boot phase, with the correct SELinux context and before any critical services that might conflict or require the module.

Method 1: Injecting `insmod` into `init.rc` via Ramdisk Modification

One direct approach is to modify the `init.rc` file (or a relevant `.rc` file it includes) within the ramdisk to execute the `insmod` command. This method requires unpacking, modifying, and repacking the `boot.img`.

Step-by-Step Guide:

  1. Obtain Your Device’s `boot.img`:

    This can typically be extracted from stock ROMs, OTA updates, or directly from the device via fastboot (`fastboot boot boot.img`).

  2. Unpack the `boot.img`:

    Use tools like `Magisk`’s built-in image patcher, `AOSP`’s `mkbootimg`/`unyaffs` (for older formats), or `abootimg` to extract the kernel and ramdisk.

    # Example using abootimg-tool
    abootimg -x boot.img
    mkdir ramdisk
    cd ramdisk
    cp ../initrd.img . 
    gunzip initrd.img
    cpio -idm < initrd.img
  3. Place Your LKM:

    Copy your compiled LKM (e.g., `my_module.ko`) into a suitable location within the extracted ramdisk, such as `/vendor/lib/modules/` (if it exists) or a new directory like `/data/local/tmp/` (though this might require `mount -o rw,remount /`). For early boot, placing it directly in a ramdisk path like `/my_modules/` is more reliable if `/data` isn’t yet fully mounted.

    # Assuming your LKM is in the parent directory
    mkdir my_modules
    cp ../my_module.ko my_modules/
  4. Modify `init.rc` (or an included `.rc` file):

    Edit `init.rc` to add the `insmod` command. Find a suitable place, for example, after basic filesystem mounts and before most services start. You’ll need the full path to your module.

    # In init.rc (or a similar .rc file)
    # ... other init commands ...
    
    # Ensure /vendor is mounted if your module is there
    # on fs
    #    mount_all /fstab.${ro.hardware}
    
    # Explicitly load your module
    exec u:r:insmod:s0 -- /vendor/bin/insmod /my_modules/my_module.ko
    
    # Or, if insmod path isn't critical (init typically finds it in PATH)
    # insmod /my_modules/my_module.ko
    
    # ... continue with other init commands ...

    Note the `exec u:r:insmod:s0` prefix. This is an explicit SELinux context transition, critical for `insmod` to have the necessary permissions. The `insmod` utility itself must also have a defined SELinux context in your policy.

  5. Repack the Ramdisk and `boot.img`:

    Compress the ramdisk and then use `mkbootimg` (or `abootimg -u`) to create the new `boot.img`.

    # Repack ramdisk
    find . | cpio -o -H newc | gzip > ../new_initrd.img
    
    # Repack boot.img (parameters depend on original boot.img, extract them first)
    mkbootimg --kernel <path_to_kernel> --ramdisk ../new_initrd.img --base <base_addr> --pagesize <pagesize> --cmdline "<cmdline>" -o new_boot.img
  6. Flash the New `boot.img`:

    Reboot into fastboot mode and flash your modified `boot.img`.

    fastboot flash boot new_boot.img
    fastboot reboot

Method 2: Creating a Custom `init` Service for LKM Loading

A cleaner and more robust approach is to create a dedicated `.rc` file defining an `init` service responsible for loading your module. This service can have its own SELinux context and dependencies.

Step-by-Step Guide:

  1. Create Your Custom `.rc` File:

    Inside your extracted ramdisk, create a new file, for example, `init.my_lkm_loader.rc`. This file will define a simple service.

    # init.my_lkm_loader.rc
    
    # Define a service to load your module
    service my_lkm_loader /vendor/bin/insmod /my_modules/my_module.ko
        class core
        user root
        group root
        seclabel u:r:my_lkm_loader:s0 # Custom SELinux context
        oneshot # Run once and exit
        disabled # Start manually or by trigger
    
    # Add a trigger to start the service early in the boot process
    on early-init
        start my_lkm_loader
    
    on post-fs-data
        # If your module depends on /data being mounted, use post-fs-data
        # You'll need to adjust the path to your module accordingly, e.g., /data/my_modules/my_module.ko
        start my_lkm_loader

    Choose `early-init` if your module is critical for early boot or does not depend on other subsystems (like `/data` partition). Use `post-fs-data` if your module resides on a data partition or relies on services initiated after `/data` is mounted.

  2. Integrate the Custom `.rc` File:

    Make sure your `init.rc` (or `init.device.rc`) includes your new `.rc` file. This is typically done with an `import` statement.

    # In init.rc
    # ... other imports ...
    import /init.my_lkm_loader.rc
    # ...
  3. Place Your LKM and Repack:

    Place your `my_module.ko` as described in Method 1. Then repack the ramdisk and `boot.img`, and flash it.

SELinux Considerations and Policy Enforcement

SELinux is the most significant hurdle for LKM autoloading. Even with root privileges, `insmod` can be denied if the SELinux policy doesn’t permit the operation. You need to ensure:

  1. The `insmod` utility itself has permission to execute.
  2. The `insmod` utility (or the `init` process loading the module) has permission to load kernel modules (`module_load` capability).
  3. The kernel module file (`.ko`) has the correct SELinux context (`file_type`) to be loaded.
  4. Any operations performed by your LKM in kernel space have appropriate SELinux permissions if they interact with user-space resources or perform privileged actions.

Example SELinux Policy (`my_lkm_loader.te`):

You will need to compile and inject custom SELinux policy. This typically involves modifying `sepolicy` sources (if you’re compiling AOSP) or creating a custom `sepolicy.cil` binary and patching the `boot.img` or `vendor_boot.img` (on newer devices). A simplified `.te` (type enforcement) file might look like this:

# my_lkm_loader.te

type my_lkm_loader, domain;
type my_lkm_loader_exec, exec_type, file_type;

init_daemon_domain(my_lkm_loader)

# Allow the init service to run insmod
allow my_lkm_loader insmod_exec:file { execute execute_no_trans read entrypoint };

# Allow the service to load kernel modules
allow my_lkm_loader self:capability module_load;

# Allow reading the module file
allow my_lkm_loader my_modules_file:file { read open getattr };

# Define the file context for your module
type my_modules_file, file_type;
file_type_auto_trans(my_lkm_loader, my_modules_file, file)

# Optional: If your module creates device nodes or interacts with /dev
# allow my_lkm_loader device:chr_file { create write read ioctl getattr setattr };
# file_type_auto_trans(my_lkm_loader, device, file)

# If insmod itself is targeted, you might need to broaden its permissions slightly for this context
# typeattribute insmod_exec sysadm_exec;

After writing your `.te` file, you’d compile it using `sepolicy_compile` or similar tools into a Common Intermediate Language (CIL) file, then merge it into the existing `sepolicy.cil` found in the ramdisk, or provide it as a separate policy module. This is an advanced topic often requiring an AOSP build environment or deep knowledge of Android’s `sepolicy` architecture.

Troubleshooting and Best Practices

  • Check `dmesg` and `logcat`:

    Always inspect `dmesg` (for kernel messages) and `logcat` (for user-space logs) after a failed boot or module load. SELinux denials will be clearly visible in these logs.

    adb shell dmesg | grep -i "selinux|insmod|my_module"
    adb shell logcat | grep -i "insmod|my_module"
  • Bootloop Prevention:

    Always have a recovery method (e.g., custom recovery like TWRP) ready when modifying `boot.img`. A faulty `boot.img` can easily lead to a bootloop.

  • Module Compatibility:

    Ensure your LKM is compiled against the exact kernel source of your device. Mismatched kernel versions or configurations will prevent the module from loading.

  • AVB (Android Verified Boot):

    Modern Android devices utilize Android Verified Boot, which verifies the integrity of `boot.img`. Modifying it will break AVB, requiring you to disable it (if possible on your device) or sign your custom `boot.img` with your own key, which is a complex process.

Conclusion

Achieving persistent LKM autoloading on Android is a nuanced task that demands a comprehensive understanding of the Android boot process, ramdisk structure, and SELinux. By carefully modifying `init.rc` or establishing a dedicated `init` service, coupled with precise SELinux policy adjustments, developers can successfully extend kernel functionality at boot time. While challenging, mastering these techniques opens up a vast array of possibilities for advanced system customization, security research, and custom hardware integration on Android platforms.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner