Skip to content

Fix manager and modules tripping DCL protection on GrapheneOS - #711

Closed
Enovale wants to merge 2 commits into
JingMatrix:masterfrom
Enovale:master
Closed

Fix manager and modules tripping DCL protection on GrapheneOS#711
Enovale wants to merge 2 commits into
JingMatrix:masterfrom
Enovale:master

Conversation

@Enovale

Copy link
Copy Markdown
Contributor

Things of note:

  • I have not tested this on a non-grapheneos device
  • I have not tested this on old versions of grapheneos, but the patch seems very resilient to change
  • I have been wondering if it's possible for modules to trigger Storage DCL but I have yet to see any so it is not patched here
  • I think it'd be helpful/possible without increasing the complexity of the patch too much to force-allow DCL for apps that are selected as a scope in any enabled module, since the user would be doing that anyway. Just not sure how to do that in the simplest way, since all the data needed to calculate that seem to be in other places than the zygisk process.

@JingMatrix

Copy link
Copy Markdown
Owner

I don't think it is really needed to add variable DGRAPHENE_SETTINGS_PACKAGE_NAME, since all OS setting package has this name com.android.settings.
Instead we should find a way to accurately detect Graphene OS. Maybe they have special build property ?

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.

@Enovale

Enovale commented May 2, 2026

Copy link
Copy Markdown
ContributorAuthor

we should find a way to accurately detect Graphene OS. Maybe they have special build property ?

We should be able to know it's graphene simply by the class existing. Trying to "prove" it's grapheneos seems needlessly strict otherwise.

you could probably pre-define a set of packages where the DCL hook should be applied.

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

Copy link
Copy Markdown
ContributorAuthor

Could AOSP or any user/system app conceivably make its own implementation of android.ext.settings.app.AswRestrictMemoryDynCodeLoading in order to hijack the patch and detect the existence of vector? thats the only scenario in which being more strict in checking for grapheneos makes sense to me. But I don't know if thats possible.

@Enovale
Enovaleforce-pushed the master branch 2 times, most recently from 44320b9 to d8ee73fCompareJuly 13, 2026 12:58
@JingMatrix

Copy link
Copy Markdown
Owner

Replaced by 805, you will be added as co-author there.

JingMatrix added a commit that referenced this pull request Aug 1, 2026
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>
Sign up for freeto 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.

2 participants

@Enovale@JingMatrix