
PoC for CVE-2021-39749, allowing starting arbitrary Activity on Android 12L Beta
This is PoC for CVE-2021-39749, which allows starting activities of other apps on Android 12L Beta regardless of their permission and exported settings
In Android 12L TaskFragmentOrganizer access (intentionally) no longer requires MANAGE_ACTIVITY_TASKS permission
Using app provided here requires disabling Hidden API Checks, you can do so through adb shell settings put global hidden_api_policy 1. These are not security boundary and there are known app-based bypasses
Here are commits fixing this bug (and few related mentioned in original report):
startActivityInTaskFragment no longer rely on Binder.getCallingUid()ResolverActivity now has relinquishTaskIdentity enabledSurfaceControl of TaskFragment is no longer providedActivityRecord#appToken to TaskFragmentOrganizer is now based on uid instead of pidYou can checkout android-12.1.0_r4, revert first 3 commits (or first 2, application will still be able shutdown device (by starting ShutdownActivity), but "Zoom and set alpha" checkbox won't work)
(First commit from that list will have merge conflicts in tests if you try to revert it, but you can ignore these)
Binder.getCallingUid() that always returns system uidBinder.getCallingUid() method returns uid of process that sent currently processed Binder transaction. That uid is stored in thread-local variable. Code handling transaction can call Binder.clearCallingIdentity() to set that variable to uid of own process to indicate to methods called later during transaction handling that permission checks should be done against itself (code handling transaction) and not caller of Binder transaction
Sometimes there are Binder.getCallingUid() that are always called after Binder.clearCallingIdentity(), therefore always return uid of own process. Sometimes this happens intentionally, for example in ActivityTaskManagerService#startDreamActivity (although thats rather convoluted way of doing Process.myUid() or Os.getuid())
I've written for myself a (Soot-based) static analysis tool that reports such Binder.getCallingUid() calls (and other permission checks) that can only happen after Binder.clearCallingIdentity(). (I have custom logic handling Jimple/Shimple IR provided by Soot, although there might be better way to do so with Soot, but thats what I have now)
In Android 12L Beta that tool found one in ActivityStartController#startActivityInTaskFragment (Note: source code was not available then as Beta releases are not open source, but Shimple is generally readable so I've been using Soot also as Java decompiler)
startActivityInTaskFragmentAs part of static analysis report I've got call hierarchy from onTransact() implementation (where Binder call starts) to startActivityInTaskFragment:
onTransact in aidl-generated code of IWindowOrganizerControllerWindowOrganizerController#applyTransaction (without CallerInfo argument)WindowOrganizerController#applyTransaction (with CallerInfo argument)WindowOrganizerController#applyHierarchyOpActivityStartController#startActivityInTaskFragmentI've found that Binder calls to applyTransaction are present in TaskFragmentOrganizer class and I've decided to use it as more convenient wrapper than doing all Binder calls directly (neither is public API so I had to use reflection anyway)
First of all, method "2." calls enforceTaskPermission, which on Android 12.0 checked signature-only MANAGE_ACTIVITY_TASKS permission which we couldn't get, however on Android 12L rules were relaxed so certain transactions can be made without permissions. It turned out that none of operations needed for doing startActivityInTaskFragment required a permission (if transaction had TaskFragmentOrganizer associated)
So we want to perform HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT. In order to do so we must have our TaskFragment registered in mLaunchTaskFragments otherwise "Not allowed to operate with invalid fragment token" exception will be reported
We can register such TaskFragment through HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, which calls createTaskFragment()