Repository navigation
Conversation
…gistration Unified EACCES before lookup at the getname wrap (name-match first, rate-limited logging), getattr insurance layer, KP_EXPORT_SYMBOL add/del prefix API (8 slots), file-gated supercall channel and danger-prefix denylist.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Registry correctness, absolute-path bypasses, and root-access regressions remain unresolved.
4 open issues
What changed in this PR
Adds pre-lookup path guarding to KernelPatch’s su compatibility layer to reduce errno-based detection of hidden /data/adb children.
Changes:
- Registers a getname hook to return uniform EACCES for unauthorized guarded-path probes.
- Adds dynamic prefix registration through kernel exports and an opt-in supercall channel.
- Extends the optional getattr guard and initializes path-resolution support.
| File | Description |
|---|---|
| kernel/patch/common/sucompat.c | Implements path guards, prefix registration, and hook wiring. |
Review effort: Balanced
| if (free_slot < 0) free_slot = i; | ||
| continue; | ||
| } | ||
| if (!strncmp(s->prefix, prefix, SU_GUARD_PREFIX_LEN)) { |
Comment on lines
+583
to
+596
| static bool su_is_guarded_path(const char *name) | ||
| { | ||
| int i; | ||
| if (su_is_adb_prefix(name)) return true; | ||
| for (i = 0; i < SU_GUARD_SLOT_MAX; i++) { | ||
| struct su_guard_slot *s = &su_guard_slots[i]; | ||
| /* ACQUIRE pairs the writer's RELEASE publication: a RELAXED reader can | ||
| * observe valid=1 before the memcpy'd prefix, see the zeroed slot, and | ||
| * (empty prefix matches every absolute path) storm EACCES system-wide. */ | ||
| if (!__atomic_load_n(&s->valid, __ATOMIC_ACQUIRE)) continue; | ||
| if (su_guard_match(name, s->prefix)) return true; | ||
| } | ||
| return false; | ||
| } |
| /* Name match first, granted check only on the rare hit: the common path | ||
| * stays pure string compares, so the guard adds no kstorage-dependent | ||
| * per-call cost that a timing probe could latch onto. */ | ||
| if (su_is_guarded_path(name)) { |
| * stays pure string compares, so the guard adds no kstorage-dependent | ||
| * per-call cost that a timing probe could latch onto. */ | ||
| if (su_is_guarded_path(name)) { | ||
| if (is_su_allow_uid(uid) || is_trusted_manager_uid(uid)) return; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
Unprivileged probes of
/data/adb/<subdir>can distinguish existence via errno:/data/adbis0700 root; DAC denies search before childlookup, so every path under the prefix returns
EACCESuniformly.EACCESitself proves the directory exists, but which child exists is unobservable.
paths with
ENOENTinstead. The errno difference becomes an existenceoracle that fingerprints the hiding stack — a detection vector KernelPatch
users should not carry.
Measured on stock (Xiaomi nabu, Android 13):
stat /data/adband every testedchild → uniform
EACCES. Measured on a hidden stack: hidden children →ENOENT.Solution
1. Name-first guard at the getname wrap
security_file_open-class hooks fire after lookup; only thegetnamewrapcan unify the answer before the filesystem ever sees the path.
su_is_adb_prefix()— exact match of the/data/adbbody and its subpaths(no false positive on
/data/adbfoo).after_getname_flags()— string-compare first; consult authorization onlyon a name hit. Hot path for non-target paths is a pure memcmp, with no
per-call kstorage lookups (also closes the timing side channel that a
lookup-first ordering would keep open). Unauthorized hit →
putname+ERR_PTR(-EACCES)before lookup. Logging is rate-limited (first 4 hits).su_register_path_probe_hooks()(su_getname_hookedguard); also assigns
su_dentry_path_raw(previously declared, neverwired since 2f8f2af).
2. getattr insurance layer
after_security_inode_getattr()returns-EACCESfor unauthorized callerson guarded prefixes. It is implemented but not globally registered by
default — available as a second layer for OEM kernels where the getname
wrap does not fire.
3. Dynamic guard prefix registration (optional API for KPMs / apps)
Kernels shipped to third-party modules often need to guard paths beyond the
hardcoded set:
su_guard_add_prefix(const char *prefix)/su_guard_del_prefix(const char *prefix)— exported via
KP_EXPORT_SYMBOL(registered as__kp_symbol_su_guard_add_prefix/..._del_prefix). 8 slots × 63 bytes,published with release/acquire semantics,
addis idempotent,-ENOSPCwhen full.
CONTROL_FEATUREsupercall with name"guard_add:/prefix"/"guard_del:/prefix"./data/adb/ap/guard_exttoexist, otherwise
-EPERM. KPM export users are not affected by thefile gate (module trust already implies kernel-code execution).
(
/data,/data/data,/storage,/system, …) is rejected with-EPERM(trailing-slash normalized; coverage checked on componentboundary, so
/datarejects but/data/adbfoois fine).Compatibility
those devices the guard is inert and native DAC
EACCESremains uniform —no regression.
Testing
kernel/kpimglinks clean, 297008 B;nmconfirmssu_guard_add_prefix/su_guard_del_prefixexported andpresent in the
__kp_symbol_*table.via
run-as(real app domain):stat /data/adb/modulesand siblings → EACCES (previously ENOENTfrom a hide module), matching stock semantics.
adb guardlines for the blocked probes.Limitations
getname wrap, so
chdir("/data")followed bystat("adb/...")never matchesthe absolute prefix. Stock DAC still answers uniformly, but a hide layer that
returns
ENOENTfor hidden paths keeps an errno oracle open through relativeprobes. cwd-aware matching is possible future work.
openat/statxsubmitted through io_uring resolvepaths in worker context and do not go through the getname wrap; the guard is
inert for those ops on kernels that have io_uring.
Notes for reviewers
callers (it bypasses the dispatcher gate by design — this is required for
oracle defense; authorization is still checked per-call).
an attacker who cannot already write
/data/adbcannot register prefixes.