Skip to content

fix(root): exec-probe su, bypass Android 13+ SELinux stat denial#610

Merged
rainxchzed merged 4 commits into
mainfrom
fix/magisk-root-detection-android16
May 16, 2026
Merged

fix(root): exec-probe su, bypass Android 13+ SELinux stat denial#610
rainxchzed merged 4 commits into
mainfrom
fix/magisk-root-detection-android16

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 16, 2026

Closes #608. Magisk alpha 30700 on Android 16 (and most Android 13+ devices) install su at `/data/adb/magisk/su`, where `untrusted_app` SELinux profile blocks `File.exists()` from reading. Root detection always returned NOT_AVAILABLE even though Magisk is present and would grant.

Replace stat-based locator with exec-based probe. Magisk / KernelSU / APatch hook the exec syscall regardless of whether the app can stat the path, so direct `Runtime.exec(path, "-c", "id")` works where `File.exists()` doesn't.

Skip IOException silently (binary not at path), keep WARN log for unexpected failures.

Test plan

  • Android compile clean
  • 13 locale whatsnew bullet
  • Reporter (@ndsphonemy) re-test on Magisk alpha 30700 / Android 16
  • Regression check on stock Android 12-14 with Magisk / KernelSU / APatch

Source: issue #608.

Summary by CodeRabbit

  • Bug Fixes

    • Improved detection of Magisk / KernelSU / APatch on Android 13+ (previously masked by system constraints)
    • Fixed Settings and download failures on Android 16 and custom ROMs by falling back to internal storage when external storage is blocked
  • Documentation

    • Updated localized “What’s New” release notes across 13+ languages for version 1.8.3

Review Change Stack

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

rainxchzed has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 38a5d48d-f772-4d5b-9901-758f79cf847b

📥 Commits

Reviewing files that changed from the base of the PR and between 29c8f61 and c1bb474.

📒 Files selected for processing (1)
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt

Walkthrough

This PR replaces file-existence checks for root binary detection with active su -c id probing and timeout handling to verify UID 0 access, caches the validated su path for service operations, and updates localized whatsnew JSON files to document improved Magisk/KernelSU/APatch detection on Android 13+.

Changes

Root Binary Detection Improvement

Layer / File(s) Summary
Root probing contract and implementation
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt
Introduced SuProbe and ProbeResultKind, replaced locateSuBinary(): String? with locateSuBinary(): SuProbe?, and added probeSu(path) to execute su -c id with timeout and parse stdout for uid=0.
Status computation and service integration
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt
computeStatus() now caches the probed su path and maps probe outcomes to RootStatus.READY (UID 0), RootStatus.PERMISSION_NEEDED (non-UID-0/timeout), or RootStatus.NOT_AVAILABLE (no runnable su). requestPermission(), installPackage(), and uninstallPackage() now use cachedSuPath ?: locateSuBinary()?.path.
Release notes documentation (all locales)
core/presentation/src/commonMain/composeResources/files/whatsnew/{18, ar, bn, es, fr, hi, it, ja, ko, pl, ru, tr, zh-CN}/18.json
Added FIXED bullet entries across localized whatsnew files describing improved detection of Magisk/KernelSU/APatch on Android 13+ (previously masked by SELinux) alongside existing Android 16/custom-ROM fixes.

Sequence Diagram

sequenceDiagram
  participant RootServiceManager
  participant locateSuBinary
  participant probeSu
  participant su as "su -c id"
  RootServiceManager->>locateSuBinary: locate working su
  locateSuBinary->>probeSu: probe each SU_PATH
  probeSu->>su: execute with timeout
  su-->>probeSu: stdout (uid=0 or non-zero)
  probeSu-->>locateSuBinary: SuProbe(path, kind)
  locateSuBinary-->>RootServiceManager: return first successful probe or null
  RootServiceManager->>RootServiceManager: cache path, map to RootStatus
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A rabbit dug deeper to find the su root,
Not trusting mere files, but seeking real proof—
su -c id now whispers "uid=0" so true,
While SELinux steps back, and detection breaks through!
In thirteen tongue-stories, the fix takes its flight. 🌍

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(root): exec-probe su, bypass Android 13+ SELinux stat denial' accurately describes the core technical change: replacing file-existence checks with exec-based probing to bypass SELinux restrictions.
Linked Issues check ✅ Passed Changes implement the objective from #608 by replacing stat-based su detection with exec-based probing, enabling root detection on Android 13+ devices where SELinux blocks File.exists() but permits runtime exec.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing root detection via exec-probe mechanism and updating localized release notes to document the fix; no unrelated modifications found.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/magisk-root-detection-android16

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rainxchzed rainxchzed mentioned this pull request May 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt`:
- Around line 275-278: The loop currently returns the first non-null probe
result which can be NOT_ZERO (a non-root or dead su), causing a bad path to be
cached; change the logic in the SU_PATHS loop to only return when probeSu(path)
indicates a successful root (e.g., the ZERO/success variant), and otherwise
continue probing other paths (skip/ignore NOT_ZERO results) so that
SuProbe(path, kind) is only returned for a confirmed working su; refer to
probeSu(...) and SuProbe(...) when making this change and ensure cachedSuPath
only gets set from a confirmed-success probe.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fd20b84b-15bc-4985-94a1-898d226d0a17

📥 Commits

Reviewing files that changed from the base of the PR and between 95e33ce and 5493c35.

📒 Files selected for processing (14)
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt
  • core/presentation/src/commonMain/composeResources/files/whatsnew/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/18.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/18.json

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

rainxchzed has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt`:
- Line 304: The BufferedReader/InputStreamReader wrapping proc.inputStream in
RootServiceManager (the assignment to output) is never closed; change the read
to use Kotlin's use{} (or try/finally) to close the reader, e.g. wrap the reader
creation (InputStreamReader/BufferedReader) with .use { it.readText().trim() }
so proc.inputStream is closed promptly and avoids file descriptor leaks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cb30be69-56db-4214-8c32-3ec101b53b77

📥 Commits

Reviewing files that changed from the base of the PR and between 5493c35 and 29c8f61.

📒 Files selected for processing (1)
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

rainxchzed has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@rainxchzed rainxchzed merged commit 6c096d2 into main May 16, 2026
1 check was pending
@rainxchzed rainxchzed deleted the fix/magisk-root-detection-android16 branch May 16, 2026 06:57
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.

Didn't see root.

1 participant