1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.permissioncontroller.permission.service
18 
19 import android.Manifest.permission
20 import android.Manifest.permission_group
21 import android.content.Context
22 import android.content.pm.PackageInfo
23 import android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
24 import android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
25 import android.content.pm.PermissionInfo
26 import android.os.Process.myUserHandle
27 import android.permission.PermissionManager
28 import android.util.Log
29 import com.android.permissioncontroller.PermissionControllerStatsLog
30 import com.android.permissioncontroller.PermissionControllerStatsLog.RUNTIME_PERMISSIONS_UPGRADE_RESULT
31 import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
32 import com.android.permissioncontroller.permission.data.LightPermInfoLiveData
33 import com.android.permissioncontroller.permission.data.PreinstalledUserPackageInfosLiveData
34 import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
35 import com.android.permissioncontroller.permission.data.UserPackageInfosLiveData
36 import com.android.permissioncontroller.permission.data.get
37 import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
38 import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
39 import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
40 import com.android.permissioncontroller.permission.utils.IPC
41 import com.android.permissioncontroller.permission.utils.KotlinUtils.grantBackgroundRuntimePermissions
42 import com.android.permissioncontroller.permission.utils.KotlinUtils.grantForegroundRuntimePermissions
43 import com.android.permissioncontroller.permission.utils.Utils.getPlatformPermissionNamesOfGroup
44 import com.android.permissioncontroller.permission.utils.Utils.getRuntimePlatformPermissionNames
45 import com.android.permissioncontroller.permission.utils.application
46 import kotlinx.coroutines.GlobalScope
47 import kotlinx.coroutines.launch
48 
49 /**
50  * This class handles upgrading the runtime permissions database
51  */
52 internal object RuntimePermissionsUpgradeController {
53     private val LOG_TAG = RuntimePermissionsUpgradeController::class.java.simpleName
54 
55     // The latest version of the runtime permissions database
56     private val LATEST_VERSION = 9
57 
58     fun upgradeIfNeeded(context: Context, onComplete: Runnable) {
59         val permissionManager = context.getSystemService(PermissionManager::class.java)
60         val currentVersion = permissionManager!!.runtimePermissionsVersion
61 
62         GlobalScope.launch(IPC) {
63             val upgradedVersion = onUpgradeLocked(context, currentVersion)
64             if (upgradedVersion != LATEST_VERSION) {
65                 Log.wtf("PermissionControllerService", "warning: upgrading permission database" +
66                     " to version " + LATEST_VERSION + " left it at " + currentVersion +
67                     " instead; this is probably a bug. Did you update " +
68                     "LATEST_VERSION?", Throwable())
69                 throw RuntimeException("db upgrade error")
70             }
71 
72             if (currentVersion != upgradedVersion) {
73                 permissionManager!!.runtimePermissionsVersion = LATEST_VERSION
74             }
75             onComplete.run()
76         }
77     }
78 
79     /**
80      * Create exemptions for select restricted permissions of select apps.
81      *
82      * @param permissionInfos permissions to exempt
83      * @param pkgs packages to exempt
84      *
85      * @return the exemptions to apply
86      */
87     private fun getExemptions(
88         permissions: Set<String>,
89         pkgs: List<LightPackageInfo>,
90         flags: Int = FLAG_PERMISSION_WHITELIST_UPGRADE
91     ): List<RestrictionExemption> {
92         val exemptions = mutableListOf<RestrictionExemption>()
93 
94         for (pkg in pkgs) {
95             for (permission in permissions intersect pkg.requestedPermissions) {
96                 exemptions.add(RestrictionExemption(pkg.packageName, permission, flags))
97             }
98         }
99 
100         return exemptions
101     }
102 
103     /**
104      * You must perform all necessary mutations to bring the runtime permissions
105      * database from the old to the new version. When you add a new upgrade step
106      * you *must* update LATEST_VERSION.
107      *
108      * <p> NOTE: Relies upon the fact that the system will attempt to upgrade every version after
109      * currentVersion in order, without skipping any versions. Should this become the case, this
110      * method MUST be updated.
111      *
112      * @param context The current context
113      * @param currentVersion The current version of the permission database
114      */
115     private suspend fun onUpgradeLocked(
116         context: Context,
117         currentVersion: Int
118     ): Int {
119         var sdkUpgradedFromP = false
120         var isNewUser = false
121 
122         if (currentVersion <= -1) {
123             sdkUpgradedFromP = true
124         } else if (currentVersion == 0) {
125             isNewUser = true
126         }
127 
128         val needBackgroundAppPermGroups = sdkUpgradedFromP && currentVersion <= 6
129         val needAccessMediaAppPermGroups = !isNewUser && currentVersion <= 7
130 
131         // All data needed by this method.
132         //
133         // All data is loaded once and then not updated.
134         val upgradeDataProvider = object : SmartUpdateMediatorLiveData<UpgradeData>() {
135             /** Provides all preinstalled packages in the system */
136             private val preinstalledPkgInfoProvider =
137                     PreinstalledUserPackageInfosLiveData[myUserHandle()]
138 
139             /** Provides all platform runtime permission infos */
140             private val platformRuntimePermissionInfoProviders =
141                     mutableListOf<LightPermInfoLiveData>()
142 
143             /** {@link #platformRuntimePermissionInfoProvider} that already provided a result */
144             private val platformRuntimePermissionInfoProvidersDone =
145                     mutableSetOf<LightPermInfoLiveData>()
146 
147             /** Provides all packages in the system */
148             private val pkgInfoProvider = UserPackageInfosLiveData[myUserHandle()]
149 
150             /** Provides all {@link LightAppPermGroup} this upgrade needs */
151             private var permGroupProviders: MutableList<LightAppPermGroupLiveData>? = null
152 
153             /** {@link #permGroupProviders} that already provided a result */
154             private val permGroupProvidersDone = mutableSetOf<LightAppPermGroupLiveData>()
155 
156             init {
157                 // First step: Load packages + perm infos
158                 // TODO ntmyren: remove once b/154796729 is fixed
159                 Log.i("RuntimePermissions", "observing UserPackageInfoLiveData for " +
160                     "${myUserHandle().identifier} in RuntimePermissionsUpgradeController")
161                 addSource(pkgInfoProvider) { pkgInfos ->
162                     if (pkgInfos != null) {
163                         removeSource(pkgInfoProvider)
164 
165                         // TODO ntmyren: remove once b/154796729 is fixed
166                         Log.i("RuntimePermissions", "observing " +
167                             "PreinstalledUserPackageInfoLiveData for ${myUserHandle().identifier}" +
168                             " in RuntimePermissionsUpgradeController")
169                         addSource(preinstalledPkgInfoProvider) { preinstalledPkgInfos ->
170                             if (preinstalledPkgInfos != null) {
171                                 removeSource(preinstalledPkgInfoProvider)
172 
173                                 update()
174                             }
175                         }
176                     }
177                 }
178 
179                 for (platformRuntimePermission in getRuntimePlatformPermissionNames()) {
180                     val permProvider = LightPermInfoLiveData[platformRuntimePermission]
181                     platformRuntimePermissionInfoProviders.add(permProvider)
182 
183                     addSource(permProvider) { permInfo ->
184                         if (permInfo != null) {
185                             platformRuntimePermissionInfoProvidersDone.add(permProvider)
186                             removeSource(permProvider)
187 
188                             update()
189                         }
190                     }
191                 }
192             }
193 
194             override fun onUpdate() {
195                 if (permGroupProviders == null && pkgInfoProvider.value != null) {
196                     // Second step: Trigger load of app-perm-groups
197 
198                     permGroupProviders = mutableListOf()
199 
200                     // Only load app-perm-groups needed for this upgrade
201                     if (needBackgroundAppPermGroups || needAccessMediaAppPermGroups) {
202                         for ((pkgName, _, requestedPerms, requestedPermFlags) in
203                                 pkgInfoProvider.value!!) {
204                             var hasAccessMedia = false
205                             var hasGrantedExternalStorage = false
206 
207                             for ((perm, flags) in requestedPerms.zip(requestedPermFlags)) {
208                                 if (needBackgroundAppPermGroups &&
209                                         perm == permission.ACCESS_BACKGROUND_LOCATION) {
210                                     permGroupProviders!!.add(LightAppPermGroupLiveData[pkgName,
211                                             permission_group.LOCATION, myUserHandle()])
212                                 }
213 
214                                 if (needAccessMediaAppPermGroups) {
215                                     if (perm == permission.ACCESS_MEDIA_LOCATION) {
216                                         hasAccessMedia = true
217                                     }
218 
219                                     if (perm == permission.READ_EXTERNAL_STORAGE &&
220                                             flags and PackageInfo.REQUESTED_PERMISSION_GRANTED
221                                             != 0) {
222                                         hasGrantedExternalStorage = true
223                                     }
224                                 }
225                             }
226 
227                             if (hasAccessMedia && hasGrantedExternalStorage) {
228                                 permGroupProviders!!.add(LightAppPermGroupLiveData[pkgName,
229                                         permission_group.STORAGE, myUserHandle()])
230                             }
231                         }
232                     }
233 
234                     // Wait until groups are loaded and then trigger third step
235                     for (permGroupProvider in permGroupProviders!!) {
236                         addSource(permGroupProvider) { group ->
237                             if (group != null) {
238                                 permGroupProvidersDone.add(permGroupProvider)
239                                 removeSource(permGroupProvider)
240 
241                                 update()
242                             }
243                         }
244                     }
245 
246                     // If no group need to be loaded, directly switch to third step
247                     if (permGroupProviders!!.isEmpty()) {
248                         update()
249                     }
250                 } else if (permGroupProviders != null &&
251                         permGroupProvidersDone.size == permGroupProviders!!.size &&
252                         preinstalledPkgInfoProvider.value != null &&
253                         platformRuntimePermissionInfoProviders.size
254                         == platformRuntimePermissionInfoProvidersDone.size) {
255                     // Third step: All packages, perm infos and perm groups are loaded, set value
256 
257                     val bgGroups = mutableListOf<LightAppPermGroup>()
258                     val storageGroups = mutableListOf<LightAppPermGroup>()
259                     val bgMicGroups = mutableListOf<LightAppPermGroup>()
260 
261                     for (group in permGroupProviders!!.mapNotNull { it.value }) {
262                         when (group.permGroupName) {
263                             permission_group.LOCATION -> {
264                                 bgGroups.add(group)
265                             }
266                             permission_group.STORAGE -> {
267                                 storageGroups.add(group)
268                             }
269                             permission_group.MICROPHONE -> {
270                                 bgMicGroups.add(group)
271                             }
272                         }
273                     }
274 
275                     val restrictedPermissions = mutableSetOf<String>()
276                     for (permInfoLiveDt in platformRuntimePermissionInfoProviders) {
277                         val permInfo = permInfoLiveDt.value!!
278 
279                         if (permInfo.flags and (PermissionInfo.FLAG_HARD_RESTRICTED or
280                                         PermissionInfo.FLAG_SOFT_RESTRICTED) == 0) {
281                             continue
282                         }
283 
284                         restrictedPermissions.add(permInfo.name)
285                     }
286 
287                     value = UpgradeData(preinstalledPkgInfoProvider.value!!, restrictedPermissions,
288                             pkgInfoProvider.value!!, bgGroups, storageGroups, bgMicGroups)
289                 }
290             }
291         }
292 
293         // Trigger loading of data and wait until data is loaded
294         val upgradeData = upgradeDataProvider.getInitializedValue(forceUpdate = true)
295 
296         // Only exempt permissions that are in the OTA. Apps that are updated via OTAs are never
297         // installed. Hence their permission are never exempted. This code replaces that by
298         // always exempting them. For non-OTA updates the installer should do the exemption.
299         // If a restricted permission can't be exempted by the installer then it should be filtered
300         // out here.
301         val preinstalledAppExemptions = getExemptions(
302                 upgradeData.restrictedPermissions,
303                 upgradeData.preinstalledPkgs)
304 
305         val (newVersion, upgradeExemptions, grants) = onUpgradeLockedDataLoaded(currentVersion,
306                 upgradeData.pkgs, upgradeData.restrictedPermissions,
307                 upgradeData.bgGroups, upgradeData.storageGroups, upgradeData.bgMicGroups)
308 
309         // Do not run in parallel. Measurements have shown that this is slower than sequential
310         for (exemption in (preinstalledAppExemptions union upgradeExemptions)) {
311             exemption.applyToPlatform(context)
312         }
313 
314         for (grant in grants) {
315             grant.applyToPlatform(context)
316         }
317 
318         return newVersion
319     }
320 
321     private fun onUpgradeLockedDataLoaded(
322         currVersion: Int,
323         pkgs: List<LightPackageInfo>,
324         restrictedPermissions: Set<String>,
325         bgApps: List<LightAppPermGroup>,
326         accessMediaApps: List<LightAppPermGroup>,
327         bgMicApps: List<LightAppPermGroup>
328     ): Triple<Int, List<RestrictionExemption>, List<Grant>> {
329         val exemptions = mutableListOf<RestrictionExemption>()
330         val grants = mutableListOf<Grant>()
331 
332         var currentVersion = currVersion
333         var sdkUpgradedFromP = false
334         var isNewUser = false
335         val bgAppsWithExemption = bgApps.map { it.packageName to it }.toMap().toMutableMap()
336 
337         if (currentVersion <= -1) {
338             Log.i(LOG_TAG, "Upgrading from Android P")
339 
340             sdkUpgradedFromP = true
341 
342             currentVersion = 0
343         } else {
344             // If the initial version is 0 the permission state was just created
345             if (currentVersion == 0) {
346                 isNewUser = true
347             }
348         }
349 
350         if (currentVersion == 0) {
351             Log.i(LOG_TAG, "Grandfathering SMS and CallLog permissions")
352 
353             val permissions = restrictedPermissions intersect
354                     (getPlatformPermissionNamesOfGroup(permission_group.SMS) +
355                     getPlatformPermissionNamesOfGroup(permission_group.CALL_LOG))
356 
357             exemptions.addAll(getExemptions(permissions, pkgs))
358 
359             currentVersion = 1
360         }
361 
362         if (currentVersion == 1) {
363             // moved to step 4->5 as it has to be after the grandfathering of loc bg perms
364             currentVersion = 2
365         }
366 
367         if (currentVersion == 2) {
368             // moved to step 5->6 to clean up broken permission state during dogfooding
369             currentVersion = 3
370         }
371 
372         if (currentVersion == 3) {
373             Log.i(LOG_TAG, "Grandfathering location background permissions")
374 
375             val bgLocExemptions = getExemptions(setOf(permission.ACCESS_BACKGROUND_LOCATION),
376                     pkgs)
377 
378             // Adjust bgApps as if the exemption was applied
379             for ((pkgName, _) in bgLocExemptions) {
380                 val bgApp = bgAppsWithExemption[pkgName] ?: continue
381                 val perm = bgApp.allPermissions[permission.ACCESS_BACKGROUND_LOCATION] ?: continue
382 
383                 val allPermissionsWithxemption = bgApp.allPermissions.toMutableMap()
384                 allPermissionsWithxemption[permission.ACCESS_BACKGROUND_LOCATION] =
385                         LightPermission(perm.pkgInfo, perm.permInfo, perm.isGrantedIncludingAppOp,
386                         perm.flags or FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
387                         perm.foregroundPerms)
388 
389                 bgAppsWithExemption[pkgName] = LightAppPermGroup(bgApp.packageInfo,
390                         bgApp.permGroupInfo, allPermissionsWithxemption,
391                         bgApp.hasInstallToRuntimeSplit, bgApp.specialLocationGrant)
392             }
393 
394             exemptions.addAll(bgLocExemptions)
395 
396             currentVersion = 4
397         }
398 
399         if (currentVersion == 4) {
400             // moved to step 5->6 to clean up broken permission state during beta 4->5 upgrade
401             currentVersion = 5
402         }
403 
404         if (currentVersion == 5) {
405             Log.i(LOG_TAG, "Grandfathering Storage permissions")
406 
407             val permissions = restrictedPermissions intersect
408                     getPlatformPermissionNamesOfGroup(permission_group.STORAGE)
409 
410             // We don't want to allow modification of storage post install, so put it
411             // on the internal system exemptlist to prevent the installer changing it.
412             exemptions.addAll(getExemptions(permissions, pkgs))
413 
414             currentVersion = 6
415         }
416 
417         if (currentVersion == 6) {
418             if (sdkUpgradedFromP) {
419                 Log.i(LOG_TAG, "Expanding location permissions")
420                 for (appPermGroup in bgAppsWithExemption.values) {
421                     if (appPermGroup.foreground.isGranted &&
422                         appPermGroup.hasBackgroundGroup &&
423                         !appPermGroup.background.isUserSet &&
424                         !appPermGroup.background.isSystemFixed &&
425                         !appPermGroup.background.isPolicyFixed &&
426                         !appPermGroup.background.isUserFixed) {
427                         grants.add(Grant(true, appPermGroup))
428                     }
429                 }
430             } else {
431                 Log.i(LOG_TAG, "Not expanding location permissions as this is not an upgrade " +
432                     "from Android P")
433             }
434 
435             currentVersion = 7
436         }
437 
438         if (currentVersion == 7) {
439             if (!isNewUser) {
440                 Log.i(LOG_TAG, "Expanding read storage to access media location")
441 
442                 for (appPermGroup in accessMediaApps) {
443                     val perm = appPermGroup.permissions[permission.ACCESS_MEDIA_LOCATION]
444                             ?: continue
445 
446                     if (!perm.isUserSet && !perm.isSystemFixed && !perm.isPolicyFixed &&
447                             !perm.isGrantedIncludingAppOp) {
448                         grants.add(Grant(false, appPermGroup,
449                                 listOf(permission.ACCESS_MEDIA_LOCATION)))
450                     }
451                 }
452             } else {
453                 Log.i(LOG_TAG, "Not expanding read storage to access media location as this is " +
454                         "a new user")
455             }
456 
457             currentVersion = 8
458         }
459 
460         if (currentVersion == 8) {
461             // Removed
462 
463             currentVersion = 9
464         }
465 
466         // XXX: Add new upgrade steps above this point.
467 
468         return Triple(currentVersion, exemptions, grants)
469     }
470 
471     /**
472      * All data needed by {@link #onUpgradeLocked}
473      */
474     private data class UpgradeData(
475         /** Preinstalled packages */
476         val preinstalledPkgs: List<LightPackageInfo>,
477         /** Restricted permissions */
478         val restrictedPermissions: Set<String>,
479         /** Currently installed packages */
480         val pkgs: List<LightPackageInfo>,
481         /**
482          * Background Location groups that need to be inspected by
483          * {@link #onUpgradeLockedDataLoaded}
484          */
485         val bgGroups: List<LightAppPermGroup>,
486         /**
487          * Storage groups that need to be inspected by {@link #onUpgradeLockedDataLoaded}
488          */
489         val storageGroups: List<LightAppPermGroup>,
490         /**
491          * Background Microphone groups that need to be inspected by
492          * {@link #onUpgradeLockedDataLoaded}
493          */
494         val bgMicGroups: List<LightAppPermGroup>
495     )
496 
497     /**
498      * A restricted permission of an app that should be exempted
499      */
500     private data class RestrictionExemption(
501         /** Name of package to exempt */
502         val pkgName: String,
503         /** Name of permissions to exempt */
504         val permission: String,
505         /** Name of permissions to exempt */
506         val flags: Int = FLAG_PERMISSION_WHITELIST_UPGRADE
507     ) {
508         /**
509          * Exempt the permission by updating the platform state.
510          *
511          * @param context context to use when calling the platform
512          */
513         fun applyToPlatform(context: Context) {
514             context.packageManager.addWhitelistedRestrictedPermission(pkgName, permission, flags)
515         }
516     }
517 
518     /**
519      * A permission group of an app that should get granted
520      */
521     private data class Grant(
522         /** Should the grant be for the foreground or background permissions */
523         private val isBackground: Boolean,
524         /** Group to be granted */
525         private val group: LightAppPermGroup,
526         /** Which of th permissions in the group should be granted */
527         private val permissions: List<String> = group.permissions.keys.toList()
528     ) {
529         /**
530          * Grant the permission by updating the platform state.
531          *
532          * @param context context to use when calling the platform
533          */
534         fun applyToPlatform(context: Context) {
535             if (isBackground) {
536                 val newGroup = grantBackgroundRuntimePermissions(context.application, group,
537                         permissions)
538 
539                 logRuntimePermissionUpgradeResult(newGroup,
540                         permissions intersect newGroup.backgroundPermNames)
541             } else {
542                 val newGroup = grantForegroundRuntimePermissions(context.application, group,
543                         permissions)
544 
545                 logRuntimePermissionUpgradeResult(newGroup,
546                         permissions intersect newGroup.foregroundPermNames)
547             }
548         }
549 
550         /**
551          * Log to the platform that permissions were granted due to an update
552          *
553          * @param permissionGroup The group that was granted
554          * @param filterPermissions Out of the group which permissions were granted
555          */
556         private fun logRuntimePermissionUpgradeResult(
557             permissionGroup: LightAppPermGroup,
558             filterPermissions: Iterable<String>
559         ) {
560             val uid = permissionGroup.packageInfo.uid
561             val packageName = permissionGroup.packageName
562             for (permName in filterPermissions) {
563                 val permission = permissionGroup.permissions[permName] ?: continue
564                 PermissionControllerStatsLog.write(RUNTIME_PERMISSIONS_UPGRADE_RESULT,
565                         permission.name, uid, packageName)
566                 Log.v(LOG_TAG, "Runtime permission upgrade logged for permissionName=" +
567                         permission.name + " uid=" + uid + " packageName=" + packageName)
568             }
569         }
570     }
571 } /* do nothing - hide constructor */
572