1 /* 2 * Copyright (C) 2010 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 android.os; 18 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import android.annotation.RequiresPermission; 22 import android.annotation.SuppressLint; 23 import android.annotation.SystemApi; 24 import android.annotation.SystemService; 25 import android.app.PendingIntent; 26 import android.content.BroadcastReceiver; 27 import android.content.ContentResolver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageManager; 32 import android.provider.Settings; 33 import android.telephony.euicc.EuiccManager; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.view.Display; 37 import android.view.WindowManager; 38 39 import com.android.internal.logging.MetricsLogger; 40 41 import libcore.io.Streams; 42 43 import java.io.BufferedReader; 44 import java.io.ByteArrayInputStream; 45 import java.io.File; 46 import java.io.FileInputStream; 47 import java.io.FileNotFoundException; 48 import java.io.FileReader; 49 import java.io.FileWriter; 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.io.RandomAccessFile; 53 import java.security.GeneralSecurityException; 54 import java.security.PublicKey; 55 import java.security.SignatureException; 56 import java.security.cert.CertificateFactory; 57 import java.security.cert.X509Certificate; 58 import java.util.ArrayList; 59 import java.util.Enumeration; 60 import java.util.HashSet; 61 import java.util.Locale; 62 import java.util.concurrent.CountDownLatch; 63 import java.util.concurrent.TimeUnit; 64 import java.util.concurrent.atomic.AtomicBoolean; 65 import java.util.zip.ZipEntry; 66 import java.util.zip.ZipFile; 67 import java.util.zip.ZipInputStream; 68 69 import sun.security.pkcs.PKCS7; 70 import sun.security.pkcs.SignerInfo; 71 72 /** 73 * RecoverySystem contains methods for interacting with the Android 74 * recovery system (the separate partition that can be used to install 75 * system updates, wipe user data, etc.) 76 */ 77 @SystemService(Context.RECOVERY_SERVICE) 78 public class RecoverySystem { 79 private static final String TAG = "RecoverySystem"; 80 81 /** 82 * Default location of zip file containing public keys (X509 83 * certs) authorized to sign OTA updates. 84 */ 85 private static final File DEFAULT_KEYSTORE = 86 new File("/system/etc/security/otacerts.zip"); 87 88 /** Send progress to listeners no more often than this (in ms). */ 89 private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500; 90 91 private static final long DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 30000L; // 30 s 92 93 private static final long MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 5000L; // 5 s 94 95 private static final long MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 60000L; // 60 s 96 97 /** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */ 98 private static final File RECOVERY_DIR = new File("/cache/recovery"); 99 private static final File LOG_FILE = new File(RECOVERY_DIR, "log"); 100 private static final File LAST_INSTALL_FILE = new File(RECOVERY_DIR, "last_install"); 101 private static final String LAST_PREFIX = "last_"; 102 private static final String ACTION_EUICC_FACTORY_RESET = 103 "com.android.internal.action.EUICC_FACTORY_RESET"; 104 105 /** used in {@link #wipeEuiccData} as package name of callback intent */ 106 private static final String PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK = "android"; 107 108 /** 109 * The recovery image uses this file to identify the location (i.e. blocks) 110 * of an OTA package on the /data partition. The block map file is 111 * generated by uncrypt. 112 * 113 * @hide 114 */ 115 public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map"); 116 117 /** 118 * UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be 119 * read by uncrypt. 120 * 121 * @hide 122 */ 123 public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file"); 124 125 /** 126 * UNCRYPT_STATUS_FILE stores the time cost (and error code in the case of a failure) 127 * of uncrypt. 128 * 129 * @hide 130 */ 131 public static final File UNCRYPT_STATUS_FILE = new File(RECOVERY_DIR, "uncrypt_status"); 132 133 // Length limits for reading files. 134 private static final int LOG_FILE_MAX_LENGTH = 64 * 1024; 135 136 // Prevent concurrent execution of requests. 137 private static final Object sRequestLock = new Object(); 138 139 private final IRecoverySystem mService; 140 141 /** 142 * Interface definition for a callback to be invoked regularly as 143 * verification proceeds. 144 */ 145 public interface ProgressListener { 146 /** 147 * Called periodically as the verification progresses. 148 * 149 * @param progress the approximate percentage of the 150 * verification that has been completed, ranging from 0 151 * to 100 (inclusive). 152 */ onProgress(int progress)153 public void onProgress(int progress); 154 } 155 156 /** @return the set of certs that can be used to sign an OTA package. */ getTrustedCerts(File keystore)157 private static HashSet<X509Certificate> getTrustedCerts(File keystore) 158 throws IOException, GeneralSecurityException { 159 HashSet<X509Certificate> trusted = new HashSet<X509Certificate>(); 160 if (keystore == null) { 161 keystore = DEFAULT_KEYSTORE; 162 } 163 ZipFile zip = new ZipFile(keystore); 164 try { 165 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 166 Enumeration<? extends ZipEntry> entries = zip.entries(); 167 while (entries.hasMoreElements()) { 168 ZipEntry entry = entries.nextElement(); 169 InputStream is = zip.getInputStream(entry); 170 try { 171 trusted.add((X509Certificate) cf.generateCertificate(is)); 172 } finally { 173 is.close(); 174 } 175 } 176 } finally { 177 zip.close(); 178 } 179 return trusted; 180 } 181 182 /** 183 * Verify the cryptographic signature of a system update package 184 * before installing it. Note that the package is also verified 185 * separately by the installer once the device is rebooted into 186 * the recovery system. This function will return only if the 187 * package was successfully verified; otherwise it will throw an 188 * exception. 189 * 190 * Verification of a package can take significant time, so this 191 * function should not be called from a UI thread. Interrupting 192 * the thread while this function is in progress will result in a 193 * SecurityException being thrown (and the thread's interrupt flag 194 * will be cleared). 195 * 196 * @param packageFile the package to be verified 197 * @param listener an object to receive periodic progress 198 * updates as verification proceeds. May be null. 199 * @param deviceCertsZipFile the zip file of certificates whose 200 * public keys we will accept. Verification succeeds if the 201 * package is signed by the private key corresponding to any 202 * public key in this file. May be null to use the system default 203 * file (currently "/system/etc/security/otacerts.zip"). 204 * 205 * @throws IOException if there were any errors reading the 206 * package or certs files. 207 * @throws GeneralSecurityException if verification failed 208 */ verifyPackage(File packageFile, ProgressListener listener, File deviceCertsZipFile)209 public static void verifyPackage(File packageFile, 210 ProgressListener listener, 211 File deviceCertsZipFile) 212 throws IOException, GeneralSecurityException { 213 final long fileLen = packageFile.length(); 214 215 final RandomAccessFile raf = new RandomAccessFile(packageFile, "r"); 216 try { 217 final long startTimeMillis = System.currentTimeMillis(); 218 if (listener != null) { 219 listener.onProgress(0); 220 } 221 222 raf.seek(fileLen - 6); 223 byte[] footer = new byte[6]; 224 raf.readFully(footer); 225 226 if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) { 227 throw new SignatureException("no signature in file (no footer)"); 228 } 229 230 final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8); 231 final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8); 232 233 byte[] eocd = new byte[commentSize + 22]; 234 raf.seek(fileLen - (commentSize + 22)); 235 raf.readFully(eocd); 236 237 // Check that we have found the start of the 238 // end-of-central-directory record. 239 if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b || 240 eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) { 241 throw new SignatureException("no signature in file (bad footer)"); 242 } 243 244 for (int i = 4; i < eocd.length-3; ++i) { 245 if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b && 246 eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) { 247 throw new SignatureException("EOCD marker found after start of EOCD"); 248 } 249 } 250 251 // Parse the signature 252 PKCS7 block = 253 new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart)); 254 255 // Take the first certificate from the signature (packages 256 // should contain only one). 257 X509Certificate[] certificates = block.getCertificates(); 258 if (certificates == null || certificates.length == 0) { 259 throw new SignatureException("signature contains no certificates"); 260 } 261 X509Certificate cert = certificates[0]; 262 PublicKey signatureKey = cert.getPublicKey(); 263 264 SignerInfo[] signerInfos = block.getSignerInfos(); 265 if (signerInfos == null || signerInfos.length == 0) { 266 throw new SignatureException("signature contains no signedData"); 267 } 268 SignerInfo signerInfo = signerInfos[0]; 269 270 // Check that the public key of the certificate contained 271 // in the package equals one of our trusted public keys. 272 boolean verified = false; 273 HashSet<X509Certificate> trusted = getTrustedCerts( 274 deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile); 275 for (X509Certificate c : trusted) { 276 if (c.getPublicKey().equals(signatureKey)) { 277 verified = true; 278 break; 279 } 280 } 281 if (!verified) { 282 throw new SignatureException("signature doesn't match any trusted key"); 283 } 284 285 // The signature cert matches a trusted key. Now verify that 286 // the digest in the cert matches the actual file data. 287 raf.seek(0); 288 final ProgressListener listenerForInner = listener; 289 SignerInfo verifyResult = block.verify(signerInfo, new InputStream() { 290 // The signature covers all of the OTA package except the 291 // archive comment and its 2-byte length. 292 long toRead = fileLen - commentSize - 2; 293 long soFar = 0; 294 295 int lastPercent = 0; 296 long lastPublishTime = startTimeMillis; 297 298 @Override 299 public int read() throws IOException { 300 throw new UnsupportedOperationException(); 301 } 302 303 @Override 304 public int read(byte[] b, int off, int len) throws IOException { 305 if (soFar >= toRead) { 306 return -1; 307 } 308 if (Thread.currentThread().isInterrupted()) { 309 return -1; 310 } 311 312 int size = len; 313 if (soFar + size > toRead) { 314 size = (int)(toRead - soFar); 315 } 316 int read = raf.read(b, off, size); 317 soFar += read; 318 319 if (listenerForInner != null) { 320 long now = System.currentTimeMillis(); 321 int p = (int)(soFar * 100 / toRead); 322 if (p > lastPercent && 323 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { 324 lastPercent = p; 325 lastPublishTime = now; 326 listenerForInner.onProgress(lastPercent); 327 } 328 } 329 330 return read; 331 } 332 }); 333 334 final boolean interrupted = Thread.interrupted(); 335 if (listener != null) { 336 listener.onProgress(100); 337 } 338 339 if (interrupted) { 340 throw new SignatureException("verification was interrupted"); 341 } 342 343 if (verifyResult == null) { 344 throw new SignatureException("signature digest verification failed"); 345 } 346 } finally { 347 raf.close(); 348 } 349 350 // Additionally verify the package compatibility. 351 if (!readAndVerifyPackageCompatibilityEntry(packageFile)) { 352 throw new SignatureException("package compatibility verification failed"); 353 } 354 } 355 356 /** 357 * Verifies the compatibility entry from an {@link InputStream}. 358 * 359 * @return the verification result. 360 */ verifyPackageCompatibility(InputStream inputStream)361 private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException { 362 ArrayList<String> list = new ArrayList<>(); 363 ZipInputStream zis = new ZipInputStream(inputStream); 364 ZipEntry entry; 365 while ((entry = zis.getNextEntry()) != null) { 366 long entrySize = entry.getSize(); 367 if (entrySize > Integer.MAX_VALUE || entrySize < 0) { 368 throw new IOException( 369 "invalid entry size (" + entrySize + ") in the compatibility file"); 370 } 371 byte[] bytes = new byte[(int) entrySize]; 372 Streams.readFully(zis, bytes); 373 list.add(new String(bytes, UTF_8)); 374 } 375 if (list.isEmpty()) { 376 throw new IOException("no entries found in the compatibility file"); 377 } 378 return (VintfObject.verify(list.toArray(new String[list.size()])) == 0); 379 } 380 381 /** 382 * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is 383 * a zip file (inside the OTA package zip). 384 * 385 * @return {@code true} if the entry doesn't exist or verification passes. 386 */ readAndVerifyPackageCompatibilityEntry(File packageFile)387 private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile) 388 throws IOException { 389 try (ZipFile zip = new ZipFile(packageFile)) { 390 ZipEntry entry = zip.getEntry("compatibility.zip"); 391 if (entry == null) { 392 return true; 393 } 394 InputStream inputStream = zip.getInputStream(entry); 395 return verifyPackageCompatibility(inputStream); 396 } 397 } 398 399 /** 400 * Verifies the package compatibility info against the current system. 401 * 402 * @param compatibilityFile the {@link File} that contains the package compatibility info. 403 * @throws IOException if there were any errors reading the compatibility file. 404 * @return the compatibility verification result. 405 * 406 * {@hide} 407 */ 408 @SystemApi 409 @SuppressLint("Doclava125") verifyPackageCompatibility(File compatibilityFile)410 public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException { 411 try (InputStream inputStream = new FileInputStream(compatibilityFile)) { 412 return verifyPackageCompatibility(inputStream); 413 } 414 } 415 416 /** 417 * Process a given package with uncrypt. No-op if the package is not on the 418 * /data partition. 419 * 420 * @param Context the Context to use 421 * @param packageFile the package to be processed 422 * @param listener an object to receive periodic progress updates as 423 * processing proceeds. May be null. 424 * @param handler the Handler upon which the callbacks will be 425 * executed. 426 * 427 * @throws IOException if there were any errors processing the package file. 428 * 429 * @hide 430 */ 431 @SystemApi 432 @RequiresPermission(android.Manifest.permission.RECOVERY) processPackage(Context context, File packageFile, final ProgressListener listener, final Handler handler)433 public static void processPackage(Context context, 434 File packageFile, 435 final ProgressListener listener, 436 final Handler handler) 437 throws IOException { 438 String filename = packageFile.getCanonicalPath(); 439 if (!filename.startsWith("/data/")) { 440 return; 441 } 442 443 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 444 IRecoverySystemProgressListener progressListener = null; 445 if (listener != null) { 446 final Handler progressHandler; 447 if (handler != null) { 448 progressHandler = handler; 449 } else { 450 progressHandler = new Handler(context.getMainLooper()); 451 } 452 progressListener = new IRecoverySystemProgressListener.Stub() { 453 int lastProgress = 0; 454 long lastPublishTime = System.currentTimeMillis(); 455 456 @Override 457 public void onProgress(final int progress) { 458 final long now = System.currentTimeMillis(); 459 progressHandler.post(new Runnable() { 460 @Override 461 public void run() { 462 if (progress > lastProgress && 463 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) { 464 lastProgress = progress; 465 lastPublishTime = now; 466 listener.onProgress(progress); 467 } 468 } 469 }); 470 } 471 }; 472 } 473 474 if (!rs.uncrypt(filename, progressListener)) { 475 throw new IOException("process package failed"); 476 } 477 } 478 479 /** 480 * Process a given package with uncrypt. No-op if the package is not on the 481 * /data partition. 482 * 483 * @param Context the Context to use 484 * @param packageFile the package to be processed 485 * @param listener an object to receive periodic progress updates as 486 * processing proceeds. May be null. 487 * 488 * @throws IOException if there were any errors processing the package file. 489 * 490 * @hide 491 */ 492 @SystemApi 493 @RequiresPermission(android.Manifest.permission.RECOVERY) processPackage(Context context, File packageFile, final ProgressListener listener)494 public static void processPackage(Context context, 495 File packageFile, 496 final ProgressListener listener) 497 throws IOException { 498 processPackage(context, packageFile, listener, null); 499 } 500 501 /** 502 * Reboots the device in order to install the given update 503 * package. 504 * Requires the {@link android.Manifest.permission#REBOOT} permission. 505 * 506 * @param context the Context to use 507 * @param packageFile the update package to install. Must be on 508 * a partition mountable by recovery. (The set of partitions 509 * known to recovery may vary from device to device. Generally, 510 * /cache and /data are safe.) 511 * 512 * @throws IOException if writing the recovery command file 513 * fails, or if the reboot itself fails. 514 */ 515 @RequiresPermission(android.Manifest.permission.RECOVERY) installPackage(Context context, File packageFile)516 public static void installPackage(Context context, File packageFile) 517 throws IOException { 518 installPackage(context, packageFile, false); 519 } 520 521 /** 522 * If the package hasn't been processed (i.e. uncrypt'd), set up 523 * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the 524 * reboot. 525 * 526 * @param context the Context to use 527 * @param packageFile the update package to install. Must be on a 528 * partition mountable by recovery. 529 * @param processed if the package has been processed (uncrypt'd). 530 * 531 * @throws IOException if writing the recovery command file fails, or if 532 * the reboot itself fails. 533 * 534 * @hide 535 */ 536 @SystemApi 537 @RequiresPermission(android.Manifest.permission.RECOVERY) installPackage(Context context, File packageFile, boolean processed)538 public static void installPackage(Context context, File packageFile, boolean processed) 539 throws IOException { 540 synchronized (sRequestLock) { 541 LOG_FILE.delete(); 542 // Must delete the file in case it was created by system server. 543 UNCRYPT_PACKAGE_FILE.delete(); 544 545 String filename = packageFile.getCanonicalPath(); 546 Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!"); 547 548 // If the package name ends with "_s.zip", it's a security update. 549 boolean securityUpdate = filename.endsWith("_s.zip"); 550 551 // If the package is on the /data partition, the package needs to 552 // be processed (i.e. uncrypt'd). The caller specifies if that has 553 // been done in 'processed' parameter. 554 if (filename.startsWith("/data/")) { 555 if (processed) { 556 if (!BLOCK_MAP_FILE.exists()) { 557 Log.e(TAG, "Package claimed to have been processed but failed to find " 558 + "the block map file."); 559 throw new IOException("Failed to find block map file"); 560 } 561 } else { 562 FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE); 563 try { 564 uncryptFile.write(filename + "\n"); 565 } finally { 566 uncryptFile.close(); 567 } 568 // UNCRYPT_PACKAGE_FILE needs to be readable and writable 569 // by system server. 570 if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false) 571 || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) { 572 Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE); 573 } 574 575 BLOCK_MAP_FILE.delete(); 576 } 577 578 // If the package is on the /data partition, use the block map 579 // file as the package name instead. 580 filename = "@/cache/recovery/block.map"; 581 } 582 583 final String filenameArg = "--update_package=" + filename + "\n"; 584 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n"; 585 final String securityArg = "--security\n"; 586 587 String command = filenameArg + localeArg; 588 if (securityUpdate) { 589 command += securityArg; 590 } 591 592 RecoverySystem rs = (RecoverySystem) context.getSystemService( 593 Context.RECOVERY_SERVICE); 594 if (!rs.setupBcb(command)) { 595 throw new IOException("Setup BCB failed"); 596 } 597 598 // Having set up the BCB (bootloader control block), go ahead and reboot 599 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 600 String reason = PowerManager.REBOOT_RECOVERY_UPDATE; 601 602 // On TV, reboot quiescently if the screen is off 603 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 604 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 605 if (wm.getDefaultDisplay().getState() != Display.STATE_ON) { 606 reason += ",quiescent"; 607 } 608 } 609 pm.reboot(reason); 610 611 throw new IOException("Reboot failed (no permissions?)"); 612 } 613 } 614 615 /** 616 * Schedule to install the given package on next boot. The caller needs to 617 * ensure that the package must have been processed (uncrypt'd) if needed. 618 * It sets up the command in BCB (bootloader control block), which will 619 * be read by the bootloader and the recovery image. 620 * 621 * @param Context the Context to use. 622 * @param packageFile the package to be installed. 623 * 624 * @throws IOException if there were any errors setting up the BCB. 625 * 626 * @hide 627 */ 628 @SystemApi 629 @RequiresPermission(android.Manifest.permission.RECOVERY) scheduleUpdateOnBoot(Context context, File packageFile)630 public static void scheduleUpdateOnBoot(Context context, File packageFile) 631 throws IOException { 632 String filename = packageFile.getCanonicalPath(); 633 boolean securityUpdate = filename.endsWith("_s.zip"); 634 635 // If the package is on the /data partition, use the block map file as 636 // the package name instead. 637 if (filename.startsWith("/data/")) { 638 filename = "@/cache/recovery/block.map"; 639 } 640 641 final String filenameArg = "--update_package=" + filename + "\n"; 642 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n"; 643 final String securityArg = "--security\n"; 644 645 String command = filenameArg + localeArg; 646 if (securityUpdate) { 647 command += securityArg; 648 } 649 650 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 651 if (!rs.setupBcb(command)) { 652 throw new IOException("schedule update on boot failed"); 653 } 654 } 655 656 /** 657 * Cancel any scheduled update by clearing up the BCB (bootloader control 658 * block). 659 * 660 * @param Context the Context to use. 661 * 662 * @throws IOException if there were any errors clearing up the BCB. 663 * 664 * @hide 665 */ 666 @SystemApi 667 @RequiresPermission(android.Manifest.permission.RECOVERY) cancelScheduledUpdate(Context context)668 public static void cancelScheduledUpdate(Context context) 669 throws IOException { 670 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 671 if (!rs.clearBcb()) { 672 throw new IOException("cancel scheduled update failed"); 673 } 674 } 675 676 /** 677 * Reboots the device and wipes the user data and cache 678 * partitions. This is sometimes called a "factory reset", which 679 * is something of a misnomer because the system partition is not 680 * restored to its factory state. Requires the 681 * {@link android.Manifest.permission#REBOOT} permission. 682 * 683 * @param context the Context to use 684 * 685 * @throws IOException if writing the recovery command file 686 * fails, or if the reboot itself fails. 687 * @throws SecurityException if the current user is not allowed to wipe data. 688 */ rebootWipeUserData(Context context)689 public static void rebootWipeUserData(Context context) throws IOException { 690 rebootWipeUserData(context, false /* shutdown */, context.getPackageName(), 691 false /* force */, false /* wipeEuicc */); 692 } 693 694 /** {@hide} */ rebootWipeUserData(Context context, String reason)695 public static void rebootWipeUserData(Context context, String reason) throws IOException { 696 rebootWipeUserData(context, false /* shutdown */, reason, false /* force */, 697 false /* wipeEuicc */); 698 } 699 700 /** {@hide} */ rebootWipeUserData(Context context, boolean shutdown)701 public static void rebootWipeUserData(Context context, boolean shutdown) 702 throws IOException { 703 rebootWipeUserData(context, shutdown, context.getPackageName(), false /* force */, 704 false /* wipeEuicc */); 705 } 706 707 /** {@hide} */ rebootWipeUserData(Context context, boolean shutdown, String reason, boolean force)708 public static void rebootWipeUserData(Context context, boolean shutdown, String reason, 709 boolean force) throws IOException { 710 rebootWipeUserData(context, shutdown, reason, force, false /* wipeEuicc */); 711 } 712 713 /** 714 * Reboots the device and wipes the user data and cache 715 * partitions. This is sometimes called a "factory reset", which 716 * is something of a misnomer because the system partition is not 717 * restored to its factory state. Requires the 718 * {@link android.Manifest.permission#REBOOT} permission. 719 * 720 * @param context the Context to use 721 * @param shutdown if true, the device will be powered down after 722 * the wipe completes, rather than being rebooted 723 * back to the regular system. 724 * @param reason the reason for the wipe that is visible in the logs 725 * @param force whether the {@link UserManager.DISALLOW_FACTORY_RESET} user restriction 726 * should be ignored 727 * @param wipeEuicc whether wipe the euicc data 728 * 729 * @throws IOException if writing the recovery command file 730 * fails, or if the reboot itself fails. 731 * @throws SecurityException if the current user is not allowed to wipe data. 732 * 733 * @hide 734 */ rebootWipeUserData(Context context, boolean shutdown, String reason, boolean force, boolean wipeEuicc)735 public static void rebootWipeUserData(Context context, boolean shutdown, String reason, 736 boolean force, boolean wipeEuicc) throws IOException { 737 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 738 if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) { 739 throw new SecurityException("Wiping data is not allowed for this user."); 740 } 741 final ConditionVariable condition = new ConditionVariable(); 742 743 Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION"); 744 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND 745 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 746 context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM, 747 android.Manifest.permission.MASTER_CLEAR, 748 new BroadcastReceiver() { 749 @Override 750 public void onReceive(Context context, Intent intent) { 751 condition.open(); 752 } 753 }, null, 0, null, null); 754 755 // Block until the ordered broadcast has completed. 756 condition.block(); 757 758 if (wipeEuicc) { 759 wipeEuiccData(context, PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK); 760 } 761 762 String shutdownArg = null; 763 if (shutdown) { 764 shutdownArg = "--shutdown_after"; 765 } 766 767 String reasonArg = null; 768 if (!TextUtils.isEmpty(reason)) { 769 reasonArg = "--reason=" + sanitizeArg(reason); 770 } 771 772 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ; 773 bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg); 774 } 775 776 /** 777 * Returns whether wipe Euicc data successfully or not. 778 * 779 * @param packageName the package name of the caller app. 780 * 781 * @hide 782 */ wipeEuiccData(Context context, final String packageName)783 public static boolean wipeEuiccData(Context context, final String packageName) { 784 ContentResolver cr = context.getContentResolver(); 785 if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) { 786 // If the eUICC isn't provisioned, there's no reason to either wipe or retain profiles, 787 // as there's nothing to wipe nor retain. 788 Log.d(TAG, "Skipping eUICC wipe/retain as it is not provisioned"); 789 return true; 790 } 791 792 EuiccManager euiccManager = (EuiccManager) context.getSystemService( 793 Context.EUICC_SERVICE); 794 if (euiccManager != null && euiccManager.isEnabled()) { 795 CountDownLatch euiccFactoryResetLatch = new CountDownLatch(1); 796 final AtomicBoolean wipingSucceeded = new AtomicBoolean(false); 797 798 BroadcastReceiver euiccWipeFinishReceiver = new BroadcastReceiver() { 799 @Override 800 public void onReceive(Context context, Intent intent) { 801 if (ACTION_EUICC_FACTORY_RESET.equals(intent.getAction())) { 802 if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { 803 int detailedCode = intent.getIntExtra( 804 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0); 805 Log.e(TAG, "Error wiping euicc data, Detailed code = " 806 + detailedCode); 807 } else { 808 Log.d(TAG, "Successfully wiped euicc data."); 809 wipingSucceeded.set(true /* newValue */); 810 } 811 euiccFactoryResetLatch.countDown(); 812 } 813 } 814 }; 815 816 Intent intent = new Intent(ACTION_EUICC_FACTORY_RESET); 817 intent.setPackage(packageName); 818 PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser( 819 context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM); 820 IntentFilter filterConsent = new IntentFilter(); 821 filterConsent.addAction(ACTION_EUICC_FACTORY_RESET); 822 HandlerThread euiccHandlerThread = new HandlerThread("euiccWipeFinishReceiverThread"); 823 euiccHandlerThread.start(); 824 Handler euiccHandler = new Handler(euiccHandlerThread.getLooper()); 825 context.getApplicationContext() 826 .registerReceiver(euiccWipeFinishReceiver, filterConsent, null, euiccHandler); 827 euiccManager.eraseSubscriptions(callbackIntent); 828 try { 829 long waitingTimeMillis = Settings.Global.getLong( 830 context.getContentResolver(), 831 Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS, 832 DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS); 833 if (waitingTimeMillis < MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) { 834 waitingTimeMillis = MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS; 835 } else if (waitingTimeMillis > MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) { 836 waitingTimeMillis = MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS; 837 } 838 if (!euiccFactoryResetLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) { 839 Log.e(TAG, "Timeout wiping eUICC data."); 840 return false; 841 } 842 } catch (InterruptedException e) { 843 Thread.currentThread().interrupt(); 844 Log.e(TAG, "Wiping eUICC data interrupted", e); 845 return false; 846 } finally { 847 context.getApplicationContext().unregisterReceiver(euiccWipeFinishReceiver); 848 } 849 return wipingSucceeded.get(); 850 } 851 return false; 852 } 853 854 /** {@hide} */ rebootPromptAndWipeUserData(Context context, String reason)855 public static void rebootPromptAndWipeUserData(Context context, String reason) 856 throws IOException { 857 String reasonArg = null; 858 if (!TextUtils.isEmpty(reason)) { 859 reasonArg = "--reason=" + sanitizeArg(reason); 860 } 861 862 final String localeArg = "--locale=" + Locale.getDefault().toString(); 863 bootCommand(context, null, "--prompt_and_wipe_data", reasonArg, localeArg); 864 } 865 866 /** 867 * Reboot into the recovery system to wipe the /cache partition. 868 * @throws IOException if something goes wrong. 869 */ rebootWipeCache(Context context)870 public static void rebootWipeCache(Context context) throws IOException { 871 rebootWipeCache(context, context.getPackageName()); 872 } 873 874 /** {@hide} */ rebootWipeCache(Context context, String reason)875 public static void rebootWipeCache(Context context, String reason) throws IOException { 876 String reasonArg = null; 877 if (!TextUtils.isEmpty(reason)) { 878 reasonArg = "--reason=" + sanitizeArg(reason); 879 } 880 881 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ; 882 bootCommand(context, "--wipe_cache", reasonArg, localeArg); 883 } 884 885 /** 886 * Reboot into recovery and wipe the A/B device. 887 * 888 * @param Context the Context to use. 889 * @param packageFile the wipe package to be applied. 890 * @param reason the reason to wipe. 891 * 892 * @throws IOException if something goes wrong. 893 * 894 * @hide 895 */ 896 @SystemApi 897 @RequiresPermission(allOf = { 898 android.Manifest.permission.RECOVERY, 899 android.Manifest.permission.REBOOT 900 }) rebootWipeAb(Context context, File packageFile, String reason)901 public static void rebootWipeAb(Context context, File packageFile, String reason) 902 throws IOException { 903 String reasonArg = null; 904 if (!TextUtils.isEmpty(reason)) { 905 reasonArg = "--reason=" + sanitizeArg(reason); 906 } 907 908 final String filename = packageFile.getCanonicalPath(); 909 final String filenameArg = "--wipe_package=" + filename; 910 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ; 911 bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg); 912 } 913 914 /** 915 * Reboot into the recovery system with the supplied argument. 916 * @param args to pass to the recovery utility. 917 * @throws IOException if something goes wrong. 918 */ bootCommand(Context context, String... args)919 private static void bootCommand(Context context, String... args) throws IOException { 920 LOG_FILE.delete(); 921 922 StringBuilder command = new StringBuilder(); 923 for (String arg : args) { 924 if (!TextUtils.isEmpty(arg)) { 925 command.append(arg); 926 command.append("\n"); 927 } 928 } 929 930 // Write the command into BCB (bootloader control block) and boot from 931 // there. Will not return unless failed. 932 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); 933 rs.rebootRecoveryWithCommand(command.toString()); 934 935 throw new IOException("Reboot failed (no permissions?)"); 936 } 937 938 // Read last_install; then report time (in seconds) and I/O (in MiB) for 939 // this update to tron. 940 // Only report on the reboots immediately after an OTA update. parseLastInstallLog(Context context)941 private static void parseLastInstallLog(Context context) { 942 try (BufferedReader in = new BufferedReader(new FileReader(LAST_INSTALL_FILE))) { 943 String line = null; 944 int bytesWrittenInMiB = -1, bytesStashedInMiB = -1; 945 int timeTotal = -1; 946 int uncryptTime = -1; 947 int sourceVersion = -1; 948 int temperatureStart = -1; 949 int temperatureEnd = -1; 950 int temperatureMax = -1; 951 int errorCode = -1; 952 int causeCode = -1; 953 954 while ((line = in.readLine()) != null) { 955 // Here is an example of lines in last_install: 956 // ... 957 // time_total: 101 958 // bytes_written_vendor: 51074 959 // bytes_stashed_vendor: 200 960 int numIndex = line.indexOf(':'); 961 if (numIndex == -1 || numIndex + 1 >= line.length()) { 962 continue; 963 } 964 String numString = line.substring(numIndex + 1).trim(); 965 long parsedNum; 966 try { 967 parsedNum = Long.parseLong(numString); 968 } catch (NumberFormatException ignored) { 969 Log.e(TAG, "Failed to parse numbers in " + line); 970 continue; 971 } 972 973 final int MiB = 1024 * 1024; 974 int scaled; 975 try { 976 if (line.startsWith("bytes")) { 977 scaled = Math.toIntExact(parsedNum / MiB); 978 } else { 979 scaled = Math.toIntExact(parsedNum); 980 } 981 } catch (ArithmeticException ignored) { 982 Log.e(TAG, "Number overflows in " + line); 983 continue; 984 } 985 986 if (line.startsWith("time")) { 987 timeTotal = scaled; 988 } else if (line.startsWith("uncrypt_time")) { 989 uncryptTime = scaled; 990 } else if (line.startsWith("source_build")) { 991 sourceVersion = scaled; 992 } else if (line.startsWith("bytes_written")) { 993 bytesWrittenInMiB = (bytesWrittenInMiB == -1) ? scaled : 994 bytesWrittenInMiB + scaled; 995 } else if (line.startsWith("bytes_stashed")) { 996 bytesStashedInMiB = (bytesStashedInMiB == -1) ? scaled : 997 bytesStashedInMiB + scaled; 998 } else if (line.startsWith("temperature_start")) { 999 temperatureStart = scaled; 1000 } else if (line.startsWith("temperature_end")) { 1001 temperatureEnd = scaled; 1002 } else if (line.startsWith("temperature_max")) { 1003 temperatureMax = scaled; 1004 } else if (line.startsWith("error")) { 1005 errorCode = scaled; 1006 } else if (line.startsWith("cause")) { 1007 causeCode = scaled; 1008 } 1009 } 1010 1011 // Don't report data to tron if corresponding entry isn't found in last_install. 1012 if (timeTotal != -1) { 1013 MetricsLogger.histogram(context, "ota_time_total", timeTotal); 1014 } 1015 if (uncryptTime != -1) { 1016 MetricsLogger.histogram(context, "ota_uncrypt_time", uncryptTime); 1017 } 1018 if (sourceVersion != -1) { 1019 MetricsLogger.histogram(context, "ota_source_version", sourceVersion); 1020 } 1021 if (bytesWrittenInMiB != -1) { 1022 MetricsLogger.histogram(context, "ota_written_in_MiBs", bytesWrittenInMiB); 1023 } 1024 if (bytesStashedInMiB != -1) { 1025 MetricsLogger.histogram(context, "ota_stashed_in_MiBs", bytesStashedInMiB); 1026 } 1027 if (temperatureStart != -1) { 1028 MetricsLogger.histogram(context, "ota_temperature_start", temperatureStart); 1029 } 1030 if (temperatureEnd != -1) { 1031 MetricsLogger.histogram(context, "ota_temperature_end", temperatureEnd); 1032 } 1033 if (temperatureMax != -1) { 1034 MetricsLogger.histogram(context, "ota_temperature_max", temperatureMax); 1035 } 1036 if (errorCode != -1) { 1037 MetricsLogger.histogram(context, "ota_non_ab_error_code", errorCode); 1038 } 1039 if (causeCode != -1) { 1040 MetricsLogger.histogram(context, "ota_non_ab_cause_code", causeCode); 1041 } 1042 1043 } catch (IOException e) { 1044 Log.e(TAG, "Failed to read lines in last_install", e); 1045 } 1046 } 1047 1048 /** 1049 * Called after booting to process and remove recovery-related files. 1050 * @return the log file from recovery, or null if none was found. 1051 * 1052 * @hide 1053 */ handleAftermath(Context context)1054 public static String handleAftermath(Context context) { 1055 // Record the tail of the LOG_FILE 1056 String log = null; 1057 try { 1058 log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n"); 1059 } catch (FileNotFoundException e) { 1060 Log.i(TAG, "No recovery log file"); 1061 } catch (IOException e) { 1062 Log.e(TAG, "Error reading recovery log", e); 1063 } 1064 1065 if (log != null) { 1066 parseLastInstallLog(context); 1067 } 1068 1069 // Only remove the OTA package if it's partially processed (uncrypt'd). 1070 boolean reservePackage = BLOCK_MAP_FILE.exists(); 1071 if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) { 1072 String filename = null; 1073 try { 1074 filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null); 1075 } catch (IOException e) { 1076 Log.e(TAG, "Error reading uncrypt file", e); 1077 } 1078 1079 // Remove the OTA package on /data that has been (possibly 1080 // partially) processed. (Bug: 24973532) 1081 if (filename != null && filename.startsWith("/data")) { 1082 if (UNCRYPT_PACKAGE_FILE.delete()) { 1083 Log.i(TAG, "Deleted: " + filename); 1084 } else { 1085 Log.e(TAG, "Can't delete: " + filename); 1086 } 1087 } 1088 } 1089 1090 // We keep the update logs (beginning with LAST_PREFIX), and optionally 1091 // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE 1092 // will be created at the end of a successful uncrypt. If seeing this 1093 // file, we keep the block map file and the file that contains the 1094 // package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for 1095 // GmsCore to avoid re-downloading everything again. 1096 String[] names = RECOVERY_DIR.list(); 1097 for (int i = 0; names != null && i < names.length; i++) { 1098 if (names[i].startsWith(LAST_PREFIX)) continue; 1099 if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue; 1100 if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue; 1101 1102 recursiveDelete(new File(RECOVERY_DIR, names[i])); 1103 } 1104 1105 return log; 1106 } 1107 1108 /** 1109 * Internally, delete a given file or directory recursively. 1110 */ recursiveDelete(File name)1111 private static void recursiveDelete(File name) { 1112 if (name.isDirectory()) { 1113 String[] files = name.list(); 1114 for (int i = 0; files != null && i < files.length; i++) { 1115 File f = new File(name, files[i]); 1116 recursiveDelete(f); 1117 } 1118 } 1119 1120 if (!name.delete()) { 1121 Log.e(TAG, "Can't delete: " + name); 1122 } else { 1123 Log.i(TAG, "Deleted: " + name); 1124 } 1125 } 1126 1127 /** 1128 * Talks to RecoverySystemService via Binder to trigger uncrypt. 1129 */ uncrypt(String packageFile, IRecoverySystemProgressListener listener)1130 private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) { 1131 try { 1132 return mService.uncrypt(packageFile, listener); 1133 } catch (RemoteException unused) { 1134 } 1135 return false; 1136 } 1137 1138 /** 1139 * Talks to RecoverySystemService via Binder to set up the BCB. 1140 */ setupBcb(String command)1141 private boolean setupBcb(String command) { 1142 try { 1143 return mService.setupBcb(command); 1144 } catch (RemoteException unused) { 1145 } 1146 return false; 1147 } 1148 1149 /** 1150 * Talks to RecoverySystemService via Binder to clear up the BCB. 1151 */ clearBcb()1152 private boolean clearBcb() { 1153 try { 1154 return mService.clearBcb(); 1155 } catch (RemoteException unused) { 1156 } 1157 return false; 1158 } 1159 1160 /** 1161 * Talks to RecoverySystemService via Binder to set up the BCB command and 1162 * reboot into recovery accordingly. 1163 */ rebootRecoveryWithCommand(String command)1164 private void rebootRecoveryWithCommand(String command) { 1165 try { 1166 mService.rebootRecoveryWithCommand(command); 1167 } catch (RemoteException ignored) { 1168 } 1169 } 1170 1171 /** 1172 * Internally, recovery treats each line of the command file as a separate 1173 * argv, so we only need to protect against newlines and nulls. 1174 */ sanitizeArg(String arg)1175 private static String sanitizeArg(String arg) { 1176 arg = arg.replace('\0', '?'); 1177 arg = arg.replace('\n', '?'); 1178 return arg; 1179 } 1180 1181 1182 /** 1183 * @removed Was previously made visible by accident. 1184 */ RecoverySystem()1185 public RecoverySystem() { 1186 mService = null; 1187 } 1188 1189 /** 1190 * @hide 1191 */ RecoverySystem(IRecoverySystem service)1192 public RecoverySystem(IRecoverySystem service) { 1193 mService = service; 1194 } 1195 } 1196