Skip to content

feat: add shell_su_adb_only feature to restrict shell root to adb sessions - #3882

Merged
Ylarod merged 4 commits into
mainfrom
feat/shell-su-adb-only
Oct 11, 2026
Merged

Ylarod merged 4 commits into
mainfrom
feat/shell-su-adb-only

Conversation

@Ylarod

@Ylarod Ylarod commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Apps that manage to run code as the shell uid (e.g. through a system service bug) would inherit the root grant of com.android.shell. This adds an opt-in feature shell_su_adb_only (disabled by default). When it is enabled and shell (uid 2000) asks for root, the process must also descend from a trusted process:

  • u:r:adbd:s0, i.e. a real adb shell session, or
  • the ksu domain, i.e. a process already granted root by KernelSU. This covers adb daemons provided by modules such as altdb, whose root daemon spawns services that drop to uid 2000 / u:r:shell:s0. It is no escalation: a shell process obtained through an app exploit only descends from zygote or init.

The feature only adds a denial on top of the allowlist and never grants root by itself. Shell still has to be granted, either in the allowlist or with allow_shell.

Kernel

  • New feature/shell_su_adb_only.c, a static-key switch with feature id 6.
  • The check runs in ksu_is_allow_uid_for_current() when uid == 2000. That covers both sucompat (faccessat/stat/execve) and the GRANT_ROOT supercall.
  • UID_GRANTED_ROOT stays a pure grant query. The lookup is split into ksu_is_uid_granted_root(), which keeps the existing uid 0 handling, so querying uid 2000 does not depend on the caller's (e.g. the Manager's) ancestry.
  • Ancestors are walked through real_parent under RCU, stopping at init. Processes detached from their session (reparented to init) are therefore denied. The walk is capped at 128 levels and denies past that.
  • The adbd SID is cached in cache_sid(), and is_adbd() is added next to is_zygote()/is_init().

Why id 6

Id 5 was webview_zygote_umount, which shipped in v3.3.0 and was removed in #3666. feature save persists every supported feature, so v3.3.0 users likely have id 5 in .feature_config. Reusing the id would silently apply that stale value to this feature, so id 5 is left unused.

ksud / Manager

  • FeatureId::ShellSuAdbOnly (ksud feature get/set/check shell_su_adb_only).
  • Settings toggle (Material and Miuix). It is persisted through ksud feature set + feature save, the same way as sulog, and supports the unsupported/managed/forced states.

Known limitations

  • Wireless debugging from a local app (LADB, Shizuku wireless mode) goes through adbd and is indistinguishable from a real adb session.
  • A process already in the shell domain could ptrace or inject into an existing adb shell process. That depends on SELinux/Yama and is not handled here.
  • Daemons started from adb shell lose the ability to get root after they detach. This is by design.

Testing

  • Kernel builds with DDK android12-5.10, android14-6.1 and android17-6.18 with no warnings. clang-format is clean.
  • ksud clippy (host) has no warnings. Manager compileDebugKotlin passes.
  • Tested on a Pixel 6 (Android 16, android14-6.1) booted with fastboot boot from the stock boot.img patched with this kernelsu.ko, with shell granted in the allowlist:
Case off on off again
adb shell su -c id root root root
nested sh -c 'sh -c "su -c id"' in adb shell root root root
ksud debug su (GRANT_ROOT) root root root
adb shell -tt (pty) – root –
detached setsid process (PPid=1), su root denied (su not redirected) root
detached setsid process (PPid=1), GRANT_ROOT root denied (EPERM) root
altdb doctor --verify-su-isolation from a detached root process (only a ksu ancestor, no adbd) ok ok ok

The altdb probe drops to the same uid/groups/capabilities and u:r:shell:s0 as real altdb services, then runs su. It got root with the feature enabled, and altdb's own KSU_NO_NEW_PRIVS isolation still blocked su. The feature can be toggled at runtime and takes effect immediately. No warnings or RCU stalls in dmesg.

  • The UID_GRANTED_ROOT split was only build-tested. Reproducing the issue on a device needs a caller outside the ksu domain, such as the Manager.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The current-process ancestry check makes the target-UID UID_GRANTED_ROOT API incorrectly report shell as ungranted.

1 open finding
What changed in this PR

Adds an opt-in restriction requiring shell root requests to originate from an adbd process ancestry.

Changes:

  • Adds feature ID 6 across kernel and ksud.
  • Enforces adbd ancestry for shell root authorization.
  • Adds persisted Manager toggles and translations.
File Description
userspace/​ksud/​src/​feature_id.rs Registers feature ID and metadata.
userspace/​ksud/​src/​cli.rs Updates CLI feature help.
uapi/​feature.h Defines the kernel feature ID.
manager/​app/​src/​main/​res/​values/​strings.xml Adds English UI strings.
manager/​app/​src/​main/​res/​values-zh-rHK/​strings.xml Adds Traditional Chinese strings.
manager/​app/​src/​main/​res/​values-zh-rCN/​strings.xml Adds Simplified Chinese strings.
manager/​app/​src/​main/​java/​me/​weishu/​kernelsu/​ui/​viewmodel/​SettingsViewModel.kt Loads, persists, and updates feature state.
manager/​app/​src/​main/​java/​me/​weishu/​kernelsu/​ui/​screen/​settings/​SettingsUiState.kt Adds state and toggle action.
manager/​app/​src/​main/​java/​me/​weishu/​kernelsu/​ui/​screen/​settings/​SettingsScreen.kt Connects the action to the view model.
manager/​app/​src/​main/​java/​me/​weishu/​kernelsu/​ui/​screen/​settings/​SettingsMiuix.kt Adds the Miuix toggle.
manager/​app/​src/​main/​java/​me/​weishu/​kernelsu/​ui/​screen/​settings/​SettingsMaterial.kt Adds the Material toggle.
manager/​app/​src/​main/​java/​me/​weishu/​kernelsu/​data/​repository/​SettingsRepositoryImpl.kt Implements ksud feature operations.
manager/​app/​src/​main/​java/​me/​weishu/​kernelsu/​data/​repository/​SettingsRepository.kt Extends the settings repository contract.
kernel/​selinux/​selinux.h Declares adbd context matching.
kernel/​selinux/​selinux.c Caches and checks the adbd SID.
kernel/​policy/​feature.c Registers the feature name.
kernel/​policy/​allowlist.c Applies the ancestry restriction.
kernel/​Kbuild Builds the new feature module.
kernel/​feature/​shell_su_adb_only.h Declares the feature interface.
kernel/​feature/​shell_su_adb_only.c Implements ancestry checking and feature handlers.
kernel/​core/​init.c Initializes and unregisters the feature.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread kernel/policy/allowlist.c
…sions

Apps that manage to run code as the shell uid (e.g. through a system
service bug) would inherit the root grant of com.android.shell. Add an
opt-in feature (id 6, disabled by default) that, when shell (uid 2000)
asks for root, additionally requires the process to descend from a
u:r:adbd:s0 process, i.e. a real `adb shell` session. Ancestors are
walked through real_parent up to init (at most 128 levels), so
processes detached from adb (reparented to init) are denied.

The check only adds a denial on top of the allowlist and never grants
root by itself. It is applied in __ksu_is_allow_uid_for_current(), which
covers both sucompat and the GRANT_ROOT supercall.

Feature id 5 is skipped on purpose: it was webview_zygote_umount in
v3.3.0, and reusing it would apply stale persisted values.

- kernel: feature/shell_su_adb_only, cache adbd SID
- ksud: add FeatureId::ShellSuAdbOnly
- manager: add settings toggle
@Ylarod
Ylarod force-pushed the feat/shell-su-adb-only branch from 499c3e5 to 1ba1ae6 Compare October 11, 2026 12:05
Modules such as altdb implement their own adb daemon: a root daemon in
the ksu domain spawns services that drop to uid 2000 / u:r:shell:s0.
Those sessions have no adbd ancestor, so su was denied once
shell_su_adb_only was enabled.

Also accept a ksu domain ancestor. This is no escalation, as only
processes already granted root by KernelSU run in the ksu domain, while
a shell process obtained through an app exploit only descends from
zygote or init.
do_uid_granted_root() passes an arbitrary target uid to
ksu_is_allow_uid_for_current(), so with shell_su_adb_only enabled a
query for uid 2000 checked the caller's (e.g. the Manager's) ancestry
and reported shell as not granted.

Split the grant lookup into ksu_is_uid_granted_root(), which keeps the
existing uid 0 handling, and apply the shell ancestry restriction only
in ksu_is_allow_uid_for_current() for actual authorization paths.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

User-facing and CLI descriptions incorrectly imply that any root-process ancestor is trusted rather than specifically a KernelSU-domain ancestor.

4 open findings
1 resolved since last review

🧠 Review effort: Balanced

Comment thread manager/app/src/main/res/values-zh-rCN/strings.xml Outdated
Comment thread manager/app/src/main/res/values-zh-rHK/strings.xml Outdated
Comment thread manager/app/src/main/res/values/strings.xml Outdated
Comment thread userspace/ksud/src/feature_id.rs Outdated
The kernel trusts ancestors in the ksu domain, not any uid 0 process.
Ordinary root system daemons don't satisfy the check, so don't imply
they do in the Manager and ksud descriptions.
@Ylarod
Ylarod merged commit 0a1f3ff into main Oct 11, 2026
24 checks passed
@Ylarod
Ylarod deleted the feat/shell-su-adb-only branch October 11, 2026 21:52
pershoot pushed a commit to KernelSU-Next/KernelSU-Next that referenced this pull request Oct 11, 2026
…sions (tiann/KernelSU#3882)

Apps that manage to run code as the shell uid (e.g. through a system
service bug) would inherit the root grant of `com.android.shell`. This
adds an opt-in feature `shell_su_adb_only` (disabled by default). When
it is enabled and shell (uid 2000) asks for root, the process must also
descend from a trusted process:

- `u:r:adbd:s0`, i.e. a real `adb shell` session, or
- the `ksu` domain, i.e. a process already granted root by KernelSU.
This covers adb daemons provided by modules such as
[altdb](https://github.com/H3XDaemon/altdb), whose root daemon spawns
services that drop to uid 2000 / `u:r:shell:s0`. It is no escalation: a
shell process obtained through an app exploit only descends from zygote
or init.

The feature only adds a denial on top of the allowlist and never grants
root by itself. Shell still has to be granted, either in the allowlist
or with `allow_shell`.

- New `feature/shell_su_adb_only.c`, a static-key switch with feature id
**6**.
- The check runs in `ksu_is_allow_uid_for_current()` when `uid == 2000`.
That covers both sucompat (faccessat/stat/execve) and the `GRANT_ROOT`
supercall.
- `UID_GRANTED_ROOT` stays a pure grant query. The lookup is split into
`ksu_is_uid_granted_root()`, which keeps the existing uid 0 handling, so
querying uid 2000 does not depend on the caller's (e.g. the Manager's)
ancestry.
- Ancestors are walked through `real_parent` under RCU, stopping at
init. Processes detached from their session (reparented to init) are
therefore denied. The walk is capped at 128 levels and denies past that.
- The adbd SID is cached in `cache_sid()`, and `is_adbd()` is added next
to `is_zygote()`/`is_init()`.

Id 5 was `webview_zygote_umount`, which shipped in v3.3.0 and was
removed in #3666. `feature save` persists every supported feature, so
v3.3.0 users likely have id 5 in `.feature_config`. Reusing the id would
silently apply that stale value to this feature, so id 5 is left unused.

- `FeatureId::ShellSuAdbOnly` (`ksud feature get/set/check
shell_su_adb_only`).
- Settings toggle (Material and Miuix). It is persisted through `ksud
feature set` + `feature save`, the same way as sulog, and supports the
unsupported/managed/forced states.

- Wireless debugging from a local app (LADB, Shizuku wireless mode) goes
through adbd and is indistinguishable from a real adb session.
- A process already in the shell domain could ptrace or inject into an
existing adb shell process. That depends on SELinux/Yama and is not
handled here.
- Daemons started from `adb shell` lose the ability to get root after
they detach. This is by design.

- Kernel builds with DDK android12-5.10, android14-6.1 and
android17-6.18 with no warnings. clang-format is clean.
- ksud clippy (host) has no warnings. Manager `compileDebugKotlin`
passes.
- Tested on a Pixel 6 (Android 16, android14-6.1) booted with `fastboot
boot` from the stock `boot.img` patched with this `kernelsu.ko`, with
shell granted in the allowlist:

| Case | off | on | off again |
|---|---|---|---|
| `adb shell su -c id` | root | root | root |
| nested `sh -c 'sh -c "su -c id"'` in adb shell | root | root | root |
| `ksud debug su` (`GRANT_ROOT`) | root | root | root |
| `adb shell -tt` (pty) | – | root | – |
| detached `setsid` process (PPid=1), `su` | root | **denied** (`su` not
redirected) | root |
| detached `setsid` process (PPid=1), `GRANT_ROOT` | root | **denied**
(`EPERM`) | root |
| `altdb doctor --verify-su-isolation` from a detached root process
(only a ksu ancestor, no adbd) | ok | ok | ok |

The altdb probe drops to the same uid/groups/capabilities and
`u:r:shell:s0` as real altdb services, then runs `su`. It got root with
the feature enabled, and altdb's own `KSU_NO_NEW_PRIVS` isolation still
blocked su. The feature can be toggled at runtime and takes effect
immediately. No warnings or RCU stalls in dmesg.
- The `UID_GRANTED_ROOT` split was only build-tested. Reproducing the
issue on a device needs a caller outside the ksu domain, such as the
Manager.

-Manager: Single-card (KernelFeaturesCard (Settings.kt))
Anatdx pushed a commit to Rouyashiki/YukiSU that referenced this pull request Oct 13, 2026
Add an opt-in ancestry check to existing shell grants while keeping grant
queries independent of the caller. Integrate feature ID 6 with C++ ksud,
forced boot configuration and the shared Manager feature controls without
changing legacy feature IDs or the current module teardown lifecycle.

Port tiann/KernelSU#3882:
tiann/KernelSU#3882
Upstream commit: 0a1f3ff71719fa2d6ec15f5568dac27fdb957fcc

Co-authored-by: Ylarod <me@ylarod.cn>
genie1997 pushed a commit to genie1997/android_kernel_xiaomi_redwood that referenced this pull request Oct 13, 2026
…sions (tiann/KernelSU#3882)

Apps that manage to run code as the shell uid (e.g. through a system
service bug) would inherit the root grant of `com.android.shell`. This
adds an opt-in feature `shell_su_adb_only` (disabled by default). When
it is enabled and shell (uid 2000) asks for root, the process must also
descend from a trusted process:

- `u:r:adbd:s0`, i.e. a real `adb shell` session, or
- the `ksu` domain, i.e. a process already granted root by KernelSU.
This covers adb daemons provided by modules such as
[altdb](https://github.com/H3XDaemon/altdb), whose root daemon spawns
services that drop to uid 2000 / `u:r:shell:s0`. It is no escalation: a
shell process obtained through an app exploit only descends from zygote
or init.

The feature only adds a denial on top of the allowlist and never grants
root by itself. Shell still has to be granted, either in the allowlist
or with `allow_shell`.

- New `feature/shell_su_adb_only.c`, a static-key switch with feature id
**6**.
- The check runs in `ksu_is_allow_uid_for_current()` when `uid == 2000`.
That covers both sucompat (faccessat/stat/execve) and the `GRANT_ROOT`
supercall.
- `UID_GRANTED_ROOT` stays a pure grant query. The lookup is split into
`ksu_is_uid_granted_root()`, which keeps the existing uid 0 handling, so
querying uid 2000 does not depend on the caller's (e.g. the Manager's)
ancestry.
- Ancestors are walked through `real_parent` under RCU, stopping at
init. Processes detached from their session (reparented to init) are
therefore denied. The walk is capped at 128 levels and denies past that.
- The adbd SID is cached in `cache_sid()`, and `is_adbd()` is added next
to `is_zygote()`/`is_init()`.

Id 5 was `webview_zygote_umount`, which shipped in v3.3.0 and was
removed in #3666. `feature save` persists every supported feature, so
v3.3.0 users likely have id 5 in `.feature_config`. Reusing the id would
silently apply that stale value to this feature, so id 5 is left unused.

- `FeatureId::ShellSuAdbOnly` (`ksud feature get/set/check
shell_su_adb_only`).
- Settings toggle (Material and Miuix). It is persisted through `ksud
feature set` + `feature save`, the same way as sulog, and supports the
unsupported/managed/forced states.

- Wireless debugging from a local app (LADB, Shizuku wireless mode) goes
through adbd and is indistinguishable from a real adb session.
- A process already in the shell domain could ptrace or inject into an
existing adb shell process. That depends on SELinux/Yama and is not
handled here.
- Daemons started from `adb shell` lose the ability to get root after
they detach. This is by design.

- Kernel builds with DDK android12-5.10, android14-6.1 and
android17-6.18 with no warnings. clang-format is clean.
- ksud clippy (host) has no warnings. Manager `compileDebugKotlin`
passes.
- Tested on a Pixel 6 (Android 16, android14-6.1) booted with `fastboot
boot` from the stock `boot.img` patched with this `kernelsu.ko`, with
shell granted in the allowlist:

| Case | off | on | off again |
|---|---|---|---|
| `adb shell su -c id` | root | root | root |
| nested `sh -c 'sh -c "su -c id"'` in adb shell | root | root | root |
| `ksud debug su` (`GRANT_ROOT`) | root | root | root |
| `adb shell -tt` (pty) | – | root | – |
| detached `setsid` process (PPid=1), `su` | root | **denied** (`su` not
redirected) | root |
| detached `setsid` process (PPid=1), `GRANT_ROOT` | root | **denied**
(`EPERM`) | root |
| `altdb doctor --verify-su-isolation` from a detached root process
(only a ksu ancestor, no adbd) | ok | ok | ok |

The altdb probe drops to the same uid/groups/capabilities and
`u:r:shell:s0` as real altdb services, then runs `su`. It got root with
the feature enabled, and altdb's own `KSU_NO_NEW_PRIVS` isolation still
blocked su. The feature can be toggled at runtime and takes effect
immediately. No warnings or RCU stalls in dmesg.
- The `UID_GRANTED_ROOT` split was only build-tested. Reproducing the
issue on a device needs a caller outside the ksu domain, such as the
Manager.

-Manager: Single-card (KernelFeaturesCard (Settings.kt))

[genie1997: this tree has no feature name table, so that hunk is dropped; init and exit sit next to selinux_hide's in this tree's order; the setuid hook here also feeds the susfs no_su flag from this check, so a shell refused root is treated as unrooted there too]
ThRE-Team added a commit to ThRE-Team/android_kernel_xiaomi_sdm845 that referenced this pull request Oct 14, 2026
feat: add shell_su_adb_only feature to restrict shell root to adb sessions (tiann/KernelSU#3882)
ThRE-Team added a commit to ThRE-Team/android_kernel_xiaomi_sdm845 that referenced this pull request Oct 15, 2026
feat: add shell_su_adb_only feature to restrict shell root to adb sessions (tiann/KernelSU#3882)
AlexLiuDev233 pushed a commit to Baka-SU/BakaSU that referenced this pull request Oct 21, 2026
…sions (tiann/KernelSU#3882)

[cherry-picked from upstream commit tiann/KernelSU@0a1f3ff]

Signed-off-by: AlexLiuDev233 <wzylin11@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants