-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Work around READ_CALL_LOG being hard-restricted
Since Android 10, READ_CALL_LOG has been marked as a hard-restricted permission, which prevents it from being granted by the user unless the app is given an exemption. There are three types of exemptions: system, upgrade, and installer. System exemptions can be given by `/system/etc/default-permissions` (only on first boot) or via roles. Neither are usable for BCR. Upgrade exemptions are only given during Android OS upgrades that further restrict existing permissions. Installer exemptions are given by whichever app installs another app, but there's no installer when it comes to adding new system apps, like BCR. Since AOSP has no sane built-in way to exempt BCR from the hard restriction, we'll do it ourselves. This commit introduces a new CLI utility baked in BCR that will talk to PermissionManager (Android 11+) or PackageManager (Android 10) to adjust its permission flags. This will unfortunately require two flashes (but only one reboot) for the initial install, because BCR must have already been loaded by the package manager for the flags to be changed. However, once the exemption has been granted, it persists across future upgrades. Fixes: #304 Signed-off-by: Andrew Gunnerson <[email protected]>
- Loading branch information
1 parent
0194da9
commit 62324b3
Showing
5 changed files
with
296 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# READ_CALL_LOG is a hard-restricted permission in Android 10+. It cannot be | ||
# granted by the user unless it is exempted by the system. The most common way | ||
# to do this is via the installer, but that's not applicable when adding new | ||
# system apps. Instead, we talk to the permission service directly over binder | ||
# to alter the flags. This requires flashing a second time after a reboot so | ||
# that the package manager is already aware of BCR. | ||
|
||
app_id=$(grep '^id=' "${MODPATH}/module.prop" | cut -d= -f2) | ||
|
||
CLASSPATH=$(find "${MODPATH}"/system/priv-app/"${app_id}" -name '*.apk') \ | ||
app_process \ | ||
/ \ | ||
com.chiller3.bcr.standalone.RemoveHardRestrictionsKt \ | ||
2>&1 | ||
|
||
case "${?}" in | ||
0|2) | ||
exit 0 | ||
;; | ||
*) | ||
rm -rv "${MODPATH}" 2>&1 | ||
exit 1 | ||
;; | ||
esac |
This file contains 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
257 changes: 257 additions & 0 deletions
257
app/src/main/java/com/chiller3/bcr/standalone/RemoveHardRestrictions.kt
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
@file:SuppressLint( | ||
"BlockedPrivateApi", | ||
"DiscouragedPrivateApi", | ||
"PrivateApi", | ||
"SoonBlockedPrivateApi", | ||
) | ||
|
||
package com.chiller3.bcr.standalone | ||
|
||
import android.Manifest | ||
import android.annotation.SuppressLint | ||
import android.content.pm.PackageManager | ||
import android.os.Build | ||
import android.os.Process | ||
import android.system.ErrnoException | ||
import androidx.annotation.RequiresApi | ||
import com.chiller3.bcr.BuildConfig | ||
import kotlin.system.exitProcess | ||
|
||
private object ActivityThreadProxy { | ||
private val CLS = Class.forName("android.app.ActivityThread") | ||
private val METHOD_GET_PACKAGE_MANAGER = CLS.getDeclaredMethod("getPackageManager") | ||
private val METHOD_GET_PERMISSION_MANAGER = CLS.getDeclaredMethod("getPermissionManager") | ||
|
||
fun getPackageManager(): PackageManagerProxy { | ||
val iface = METHOD_GET_PACKAGE_MANAGER.invoke(null)!! | ||
return PackageManagerProxy(iface) | ||
} | ||
|
||
@RequiresApi(Build.VERSION_CODES.R) | ||
fun getPermissionManager(): PermissionManagerProxy { | ||
val iface = METHOD_GET_PERMISSION_MANAGER.invoke(null)!! | ||
return PermissionManagerProxy(iface) | ||
} | ||
} | ||
|
||
private class PackageManagerProxy(private val iface: Any) { | ||
companion object { | ||
private val CLS = Class.forName("android.content.pm.IPackageManager") | ||
private val METHOD_IS_PACKAGE_AVAILABLE = CLS.getDeclaredMethod( | ||
"isPackageAvailable", String::class.java, Int::class.java) | ||
// Android 10 only | ||
private val METHOD_GET_PERMISSION_FLAGS by lazy { | ||
CLS.getDeclaredMethod( | ||
"getPermissionFlags", | ||
String::class.java, | ||
String::class.java, | ||
Int::class.java, | ||
) | ||
} | ||
// Android 10 only | ||
private val METHOD_UPDATE_PERMISSION_FLAGS by lazy { | ||
CLS.getDeclaredMethod( | ||
"updatePermissionFlags", | ||
String::class.java, | ||
String::class.java, | ||
Int::class.java, | ||
Int::class.java, | ||
Int::class.java, | ||
) | ||
} | ||
|
||
private val WRAPPER_CLS = PackageManager::class.java | ||
val FLAG_PERMISSION_APPLY_RESTRICTION = WRAPPER_CLS.getDeclaredField( | ||
"FLAG_PERMISSION_APPLY_RESTRICTION").getInt(null) | ||
val FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = WRAPPER_CLS.getDeclaredField( | ||
"FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT").getInt(null) | ||
val FLAG_PERMISSION_RESTRICTION_ANY_EXEMPT = WRAPPER_CLS.getDeclaredField( | ||
"FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT").getInt(null) | ||
} | ||
|
||
fun isPackageAvailable(packageName: String, userId: Int): Boolean { | ||
return METHOD_IS_PACKAGE_AVAILABLE.invoke(iface, packageName, userId) as Boolean | ||
} | ||
|
||
fun getPermissionFlags(permissionName: String, packageName: String, userId: Int): Int { | ||
return METHOD_GET_PERMISSION_FLAGS.invoke(iface, permissionName, packageName, userId) as Int | ||
} | ||
|
||
fun updatePermissionFlags( | ||
permissionName: String, | ||
packageName: String, | ||
flagMask: Int, | ||
flagValues: Int, | ||
userId: Int, | ||
) { | ||
METHOD_UPDATE_PERMISSION_FLAGS.invoke( | ||
iface, | ||
permissionName, | ||
packageName, | ||
flagMask, | ||
flagValues, | ||
userId, | ||
) | ||
} | ||
|
||
} | ||
|
||
@RequiresApi(Build.VERSION_CODES.R) | ||
private class PermissionManagerProxy(private val iface: Any) { | ||
companion object { | ||
private val CLS = Class.forName("android.permission.IPermissionManager") | ||
private val METHOD_GET_PERMISSION_FLAGS = | ||
CLS.getDeclaredMethod( | ||
"getPermissionFlags", | ||
String::class.java, | ||
String::class.java, | ||
Int::class.java, | ||
) | ||
private val METHOD_UPDATE_PERMISSION_FLAGS = | ||
CLS.getDeclaredMethod( | ||
"updatePermissionFlags", | ||
String::class.java, | ||
String::class.java, | ||
Int::class.java, | ||
Int::class.java, | ||
Boolean::class.java, | ||
Int::class.java, | ||
) | ||
} | ||
|
||
fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int { | ||
return METHOD_GET_PERMISSION_FLAGS(iface, packageName, permissionName, userId) as Int | ||
} | ||
|
||
fun updatePermissionFlags( | ||
packageName: String, | ||
permissionName: String, | ||
flagMask: Int, | ||
flagValues: Int, | ||
checkAdjustPolicyFlagPermission: Boolean, | ||
userId: Int, | ||
) { | ||
METHOD_UPDATE_PERMISSION_FLAGS.invoke( | ||
iface, | ||
packageName, | ||
permissionName, | ||
flagMask, | ||
flagValues, | ||
checkAdjustPolicyFlagPermission, | ||
userId, | ||
) | ||
} | ||
} | ||
|
||
private fun switchToSystemUid() { | ||
if (Process.myUid() != Process.SYSTEM_UID) { | ||
val setUid = Process::class.java.getDeclaredMethod("setUid", Int::class.java) | ||
val errno = setUid.invoke(null, Process.SYSTEM_UID) as Int | ||
|
||
if (errno != 0) { | ||
throw Exception("Failed to switch to SYSTEM (${Process.SYSTEM_UID}) user", | ||
ErrnoException("setuid", errno)) | ||
} | ||
if (Process.myUid() != Process.SYSTEM_UID) { | ||
throw IllegalStateException("UID didn't actually change: " + | ||
"${Process.myUid()} != ${Process.SYSTEM_UID}") | ||
} | ||
} | ||
} | ||
|
||
@Suppress("SameParameterValue") | ||
private fun removeRestriction(packageName: String, permission: String, userId: Int): Boolean { | ||
val packageManager = ActivityThreadProxy.getPackageManager() | ||
if (!packageManager.isPackageAvailable(packageName, userId)) { | ||
throw IllegalArgumentException("Package $packageName is not installed for user $userId") | ||
} | ||
|
||
val (getFlags, updateFlags) = if (Build.VERSION.SDK_INT in | ||
Build.VERSION_CODES.R..Build.VERSION_CODES.TIRAMISU) { | ||
val permissionManager = ActivityThreadProxy.getPermissionManager() | ||
|
||
Pair( | ||
{ permissionManager.getPermissionFlags(packageName, permission, userId) }, | ||
{ mask: Int, set: Int -> | ||
permissionManager.updatePermissionFlags( | ||
packageName, permission, mask, set, false, userId) | ||
}, | ||
) | ||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | ||
Pair( | ||
{ packageManager.getPermissionFlags(permission, packageName, userId) }, | ||
{ mask: Int, set: Int -> | ||
packageManager.updatePermissionFlags(permission, packageName, mask, set, userId) | ||
}, | ||
) | ||
} else { | ||
throw IllegalStateException("Not supported on SDK version ${Build.VERSION.SDK_INT}") | ||
} | ||
|
||
val oldFlags = getFlags() | ||
|
||
updateFlags( | ||
PackageManagerProxy.FLAG_PERMISSION_RESTRICTION_ANY_EXEMPT or | ||
PackageManagerProxy.FLAG_PERMISSION_APPLY_RESTRICTION, | ||
PackageManagerProxy.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, | ||
) | ||
|
||
val newFlags = getFlags() | ||
if (newFlags and PackageManagerProxy.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT == 0) { | ||
throw IllegalStateException("RESTRICTION_SYSTEM_EXEMPT flag did not get added") | ||
} | ||
if (newFlags and PackageManagerProxy.FLAG_PERMISSION_APPLY_RESTRICTION != 0) { | ||
throw IllegalStateException("APPLY_RESTRICTION flag did not get removed") | ||
} | ||
|
||
return newFlags != oldFlags | ||
} | ||
|
||
fun mainInternal() { | ||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { | ||
// Android 9 does not have FLAG_PERMISSION_APPLY_RESTRICTION | ||
System.err.println("Android 9 does not have hard-restricted permissions") | ||
return | ||
} | ||
|
||
switchToSystemUid() | ||
|
||
val packageManager = ActivityThreadProxy.getPackageManager() | ||
if (!packageManager.isPackageAvailable(BuildConfig.APPLICATION_ID, 0)) { | ||
System.err.println(""" | ||
---------------- NOTE ---------------- | ||
Android 10+ marks the READ_CALL_LOG | ||
permission as being hard restricted. | ||
This makes it impossible to grant the | ||
(optional) permission, even from | ||
Android's settings. To remove this | ||
restriction for BCR only, reboot and | ||
reflash one more time. This procedure | ||
only needs to be done once and will | ||
persist across upgrades. This does not | ||
affect other apps and the changes go | ||
away when BCR is uninstalled. | ||
-------------------------------------- | ||
""".trimIndent()) | ||
exitProcess(2) | ||
} | ||
|
||
val changed = removeRestriction(BuildConfig.APPLICATION_ID, Manifest.permission.READ_CALL_LOG, 0) | ||
val suffix = "from ${BuildConfig.APPLICATION_ID} for ${Manifest.permission.READ_CALL_LOG}" | ||
|
||
if (changed) { | ||
println("Successfully removed hard restriction $suffix") | ||
} else { | ||
println("Hard restriction already removed $suffix") | ||
} | ||
} | ||
|
||
fun main() { | ||
try { | ||
mainInternal() | ||
} catch (e: Exception) { | ||
// Otherwise, exceptions go to the logcat | ||
e.printStackTrace() | ||
exitProcess(1) | ||
} | ||
} |