The Android Common Kernel baseline is android15-6.6 at
2bdbbebc3bff5804c139a0806aeb96187b3cbb24. Bionic is pinned to
731631f300090436d7f5df80d50b6275c8c60a93, and AOSP system/core to
a3b721a32242006b59cb12bd62c9133632af3a2d.
These source revisions establish the contracts below. They do not establish compatibility with a particular vendor kernel or security policy.
| Contract | Primary reference |
|---|---|
| Loaded module rows, missing reference counts, permanent markers and states | ACK procfs.c |
| Module permission, image validation, loading and removal checks | ACK main.c |
| ELF section-table layout, offsets and sizes | System V ELF sections |
| Release-prefix comparison when symbol CRCs exist | ACK version.c |
| Parameter getter output and appended newline | ACK params.c |
| Module holder links and per-module taint flags | ACK sysfs.c and main.c |
| KernelSU module target name used by the application protection list | KernelSU Kbuild |
| Module name buffer, including its terminating NUL | ACK module.h and moduleparam.h |
| Kbuild replacement of hyphens and commas in module names | ACK Makefile.lib |
| ARM64 UAPI syscall numbers | ACK asm-generic/unistd.h and ARM64 inclusion |
| Bionic register transfer and errno handling | ARM64 syscall.S |
| AOSP file-based module loading and non-forced removal | libmodprobe_ext.cpp |
| GKI, vendor modules and symbol restrictions | AOSP kernel modules overview |
The feature contract is version 3. Backend and WebUI must be updated together; the feature registry rejects mismatched contracts before exposing operations. The shared command-envelope API remains version 1.
Execution is restricted to Android AArch64 root processes using the real /
sysroot. The adapter uses libc's target-specific constants. Compile-time assertions
check AArch64 numbers 105 (init_module), 106 (delete_module) and 273
(finit_module), along with the 64-bit C long width. Bionic performs the register
transfer to x8 and x0–x5 and translates kernel errors into errno.
Loading uses init_module with the same owned image bytes that were inspected and
hashed. This avoids reopening a mutable path between digest verification and the
syscall. In contrast, AOSP libmodprobe uses finit_module on an opened file. These
interfaces have different security-hook paths; a policy can reject the memory-image
operation even when a file-based load is permitted. There is no automatic fallback
to another loading interface after rejection.
Preflight requires a little-endian ARM64 ELF64 image and one nonempty vermagic entry.
The release-prefix comparison is advisory: the kernel can ignore this prefix when
symbol CRCs are available. A matching prefix does not establish ABI, symbol or
signature compatibility. ELF metadata is descriptive, not proof of trusted code.
The displayed name uses the first name metadata entry. Without one it is a
filename estimate, replacing hyphens and commas with underscores as in Kbuild.
The interface marks inferred names and repeated name entries explicitly. The
kernel's embedded module structure ultimately determines its runtime name;
neither the filename nor descriptive metadata attests to that identity.
Removal requests use O_NONBLOCK; O_TRUNC is never passed. Live state, zero
references and absence of dependents/holders are preliminary checks. The kernel
still checks permissions, references and exit support at the actual syscall.
Missing reference counts (-) remain unknown and prevent an unload offer.
Entries visible only in sysfs are not classified as unloadable modules.
Missing or partially unreadable holder listings remain unknown and prevent removal;
an empty successfully read directory is a distinct state. The details view retains
known dependents and reports incomplete holder state. Its taint field comes from
the individual module, not the global /proc/sys/kernel/tainted bitmask. An empty
readable taint attribute means no module taint flags; an unreadable one is unknown.
Application policy blocks removal of kernelsu, including when it reports zero
references. The backend applies this rule to direct CLI/worker requests as well as
WebUI requests, with no override field. unload_protected is supplied by the backend
so the UI does not maintain a second name list. The list currently contains only
the official KernelSU target; it does not discover every root fork or critical
vendor driver. An unlisted name or zero reference count does not establish safety.
The WebUI requires a confirmation dialog for each mutation. The request's
confirmed flag expresses caller intent; a root CLI caller can set it directly,
so it does not attest that a person saw the dialog.
The ARM64 name buffer holds 56 bytes including NUL, so mutations require names of
at most 55 bytes. Browsing permits up to 128 bytes for sysfs entries. Supported
name characters are ASCII letters, digits, underscores, hyphens, periods and plus
signs; . and .. are rejected as path components. This is an application input
policy, not a claim that the kernel enforces the same character whitelist.
Mutations run in an isolated worker with a private input file and private output file. A file lock serializes operations. Lock contention is distinguished from other I/O failures. The parent validates envelope version, command, action and the typed result, rather than accepting an arbitrary success object. Failure envelopes must accompany an unsuccessful process exit; a contradictory exit/envelope pair reports an unknown outcome. Result names must fit the ARM64 kernel buffer, including when a worker reports success. Successful removal results must identify the requested module. Directory permission changes operate on an opened directory descriptor; the final directory and lock components reject symlinks, and the lock must be a regular file. Ancestor directories remain part of the trusted application storage boundary.
The response budget is 30 seconds. Error paths terminate the userspace worker and attempt bounded reaping. An uninterruptible kernel init/exit routine may continue; timeout, missing output and abnormal termination report an unknown outcome. No mutation is retried automatically. Refreshing state does not prove rollback.
Inspection accepts regular, uncompressed .ko files up to 64 MiB. The ELF reader
is isolated inside the feature and exposes a stable architecture enum. Invalid
UTF-8 metadata is displayed with replacement characters and a warning; SHA-256
still covers original bytes. Parameter values preserve whitespace and remove only
the one newline appended by the kernel getter. Directory scan limits and partial
read failures are reported without discarding unrelated entries.
Metadata is limited to 1 MiB, 8192 entries and 128 distinct keys of at most 128
bytes each. These are application resource budgets, not ELF or kernel limits;
over-budget input is rejected rather than silently truncated. The metadata view
mounts its contents only when expanded.
Inspection requires one .modinfo section and rejects duplicate sections instead
of silently selecting one. Invalid metadata offsets report invalid_input rather
than a generic operation failure. These checks do not implement the kernel's full
ELF, symbol, signature or ABI validation.
Load parameters have an application budget of 4096 UTF-8 bytes, separate from JavaScript string length and from kernel argument limits. Both backend and WebUI reject Unicode control characters (U+0000–U+001F and U+007F–U+009F). The UI reports invalid parameters before confirmation and preserves the input for correction.
The interface follows the existing Material 3 Expressive component system and its logical spacing, rounded surfaces and state layers. Applicable guidance:
- Material 3 lists: consistent content/action slots, scannable rows and segmented grouping.
- Android grids and units and touch targets: scalable layouts and at least 48 CSS-pixel touch regions in this WebUI.
- Apple accessibility and layout: legibility, focus, feedback, adaptation to larger text and reachable controls.
- Apple right-to-left guidance: logical row alignment with separately isolated technical strings.
- Apple alerts: mutation confirmation buttons name the actual action, retain cancellation and preserve their target text through the closing transition. Module names in confirmation sentences have independent left-to-right isolation.
- WCAG text contrast: at least 4.5:1 for ordinary text, including active destructive labels and confirmation controls in both themes.
The feature provides 16 locales. Runtime states, empty states, action labels and metadata labels are translated; numbers follow the selected locale. Paths, module names and hashes retain their character direction without forcing the whole row to left alignment. Message keys, nonempty translations and named placeholders are checked across locales. File inspection supports Enter from the path field. Inspection progress is announced, and errors remain visible with a details action. Changing the path clears errors for the previous input revision. Invalid path and parameter fields link their accessible descriptions to the corresponding error. Cancel restores the initiating control's focus; accepted operations move focus to the active tab because mutations can disable or remove the initiating control.
From the repository root:
cargo test -p duck-kernel-modules --locked
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo check -p duck-kernel-modules --locked --target aarch64-linux-android
cargo check -p duckd --locked --no-default-features --features kernel-modules
cargo xtask line-limitFrom ui/, after building the host duckd binary:
pnpm test
pnpm typecheck
pnpm lint
pnpm format:check
pnpm exec playwright test e2e/kernel-modules.spec.ts
pnpm exec playwright test e2e/layout.spec.ts e2e/screenshots.spec.tsCoverage includes procfs variants, byte order/class checks, changed files, parameter whitespace, scan limits, worker protocol failures, bounded timeout cleanup, confirmation, pending/error feedback, keyboard focus, draft preservation, 200% text, narrow screens and RTL alignment. Browser tests use a simulated root host. Cross-compilation and host tests do not verify real-device module loading, unloading, module initialization or vendor LSM/signature acceptance.