website/docs/guide/module.md
KernelSU provides a module mechanism that achieves the effect of modifying the system directory while maintaining the integrity of the system partition. This mechanism is commonly known as "systemless".
The module mechanism of KernelSU is almost the same as that of Magisk. If you're familiar with Magisk module development, developing KernelSU modules is very similar. You can skip the introduction of modules below and just read Difference with Magisk.
::: warning METAMODULE ONLY NEEDED FOR SYSTEM FILE MODIFICATION
KernelSU uses a metamodule architecture for mounting the system directory. Only if your module needs to modify /system files (via the system directory) do you need to install a metamodule (such as meta-overlayfs). Other module features like scripts, sepolicy rules, and system.prop work without a metamodule.
:::
KernelSU's modules support displaying interfaces and interacting with users. For more details, refer to the WebUI documentation.
KernelSU provides a built-in configuration system that allows modules to store persistent or temporary key-value settings. For more details, refer to the Module Configuration documentation.
KernelSU ships with a feature-complete BusyBox binary (including full SELinux support). The executable is located at /data/adb/ksu/bin/busybox. KernelSU's BusyBox supports runtime toggle-able "ASH Standalone Shell Mode". What this Standalone Mode means is that when running in the ash shell of BusyBox, every single command will directly use the applet within BusyBox, regardless of what is set as PATH. For example, commands like ls, rm, chmod will NOT use what is in PATH (in the case of Android by default it will be /system/bin/ls, /system/bin/rm, and /system/bin/chmod respectively), but will instead directly call internal BusyBox applets. This makes sure that scripts always run in a predictable environment and always have the full suite of commands no matter which Android version it is running on. To force a command not to use BusyBox, you have to call the executable with full paths.
Every single shell script running in the context of KernelSU will be executed in BusyBox's ash shell with Standalone Mode enabled. For what is relevant to 3rd party developers, this includes all boot scripts and module installation scripts.
For those who want to use this Standalone Mode feature outside of KernelSU, there are 2 ways to enable it:
ASH_STANDALONE to 1
Example: ASH_STANDALONE=1 /data/adb/ksu/bin/busybox sh <script>/data/adb/ksu/bin/busybox sh -o standalone <script>To make sure all subsequent sh shell executed also runs in Standalone Mode, option 1 is the preferred method (and this is what KernelSU and the KernelSU manager use internally) as environment variables are inherited down to child processes.
::: tip DIFFERENCE WITH MAGISK KernelSU's BusyBox is now using the binary file compiled directly from the Magisk project. Thanks to Magisk! Therefore, you don't need to worry about compatibility issues between BusyBox scripts in Magisk and KernelSU, as they're exactly the same! :::
A KernelSU module is a folder placed in /data/adb/modules with the structure below:
/data/adb/modules
├── .
├── .
|
├── $MODID <--- The folder is named with the ID of the module
│ │
│ │ *** Module Identity ***
│ │
│ ├── module.prop <--- This file stores the metadata of the module
│ │
│ │ *** Main Contents ***
│ │
│ ├── system <--- This folder will be mounted if skip_mount does not exist
│ │ ├── ...
│ │ ├── ...
│ │ └── ...
│ │
│ │ *** Status Flags ***
│ │
│ ├── skip_mount <--- If exists, KernelSU will NOT mount your system folder
│ ├── disable <--- If exists, the module will be disabled
│ ├── remove <--- If exists, the module will be removed next reboot
│ │
│ │ *** Optional Files ***
│ │
│ ├── post-fs-data.sh <--- This script will be executed in post-fs-data
│ ├── post-mount.sh <--- This script will be executed in post-mount
│ ├── service.sh <--- This script will be executed in late_start service
│ ├── boot-completed.sh <--- This script will be executed on boot completed
| ├── uninstall.sh <--- This script will be executed when KernelSU removes your module
| ├── action.sh <--- This script will be executed when user click the Action button in KernelSU app
│ ├── system.prop <--- Properties in this file will be loaded as system properties by resetprop
│ ├── sepolicy.rule <--- Additional custom sepolicy rules
│ │
│ │ *** Auto Generated, DO NOT MANUALLY CREATE OR MODIFY ***
│ │
│ ├── vendor <--- A symlink to $MODID/system/vendor
│ ├── product <--- A symlink to $MODID/system/product
│ ├── system_ext <--- A symlink to $MODID/system/system_ext
│ │
│ │ *** Any additional files / folders are allowed ***
│ │
│ ├── ...
│ └── ...
|
├── another_module
│ ├── .
│ └── .
├── .
├── .
::: tip DIFFERENCE WITH MAGISK KernelSU doesn't have built-in support for Zygisk, so there is no content related to Zygisk in the module. However, you can use ZygiskNext to support Zygisk modules. In this case, the content of the Zygisk module is identical to that supported by Magisk. :::
module.prop is a configuration file for a module. In KernelSU, if a module doesn't contain this file, it won't be recognized as a module. The format of this file is as follows:
id=<string>
name=<string>
version=<string>
versionCode=<int>
author=<string>
description=<string>
updateJson=<url> (optional)
actionIcon=<path> (optional)
webuiIcon=<path> (optional)
id has to match this regular expression: ^[a-zA-Z][a-zA-Z0-9._-]+$
Example: ✓ a_module, ✓ a.module, ✓ module-101, ✗ a module, ✗ 1_module, ✗ -a-module
This is the unique identifier of your module. You should not change it once published.
versionCode has to be an integer. This is used to compare versions.
Others that were not mentioned above can be any single line string.
Make sure to use the UNIX (LF) line break type and not the Windows (CR+LF) or Macintosh (CR).
actionIcon and webuiIcon are optional icon paths used as the default
icons for the module action shortcut and WebUI shortcut in the Manager. These
paths must be relative to the module root directory. For example,
actionIcon=icon/icon.png will be resolved as <MODDIR>/icon/icon.png.
::: tip DYNAMIC DESCRIPTION
The description field can be dynamically overridden at runtime using the module configuration system. See Overriding Module Description for details.
:::
Please read the Boot scripts section to understand the difference between post-fs-data.sh and service.sh. For most module developers, service.sh should be good enough if you just need to run a boot script, if you need to run the script after boot completed, please use boot-completed.sh. If you want to do something after mounting OverlayFS, please use post-mount.sh.
In all scripts of your module, please use MODDIR=${0%/*} to get your module's base directory path; do NOT hardcode your module path in scripts.
::: tip DIFFERENCE WITH MAGISK
You can use the environment variable KSU to determine if a script is running in KernelSU or Magisk. If running in KernelSU, this value will be set to true.
:::
system directoryThe contents of this directory will be overlaid on top of the system's /system partition after the system is booted. This means that:
::: tip METAMODULE REQUIREMENT
The system directory is only mounted if you have a metamodule installed that provides mounting functionality (such as meta-overlayfs). The metamodule handles how modules are mounted. See the Metamodule Guide for more information.
:::
If you want to delete a file or folder in the original system directory, you need to create a file with the same name as the file/folder in the module directory using mknod filename c 0 0. This way, the OverlayFS system will automatically "whiteout" this file as if it has been deleted (the /system partition isn't actually changed).
You can also declare a variable named REMOVE containing a list of directories in customize.sh to execute removal operations, and KernelSU will automatically execute mknod <TARGET> c 0 0 in the corresponding directories of the module. For example:
REMOVE="
/system/app/YouTube
/system/app/Bloatware
"
The above list will execute mknod $MODPATH/system/app/YouTube c 0 0 and mknod $MODPATH/system/app/Bloatware c 0 0, /system/app/YouTube and /system/app/Bloatware will be removed after the module takes effect.
If you want to replace a directory in the system, you need to create a directory with the same path in your module directory, and then set the attribute setfattr -n trusted.overlay.opaque -v y <TARGET> for this directory. This way, the OverlayFS system will automatically replace the corresponding directory in the system (without changing the /system partition).
You can declare a variable named REPLACE in your customize.sh file, which includes a list of directories to be replaced, and KernelSU will automatically perform the corresponding operations in your module directory. For example:
REPLACE="
/system/app/YouTube
/system/app/Bloatware
"
This list will automatically create the directories $MODPATH/system/app/YouTube and $MODPATH/system/app/Bloatware, and then execute setfattr -n trusted.overlay.opaque -v y $MODPATH/system/app/YouTube and setfattr -n trusted.overlay.opaque -v y $MODPATH/system/app/Bloatware. After the module takes effect, /system/app/YouTube and /system/app/Bloatware will be replaced with empty directories.
::: tip DIFFERENCE WITH MAGISK
KernelSU uses a metamodule architecture where mounting is delegated to pluggable metamodules. The official meta-overlayfs metamodule uses the kernel's OverlayFS for systemless modifications, while Magisk uses magic mount (bind mount) built directly into its core. Both achieve the same goal: modifying /system files without physically modifying the /system partition. KernelSU's approach provides more flexibility and reduces detection surface.
:::
If you're interested in OverlayFS, it's recommended to read the Linux Kernel's documentation on OverlayFS. For details on KernelSU's metamodule system, see the Metamodule Guide.
This file follows the same format as build.prop. Each line comprises of [key]=[value].
If your module requires some additional sepolicy patches, please add those rules into this file. Each line in this file will be treated as a policy statement.
A KernelSU module installer is a KernelSU module packaged in a ZIP file that can be flashed in the KernelSU manager. The simplest KernelSU module installer is just a KernelSU module packed as a ZIP file.
module.zip
│
├── customize.sh <--- (Optional, more details later)
│ This script will be sourced by update-binary
├── ...
├── ... /* The rest of module's files */
│
::: warning KernelSU module is NOT compatible for installation in a custom Recovery! :::
If you need to customize the module installation process, optionally you can create a script in the installer named customize.sh. This script will be sourced (not executed) by the module installer script after all files are extracted and default permissions and secontext are applied. This is very useful if your module requires additional setup based on the device ABI, or you need to set special permissions/secontext for some of your module files.
If you would like to fully control and customize the installation process, declare SKIPUNZIP=1 in customize.sh to skip all default installation steps. By doing so, your customize.sh will be responsible to install everything by itself.
The customize.sh script runs in KernelSU's BusyBox ash shell with Standalone Mode enabled. The following variables and functions are available:
KSU (bool): a variable to mark that the script is running in the KernelSU environment, and the value of this variable will always be true. You can use it to distinguish between KernelSU and Magisk.KSU_VER (string): the version string of currently installed KernelSU (e.g. v0.4.0).KSU_VER_CODE (int): the version code of currently installed KernelSU in userspace (e.g. 10672).KSU_KERNEL_VER_CODE (int): the version code of currently installed KernelSU in kernel space (e.g. 10672).BOOTMODE (bool): always be true in KernelSU.MODPATH (path): the path where your module files should be installed.TMPDIR (path): a place where you can temporarily store files.ZIPFILE (path): your module's installation ZIP.ARCH (string): the CPU architecture of the device. Value is either arm, arm64, x86, or x64.IS64BIT (bool): true if $ARCH is either arm64 or x64.API (int): the API level (Android version) of the device (e.g., 23 for Android 6.0).::: warning
In KernelSU, MAGISK_VER_CODE is always 25200, and MAGISK_VER is always v25.2. Please don't use these two variables to determine whether KernelSU is running or not.
:::
ui_print <msg>
print <msg> to console
Avoid using 'echo' as it will not display in custom recovery's console
abort <msg>
print error message <msg> to console and terminate the installation
Avoid using 'exit' as it will skip the termination cleanup steps
set_perm <target> <owner> <group> <permission> [context]
if [context] is not set, the default is "u:object_r:system_file:s0"
this function is a shorthand for the following commands:
chown owner.group target
chmod permission target
chcon context target
set_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission> [context]
if [context] is not set, the default is "u:object_r:system_file:s0"
for all files in <directory>, it will call:
set_perm file owner group filepermission context
for all directories in <directory> (including itself), it will call:
set_perm dir owner group dirpermission context
In KernelSU, scripts are divided into two types based on their running mode: post-fs-data mode and late_start service mode.
setprop will deadlock the boot process! Please use resetprop -n <prop_name> <prop_value> instead.In KernelSU, startup scripts are divided into two types based on their storage location: general scripts and module scripts.
/data/adb/post-fs-data.d, /data/adb/service.d, /data/adb/post-mount.d or /data/adb/boot-completed.d.chmod +x script.sh).post-fs-data.d runs in post-fs-data mode, and scripts in service.d runs in late_start service mode.post-fs-data.sh runs in post-fs-data mode, service.sh runs in late_start service mode, boot-completed.sh runs on boot completed, post-mount.sh runs on OverlayFS mounted.All boot scripts will run in KernelSU's BusyBox ash shell with Standalone Mode enabled.
The following is the relevant boot process for Android (some parts are omitted), which includes the operation of KernelSU (with leading asterisks), and can help you better understand the purpose of these module scripts:
0. Bootloader (nothing on screen)
load patched boot.img
load kernel:
- GKI mode: GKI kernel with KernelSU integrated
- LKM mode: stock kernel
...
1. kernel exec init (OEM logo on screen):
- GKI mode: stock init
- LKM mode: exec ksuinit, insmod kernelsu.ko, exec stock init
mount /dev, /dev/pts, /proc, /sys, etc.
property-init -> read default props
read init.rc
...
early-init -> init -> late_init
early-fs
start vold
fs
mount /vendor, /system, /persist, etc.
post-fs-data
*safe mode check
*execute general scripts in post-fs-data.d/
*load sepolicy.rule
*execute metamodule's post-fs-data.sh (if exists)
*execute module scripts post-fs-data.sh
**(Zygisk)./bin/zygisk-ptrace64 monitor
*(pre)load system.prop (same as resetprop -n)
*execute metamodule's metamount.sh (mounts all modules)
*execute general scripts in post-mount.d/
*execute metamodule's post-mount.sh (if exists)
*execute module scripts post-mount.sh
zygote-start
load_all_props_action
*execute resetprop (actual set props for resetprop with -n option)
... -> boot
class_start core
start-service logd, console, vold, etc.
class_start main
start-service adb, netd (iptables), zygote, etc.
2. kernel2user init (ROM animation on screen, start by service bootanim)
*execute general scripts in service.d/
*execute metamodule's service.sh (if exists)
*execute module scripts service.sh
*set props for resetprop without -p option
**(Zygisk) hook zygote (start zygiskd)
**(Zygisk) mount zygisksu/module.prop
start system apps (autostart)
...
boot complete (broadcast ACTION_BOOT_COMPLETED event)
*execute general scripts in boot-completed.d/
*execute metamodule's boot-completed.sh (if exists)
*execute module scripts boot-completed.sh
3. User operable (lock screen)
input password to decrypt /data/data
*actual set props for resetprop with -p option
start user apps (autostart)
If you're interested in Android Init Language, it's recommended to read its documentation.
In addition to the standard boot flow described above, KernelSU supports a late-load mode for LKM (Loadable Kernel Module) scenarios. In this mode, the KernelSU kernel module is loaded after the system has fully booted, rather than during the init process.
Late-load is triggered by running the ksud late-load command. This command:
kernelsu.ko from embedded assets.Since the system is already fully running, certain boot-time mechanisms are unavailable or unnecessary.
| Behavior | Standard boot | Late-load mode |
|---|---|---|
| Kernel module loaded by init (PID 1) | Yes | No (loaded after boot) |
| kprobe hooks for ksud (execve/read/fstat/input) | Yes | Skipped |
| Safe mode detection (volume key) | Yes | Always disabled |
| Boot log capture (logcat/dmesg) | Yes | Skipped |
| Magisk coexistence check | Yes | Skipped |
post-fs-data event reported to kernel | Yes | Skipped |
boot-completed event reported to kernel | Yes | Set directly during init |
post-fs-data.sh / post-fs-data.d/ scripts | Yes | Replaced by late-load stage |
system.prop loading | Yes | Yes |
| OverlayFS mount (metamodule) | Yes | Yes |
post-mount.sh / post-mount.d/ scripts | Yes | Yes |
service.sh / service.d/ scripts | Yes | Yes |
boot-completed.sh / boot-completed.d/ scripts | Yes | Yes |
KSU_LATE_LOAD environment variable | Not set | Set to 1 |
Kernel info flag 0x4 | Not set | Set |
In late-load mode, the script execution order is:
ksud late-load:
1. Load kernelsu.ko (if not already loaded)
2. Extract binaries, handle module updates, load SELinux rules, init features
3. Execute late-load.d/ and module late-load scripts (blocking)
4. Load system.prop (resetprop -n)
5. Execute metamodule mount script (OverlayFS)
6. Execute post-mount.d/ and module post-mount.sh (blocking)
7. Execute service.d/ and module service.sh (non-blocking)
8. Execute boot-completed.d/ and module boot-completed.sh (non-blocking)
Modules can provide a late-load.sh script that runs only in late-load mode, as a replacement for post-fs-data.sh. This script runs before OverlayFS mounting, similar to post-fs-data.sh in the standard flow.
Additionally, general scripts can be placed in /data/adb/late-load.d/ to run during this stage.
Modules can detect late-load mode by checking the KSU_LATE_LOAD environment variable:
if [ "$KSU_LATE_LOAD" = "1" ]; then
# Running in late-load mode
echo "Late-load mode detected"
fi
This allows modules to adjust their behavior accordingly, for example skipping operations that are only needed during early boot.