1 /* 2 * Copyright (C) 2016 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.packageinstaller; 18 19 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid; 20 21 import android.Manifest; 22 import android.app.Activity; 23 import android.app.DialogFragment; 24 import android.app.admin.DevicePolicyManager; 25 import android.content.ContentResolver; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageInstaller; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ProviderInfo; 32 import android.net.Uri; 33 import android.os.Build; 34 import android.os.Bundle; 35 import android.os.Process; 36 import android.os.UserManager; 37 import android.text.TextUtils; 38 import android.util.EventLog; 39 import android.util.Log; 40 41 import androidx.annotation.NonNull; 42 import androidx.annotation.Nullable; 43 44 import java.util.Arrays; 45 46 /** 47 * Select which activity is the first visible activity of the installation and forward the intent to 48 * it. 49 */ 50 public class InstallStart extends Activity { 51 private static final String TAG = InstallStart.class.getSimpleName(); 52 53 private static final String DOWNLOADS_AUTHORITY = "downloads"; 54 55 private PackageManager mPackageManager; 56 private UserManager mUserManager; 57 private boolean mAbortInstall = false; 58 private boolean mShouldFinish = true; 59 60 private final boolean mLocalLOGV = false; 61 62 @Override onCreate(@ullable Bundle savedInstanceState)63 protected void onCreate(@Nullable Bundle savedInstanceState) { 64 super.onCreate(savedInstanceState); 65 mPackageManager = getPackageManager(); 66 mUserManager = getSystemService(UserManager.class); 67 68 Intent intent = getIntent(); 69 String callingPackage = getCallingPackage(); 70 String callingAttributionTag = null; 71 72 final boolean isSessionInstall = 73 PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction()) 74 || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction()); 75 76 // If the activity was started via a PackageInstaller session, we retrieve the calling 77 // package from that session 78 final int sessionId = (isSessionInstall 79 ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1) 80 : -1); 81 if (callingPackage == null && sessionId != -1) { 82 PackageInstaller packageInstaller = getPackageManager().getPackageInstaller(); 83 PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId); 84 callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null; 85 callingAttributionTag = 86 (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null; 87 } 88 89 final ApplicationInfo sourceInfo = getSourceInfo(callingPackage); 90 // Uid of the source package, coming from ActivityManager 91 int callingUid = getLaunchedFromUid(); 92 if (callingUid == Process.INVALID_UID) { 93 Log.e(TAG, "Could not determine the launching uid."); 94 } 95 // Uid of the source package, with a preference to uid from ApplicationInfo 96 final int originatingUid = sourceInfo != null ? sourceInfo.uid : callingUid; 97 98 if (callingUid == Process.INVALID_UID && sourceInfo == null) { 99 mAbortInstall = true; 100 } 101 102 boolean isDocumentsManager = checkPermission(Manifest.permission.MANAGE_DOCUMENTS, 103 -1, callingUid) == PackageManager.PERMISSION_GRANTED; 104 boolean isTrustedSource = false; 105 if (sourceInfo != null && sourceInfo.isPrivilegedApp()) { 106 isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false) || ( 107 originatingUid != Process.INVALID_UID && checkPermission( 108 Manifest.permission.INSTALL_PACKAGES, -1 /* pid */, originatingUid) 109 == PackageManager.PERMISSION_GRANTED); 110 } 111 112 if (!isTrustedSource && !isSystemDownloadsProvider(callingUid) && !isDocumentsManager 113 && originatingUid != Process.INVALID_UID) { 114 final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid); 115 if (targetSdkVersion < 0) { 116 Log.w(TAG, "Cannot get target sdk version for uid " + originatingUid); 117 // Invalid originating uid supplied. Abort install. 118 mAbortInstall = true; 119 } else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission( 120 originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) { 121 Log.e(TAG, "Requesting uid " + originatingUid + " needs to declare permission " 122 + Manifest.permission.REQUEST_INSTALL_PACKAGES); 123 mAbortInstall = true; 124 } 125 } 126 127 if (sessionId != -1 && !isCallerSessionOwner(originatingUid, sessionId)) { 128 mAbortInstall = true; 129 } 130 131 checkDevicePolicyRestrictions(); 132 133 final String installerPackageNameFromIntent = getIntent().getStringExtra( 134 Intent.EXTRA_INSTALLER_PACKAGE_NAME); 135 if (installerPackageNameFromIntent != null) { 136 final String callingPkgName = getLaunchedFromPackage(); 137 if (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName) 138 && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES, 139 callingPkgName) != PackageManager.PERMISSION_GRANTED) { 140 Log.e(TAG, "The given installer package name " + installerPackageNameFromIntent 141 + " is invalid. Remove it."); 142 EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(), 143 "Invalid EXTRA_INSTALLER_PACKAGE_NAME"); 144 getIntent().removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); 145 } 146 } 147 148 if (mAbortInstall) { 149 setResult(RESULT_CANCELED); 150 if (mShouldFinish) { 151 finish(); 152 } 153 return; 154 } 155 156 Intent nextActivity = new Intent(intent); 157 nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 158 | Intent.FLAG_GRANT_READ_URI_PERMISSION); 159 160 // The the installation source as the nextActivity thinks this activity is the source, hence 161 // set the originating UID and sourceInfo explicitly 162 nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage); 163 nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_ATTRIBUTION_TAG, 164 callingAttributionTag); 165 nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo); 166 nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid); 167 168 if (isSessionInstall) { 169 nextActivity.setClass(this, PackageInstallerActivity.class); 170 } else { 171 Uri packageUri = intent.getData(); 172 173 if (packageUri != null 174 && packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT) 175 && canPackageQuery(callingUid, packageUri)) { 176 // [IMPORTANT] This path is deprecated, but should still work. Only necessary 177 // features should be added. 178 179 // Stage a session with this file to prevent it from being changed underneath 180 // this process. 181 nextActivity.setClass(this, InstallStaging.class); 182 } else if (packageUri != null && PackageInstallerActivity.SCHEME_PACKAGE.equals( 183 packageUri.getScheme())) { 184 nextActivity.setClass(this, PackageInstallerActivity.class); 185 } else { 186 Intent result = new Intent(); 187 result.putExtra(Intent.EXTRA_INSTALL_RESULT, 188 PackageManager.INSTALL_FAILED_INVALID_URI); 189 setResult(RESULT_FIRST_USER, result); 190 191 nextActivity = null; 192 } 193 } 194 195 if (nextActivity != null) { 196 try { 197 startActivity(nextActivity); 198 } catch (SecurityException e) { 199 Intent result = new Intent(); 200 result.putExtra(Intent.EXTRA_INSTALL_RESULT, 201 PackageManager.INSTALL_FAILED_INVALID_URI); 202 setResult(RESULT_FIRST_USER, result); 203 } 204 } 205 finish(); 206 } 207 isUidRequestingPermission(int uid, String permission)208 private boolean isUidRequestingPermission(int uid, String permission) { 209 final String[] packageNames = mPackageManager.getPackagesForUid(uid); 210 if (packageNames == null) { 211 return false; 212 } 213 for (final String packageName : packageNames) { 214 final PackageInfo packageInfo; 215 try { 216 packageInfo = mPackageManager.getPackageInfo(packageName, 217 PackageManager.GET_PERMISSIONS); 218 } catch (PackageManager.NameNotFoundException e) { 219 // Ignore and try the next package 220 continue; 221 } 222 if (packageInfo.requestedPermissions != null 223 && Arrays.asList(packageInfo.requestedPermissions).contains(permission)) { 224 return true; 225 } 226 } 227 return false; 228 } 229 230 /** 231 * @return the ApplicationInfo for the installation source (the calling package), if available 232 */ getSourceInfo(@ullable String callingPackage)233 private ApplicationInfo getSourceInfo(@Nullable String callingPackage) { 234 if (callingPackage != null) { 235 try { 236 return getPackageManager().getApplicationInfo(callingPackage, 0); 237 } catch (PackageManager.NameNotFoundException ex) { 238 // ignore 239 } 240 } 241 return null; 242 } 243 isSystemDownloadsProvider(int uid)244 private boolean isSystemDownloadsProvider(int uid) { 245 final ProviderInfo downloadProviderPackage = getPackageManager().resolveContentProvider( 246 DOWNLOADS_AUTHORITY, 0); 247 if (downloadProviderPackage == null) { 248 // There seems to be no currently enabled downloads provider on the system. 249 return false; 250 } 251 final ApplicationInfo appInfo = downloadProviderPackage.applicationInfo; 252 return ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 253 && uid == appInfo.uid); 254 } 255 256 @NonNull canPackageQuery(int callingUid, Uri packageUri)257 private boolean canPackageQuery(int callingUid, Uri packageUri) { 258 ProviderInfo info = mPackageManager.resolveContentProvider(packageUri.getAuthority(), 259 PackageManager.ComponentInfoFlags.of(0)); 260 if (info == null) { 261 return false; 262 } 263 String targetPackage = info.packageName; 264 265 String[] callingPackages = mPackageManager.getPackagesForUid(callingUid); 266 if (callingPackages == null) { 267 return false; 268 } 269 for (String callingPackage: callingPackages) { 270 try { 271 if (mPackageManager.canPackageQuery(callingPackage, targetPackage)) { 272 return true; 273 } 274 } catch (PackageManager.NameNotFoundException e) { 275 // no-op 276 } 277 } 278 return false; 279 } 280 isCallerSessionOwner(int originatingUid, int sessionId)281 private boolean isCallerSessionOwner(int originatingUid, int sessionId) { 282 if (originatingUid == Process.ROOT_UID) { 283 return true; 284 } 285 PackageInstaller packageInstaller = getPackageManager().getPackageInstaller(); 286 PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId); 287 if (sessionInfo == null) { 288 return false; 289 } 290 int installerUid = sessionInfo.getInstallerUid(); 291 return originatingUid == installerUid; 292 } 293 checkDevicePolicyRestrictions()294 private void checkDevicePolicyRestrictions() { 295 final String[] restrictions = new String[] { 296 UserManager.DISALLOW_INSTALL_APPS, 297 UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, 298 UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY 299 }; 300 301 final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class); 302 for (String restriction : restrictions) { 303 if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) { 304 continue; 305 } 306 307 mAbortInstall = true; 308 309 // If the given restriction is set by an admin, display information about the 310 // admin enforcing the restriction for the affected user. If not enforced by the admin, 311 // show the system dialog. 312 final Intent showAdminSupportDetailsIntent = dpm.createAdminSupportIntent(restriction); 313 if (showAdminSupportDetailsIntent != null) { 314 if (mLocalLOGV) Log.i(TAG, "starting " + showAdminSupportDetailsIntent); 315 startActivity(showAdminSupportDetailsIntent); 316 } else { 317 if (mLocalLOGV) Log.i(TAG, "Restriction set by system: " + restriction); 318 mShouldFinish = false; 319 showDialogInner(restriction); 320 } 321 break; 322 } 323 } 324 325 /** 326 * Replace any dialog shown by the dialog with the one for the given 327 * {@link #createDialog(String)}. 328 * 329 * @param restriction The restriction to create the dialog for 330 */ showDialogInner(String restriction)331 private void showDialogInner(String restriction) { 332 if (mLocalLOGV) Log.i(TAG, "showDialogInner(" + restriction + ")"); 333 DialogFragment currentDialog = 334 (DialogFragment) getFragmentManager().findFragmentByTag("dialog"); 335 if (currentDialog != null) { 336 currentDialog.dismissAllowingStateLoss(); 337 } 338 339 DialogFragment newDialog = createDialog(restriction); 340 if (newDialog != null) { 341 getFragmentManager().beginTransaction() 342 .add(newDialog, "dialog").commitAllowingStateLoss(); 343 } 344 } 345 346 /** 347 * Create a new dialog. 348 * 349 * @param restriction The restriction to create the dialog for 350 * @return The dialog 351 */ createDialog(String restriction)352 private DialogFragment createDialog(String restriction) { 353 if (mLocalLOGV) Log.i(TAG, "createDialog(" + restriction + ")"); 354 switch (restriction) { 355 case UserManager.DISALLOW_INSTALL_APPS: 356 return PackageUtil.SimpleErrorDialog.newInstance( 357 R.string.install_apps_user_restriction_dlg_text); 358 case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES: 359 case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY: 360 return PackageUtil.SimpleErrorDialog.newInstance( 361 R.string.unknown_apps_user_restriction_dlg_text); 362 } 363 return null; 364 } 365 } 366