diff options
5 files changed, 110 insertions, 93 deletions
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt index a23df64f3582..c260426d47f5 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt @@ -95,9 +95,22 @@ class InstallRepository(private val context: Context) { */ var stagedSessionId = SessionInfo.INVALID_ID private set + + /** + * UID of the last caller of Pia. This can point to a 3P installer if it uses intents to install + * an APK, or receives a + * [STATUS_PENDING_USER_ACTION][PackageInstaller.STATUS_PENDING_USER_ACTION] status code. + * It may point to Pia, when it receives the STATUS_PENDING_USER_ACTION status code in case of + * an update-ownership change. + */ private var callingUid = Process.INVALID_UID + + /** + * UID of the origin of the installation. This UID is used to fetch the app-label of the + * source of the install, and also check whether the source app has the AppOp to install other + * apps. + */ private var originatingUid = Process.INVALID_UID - private var originatingUidFromSessionInfo = Process.INVALID_UID private var callingPackage: String? = null private var sessionStager: SessionStager? = null private lateinit var intent: Intent @@ -136,68 +149,60 @@ class InstallRepository(private val context: Context) { stagedSessionId = intent.getIntExtra(EXTRA_STAGED_SESSION_ID, SessionInfo.INVALID_ID) callingPackage = callerInfo.packageName - - // Uid of the source package, coming from ActivityManager callingUid = callerInfo.uid - if (callingUid == Process.INVALID_UID) { - Log.e(LOG_TAG, "Could not determine the launching uid.") + + val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage) + if (callingUid == Process.INVALID_UID && sourceInfo == null) { + // Caller's identity could not be determined. Abort the install + Log.e(LOG_TAG, "Cannot determine caller since UID is invalid and sourceInfo is null") + return InstallAborted(ABORT_REASON_INTERNAL_ERROR) } - originatingUidFromSessionInfo = callingUid + originatingUid = callingUid val sessionInfo: SessionInfo? = if (sessionId != SessionInfo.INVALID_ID) packageInstaller.getSessionInfo(sessionId) else null if (sessionInfo != null) { - callingPackage = sessionInfo.installerPackageName callingAttributionTag = sessionInfo.installerAttributionTag if (sessionInfo.originatingUid != Process.INVALID_UID) { - originatingUidFromSessionInfo = sessionInfo.originatingUid + originatingUid = sessionInfo.originatingUid } } - val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage) - // Uid of the source package, with a preference to uid from ApplicationInfo - originatingUid = sourceInfo?.uid ?: callingUid appOpRequestInfo = AppOpRequestInfo( getPackageNameForUid(context, originatingUid, callingPackage), - originatingUid, callingAttributionTag + originatingUid, + callingAttributionTag ) - if(localLogv) { - Log.i(LOG_TAG, "Intent: $intent\n" + - "sessionId: $sessionId\n" + - "staged sessionId: $stagedSessionId\n" + - "calling package: $callingPackage\n" + - "callingUid: $callingUid\n" + - "originatingUid: $originatingUid") - } - - if (callingUid == Process.INVALID_UID && sourceInfo == null) { - // Caller's identity could not be determined. Abort the install - Log.e(LOG_TAG, "Cannot determine caller since UID is invalid and sourceInfo is null") - return InstallAborted(ABORT_REASON_INTERNAL_ERROR) + if (localLogv) { + Log.i( + LOG_TAG, "Intent: $intent\n" + + "sessionId: $sessionId\n" + + "staged sessionId: $stagedSessionId\n" + + "calling package: $callingPackage\n" + + "callingUid: $callingUid\n" + + "originatingUid: $originatingUid\n" + + "sourceInfo: $sourceInfo" + ) } if ((sessionId != SessionInfo.INVALID_ID - && !isCallerSessionOwner(packageInstaller, originatingUid, sessionId)) + && !isCallerSessionOwner(packageInstaller, callingUid, sessionId)) || (stagedSessionId != SessionInfo.INVALID_ID && !isCallerSessionOwner(packageInstaller, Process.myUid(), stagedSessionId)) ) { - Log.e(LOG_TAG, "UID is not the owner of the session:\n" + - "CallingUid: $originatingUid | SessionId: $sessionId\n" + - "My UID: ${Process.myUid()} | StagedSessionId: $stagedSessionId") + Log.e( + LOG_TAG, "UID is not the owner of the session:\n" + + "CallingUid: $callingUid | SessionId: $sessionId\n" + + "My UID: ${Process.myUid()} | StagedSessionId: $stagedSessionId" + ) return InstallAborted(ABORT_REASON_INTERNAL_ERROR) } - isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, originatingUid) - if (!isInstallPermissionGrantedOrRequested( - context, callingUid, originatingUid, isTrustedSource - ) - ) { - Log.e(LOG_TAG, "UID $originatingUid needs to declare " + - Manifest.permission.REQUEST_INSTALL_PACKAGES - ) + isTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, this.intent, callingUid) + if (!isInstallPermissionGrantedOrRequested(context, callingUid, isTrustedSource)) { return InstallAborted(ABORT_REASON_INTERNAL_ERROR) } @@ -205,7 +210,7 @@ class InstallRepository(private val context: Context) { if (restriction != null) { val adminSupportDetailsIntent = devicePolicyManager!!.createAdminSupportIntent(restriction) - Log.e(LOG_TAG, "$restriction set in place. Cannot install." ) + Log.e(LOG_TAG, "$restriction set in place. Cannot install.") return InstallAborted( ABORT_REASON_POLICY, message = restriction, resultIntent = adminSupportDetailsIntent ) @@ -230,12 +235,12 @@ class InstallRepository(private val context: Context) { private fun isInstallRequestFromTrustedSource( sourceInfo: ApplicationInfo?, intent: Intent, - originatingUid: Int, + callingUid: Int, ): Boolean { val isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false) return (sourceInfo != null && sourceInfo.isPrivilegedApp && (isNotUnknownSource - || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, originatingUid))) + || isPermissionGranted(context, Manifest.permission.INSTALL_PACKAGES, callingUid))) } private fun getDevicePolicyRestrictions(): String? { @@ -320,7 +325,7 @@ class InstallRepository(private val context: Context) { Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK ), activityResultCode = Activity.RESULT_FIRST_USER, - errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE + errorDialogType = if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE ) return } @@ -401,8 +406,9 @@ class InstallRepository(private val context: Context) { Log.e(LOG_TAG, "Cannot parse package $debugPathName. Assuming defaults.", e) params.setSize(pfd.statSize) } catch (e: IOException) { - Log.e(LOG_TAG, "Cannot calculate installed size $debugPathName. " + - "Try only apk size.", e + Log.e( + LOG_TAG, "Cannot calculate installed size $debugPathName. " + + "Try only apk size.", e ) } } else { @@ -661,10 +667,9 @@ class InstallRepository(private val context: Context) { if (isAppUpdating(pkgInfo)) { val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo) - val originatingPackageNameFromSessionInfo = - getPackageNameForUid(context, originatingUidFromSessionInfo, callingPackage) - val requestedUpdateOwnerLabel = - getApplicationLabel(originatingPackageNameFromSessionInfo) + val originatingPackageName = + getPackageNameForUid(context, originatingUid, callingPackage) + val requestedUpdateOwnerLabel = getApplicationLabel(originatingPackageName) if (!TextUtils.isEmpty(existingUpdateOwnerLabel) && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP @@ -768,14 +773,14 @@ class InstallRepository(private val context: Context) { } private fun handleUnknownSources(requestInfo: AppOpRequestInfo): InstallStage { - if (requestInfo.callingPackage == null) { + if (requestInfo.originatingPackage == null) { Log.i(LOG_TAG, "No source found for package " + newPackageInfo?.packageName) return InstallUserActionRequired(USER_ACTION_REASON_ANONYMOUS_SOURCE) } // Shouldn't use static constant directly, see b/65534401. val appOpStr = AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES) val appOpMode = appOpsManager!!.noteOpNoThrow( - appOpStr!!, requestInfo.originatingUid, requestInfo.callingPackage, + appOpStr!!, requestInfo.originatingUid, requestInfo.originatingPackage, requestInfo.attributionTag, "Started package installation activity" ) if (localLogv) { @@ -786,20 +791,20 @@ class InstallRepository(private val context: Context) { AppOpsManager.MODE_DEFAULT, AppOpsManager.MODE_ERRORED -> { if (appOpMode == AppOpsManager.MODE_DEFAULT) { appOpsManager.setMode( - appOpStr, requestInfo.originatingUid, requestInfo.callingPackage, + appOpStr, requestInfo.originatingUid, requestInfo.originatingPackage, AppOpsManager.MODE_ERRORED ) } try { val sourceInfo = - packageManager.getApplicationInfo(requestInfo.callingPackage, 0) + packageManager.getApplicationInfo(requestInfo.originatingPackage, 0) val sourceAppSnippet = getAppSnippet(context, sourceInfo) InstallUserActionRequired( USER_ACTION_REASON_UNKNOWN_SOURCE, appSnippet = sourceAppSnippet, - dialogMessage = requestInfo.callingPackage + sourceApp = requestInfo.originatingPackage ) } catch (e: PackageManager.NameNotFoundException) { - Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.callingPackage) + Log.e(LOG_TAG, "Did not find appInfo for " + requestInfo.originatingPackage) InstallAborted(ABORT_REASON_INTERNAL_ERROR) } } @@ -841,8 +846,10 @@ class InstallRepository(private val context: Context) { setStageBasedOnResult(PackageInstaller.STATUS_SUCCESS, -1, null) } catch (e: PackageManager.NameNotFoundException) { setStageBasedOnResult( - PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, - null) + PackageInstaller.STATUS_FAILURE, + PackageManager.INSTALL_FAILED_INTERNAL_ERROR, + null + ) } return } @@ -862,7 +869,8 @@ class InstallRepository(private val context: Context) { } } catch (e: OutOfIdsException) { setStageBasedOnResult( - PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null) + PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null + ) return } val broadcastIntent = Intent(BROADCAST_ACTION) @@ -880,7 +888,8 @@ class InstallRepository(private val context: Context) { Log.e(LOG_TAG, "Session $stagedSessionId could not be opened.", e) packageInstaller.abandonSession(stagedSessionId) setStageBasedOnResult( - PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null) + PackageInstaller.STATUS_FAILURE, PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null + ) } } @@ -890,9 +899,11 @@ class InstallRepository(private val context: Context) { message: String?, ) { if (localLogv) { - Log.i(LOG_TAG, "Status code: $statusCode\n" + - "legacy status: $legacyStatus\n" + - "message: $message") + Log.i( + LOG_TAG, "Status code: $statusCode\n" + + "legacy status: $legacyStatus\n" + + "message: $message" + ) } if (statusCode == PackageInstaller.STATUS_SUCCESS) { val shouldReturnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false) @@ -905,11 +916,12 @@ class InstallRepository(private val context: Context) { _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent)) } else { if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) { - _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message)) + _installResult.setValue( + InstallFailed(appSnippet, statusCode, legacyStatus, message) + ) } else { _installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR)) } - } } @@ -953,7 +965,7 @@ class InstallRepository(private val context: Context) { data class CallerInfo(val packageName: String?, val uid: Int) data class AppOpRequestInfo( - val callingPackage: String?, + val originatingPackage: String?, val originatingUid: Int, val attributionTag: String?, ) diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt index bbb9bca6db51..5dd4d2905f47 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt @@ -43,7 +43,10 @@ data class InstallUserActionRequired( val actionReason: Int, private val appSnippet: PackageUtil.AppSnippet? = null, val isAppUpdating: Boolean = false, - val dialogMessage: String? = null, + /** + * This holds either a package name or the app label of the install source. + */ + val sourceApp: String? = null, ) : InstallStage(STAGE_USER_ACTION_REQUIRED) { val appIcon: Drawable? diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt index bae6f6876580..85728528a25c 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt @@ -128,18 +128,19 @@ object PackageUtil { /** * @param context the [Context] object - * @param callingUid the UID of the caller who's permission is being checked - * @param originatingUid the UID from where install is being originated. This could be same as - * callingUid or it will be the UID of the package performing a session based install - * @param isTrustedSource whether install request is coming from a privileged app or an app that - * has [Manifest.permission.INSTALL_PACKAGES] permission granted - * @return `true` if the package is granted the said permission + * @param callingUid the UID of the caller of Pia + * @param isTrustedSource indicates whether install request is coming from a privileged app + * that has passed EXTRA_NOT_UNKNOWN_SOURCE as `true` in the installation intent, or that has + * the [INSTALL_PACKAGES][Manifest.permission.INSTALL_PACKAGES] permission granted. + * + * @return `true` if the package is either a system downloads provider, a document manager, + * a trusted source, or has declared the + * [REQUEST_INSTALL_PACKAGES][Manifest.permission.REQUEST_INSTALL_PACKAGES] in its manifest. */ @JvmStatic fun isInstallPermissionGrantedOrRequested( context: Context, callingUid: Int, - originatingUid: Int, isTrustedSource: Boolean, ): Boolean { val isDocumentsManager = @@ -148,19 +149,18 @@ object PackageUtil { getSystemDownloadsProviderInfo(context.packageManager, callingUid) != null if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) { - val targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid) + val targetSdkVersion = getMaxTargetSdkVersionForUid(context, callingUid) if (targetSdkVersion < 0) { - // Invalid originating uid supplied. Abort install. - Log.w(LOG_TAG, "Cannot get target sdk version for uid $originatingUid") + // Invalid calling uid supplied. Abort install. + Log.e(LOG_TAG, "Cannot get target SDK version for uid $callingUid") return false } else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission( - context.packageManager, originatingUid, - Manifest.permission.REQUEST_INSTALL_PACKAGES + context.packageManager, callingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES ) ) { Log.e( - LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission " + LOG_TAG, "Requesting uid " + callingUid + " needs to declare permission " + Manifest.permission.REQUEST_INSTALL_PACKAGES ) return false @@ -204,13 +204,13 @@ object PackageUtil { * @return `true` if the caller is the session owner */ @JvmStatic - fun isCallerSessionOwner(pi: PackageInstaller, originatingUid: Int, sessionId: Int): Boolean { - if (originatingUid == Process.ROOT_UID) { + fun isCallerSessionOwner(pi: PackageInstaller, callingUid: Int, sessionId: Int): Boolean { + if (callingUid == Process.ROOT_UID) { return true } val sessionInfo = pi.getSessionInfo(sessionId) ?: return false val installerUid = sessionInfo.getInstallerUid() - return originatingUid == installerUid + return callingUid == installerUid } /** @@ -362,8 +362,8 @@ object PackageUtil { * @return the packageName corresponding to a UID. */ @JvmStatic - fun getPackageNameForUid(context: Context, sourceUid: Int, callingPackage: String?): String? { - if (sourceUid == Process.INVALID_UID) { + fun getPackageNameForUid(context: Context, uid: Int, preferredPkgName: String?): String? { + if (uid == Process.INVALID_UID) { return null } // If the sourceUid belongs to the system downloads provider, we explicitly return the @@ -371,20 +371,21 @@ object PackageUtil { // packages, resulting in uncertainty about which package will end up first in the list // of packages associated with this UID val pm = context.packageManager - val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, sourceUid) + val systemDownloadProviderInfo = getSystemDownloadsProviderInfo(pm, uid) if (systemDownloadProviderInfo != null) { return systemDownloadProviderInfo.packageName } - val packagesForUid = pm.getPackagesForUid(sourceUid) ?: return null + + val packagesForUid = pm.getPackagesForUid(uid) ?: return null if (packagesForUid.size > 1) { - if (callingPackage != null) { + Log.i(LOG_TAG, "Multiple packages found for source uid $uid") + if (preferredPkgName != null) { for (packageName in packagesForUid) { - if (packageName == callingPackage) { + if (packageName == preferredPkgName) { return packageName } } } - Log.i(LOG_TAG, "Multiple packages found for source uid $sourceUid") } return packagesForUid[0] } @@ -439,7 +440,7 @@ object PackageUtil { */ data class AppSnippet(var label: CharSequence?, var icon: Drawable?) { override fun toString(): String { - return "AppSnippet[label = ${label}, hasIcon = ${icon != null}]" + return "AppSnippet[label = $label, hasIcon = ${icon != null}]" } } } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java index a95137d57a32..343a213780b3 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java @@ -63,7 +63,7 @@ public class ExternalSourcesBlockedFragment extends DialogFragment { .setMessage(R.string.untrusted_external_source_warning) .setPositiveButton(R.string.external_sources_settings, (dialog, which) -> mInstallActionListener.sendUnknownAppsIntent( - mDialogData.getDialogMessage())) + mDialogData.getSourceApp())) .setNegativeButton(R.string.cancel, (dialog, which) -> mInstallActionListener.onNegativeResponse( mDialogData.getStageCode())) diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java index 99b1eec9cd9e..e186590fa5e2 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java @@ -64,7 +64,7 @@ public class InstallConfirmationFragment extends DialogFragment { int positiveBtnTextRes; if (mDialogData.isAppUpdating()) { - if (mDialogData.getDialogMessage() != null) { + if (mDialogData.getSourceApp() != null) { positiveBtnTextRes = R.string.update_anyway; } else { positiveBtnTextRes = R.string.update; @@ -88,9 +88,10 @@ public class InstallConfirmationFragment extends DialogFragment { TextView viewToEnable; if (mDialogData.isAppUpdating()) { viewToEnable = dialogView.requireViewById(R.id.install_confirm_question_update); - String dialogMessage = mDialogData.getDialogMessage(); - if (dialogMessage != null) { - viewToEnable.setText(Html.fromHtml(dialogMessage, Html.FROM_HTML_MODE_LEGACY)); + String sourcePackageName = mDialogData.getSourceApp(); + if (sourcePackageName != null) { + // Show the update-ownership change message + viewToEnable.setText(Html.fromHtml(sourcePackageName, Html.FROM_HTML_MODE_LEGACY)); } } else { viewToEnable = dialogView.requireViewById(R.id.install_confirm_question); |