Fix manager and modules tripping DCL protection on GrapheneOS - #711
Fix manager and modules tripping DCL protection on GrapheneOS#711Enovale wants to merge 2 commits into
Conversation
JingMatrix
commented
May 2, 2026
I don't think it is really needed to add variable It is better that you put your code of hook in a separated class intead of inside [ParasiticManagerHooker.kt], where you could probably pre-define a set of packages where the DCL hook should be applied. |
We should be able to know it's graphene simply by the class existing. Trying to "prove" it's grapheneos seems needlessly strict otherwise.
Adding more things to the list is not the issue, the issue is getting the list of currently enabled scopes from the config cache. I've tried adding a method to the daemon bridge to request all scopes, and it typically works, but it's randomly blank sometimes and the patch doesn't work overall. Not sure why. |
Enovale
commented
May 25, 2026
Could AOSP or any user/system app conceivably make its own implementation of |
44320b9 to
d8ee73fCompareJingMatrix
commented
Jul 30, 2026
Replaced by 805, you will be added as co-author there. |
GrapheneOS ships a "Restrict dynamic code loading" exploit-protection setting
that is immutable and enabled for system apps. The parasitic manager runs
inside com.android.shell, a system app, so GrapheneOS forbids it from loading
the manager's DEX and the manager never starts.
GrapheneOS enforces DCL through two channels, both fed by the same per-app
verdict, and each has to be cleared at that shared source rather than at the
symptom:
- The ART DexFile checks (DynCodeLoading.getAppBindFlags) reject the
manager's transplanted /proc/self/fd DEX as "DCL via storage".
- The kernel grapheneos_flags written at zygote specialize keep
DENY_EXECMEM/DENY_EXECMOD set, so LSPlant/Dobby cannot make its inline-hook
trampoline executable and the process takes a SIGSEGV
(TSEC_FLAG_DENY_EXECMEM: op denied). The manager's isolated WebView process
likewise keeps DENY_EXECMEM, disabling Chromium's JIT.
getAppBindFlags and SELinuxFlags.{get,getForWebViewProcess} all read
AswRestrict{Memory,Storage,WebView}DynCodeLoading.get(), which honours a
non-null getImmutableValue. So the hook is on getImmutableValue for all three
switches in system_server, returning false (allowed) for the single host
package and deferring to GrapheneOS's original verdict for every other app. A
non-null result takes precedence over both the user toggle and the default, so
this forces the setting to allowed regardless of the user's configuration.
The manager host ends up an ordinary DCL-allowed app: the DexFile checks are
off and the exec* SELinux flags cleared, while ptrace denial, hardened_malloc
and MTE stay intact. GrapheneOS is detected by the classes being present, so
the hook is a no-op on every other system; verified against the GrapheneOS 14,
16 and 17 branches.
The scope is intentionally narrow: only com.android.shell is affected, and
only in system_server, where the value is computed. Modules are excluded --
a normal app already exposes a user-configurable DCL toggle on GrapheneOS and
can be allowed without a patch.
Supersedes #711 with a smaller footprint: the GRAPHENE_SETTINGS_PACKAGE_NAME
build variable is dropped and the hook resides in its own class. The technique
was originally developed by @Enovale in #711 and in discussion #340.
Co-authored-by: Enovale <17408285+Enovale@users.noreply.github.com>
Things of note: