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.server.pm; 18 19 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE; 20 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; 21 import static android.system.OsConstants.O_CREAT; 22 import static android.system.OsConstants.O_RDWR; 23 24 import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION; 25 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION; 26 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; 27 import static com.android.server.pm.PackageManagerService.STUB_SUFFIX; 28 import static com.android.server.pm.PackageManagerService.TAG; 29 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.app.AppGlobals; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.PackageInfoLite; 36 import android.content.pm.PackageManager; 37 import android.content.pm.PackageParser; 38 import android.content.pm.ResolveInfo; 39 import android.content.pm.Signature; 40 import android.content.pm.parsing.ApkLiteParseUtils; 41 import android.content.pm.parsing.PackageLite; 42 import android.content.pm.parsing.result.ParseResult; 43 import android.content.pm.parsing.result.ParseTypeImpl; 44 import android.os.Build; 45 import android.os.Debug; 46 import android.os.Environment; 47 import android.os.FileUtils; 48 import android.os.Process; 49 import android.os.RemoteException; 50 import android.os.SystemProperties; 51 import android.os.UserHandle; 52 import android.os.incremental.IncrementalManager; 53 import android.os.incremental.V4Signature; 54 import android.os.incremental.V4Signature.HashingInfo; 55 import android.service.pm.PackageServiceDumpProto; 56 import android.system.ErrnoException; 57 import android.system.Os; 58 import android.util.ArraySet; 59 import android.util.Log; 60 import android.util.Slog; 61 import android.util.proto.ProtoOutputStream; 62 63 import com.android.internal.content.NativeLibraryHelper; 64 import com.android.internal.content.PackageHelper; 65 import com.android.internal.util.ArrayUtils; 66 import com.android.internal.util.FastPrintWriter; 67 import com.android.internal.util.HexDump; 68 import com.android.server.EventLogTags; 69 import com.android.server.pm.dex.DexManager; 70 import com.android.server.pm.dex.PackageDexUsage; 71 import com.android.server.pm.parsing.pkg.AndroidPackage; 72 73 import dalvik.system.VMRuntime; 74 75 import libcore.io.IoUtils; 76 77 import java.io.BufferedReader; 78 import java.io.File; 79 import java.io.FileDescriptor; 80 import java.io.FileInputStream; 81 import java.io.FileOutputStream; 82 import java.io.FileReader; 83 import java.io.FilenameFilter; 84 import java.io.IOException; 85 import java.io.InputStream; 86 import java.io.OutputStream; 87 import java.io.PrintWriter; 88 import java.nio.file.Path; 89 import java.security.cert.CertificateEncodingException; 90 import java.security.cert.CertificateException; 91 import java.text.SimpleDateFormat; 92 import java.util.ArrayList; 93 import java.util.Arrays; 94 import java.util.Collection; 95 import java.util.Collections; 96 import java.util.Date; 97 import java.util.LinkedList; 98 import java.util.List; 99 import java.util.function.Predicate; 100 import java.util.zip.GZIPInputStream; 101 102 /** 103 * Class containing helper methods for the PackageManagerService. 104 * 105 * {@hide} 106 */ 107 public class PackageManagerServiceUtils { 108 private static final long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000; 109 private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 3 * 1000 * 1000; // 3MB 110 111 public final static Predicate<PackageSetting> REMOVE_IF_NULL_PKG = 112 pkgSetting -> pkgSetting.pkg == null; 113 getPackageNamesForIntent(Intent intent, int userId)114 private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) { 115 List<ResolveInfo> ris = null; 116 try { 117 ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId) 118 .getList(); 119 } catch (RemoteException e) { 120 } 121 ArraySet<String> pkgNames = new ArraySet<String>(); 122 if (ris != null) { 123 for (ResolveInfo ri : ris) { 124 pkgNames.add(ri.activityInfo.packageName); 125 } 126 } 127 return pkgNames; 128 } 129 130 // Sort a list of apps by their last usage, most recently used apps first. The order of 131 // packages without usage data is undefined (but they will be sorted after the packages 132 // that do have usage data). sortPackagesByUsageDate(List<PackageSetting> pkgSettings, PackageManagerService packageManagerService)133 public static void sortPackagesByUsageDate(List<PackageSetting> pkgSettings, 134 PackageManagerService packageManagerService) { 135 if (!packageManagerService.isHistoricalPackageUsageAvailable()) { 136 return; 137 } 138 139 Collections.sort(pkgSettings, (pkgSetting1, pkgSetting2) -> 140 Long.compare( 141 pkgSetting2.getPkgState().getLatestForegroundPackageUseTimeInMills(), 142 pkgSetting1.getPkgState().getLatestForegroundPackageUseTimeInMills()) 143 ); 144 } 145 146 // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the 147 // package will be removed from {@code packages} and added to {@code result} with its 148 // dependencies. If usage data is available, the positive packages will be sorted by usage 149 // data (with {@code sortTemp} as temporary storage). applyPackageFilter( Predicate<PackageSetting> filter, Collection<PackageSetting> result, Collection<PackageSetting> packages, @NonNull List<PackageSetting> sortTemp, PackageManagerService packageManagerService)150 private static void applyPackageFilter( 151 Predicate<PackageSetting> filter, 152 Collection<PackageSetting> result, 153 Collection<PackageSetting> packages, 154 @NonNull List<PackageSetting> sortTemp, 155 PackageManagerService packageManagerService) { 156 for (PackageSetting pkgSetting : packages) { 157 if (filter.test(pkgSetting)) { 158 sortTemp.add(pkgSetting); 159 } 160 } 161 162 sortPackagesByUsageDate(sortTemp, packageManagerService); 163 packages.removeAll(sortTemp); 164 165 for (PackageSetting pkgSetting : sortTemp) { 166 result.add(pkgSetting); 167 168 List<PackageSetting> deps = 169 packageManagerService.findSharedNonSystemLibraries(pkgSetting); 170 if (!deps.isEmpty()) { 171 deps.removeAll(result); 172 result.addAll(deps); 173 packages.removeAll(deps); 174 } 175 } 176 177 sortTemp.clear(); 178 } 179 180 // Sort apps by importance for dexopt ordering. Important apps are given 181 // more priority in case the device runs out of space. getPackagesForDexopt( Collection<PackageSetting> packages, PackageManagerService packageManagerService)182 public static List<PackageSetting> getPackagesForDexopt( 183 Collection<PackageSetting> packages, 184 PackageManagerService packageManagerService) { 185 return getPackagesForDexopt(packages, packageManagerService, DEBUG_DEXOPT); 186 } 187 getPackagesForDexopt( Collection<PackageSetting> pkgSettings, PackageManagerService packageManagerService, boolean debug)188 public static List<PackageSetting> getPackagesForDexopt( 189 Collection<PackageSetting> pkgSettings, 190 PackageManagerService packageManagerService, 191 boolean debug) { 192 List<PackageSetting> result = new LinkedList<>(); 193 ArrayList<PackageSetting> remainingPkgSettings = new ArrayList<>(pkgSettings); 194 195 // First, remove all settings without available packages 196 remainingPkgSettings.removeIf(REMOVE_IF_NULL_PKG); 197 198 ArrayList<PackageSetting> sortTemp = new ArrayList<>(remainingPkgSettings.size()); 199 200 // Give priority to core apps. 201 applyPackageFilter(pkgSetting -> pkgSetting.pkg.isCoreApp(), result, remainingPkgSettings, sortTemp, 202 packageManagerService); 203 204 // Give priority to system apps that listen for pre boot complete. 205 Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); 206 final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM); 207 applyPackageFilter(pkgSetting -> pkgNames.contains(pkgSetting.name), result, 208 remainingPkgSettings, sortTemp, packageManagerService); 209 210 // Give priority to apps used by other apps. 211 DexManager dexManager = packageManagerService.getDexManager(); 212 applyPackageFilter(pkgSetting -> 213 dexManager.getPackageUseInfoOrDefault(pkgSetting.name) 214 .isAnyCodePathUsedByOtherApps(), 215 result, remainingPkgSettings, sortTemp, packageManagerService); 216 217 // Filter out packages that aren't recently used, add all remaining apps. 218 // TODO: add a property to control this? 219 Predicate<PackageSetting> remainingPredicate; 220 if (!remainingPkgSettings.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) { 221 if (debug) { 222 Log.i(TAG, "Looking at historical package use"); 223 } 224 // Get the package that was used last. 225 PackageSetting lastUsed = Collections.max(remainingPkgSettings, 226 (pkgSetting1, pkgSetting2) -> Long.compare( 227 pkgSetting1.getPkgState().getLatestForegroundPackageUseTimeInMills(), 228 pkgSetting2.getPkgState().getLatestForegroundPackageUseTimeInMills())); 229 if (debug) { 230 Log.i(TAG, "Taking package " + lastUsed.name 231 + " as reference in time use"); 232 } 233 long estimatedPreviousSystemUseTime = lastUsed.getPkgState() 234 .getLatestForegroundPackageUseTimeInMills(); 235 // Be defensive if for some reason package usage has bogus data. 236 if (estimatedPreviousSystemUseTime != 0) { 237 final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS; 238 remainingPredicate = pkgSetting -> pkgSetting.getPkgState() 239 .getLatestForegroundPackageUseTimeInMills() >= cutoffTime; 240 } else { 241 // No meaningful historical info. Take all. 242 remainingPredicate = pkgSetting -> true; 243 } 244 sortPackagesByUsageDate(remainingPkgSettings, packageManagerService); 245 } else { 246 // No historical info. Take all. 247 remainingPredicate = pkgSetting -> true; 248 } 249 applyPackageFilter(remainingPredicate, result, remainingPkgSettings, sortTemp, 250 packageManagerService); 251 252 if (debug) { 253 Log.i(TAG, "Packages to be dexopted: " + packagesToString(result)); 254 Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgSettings)); 255 } 256 257 return result; 258 } 259 260 /** 261 * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>. 262 * Package is considered active, if: 263 * 1) It was active in foreground. 264 * 2) It was active in background and also used by other apps. 265 * 266 * If it doesn't have sufficient information about the package, it return <code>false</code>. 267 */ isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis, long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo, long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis)268 public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis, 269 long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo, 270 long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) { 271 272 if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) { 273 return false; 274 } 275 276 // If the app was active in foreground during the threshold period. 277 boolean isActiveInForeground = (currentTimeInMillis 278 - latestForegroundPackageUseTimeInMillis) 279 < thresholdTimeinMillis; 280 281 if (isActiveInForeground) { 282 return false; 283 } 284 285 // If the app was active in background during the threshold period and was used 286 // by other packages. 287 boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis 288 - latestPackageUseTimeInMillis) 289 < thresholdTimeinMillis) 290 && packageUseInfo.isAnyCodePathUsedByOtherApps(); 291 292 return !isActiveInBackgroundAndUsedByOtherPackages; 293 } 294 295 /** 296 * Returns the canonicalized path of {@code path} as per {@code realpath(3)} 297 * semantics. 298 */ 299 public static String realpath(File path) throws IOException { 300 try { 301 return Os.realpath(path.getAbsolutePath()); 302 } catch (ErrnoException ee) { 303 throw ee.rethrowAsIOException(); 304 } 305 } 306 307 public static String packagesToString(List<PackageSetting> pkgSettings) { 308 StringBuilder sb = new StringBuilder(); 309 for (int index = 0; index < pkgSettings.size(); index++) { 310 if (sb.length() > 0) { 311 sb.append(", "); 312 } 313 sb.append(pkgSettings.get(index).name); 314 } 315 return sb.toString(); 316 } 317 318 /** 319 * Verifies that the given string {@code isa} is a valid supported isa on 320 * the running device. 321 */ 322 public static boolean checkISA(String isa) { 323 for (String abi : Build.SUPPORTED_ABIS) { 324 if (VMRuntime.getInstructionSet(abi).equals(isa)) { 325 return true; 326 } 327 } 328 return false; 329 } 330 331 public static long getLastModifiedTime(AndroidPackage pkg) { 332 final File srcFile = new File(pkg.getPath()); 333 if (!srcFile.isDirectory()) { 334 return srcFile.lastModified(); 335 } 336 final File baseFile = new File(pkg.getBaseApkPath()); 337 long maxModifiedTime = baseFile.lastModified(); 338 if (pkg.getSplitCodePaths() != null) { 339 for (int i = pkg.getSplitCodePaths().length - 1; i >=0; --i) { 340 final File splitFile = new File(pkg.getSplitCodePaths()[i]); 341 maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified()); 342 } 343 } 344 return maxModifiedTime; 345 } 346 347 private static File getSettingsProblemFile() { 348 File dataDir = Environment.getDataDirectory(); 349 File systemDir = new File(dataDir, "system"); 350 File fname = new File(systemDir, "uiderrors.txt"); 351 return fname; 352 } 353 354 public static void dumpCriticalInfo(ProtoOutputStream proto) { 355 final File file = getSettingsProblemFile(); 356 final long skipSize = file.length() - MAX_CRITICAL_INFO_DUMP_SIZE; 357 try (BufferedReader in = new BufferedReader(new FileReader(file))) { 358 if (skipSize > 0) { 359 in.skip(skipSize); 360 } 361 String line = null; 362 while ((line = in.readLine()) != null) { 363 if (line.contains("ignored: updated version")) continue; 364 proto.write(PackageServiceDumpProto.MESSAGES, line); 365 } 366 } catch (IOException ignored) { 367 } 368 } 369 dumpCriticalInfo(PrintWriter pw, String msg)370 public static void dumpCriticalInfo(PrintWriter pw, String msg) { 371 final File file = getSettingsProblemFile(); 372 final long skipSize = file.length() - MAX_CRITICAL_INFO_DUMP_SIZE; 373 try (BufferedReader in = new BufferedReader(new FileReader(file))) { 374 if (skipSize > 0) { 375 in.skip(skipSize); 376 } 377 String line = null; 378 while ((line = in.readLine()) != null) { 379 if (line.contains("ignored: updated version")) continue; 380 if (msg != null) { 381 pw.print(msg); 382 } 383 pw.println(line); 384 } 385 } catch (IOException ignored) { 386 } 387 } 388 logCriticalInfo(int priority, String msg)389 public static void logCriticalInfo(int priority, String msg) { 390 Slog.println(priority, TAG, msg); 391 EventLogTags.writePmCriticalInfo(msg); 392 try { 393 File fname = getSettingsProblemFile(); 394 FileOutputStream out = new FileOutputStream(fname, true); 395 PrintWriter pw = new FastPrintWriter(out); 396 SimpleDateFormat formatter = new SimpleDateFormat(); 397 String dateString = formatter.format(new Date(System.currentTimeMillis())); 398 pw.println(dateString + ": " + msg); 399 pw.close(); 400 FileUtils.setPermissions( 401 fname.toString(), 402 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH, 403 -1, -1); 404 } catch (java.io.IOException e) { 405 } 406 } 407 408 /** Enforces that if the caller is shell, it does not have the provided user restriction. */ enforceShellRestriction( UserManagerInternal userManager, String restriction, int callingUid, int userHandle)409 public static void enforceShellRestriction( 410 UserManagerInternal userManager, String restriction, int callingUid, int userHandle) { 411 if (callingUid == Process.SHELL_UID) { 412 if (userHandle >= 0 413 && userManager.hasUserRestriction( 414 restriction, userHandle)) { 415 throw new SecurityException("Shell does not have permission to access user " 416 + userHandle); 417 } else if (userHandle < 0) { 418 Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user " 419 + userHandle + "\n\t" + Debug.getCallers(3)); 420 } 421 } 422 } 423 424 /** 425 * Enforces that the caller must be either the system process or the phone process. 426 * If not, throws a {@link SecurityException}. 427 */ enforceSystemOrPhoneCaller(String methodName, int callingUid)428 public static void enforceSystemOrPhoneCaller(String methodName, int callingUid) { 429 if (callingUid != Process.PHONE_UID && callingUid != Process.SYSTEM_UID) { 430 throw new SecurityException( 431 "Cannot call " + methodName + " from UID " + callingUid); 432 } 433 } 434 435 /** 436 * Derive the value of the {@code cpuAbiOverride} based on the provided 437 * value. 438 */ deriveAbiOverride(String abiOverride)439 public static String deriveAbiOverride(String abiOverride) { 440 if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) { 441 return null; 442 } 443 return abiOverride; 444 } 445 446 /** 447 * Compares two sets of signatures. Returns: 448 * <br /> 449 * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null, 450 * <br /> 451 * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null, 452 * <br /> 453 * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null, 454 * <br /> 455 * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical, 456 * <br /> 457 * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ. 458 */ compareSignatures(Signature[] s1, Signature[] s2)459 public static int compareSignatures(Signature[] s1, Signature[] s2) { 460 if (s1 == null) { 461 return s2 == null 462 ? PackageManager.SIGNATURE_NEITHER_SIGNED 463 : PackageManager.SIGNATURE_FIRST_NOT_SIGNED; 464 } 465 466 if (s2 == null) { 467 return PackageManager.SIGNATURE_SECOND_NOT_SIGNED; 468 } 469 470 if (s1.length != s2.length) { 471 return PackageManager.SIGNATURE_NO_MATCH; 472 } 473 474 // Since both signature sets are of size 1, we can compare without HashSets. 475 if (s1.length == 1) { 476 return s1[0].equals(s2[0]) ? 477 PackageManager.SIGNATURE_MATCH : 478 PackageManager.SIGNATURE_NO_MATCH; 479 } 480 481 ArraySet<Signature> set1 = new ArraySet<Signature>(); 482 for (Signature sig : s1) { 483 set1.add(sig); 484 } 485 ArraySet<Signature> set2 = new ArraySet<Signature>(); 486 for (Signature sig : s2) { 487 set2.add(sig); 488 } 489 // Make sure s2 contains all signatures in s1. 490 if (set1.equals(set2)) { 491 return PackageManager.SIGNATURE_MATCH; 492 } 493 return PackageManager.SIGNATURE_NO_MATCH; 494 } 495 496 /** 497 * Returns true if the signature set of the package is identical to the specified signature 498 * set or if the signing details of the package are unknown. 499 */ comparePackageSignatures(PackageSetting pkgSetting, Signature[] signatures)500 public static boolean comparePackageSignatures(PackageSetting pkgSetting, 501 Signature[] signatures) { 502 return pkgSetting.signatures.mSigningDetails 503 == PackageParser.SigningDetails.UNKNOWN 504 || compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, signatures) 505 == PackageManager.SIGNATURE_MATCH; 506 } 507 508 /** 509 * Used for backward compatibility to make sure any packages with 510 * certificate chains get upgraded to the new style. {@code existingSigs} 511 * will be in the old format (since they were stored on disk from before the 512 * system upgrade) and {@code scannedSigs} will be in the newer format. 513 */ matchSignaturesCompat(String packageName, PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures)514 private static boolean matchSignaturesCompat(String packageName, 515 PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) { 516 ArraySet<Signature> existingSet = new ArraySet<Signature>(); 517 for (Signature sig : packageSignatures.mSigningDetails.signatures) { 518 existingSet.add(sig); 519 } 520 ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>(); 521 for (Signature sig : parsedSignatures.signatures) { 522 try { 523 Signature[] chainSignatures = sig.getChainSignatures(); 524 for (Signature chainSig : chainSignatures) { 525 scannedCompatSet.add(chainSig); 526 } 527 } catch (CertificateEncodingException e) { 528 scannedCompatSet.add(sig); 529 } 530 } 531 // make sure the expanded scanned set contains all signatures in the existing one 532 if (scannedCompatSet.equals(existingSet)) { 533 // migrate the old signatures to the new scheme 534 packageSignatures.mSigningDetails = parsedSignatures; 535 return true; 536 } else if (parsedSignatures.hasPastSigningCertificates()) { 537 538 // well this sucks: the parsed package has probably rotated signing certificates, but 539 // we don't have enough information to determine if the new signing certificate was 540 // blessed by the old one 541 logCriticalInfo(Log.INFO, "Existing package " + packageName + " has flattened signing " 542 + "certificate chain. Unable to install newer version with rotated signing " 543 + "certificate."); 544 } 545 return false; 546 } 547 matchSignaturesRecover( String packageName, PackageParser.SigningDetails existingSignatures, PackageParser.SigningDetails parsedSignatures, @PackageParser.SigningDetails.CertCapabilities int flags)548 private static boolean matchSignaturesRecover( 549 String packageName, 550 PackageParser.SigningDetails existingSignatures, 551 PackageParser.SigningDetails parsedSignatures, 552 @PackageParser.SigningDetails.CertCapabilities int flags) { 553 String msg = null; 554 try { 555 if (parsedSignatures.checkCapabilityRecover(existingSignatures, flags)) { 556 logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for " 557 + packageName); 558 return true; 559 } 560 } catch (CertificateException e) { 561 msg = e.getMessage(); 562 } 563 logCriticalInfo(Log.INFO, 564 "Failed to recover certificates for " + packageName + ": " + msg); 565 return false; 566 } 567 568 /** 569 * Make sure the updated priv app is signed with the same key as the original APK file on the 570 * /system partition. 571 * 572 * <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data 573 * and is not tamperproof. 574 */ matchSignatureInSystem(PackageSetting pkgSetting, PackageSetting disabledPkgSetting)575 private static boolean matchSignatureInSystem(PackageSetting pkgSetting, 576 PackageSetting disabledPkgSetting) { 577 if (pkgSetting.signatures.mSigningDetails.checkCapability( 578 disabledPkgSetting.signatures.mSigningDetails, 579 PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA) 580 || disabledPkgSetting.signatures.mSigningDetails.checkCapability( 581 pkgSetting.signatures.mSigningDetails, 582 PackageParser.SigningDetails.CertCapabilities.ROLLBACK)) { 583 return true; 584 } else { 585 logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " + 586 pkgSetting.name); 587 return false; 588 } 589 } 590 591 /** Default is to not use fs-verity since it depends on kernel support. */ 592 private static final int FSVERITY_DISABLED = 0; 593 594 /** 595 * Experimental implementation targeting priv apps, with Android specific kernel patches to 596 * extend fs-verity. 597 */ 598 private static final int FSVERITY_LEGACY = 1; 599 600 /** Standard fs-verity. */ 601 private static final int FSVERITY_ENABLED = 2; 602 603 /** Returns true if standard APK Verity is enabled. */ isApkVerityEnabled()604 static boolean isApkVerityEnabled() { 605 return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R 606 || SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) 607 == FSVERITY_ENABLED; 608 } 609 isLegacyApkVerityEnabled()610 static boolean isLegacyApkVerityEnabled() { 611 return SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_LEGACY; 612 } 613 614 /** Returns true to force apk verification if the package is considered privileged. */ isApkVerificationForced(@ullable PackageSetting ps)615 static boolean isApkVerificationForced(@Nullable PackageSetting ps) { 616 // TODO(b/154310064): re-enable. 617 return false; 618 } 619 620 /** 621 * Verifies that signatures match. 622 * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}. 623 * @throws PackageManagerException if the signatures did not match. 624 */ verifySignatures(PackageSetting pkgSetting, PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures, boolean compareCompat, boolean compareRecover, boolean isRollback)625 public static boolean verifySignatures(PackageSetting pkgSetting, 626 PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures, 627 boolean compareCompat, boolean compareRecover, boolean isRollback) 628 throws PackageManagerException { 629 final String packageName = pkgSetting.name; 630 boolean compatMatch = false; 631 if (pkgSetting.signatures.mSigningDetails.signatures != null) { 632 // Already existing package. Make sure signatures match 633 boolean match = parsedSignatures.checkCapability( 634 pkgSetting.signatures.mSigningDetails, 635 PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA) 636 || pkgSetting.signatures.mSigningDetails.checkCapability( 637 parsedSignatures, 638 PackageParser.SigningDetails.CertCapabilities.ROLLBACK); 639 if (!match && compareCompat) { 640 match = matchSignaturesCompat(packageName, pkgSetting.signatures, 641 parsedSignatures); 642 compatMatch = match; 643 } 644 if (!match && compareRecover) { 645 match = matchSignaturesRecover( 646 packageName, 647 pkgSetting.signatures.mSigningDetails, 648 parsedSignatures, 649 PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA) 650 || matchSignaturesRecover( 651 packageName, 652 parsedSignatures, 653 pkgSetting.signatures.mSigningDetails, 654 PackageParser.SigningDetails.CertCapabilities.ROLLBACK); 655 } 656 657 if (!match && isApkVerificationForced(disabledPkgSetting)) { 658 match = matchSignatureInSystem(pkgSetting, disabledPkgSetting); 659 } 660 661 if (!match && isRollback) { 662 // Since a rollback can only be initiated for an APK previously installed on the 663 // device allow rolling back to a previous signing key even if the rollback 664 // capability has not been granted. 665 match = pkgSetting.signatures.mSigningDetails.hasAncestorOrSelf(parsedSignatures); 666 } 667 668 if (!match) { 669 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, 670 "Package " + packageName + 671 " signatures do not match previously installed version; ignoring!"); 672 } 673 } 674 // Check for shared user signatures 675 if (pkgSetting.getSharedUser() != null 676 && pkgSetting.getSharedUser().signatures.mSigningDetails 677 != PackageParser.SigningDetails.UNKNOWN) { 678 679 // Already existing package. Make sure signatures match. In case of signing certificate 680 // rotation, the packages with newer certs need to be ok with being sharedUserId with 681 // the older ones. We check to see if either the new package is signed by an older cert 682 // with which the current sharedUser is ok, or if it is signed by a newer one, and is ok 683 // with being sharedUser with the existing signing cert. 684 boolean match = 685 parsedSignatures.checkCapability( 686 pkgSetting.getSharedUser().signatures.mSigningDetails, 687 PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID) 688 || pkgSetting.getSharedUser().signatures.mSigningDetails.checkCapability( 689 parsedSignatures, 690 PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID); 691 // Special case: if the sharedUserId capability check failed it could be due to this 692 // being the only package in the sharedUserId so far and the lineage being updated to 693 // deny the sharedUserId capability of the previous key in the lineage. 694 if (!match && pkgSetting.getSharedUser().packages.size() == 1 695 && pkgSetting.getSharedUser().packages.valueAt(0).name.equals(packageName)) { 696 match = true; 697 } 698 if (!match && compareCompat) { 699 match = matchSignaturesCompat( 700 packageName, pkgSetting.getSharedUser().signatures, parsedSignatures); 701 } 702 if (!match && compareRecover) { 703 match = 704 matchSignaturesRecover(packageName, 705 pkgSetting.getSharedUser().signatures.mSigningDetails, 706 parsedSignatures, 707 PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID) 708 || matchSignaturesRecover(packageName, 709 parsedSignatures, 710 pkgSetting.getSharedUser().signatures.mSigningDetails, 711 PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID); 712 compatMatch |= match; 713 } 714 if (!match) { 715 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, 716 "Package " + packageName 717 + " has no signatures that match those in shared user " 718 + pkgSetting.getSharedUser().name + "; ignoring!"); 719 } 720 // It is possible that this package contains a lineage that blocks sharedUserId access 721 // to an already installed package in the sharedUserId signed with a previous key. 722 // Iterate over all of the packages in the sharedUserId and ensure any that are signed 723 // with a key in this package's lineage have the SHARED_USER_ID capability granted. 724 if (parsedSignatures.hasPastSigningCertificates()) { 725 for (PackageSetting shUidPkgSetting : pkgSetting.getSharedUser().packages) { 726 // if the current package in the sharedUserId is the package being updated then 727 // skip this check as the update may revoke the sharedUserId capability from 728 // the key with which this app was previously signed. 729 if (packageName.equals(shUidPkgSetting.name)) { 730 continue; 731 } 732 PackageParser.SigningDetails shUidSigningDetails = 733 shUidPkgSetting.getSigningDetails(); 734 // The capability check only needs to be performed against the package if it is 735 // signed with a key that is in the lineage of the package being installed. 736 if (parsedSignatures.hasAncestor(shUidSigningDetails)) { 737 if (!parsedSignatures.checkCapability(shUidSigningDetails, 738 PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)) { 739 throw new PackageManagerException( 740 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, 741 "Package " + packageName 742 + " revoked the sharedUserId capability from the " 743 + "signing key used to sign " + shUidPkgSetting.name); 744 } 745 } 746 } 747 } 748 // If the lineage of this package diverges from the lineage of the sharedUserId then 749 // do not allow the installation to proceed. 750 if (!parsedSignatures.hasCommonAncestor( 751 pkgSetting.getSharedUser().signatures.mSigningDetails)) { 752 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, 753 "Package " + packageName + " has a signing lineage " 754 + "that diverges from the lineage of the sharedUserId"); 755 } 756 } 757 return compatMatch; 758 } 759 decompressFile(File srcFile, File dstFile)760 public static int decompressFile(File srcFile, File dstFile) throws ErrnoException { 761 if (DEBUG_COMPRESSION) { 762 Slog.i(TAG, "Decompress file" 763 + "; src: " + srcFile.getAbsolutePath() 764 + ", dst: " + dstFile.getAbsolutePath()); 765 } 766 try ( 767 InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile)); 768 OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/); 769 ) { 770 FileUtils.copy(fileIn, fileOut); 771 Os.chmod(dstFile.getAbsolutePath(), 0644); 772 return PackageManager.INSTALL_SUCCEEDED; 773 } catch (IOException e) { 774 logCriticalInfo(Log.ERROR, "Failed to decompress file" 775 + "; src: " + srcFile.getAbsolutePath() 776 + ", dst: " + dstFile.getAbsolutePath()); 777 } 778 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 779 } 780 getCompressedFiles(String codePath)781 public static File[] getCompressedFiles(String codePath) { 782 final File stubCodePath = new File(codePath); 783 final String stubName = stubCodePath.getName(); 784 785 // The layout of a compressed package on a given partition is as follows : 786 // 787 // Compressed artifacts: 788 // 789 // /partition/ModuleName/foo.gz 790 // /partation/ModuleName/bar.gz 791 // 792 // Stub artifact: 793 // 794 // /partition/ModuleName-Stub/ModuleName-Stub.apk 795 // 796 // In other words, stub is on the same partition as the compressed artifacts 797 // and in a directory that's suffixed with "-Stub". 798 int idx = stubName.lastIndexOf(STUB_SUFFIX); 799 if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) { 800 return null; 801 } 802 803 final File stubParentDir = stubCodePath.getParentFile(); 804 if (stubParentDir == null) { 805 Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath); 806 return null; 807 } 808 809 final File compressedPath = new File(stubParentDir, stubName.substring(0, idx)); 810 final File[] files = compressedPath.listFiles(new FilenameFilter() { 811 @Override 812 public boolean accept(File dir, String name) { 813 return name.toLowerCase().endsWith(COMPRESSED_EXTENSION); 814 } 815 }); 816 817 if (DEBUG_COMPRESSION && files != null && files.length > 0) { 818 Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files)); 819 } 820 821 return files; 822 } 823 compressedFileExists(String codePath)824 public static boolean compressedFileExists(String codePath) { 825 final File[] compressedFiles = getCompressedFiles(codePath); 826 return compressedFiles != null && compressedFiles.length > 0; 827 } 828 829 /** 830 * Parse given package and return minimal details. 831 */ getMinimalPackageInfo(Context context, PackageLite pkg, String packagePath, int flags, String abiOverride)832 public static PackageInfoLite getMinimalPackageInfo(Context context, PackageLite pkg, 833 String packagePath, int flags, String abiOverride) { 834 final PackageInfoLite ret = new PackageInfoLite(); 835 if (packagePath == null || pkg == null) { 836 Slog.i(TAG, "Invalid package file " + packagePath); 837 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 838 return ret; 839 } 840 841 final File packageFile = new File(packagePath); 842 final long sizeBytes; 843 try { 844 sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride); 845 } catch (IOException e) { 846 if (!packageFile.exists()) { 847 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; 848 } else { 849 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 850 } 851 852 return ret; 853 } 854 855 final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, 856 pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags); 857 858 ret.packageName = pkg.getPackageName(); 859 ret.splitNames = pkg.getSplitNames(); 860 ret.versionCode = pkg.getVersionCode(); 861 ret.versionCodeMajor = pkg.getVersionCodeMajor(); 862 ret.baseRevisionCode = pkg.getBaseRevisionCode(); 863 ret.splitRevisionCodes = pkg.getSplitRevisionCodes(); 864 ret.installLocation = pkg.getInstallLocation(); 865 ret.verifiers = pkg.getVerifiers(); 866 ret.recommendedInstallLocation = recommendedInstallLocation; 867 ret.multiArch = pkg.isMultiArch(); 868 ret.debuggable = pkg.isDebuggable(); 869 870 return ret; 871 } 872 873 /** 874 * Calculate estimated footprint of given package post-installation. 875 * 876 * @return -1 if there's some error calculating the size, otherwise installed size of the 877 * package. 878 */ calculateInstalledSize(String packagePath, String abiOverride)879 public static long calculateInstalledSize(String packagePath, String abiOverride) { 880 final File packageFile = new File(packagePath); 881 try { 882 final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); 883 final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( 884 input.reset(), packageFile, /* flags */ 0); 885 if (result.isError()) { 886 throw new PackageManagerException(result.getErrorCode(), 887 result.getErrorMessage(), result.getException()); 888 } 889 return PackageHelper.calculateInstalledSize(result.getResult(), abiOverride); 890 } catch (PackageManagerException | IOException e) { 891 Slog.w(TAG, "Failed to calculate installed size: " + e); 892 return -1; 893 } 894 } 895 896 /** 897 * Checks whenever downgrade of an app is permitted. 898 * 899 * @param installFlags flags of the current install. 900 * @param isAppDebuggable if the currently installed version of the app is debuggable. 901 * @return {@code true} if downgrade is permitted according to the {@code installFlags} and 902 * {@code applicationFlags}. 903 */ isDowngradePermitted(int installFlags, boolean isAppDebuggable)904 public static boolean isDowngradePermitted(int installFlags, boolean isAppDebuggable) { 905 // If installed, the package will get access to data left on the device by its 906 // predecessor. As a security measure, this is permitted only if this is not a 907 // version downgrade or if the predecessor package is marked as debuggable and 908 // a downgrade is explicitly requested. 909 // 910 // On debuggable platform builds, downgrades are permitted even for 911 // non-debuggable packages to make testing easier. Debuggable platform builds do 912 // not offer security guarantees and thus it's OK to disable some security 913 // mechanisms to make debugging/testing easier on those builds. However, even on 914 // debuggable builds downgrades of packages are permitted only if requested via 915 // installFlags. This is because we aim to keep the behavior of debuggable 916 // platform builds as close as possible to the behavior of non-debuggable 917 // platform builds. 918 // 919 // In case of user builds, downgrade is permitted only for the system server initiated 920 // sessions. This is enforced by INSTALL_ALLOW_DOWNGRADE flag parameter. 921 final boolean downgradeRequested = 922 (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0; 923 if (!downgradeRequested) { 924 return false; 925 } 926 final boolean isDebuggable = Build.IS_DEBUGGABLE || isAppDebuggable; 927 if (isDebuggable) { 928 return true; 929 } 930 return (installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) != 0; 931 } 932 933 /** 934 * Copy package to the target location. 935 * 936 * @param packagePath absolute path to the package to be copied. Can be 937 * a single monolithic APK file or a cluster directory 938 * containing one or more APKs. 939 * @return returns status code according to those in 940 * {@link PackageManager} 941 */ copyPackage(String packagePath, File targetDir)942 public static int copyPackage(String packagePath, File targetDir) { 943 if (packagePath == null) { 944 return PackageManager.INSTALL_FAILED_INVALID_URI; 945 } 946 947 try { 948 final File packageFile = new File(packagePath); 949 final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); 950 final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( 951 input.reset(), packageFile, /* flags */ 0); 952 if (result.isError()) { 953 Slog.w(TAG, "Failed to parse package at " + packagePath); 954 return result.getErrorCode(); 955 } 956 final PackageLite pkg = result.getResult(); 957 copyFile(pkg.getBaseApkPath(), targetDir, "base.apk"); 958 if (!ArrayUtils.isEmpty(pkg.getSplitNames())) { 959 for (int i = 0; i < pkg.getSplitNames().length; i++) { 960 copyFile(pkg.getSplitApkPaths()[i], targetDir, 961 "split_" + pkg.getSplitNames()[i] + ".apk"); 962 } 963 } 964 return PackageManager.INSTALL_SUCCEEDED; 965 } catch (IOException | ErrnoException e) { 966 Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e); 967 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; 968 } 969 } 970 copyFile(String sourcePath, File targetDir, String targetName)971 private static void copyFile(String sourcePath, File targetDir, String targetName) 972 throws ErrnoException, IOException { 973 if (!FileUtils.isValidExtFilename(targetName)) { 974 throw new IllegalArgumentException("Invalid filename: " + targetName); 975 } 976 Slog.d(TAG, "Copying " + sourcePath + " to " + targetName); 977 978 final File targetFile = new File(targetDir, targetName); 979 final FileDescriptor targetFd = Os.open(targetFile.getAbsolutePath(), 980 O_RDWR | O_CREAT, 0644); 981 Os.chmod(targetFile.getAbsolutePath(), 0644); 982 FileInputStream source = null; 983 try { 984 source = new FileInputStream(sourcePath); 985 FileUtils.copy(source.getFD(), targetFd); 986 } finally { 987 IoUtils.closeQuietly(source); 988 } 989 } 990 991 /** 992 * Recursively create target directory 993 */ makeDirRecursive(File targetDir, int mode)994 public static void makeDirRecursive(File targetDir, int mode) throws ErrnoException { 995 final Path targetDirPath = targetDir.toPath(); 996 final int directoriesCount = targetDirPath.getNameCount(); 997 File currentDir; 998 for (int i = 1; i <= directoriesCount; i++) { 999 currentDir = targetDirPath.subpath(0, i).toFile(); 1000 if (currentDir.exists()) { 1001 continue; 1002 } 1003 Os.mkdir(currentDir.getAbsolutePath(), mode); 1004 Os.chmod(currentDir.getAbsolutePath(), mode); 1005 } 1006 } 1007 1008 /** 1009 * Returns a string that's compatible with the verification root hash extra. 1010 * @see PackageManager#EXTRA_VERIFICATION_ROOT_HASH 1011 */ 1012 @NonNull buildVerificationRootHashString(@onNull String baseFilename, @Nullable String[] splitFilenameArray)1013 public static String buildVerificationRootHashString(@NonNull String baseFilename, 1014 @Nullable String[] splitFilenameArray) { 1015 final StringBuilder sb = new StringBuilder(); 1016 final String baseFilePath = 1017 baseFilename.substring(baseFilename.lastIndexOf(File.separator) + 1); 1018 sb.append(baseFilePath).append(":"); 1019 final byte[] baseRootHash = getRootHash(baseFilename); 1020 if (baseRootHash == null) { 1021 sb.append("0"); 1022 } else { 1023 sb.append(HexDump.toHexString(baseRootHash)); 1024 } 1025 if (splitFilenameArray == null || splitFilenameArray.length == 0) { 1026 return sb.toString(); 1027 } 1028 1029 for (int i = splitFilenameArray.length - 1; i >= 0; i--) { 1030 final String splitFilename = splitFilenameArray[i]; 1031 final String splitFilePath = 1032 splitFilename.substring(splitFilename.lastIndexOf(File.separator) + 1); 1033 final byte[] splitRootHash = getRootHash(splitFilename); 1034 sb.append(";").append(splitFilePath).append(":"); 1035 if (splitRootHash == null) { 1036 sb.append("0"); 1037 } else { 1038 sb.append(HexDump.toHexString(splitRootHash)); 1039 } 1040 } 1041 return sb.toString(); 1042 } 1043 1044 /** 1045 * Returns the root has for the given file. 1046 * <p>Otherwise, returns {@code null} if the root hash could not be found or calculated. 1047 * <p>NOTE: This currently only works on files stored on the incremental file system. The 1048 * eventual goal is that this hash [among others] can be retrieved for any file. 1049 */ 1050 @Nullable getRootHash(String filename)1051 private static byte[] getRootHash(String filename) { 1052 try { 1053 final byte[] baseFileSignature = 1054 IncrementalManager.unsafeGetFileSignature(filename); 1055 if (baseFileSignature == null) { 1056 throw new IOException("File signature not present"); 1057 } 1058 final V4Signature signature = 1059 V4Signature.readFrom(baseFileSignature); 1060 if (signature.hashingInfo == null) { 1061 throw new IOException("Hashing info not present"); 1062 } 1063 final HashingInfo hashInfo = 1064 HashingInfo.fromByteArray(signature.hashingInfo); 1065 if (ArrayUtils.isEmpty(hashInfo.rawRootHash)) { 1066 throw new IOException("Root has not present"); 1067 } 1068 return hashInfo.rawRootHash; 1069 } catch (IOException ignore) { 1070 Slog.e(TAG, "ERROR: could not load root hash from incremental install"); 1071 } 1072 return null; 1073 } 1074 } 1075