Skip to content

Commit

Permalink
Guard IPC buffer access.
Browse files Browse the repository at this point in the history
Funnel data access that traverses the IPC buffer to prevent overflows and `android.os.DeadObjectException` on older Android versions with smaller buffer sizes.
Fixes crash on ~ Android 6 when loading a lot of app data simultaneously.
  • Loading branch information
d4rken committed Nov 1, 2022
1 parent 1845729 commit c2afbf9
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 86 deletions.
8 changes: 5 additions & 3 deletions app/src/main/java/eu/darken/myperm/apps/core/AppRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.myperm.apps.core.container.*
import eu.darken.myperm.apps.core.known.AKnownPkg
import eu.darken.myperm.common.IPCFunnel
import eu.darken.myperm.common.coroutine.AppScope
import eu.darken.myperm.common.coroutine.DispatcherProvider
import eu.darken.myperm.common.debug.logging.Logging.Priority.ERROR
Expand All @@ -28,6 +29,7 @@ class AppRepo @Inject constructor(
@AppScope private val appScope: CoroutineScope,
private val dispatcherProvider: DispatcherProvider,
packageEventListener: PackageEventListener,
private val ipcFunnel: IPCFunnel,
) {

private val refreshTrigger = MutableStateFlow(UUID.randomUUID())
Expand All @@ -48,23 +50,23 @@ class AppRepo @Inject constructor(
val pkgs = coroutineScope {
val normal = async(dispatcherProvider.Default) {
measureTimedValue {
getNormalPkgs(context)
getNormalPkgs(ipcFunnel)
}.let {
log(TAG) { "Perf: Primary profile pkgs took ${it.duration.inWholeMilliseconds}ms" }
it.value
}
}
val secondaryProfile = async(dispatcherProvider.Default) {
measureTimedValue {
getSecondaryProfilePkgs(context)
getSecondaryProfilePkgs(ipcFunnel)
}.let {
log(TAG) { "Perf: Secondary profile pkgs took ${it.duration.inWholeMilliseconds}ms" }
it.value
}
}
val uninstalledPkgs = async(dispatcherProvider.Default) {
measureTimedValue {
getSecondaryUserPkgs(context).filter { uninstalled ->
getSecondaryUserPkgs(ipcFunnel).filter { uninstalled ->
secondaryProfile.await().none { it.id.pkgName == uninstalled.id.pkgName }
}
}.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fun PackageManager.getPermissionInfo2(
null
}

val PackageManager.GET_UNINSTALLED_PACKAGES_COMPAT
val GET_UNINSTALLED_PACKAGES_COMPAT: Int
get() = when {
hasApiLevel(Build.VERSION_CODES.N) -> PackageManager.MATCH_UNINSTALLED_PACKAGES
else -> PackageManager.GET_UNINSTALLED_PACKAGES
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import eu.darken.myperm.apps.core.Pkg
import eu.darken.myperm.apps.core.features.*
import eu.darken.myperm.apps.core.getIcon2
import eu.darken.myperm.apps.core.getLabel2
import eu.darken.myperm.common.IPCFunnel
import eu.darken.myperm.common.debug.logging.log
import eu.darken.myperm.permissions.core.Permission
import eu.darken.myperm.permissions.core.known.APerm
Expand Down Expand Up @@ -81,20 +82,20 @@ class PrimaryProfilePkg(
override fun toString(): String = "PrimaryProfilePkg(packageName=$packageName, userHandle=$userHandle)"
}

private fun PackageInfo.toNormalPkg(context: Context): PrimaryProfilePkg = PrimaryProfilePkg(
private suspend fun PackageInfo.toNormalPkg(ipcFunnel: IPCFunnel): PrimaryProfilePkg = PrimaryProfilePkg(
packageInfo = this,
installerInfo = getInstallerInfo(context.packageManager),
extraPermissions = determineSpecialPermissions(context),
batteryOptimization = determineBatteryOptimization(context),
accessibilityServices = determineAccessibilityServices(context),
installerInfo = getInstallerInfo(ipcFunnel),
extraPermissions = determineSpecialPermissions(ipcFunnel),
batteryOptimization = determineBatteryOptimization(ipcFunnel),
accessibilityServices = determineAccessibilityServices(ipcFunnel),
)

suspend fun getNormalPkgs(context: Context): Collection<BasePkg> {
suspend fun getNormalPkgs(ipcFunnel: IPCFunnel): Collection<BasePkg> {
log(AppRepo.TAG) { "getNormalPkgs()" }

return coroutineScope {
context.packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS)
.map { async { it.toNormalPkg(context) } }
ipcFunnel.packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS)
.map { async { it.toNormalPkg(ipcFunnel) } }
.awaitAll()
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package eu.darken.myperm.apps.core.container

import android.content.Context
import android.content.pm.*
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PermissionInfo
import android.graphics.drawable.Drawable
import android.os.Process
import android.os.UserHandle
import android.os.UserManager
import androidx.core.content.ContextCompat
import eu.darken.myperm.apps.core.AppRepo
import eu.darken.myperm.apps.core.GET_UNINSTALLED_PACKAGES_COMPAT
import eu.darken.myperm.apps.core.Pkg
import eu.darken.myperm.apps.core.features.*
import eu.darken.myperm.common.IPCFunnel
import eu.darken.myperm.common.debug.logging.Logging.Priority.*
import eu.darken.myperm.common.debug.logging.log
import eu.darken.myperm.permissions.core.Permission
Expand Down Expand Up @@ -72,21 +74,18 @@ class SecondaryProfilePkg(
override fun toString(): String = "SecondaryProfilePkg(packageName=$packageName, userHandle=$userHandle)"
}

suspend fun getSecondaryProfilePkgs(context: Context): Collection<BasePkg> = coroutineScope {
val packageManager = context.packageManager
val launcherApps = ContextCompat.getSystemService(context, LauncherApps::class.java)!!
val userManager = ContextCompat.getSystemService(context, UserManager::class.java)!!
suspend fun getSecondaryProfilePkgs(ipcFunnel: IPCFunnel): Collection<BasePkg> = coroutineScope {

val profiles = userManager.userProfiles
val profiles = ipcFunnel.userManager.userProfiles()

if (profiles.size < 2) return@coroutineScope emptySet()

log(AppRepo.TAG, INFO) { "Found multiple user profiles: $profiles" }
val extraProfiles = profiles - Process.myUserHandle()

fun determineForHandle(userHandle: UserHandle): Collection<BasePkg> {
suspend fun determineForHandle(userHandle: UserHandle): Collection<BasePkg> {
val launcherInfos = try {
launcherApps.getActivityList(null, userHandle)
ipcFunnel.launcherApps.getActivityList(null, userHandle)
} catch (e: SecurityException) {
log(AppRepo.TAG, ERROR) { "Failed to retrieve activity list for $userHandle" }
emptyList()
Expand All @@ -95,15 +94,15 @@ suspend fun getSecondaryProfilePkgs(context: Context): Collection<BasePkg> = cor
return launcherInfos.mapNotNull { lai ->
val appInfo = lai.applicationInfo

var pkgInfo = packageManager.getPackageArchiveInfo(
var pkgInfo = ipcFunnel.packageManager.getPackageArchiveInfo(
appInfo.packageName,
packageManager.GET_UNINSTALLED_PACKAGES_COMPAT
GET_UNINSTALLED_PACKAGES_COMPAT
)

if (pkgInfo == null) {
log(AppRepo.TAG, VERBOSE) { "Failed to get info from packagemanager for $appInfo" }
pkgInfo =
packageManager.getPackageArchiveInfo(appInfo.sourceDir, PackageManager.GET_PERMISSIONS)
ipcFunnel.packageManager.getPackageArchiveInfo(appInfo.sourceDir, PackageManager.GET_PERMISSIONS)
}

if (pkgInfo == null) {
Expand All @@ -113,10 +112,10 @@ suspend fun getSecondaryProfilePkgs(context: Context): Collection<BasePkg> = cor

SecondaryProfilePkg(
packageInfo = pkgInfo,
installerInfo = pkgInfo.getInstallerInfo(packageManager),
installerInfo = pkgInfo.getInstallerInfo(ipcFunnel),
launcherAppInfo = appInfo,
userHandle = userHandle,
extraPermissions = pkgInfo.determineSpecialPermissions(context),
extraPermissions = pkgInfo.determineSpecialPermissions(ipcFunnel),
).also { log(AppRepo.TAG) { "PKG[profile=${userHandle}}: $it" } }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import android.content.pm.PermissionInfo
import android.graphics.drawable.Drawable
import android.os.Process
import android.os.UserHandle
import android.os.UserManager
import androidx.core.content.ContextCompat
import eu.darken.myperm.R
import eu.darken.myperm.apps.core.*
import eu.darken.myperm.apps.core.features.*
import eu.darken.myperm.common.IPCFunnel
import eu.darken.myperm.common.debug.logging.log
import eu.darken.myperm.permissions.core.Permission
import eu.darken.myperm.permissions.core.known.APerm
Expand Down Expand Up @@ -72,26 +71,23 @@ class SecondaryUserPkg(
override fun toString(): String = "SecondaryUserPkg(packageName=$packageName, userHandle=$userHandle)"
}

suspend fun getSecondaryUserPkgs(context: Context): Collection<BasePkg> = coroutineScope {
suspend fun getSecondaryUserPkgs(ipcFunnel: IPCFunnel): Collection<BasePkg> = coroutineScope {
log(AppRepo.TAG) { "getSecondaryPkgs()" }
val packageManager = context.packageManager

val normal = packageManager.getInstalledPackages(0).map { it.packageName }
val uninstalled = packageManager.getInstalledPackages(
PackageManager.GET_PERMISSIONS or packageManager.GET_UNINSTALLED_PACKAGES_COMPAT
val normal = ipcFunnel.packageManager.getInstalledPackages(0).map { it.packageName }
val uninstalled = ipcFunnel.packageManager.getInstalledPackages(
PackageManager.GET_PERMISSIONS or GET_UNINSTALLED_PACKAGES_COMPAT
)
val newOnes = uninstalled.filter { !normal.contains(it.packageName) }

val userManager = ContextCompat.getSystemService(context, UserManager::class.java)!!

newOnes
.map { pkg ->
async {
SecondaryUserPkg(
packageInfo = pkg,
installerInfo = pkg.getInstallerInfo(packageManager),
userHandle = userManager.tryCreateUserHandle(11) ?: Process.myUserHandle(),
extraPermissions = pkg.determineSpecialPermissions(context),
installerInfo = pkg.getInstallerInfo(ipcFunnel),
userHandle = ipcFunnel.userManager.tryCreateUserHandle(11) ?: Process.myUserHandle(),
extraPermissions = pkg.determineSpecialPermissions(ipcFunnel),
).also { log(AppRepo.TAG) { "PKG[secondary]: $it" } }
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
package eu.darken.myperm.apps.core.features

import android.accessibilityservice.AccessibilityServiceInfo
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.view.accessibility.AccessibilityManager
import eu.darken.myperm.common.IPCFunnel
import eu.darken.myperm.permissions.core.known.APerm

data class AccessibilityService(
val isEnabled: Boolean,
val label: String,
)

fun PackageInfo.determineAccessibilityServices(context: Context): List<AccessibilityService> {
val pm = context.packageManager
val acsMan = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
val pkgInfo = pm.getPackageInfo(packageName, PackageManager.GET_SERVICES)
suspend fun PackageInfo.determineAccessibilityServices(ipcFunnel: IPCFunnel): List<AccessibilityService> {
val pkgInfo = ipcFunnel.packageManager.getPackageInfo(packageName, PackageManager.GET_SERVICES)

val enabledAcs = acsMan.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
val enabledAcs =
ipcFunnel.accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)

return pkgInfo.services
return pkgInfo?.services
?.filter { it.permission == APerm.BIND_ACCESSIBILITY_SERVICE.id.value }
?.map {
AccessibilityService(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package eu.darken.myperm.apps.core.features

import android.content.Context
import android.content.pm.PackageInfo
import android.os.PowerManager
import eu.darken.myperm.common.IPCFunnel
import eu.darken.myperm.common.hasApiLevel
import eu.darken.myperm.permissions.core.known.APerm

Expand All @@ -13,15 +12,14 @@ enum class BatteryOptimization {
UNKNOWN,
}

fun PackageInfo.determineBatteryOptimization(context: Context): BatteryOptimization {
suspend fun PackageInfo.determineBatteryOptimization(ipcFunnel: IPCFunnel): BatteryOptimization {
if (!hasApiLevel(23)) return BatteryOptimization.IGNORED
if (requestedPermissions == null) return BatteryOptimization.MANAGED_BY_SYSTEM
if (requestedPermissions.none { it == APerm.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS.id.value }) {
return BatteryOptimization.MANAGED_BY_SYSTEM
}

val pwrm = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
return if (pwrm.isIgnoringBatteryOptimizations(packageName)) {
return if (ipcFunnel.powerManager.isIgnoringBatteryOptimizations(packageName)) {
BatteryOptimization.IGNORED
} else {
BatteryOptimization.OPTIMIZED
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package eu.darken.myperm.apps.core.features

import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import eu.darken.myperm.common.IPCFunnel
import eu.darken.myperm.permissions.core.known.AExtraPerm

fun PackageInfo.determineSpecialPermissions(context: Context): Collection<UsesPermission> {
val pm = context.packageManager
suspend fun PackageInfo.determineSpecialPermissions(ipcFunnel: IPCFunnel): Collection<UsesPermission> {
val permissions = mutableSetOf<UsesPermission>()

val withActivities = try {
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
ipcFunnel.packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
} catch (e: PackageManager.NameNotFoundException) {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import eu.darken.myperm.apps.core.Pkg
import eu.darken.myperm.apps.core.known.AKnownPkg
import eu.darken.myperm.apps.core.known.toKnownPkg
import eu.darken.myperm.apps.core.toContainer
import eu.darken.myperm.common.IPCFunnel
import eu.darken.myperm.common.debug.logging.Logging.Priority.WARN
import eu.darken.myperm.common.debug.logging.asLog
import eu.darken.myperm.common.debug.logging.log
Expand Down Expand Up @@ -50,17 +51,17 @@ fun Installed.isSideloaded(): Boolean {
return installerInfo.allInstallers.none { it.id == AKnownPkg.GooglePlay.id }
}

fun PackageInfo.getInstallerInfo(
packageManager: PackageManager,
suspend fun PackageInfo.getInstallerInfo(
ipcFunnel: IPCFunnel,
): InstallerInfo = if (hasApiLevel(Build.VERSION_CODES.R)) {
getInstallerInfoApi30(packageManager)
getInstallerInfoApi30(ipcFunnel)
} else {
getInstallerInfoLegacy(packageManager)
getInstallerInfoLegacy(ipcFunnel)
}

private fun PackageInfo.getInstallerInfoApi30(packageManager: PackageManager): InstallerInfo {
private suspend fun PackageInfo.getInstallerInfoApi30(ipcFunnel: IPCFunnel): InstallerInfo {
val sourceInfo = try {
packageManager.getInstallSourceInfo(packageName)
ipcFunnel.packageManager.getInstallSourceInfo(packageName)
} catch (_: PackageManager.NameNotFoundException) {
null
}
Expand All @@ -83,10 +84,9 @@ private fun PackageInfo.getInstallerInfoApi30(packageManager: PackageManager): I
)
}

@Suppress("DEPRECATION")
private fun PackageInfo.getInstallerInfoLegacy(packageManager: PackageManager): InstallerInfo {
private suspend fun PackageInfo.getInstallerInfoLegacy(ipcFunnel: IPCFunnel): InstallerInfo {
val installingPkg = try {
packageManager.getInstallerPackageName(packageName)
ipcFunnel.packageManager.getInstallerPackageName(packageName)
?.let { Pkg.Id(it) }
?.let { it.toKnownPkg() ?: it.toContainer() }
} catch (e: IllegalArgumentException) {
Expand Down
Loading

0 comments on commit c2afbf9

Please sign in to comment.