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