diff --git a/services/backup/java/com/android/server/backup/FileMetadata.java b/services/backup/java/com/android/server/backup/FileMetadata.java index afddd3e725e1e..01af195d2aab7 100644 --- a/services/backup/java/com/android/server/backup/FileMetadata.java +++ b/services/backup/java/com/android/server/backup/FileMetadata.java @@ -34,6 +34,8 @@ public class FileMetadata { public long mode; // e.g. 0666 (actually int) public long mtime; // last mod time, UTC time_t (actually int) public long size; // bytes of content + public int version; // App version. + public boolean hasApk; // Whether backup file contains apk. @Override public String toString() { diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java index 1d5be28d0c87f..888dcd7e7a5ec 100644 --- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java +++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java @@ -16,6 +16,16 @@ package com.android.server.backup.restore; +import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME; +import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_METADATA_FILENAME; +import static com.android.server.backup.RefactoredBackupManagerService.DEBUG; +import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG; +import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT; +import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; +import static com.android.server.backup.RefactoredBackupManagerService.TAG; +import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL; +import static com.android.server.backup.RefactoredBackupManagerService + .TIMEOUT_SHARED_BACKUP_INTERVAL; import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT; import android.app.ApplicationThreadConstants; @@ -52,58 +62,61 @@ import java.util.HashSet; */ public class FullRestoreEngine extends RestoreEngine { - private RefactoredBackupManagerService backupManagerService; + private final RefactoredBackupManagerService mBackupManagerService; // Task in charge of monitoring timeouts - BackupRestoreTask mMonitorTask; + private final BackupRestoreTask mMonitorTask; + + private final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver(); + private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver(); // Dedicated observer, if any - IFullBackupRestoreObserver mObserver; + private IFullBackupRestoreObserver mObserver; - IBackupManagerMonitor mMonitor; + final IBackupManagerMonitor mMonitor; // Where we're delivering the file data as we go - IBackupAgent mAgent; + private IBackupAgent mAgent; // Are we permitted to only deliver a specific package's metadata? - PackageInfo mOnlyPackage; + final PackageInfo mOnlyPackage; - boolean mAllowApks; - boolean mAllowObbs; + final boolean mAllowApks; + private final boolean mAllowObbs; // Which package are we currently handling data for? - String mAgentPackage; + private String mAgentPackage; // Info for working with the target app process - ApplicationInfo mTargetApp; + private ApplicationInfo mTargetApp; // Machinery for restoring OBBs - FullBackupObbConnection mObbConnection = null; + private FullBackupObbConnection mObbConnection = null; // possible handling states for a given package in the restore dataset - final HashMap mPackagePolicies + private final HashMap mPackagePolicies = new HashMap<>(); // installer package names for each encountered app, derived from the manifests - final HashMap mPackageInstallers = new HashMap<>(); + private final HashMap mPackageInstallers = new HashMap<>(); // Signatures for a given package found in its manifest file - final HashMap mManifestSignatures + private final HashMap mManifestSignatures = new HashMap<>(); // Packages we've already wiped data on when restoring their first file - final HashSet mClearedPackages = new HashSet<>(); + private final HashSet mClearedPackages = new HashSet<>(); // How much data have we moved? - long mBytes; + private long mBytes; // Working buffer - byte[] mBuffer; + final byte[] mBuffer; // Pipes for moving data - ParcelFileDescriptor[] mPipes = null; + private ParcelFileDescriptor[] mPipes = null; // Widget blob to be restored out-of-band - byte[] mWidgetData = null; + private byte[] mWidgetData = null; final int mEphemeralOpToken; @@ -111,7 +124,7 @@ public class FullRestoreEngine extends RestoreEngine { BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer, IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks, boolean allowObbs, int ephemeralOpToken) { - this.backupManagerService = backupManagerService; + mBackupManagerService = backupManagerService; mEphemeralOpToken = ephemeralOpToken; mMonitorTask = monitorTask; mObserver = observer; @@ -134,7 +147,7 @@ public class FullRestoreEngine extends RestoreEngine { public boolean restoreOneFile(InputStream instream, boolean mustKillAgent, byte[] buffer, PackageInfo onlyPackage, boolean allowApks, int token, IBackupManagerMonitor monitor) { if (!isRunning()) { - Slog.w(RefactoredBackupManagerService.TAG, "Restore engine used after halting"); + Slog.w(TAG, "Restore engine used after halting"); return false; } @@ -150,12 +163,12 @@ public class FullRestoreEngine extends RestoreEngine { FileMetadata info; try { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.v(RefactoredBackupManagerService.TAG, "Reading tar header for restoring file"); + if (MORE_DEBUG) { + Slog.v(TAG, "Reading tar header for restoring file"); } info = tarBackupReader.readTarHeaders(); if (info != null) { - if (RefactoredBackupManagerService.MORE_DEBUG) { + if (MORE_DEBUG) { info.dump(); } @@ -165,9 +178,7 @@ public class FullRestoreEngine extends RestoreEngine { // one app's data but see a different app's on the wire if (onlyPackage != null) { if (!pkg.equals(onlyPackage.packageName)) { - Slog.w(RefactoredBackupManagerService.TAG, - "Expected data for " + onlyPackage - + " but saw " + pkg); + Slog.w(TAG, "Expected data for " + onlyPackage + " but saw " + pkg); setResult(RestoreEngine.TRANSPORT_FAILURE); setRunning(false); return false; @@ -183,9 +194,8 @@ public class FullRestoreEngine extends RestoreEngine { // Clean up the previous agent relationship if necessary, // and let the observer know we're considering a new app. if (mAgent != null) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Saw new package; finalizing old one"); + if (DEBUG) { + Slog.d(TAG, "Saw new package; finalizing old one"); } // Now we're really done tearDownPipes(); @@ -195,19 +205,20 @@ public class FullRestoreEngine extends RestoreEngine { } } - if (info.path.equals(RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME)) { - RestorePolicy appManifest = tarBackupReader.readAppManifest( - backupManagerService.getPackageManager(), allowApks, - mManifestSignatures, info); - mPackagePolicies.put(pkg, appManifest); + if (info.path.equals(BACKUP_MANIFEST_FILENAME)) { + Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures( + info); + RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy( + mBackupManagerService.getPackageManager(), allowApks, info, signatures); + mManifestSignatures.put(info.packageName, signatures); + mPackagePolicies.put(pkg, restorePolicy); mPackageInstallers.put(pkg, info.installerPackageName); // We've read only the manifest content itself at this point, // so consume the footer before looping around to the next // input file tarBackupReader.skipTarPadding(info.size); mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg); - } else if (info.path.equals( - RefactoredBackupManagerService.BACKUP_METADATA_FILENAME)) { + } else if (info.path.equals(BACKUP_METADATA_FILENAME)) { // Metadata blobs! tarBackupReader.readMetadata(info); @@ -234,18 +245,17 @@ public class FullRestoreEngine extends RestoreEngine { // If we're in accept-if-apk state, then the first file we // see MUST be the apk. if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "APK file; installing"); + if (DEBUG) { + Slog.d(TAG, "APK file; installing"); } // Try to install the app. String installerName = mPackageInstallers.get(pkg); boolean isSuccessfullyInstalled = RestoreUtils.installApk( - instream, backupManagerService.getPackageManager(), + instream, mBackupManagerService.getPackageManager(), mInstallObserver, mDeleteObserver, mManifestSignatures, mPackagePolicies, info, installerName, - bytesReadListener, backupManagerService.getDataDir() - ); + bytesReadListener, mBackupManagerService.getDataDir() + ); // good to go; promote to ACCEPT mPackagePolicies.put(pkg, isSuccessfullyInstalled ? RestorePolicy.ACCEPT @@ -265,9 +275,8 @@ public class FullRestoreEngine extends RestoreEngine { case ACCEPT: if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "apk present but ACCEPT"); + if (DEBUG) { + Slog.d(TAG, "apk present but ACCEPT"); } // we can take the data without the apk, so we // *want* to do so. skip the apk by declaring this @@ -281,8 +290,7 @@ public class FullRestoreEngine extends RestoreEngine { // Something has gone dreadfully wrong when determining // the restore policy from the manifest. Ignore the // rest of this package's data. - Slog.e(RefactoredBackupManagerService.TAG, - "Invalid policy from manifest"); + Slog.e(TAG, "Invalid policy from manifest"); okay = false; mPackagePolicies.put(pkg, RestorePolicy.IGNORE); break; @@ -295,19 +303,17 @@ public class FullRestoreEngine extends RestoreEngine { // If the policy is satisfied, go ahead and set up to pipe the // data to the agent. - if (RefactoredBackupManagerService.MORE_DEBUG && okay && mAgent != null) { - Slog.i(RefactoredBackupManagerService.TAG, - "Reusing existing agent instance"); + if (MORE_DEBUG && okay && mAgent != null) { + Slog.i(TAG, "Reusing existing agent instance"); } if (okay && mAgent == null) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Need to launch agent for " + pkg); + if (MORE_DEBUG) { + Slog.d(TAG, "Need to launch agent for " + pkg); } try { mTargetApp = - backupManagerService.getPackageManager().getApplicationInfo( + mBackupManagerService.getPackageManager().getApplicationInfo( pkg, 0); // If we haven't sent any data to this app yet, we probably @@ -317,29 +323,28 @@ public class FullRestoreEngine extends RestoreEngine { // responsible for coherently managing a full // restore. if (mTargetApp.backupAgentName == null) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, + if (DEBUG) { + Slog.d(TAG, "Clearing app data preparatory to full restore"); } - backupManagerService.clearApplicationDataSynchronous(pkg); + mBackupManagerService.clearApplicationDataSynchronous(pkg); } else { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, "backup agent (" + if (MORE_DEBUG) { + Slog.d(TAG, "backup agent (" + mTargetApp.backupAgentName + ") => no clear"); } } mClearedPackages.add(pkg); } else { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "We've initialized this app already; no clear " - + "required"); + if (MORE_DEBUG) { + Slog.d(TAG, "We've initialized this app already; no clear " + + "required"); } } // All set; now set up the IPC and launch the agent setUpPipes(); - mAgent = backupManagerService.bindToAgentSynchronous(mTargetApp, + mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp, ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL); mAgentPackage = pkg; } catch (IOException e) { @@ -349,9 +354,7 @@ public class FullRestoreEngine extends RestoreEngine { } if (mAgent == null) { - Slog.e( - RefactoredBackupManagerService.TAG, - "Unable to create agent for " + pkg); + Slog.e(TAG, "Unable to create agent for " + pkg); okay = false; tearDownPipes(); mPackagePolicies.put(pkg, RestorePolicy.IGNORE); @@ -361,7 +364,7 @@ public class FullRestoreEngine extends RestoreEngine { // Sanity check: make sure we never give data to the wrong app. This // should never happen but a little paranoia here won't go amiss. if (okay && !pkg.equals(mAgentPackage)) { - Slog.e(RefactoredBackupManagerService.TAG, "Restoring data for " + pkg + Slog.e(TAG, "Restoring data for " + pkg + " but agent is for " + mAgentPackage); okay = false; } @@ -373,84 +376,74 @@ public class FullRestoreEngine extends RestoreEngine { if (okay) { boolean agentSuccess = true; long toCopy = info.size; - final boolean isSharedStorage = pkg.equals( - RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE); + final boolean isSharedStorage = pkg.equals(SHARED_BACKUP_AGENT_PACKAGE); final long timeout = isSharedStorage ? - RefactoredBackupManagerService.TIMEOUT_SHARED_BACKUP_INTERVAL : - RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL; + TIMEOUT_SHARED_BACKUP_INTERVAL : + TIMEOUT_RESTORE_INTERVAL; try { - backupManagerService.prepareOperationTimeout(token, + mBackupManagerService.prepareOperationTimeout(token, timeout, mMonitorTask, - RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT); + OP_TYPE_RESTORE_WAIT); if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Restoring OBB file for " + pkg - + " : " + info.path); + if (DEBUG) { + Slog.d(TAG, "Restoring OBB file for " + pkg + + " : " + info.path); } mObbConnection.restoreObbFile(pkg, mPipes[0], info.size, info.type, info.path, info.mode, info.mtime, token, - backupManagerService.getBackupManagerBinder()); + mBackupManagerService.getBackupManagerBinder()); } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) { // This is only possible during adb restore. // TODO: Refactor to clearly separate the flows. - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Restoring key-value file for " + pkg - + " : " + info.path); + if (DEBUG) { + Slog.d(TAG, "Restoring key-value file for " + pkg + + " : " + info.path); } KeyValueAdbRestoreEngine restoreEngine = new KeyValueAdbRestoreEngine( - backupManagerService, - backupManagerService.getDataDir(), info, mPipes[0], + mBackupManagerService, + mBackupManagerService.getDataDir(), info, mPipes[0], mAgent, token); new Thread(restoreEngine, "restore-key-value-runner").start(); } else { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Invoking agent to restore file " - + info.path); + if (MORE_DEBUG) { + Slog.d(TAG, "Invoking agent to restore file " + info.path); } // fire up the app's agent listening on the socket. If // the agent is running in the system process we can't // just invoke it asynchronously, so we provide a thread // for it here. if (mTargetApp.processName.equals("system")) { - Slog.d(RefactoredBackupManagerService.TAG, - "system process agent - spinning a thread"); + Slog.d(TAG, "system process agent - spinning a thread"); RestoreFileRunnable runner = new RestoreFileRunnable( - backupManagerService, mAgent, info, mPipes[0], token); + mBackupManagerService, mAgent, info, mPipes[0], token); new Thread(runner, "restore-sys-runner").start(); } else { mAgent.doRestoreFile(mPipes[0], info.size, info.type, info.domain, info.path, info.mode, info.mtime, - token, backupManagerService.getBackupManagerBinder()); + token, mBackupManagerService.getBackupManagerBinder()); } } } catch (IOException e) { // couldn't dup the socket for a process-local restore - Slog.d(RefactoredBackupManagerService.TAG, - "Couldn't establish restore"); + Slog.d(TAG, "Couldn't establish restore"); agentSuccess = false; okay = false; } catch (RemoteException e) { // whoops, remote entity went away. We'll eat the content // ourselves, then, and not copy it over. - Slog.e(RefactoredBackupManagerService.TAG, - "Agent crashed during full restore"); + Slog.e(TAG, "Agent crashed during full restore"); agentSuccess = false; okay = false; } // Copy over the data if the agent is still good if (okay) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.v(RefactoredBackupManagerService.TAG, - " copying to restore agent: " - + toCopy + " bytes"); + if (MORE_DEBUG) { + Slog.v(TAG, " copying to restore agent: " + toCopy + " bytes"); } boolean pipeOkay = true; FileOutputStream pipe = new FileOutputStream( @@ -473,9 +466,8 @@ public class FullRestoreEngine extends RestoreEngine { try { pipe.write(buffer, 0, nRead); } catch (IOException e) { - Slog.e(RefactoredBackupManagerService.TAG, - "Failed to write to restore pipe: " - + e.getMessage()); + Slog.e(TAG, "Failed to write to restore pipe: " + + e.getMessage()); pipeOkay = false; } } @@ -487,15 +479,14 @@ public class FullRestoreEngine extends RestoreEngine { // and now that we've sent it all, wait for the remote // side to acknowledge receipt - agentSuccess = backupManagerService.waitUntilOperationComplete(token); + agentSuccess = mBackupManagerService.waitUntilOperationComplete(token); } // okay, if the remote end failed at any point, deal with // it by ignoring the rest of the restore on it if (!agentSuccess) { - Slog.w(RefactoredBackupManagerService.TAG, - "Agent failure restoring " + pkg + "; ending restore"); - backupManagerService.getBackupHandler().removeMessages( + Slog.w(TAG, "Agent failure restoring " + pkg + "; ending restore"); + mBackupManagerService.getBackupHandler().removeMessages( MSG_RESTORE_OPERATION_TIMEOUT); tearDownPipes(); tearDownAgent(mTargetApp); @@ -516,8 +507,8 @@ public class FullRestoreEngine extends RestoreEngine { // dropped file, or an already-ignored package: skip to the // next stream entry by reading and discarding this file. if (!okay) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, "[discarding file content]"); + if (MORE_DEBUG) { + Slog.d(TAG, "[discarding file content]"); } long bytesToConsume = (info.size + 511) & ~511; while (bytesToConsume > 0) { @@ -536,9 +527,8 @@ public class FullRestoreEngine extends RestoreEngine { } } } catch (IOException e) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.w(RefactoredBackupManagerService.TAG, - "io exception on restore socket read: " + e.getMessage()); + if (DEBUG) { + Slog.w(TAG, "io exception on restore socket read: " + e.getMessage()); } setResult(RestoreEngine.TRANSPORT_FAILURE); info = null; @@ -546,9 +536,8 @@ public class FullRestoreEngine extends RestoreEngine { // If we got here we're either running smoothly or we've finished if (info == null) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, - "No [more] data for this package; tearing down"); + if (MORE_DEBUG) { + Slog.i(TAG, "No [more] data for this package; tearing down"); } tearDownPipes(); setRunning(false); @@ -559,11 +548,11 @@ public class FullRestoreEngine extends RestoreEngine { return (info != null); } - void setUpPipes() throws IOException { + private void setUpPipes() throws IOException { mPipes = ParcelFileDescriptor.createPipe(); } - void tearDownPipes() { + private void tearDownPipes() { // Teardown might arise from the inline restore processing or from the asynchronous // timeout mechanism, and these might race. Make sure we don't try to close and // null out the pipes twice. @@ -575,16 +564,16 @@ public class FullRestoreEngine extends RestoreEngine { mPipes[1].close(); mPipes[1] = null; } catch (IOException e) { - Slog.w(RefactoredBackupManagerService.TAG, "Couldn't close agent pipes", e); + Slog.w(TAG, "Couldn't close agent pipes", e); } mPipes = null; } } } - void tearDownAgent(ApplicationInfo app) { + private void tearDownAgent(ApplicationInfo app) { if (mAgent != null) { - backupManagerService.tearDownAgentAndKill(app); + mBackupManagerService.tearDownAgentAndKill(app); mAgent = null; } } @@ -595,13 +584,10 @@ public class FullRestoreEngine extends RestoreEngine { setRunning(false); } - final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver(); - final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver(); - private static boolean isRestorableFile(FileMetadata info) { if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, "Dropping cache file path " + info.path); + if (MORE_DEBUG) { + Slog.i(TAG, "Dropping cache file path " + info.path); } return false; } @@ -612,9 +598,8 @@ public class FullRestoreEngine extends RestoreEngine { // API. Respect the no-backup intention and don't let the data get to // the app. if (info.path.startsWith("no_backup/")) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, - "Dropping no_backup file path " + info.path); + if (MORE_DEBUG) { + Slog.i(TAG, "Dropping no_backup file path " + info.path); } return false; } @@ -626,8 +611,8 @@ public class FullRestoreEngine extends RestoreEngine { private static boolean isCanonicalFilePath(String path) { if (path.contains("..") || path.contains("//")) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.w(RefactoredBackupManagerService.TAG, "Dropping invalid path " + path); + if (MORE_DEBUG) { + Slog.w(TAG, "Dropping invalid path " + path); } return false; } @@ -641,8 +626,7 @@ public class FullRestoreEngine extends RestoreEngine { // TODO: use a more user-friendly name string mObserver.onRestorePackage(name); } catch (RemoteException e) { - Slog.w(RefactoredBackupManagerService.TAG, - "full restore observer went away: restorePackage"); + Slog.w(TAG, "full restore observer went away: restorePackage"); mObserver = null; } } diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java index 6b265612408db..f3a68a08e9d43 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java @@ -16,6 +16,20 @@ package com.android.server.backup.restore; +import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_FILE_HEADER_MAGIC; +import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_FILE_VERSION; +import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME; +import static com.android.server.backup.RefactoredBackupManagerService.BACKUP_METADATA_FILENAME; +import static com.android.server.backup.RefactoredBackupManagerService.DEBUG; +import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG; +import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT; +import static com.android.server.backup.RefactoredBackupManagerService.PBKDF_CURRENT; +import static com.android.server.backup.RefactoredBackupManagerService.PBKDF_FALLBACK; +import static com.android.server.backup.RefactoredBackupManagerService.SETTINGS_PACKAGE; +import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; +import static com.android.server.backup.RefactoredBackupManagerService.TAG; +import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL; +import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL; import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT; import android.app.ApplicationThreadConstants; @@ -32,6 +46,7 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.backup.FileMetadata; import com.android.server.backup.KeyValueAdbRestoreEngine; import com.android.server.backup.PackageManagerBackupAgent; @@ -43,7 +58,6 @@ import com.android.server.backup.utils.PasswordUtils; import com.android.server.backup.utils.RestoreUtils; import com.android.server.backup.utils.TarBackupReader; -import java.io.DataInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -68,38 +82,44 @@ import javax.crypto.spec.SecretKeySpec; public class PerformAdbRestoreTask implements Runnable { - private RefactoredBackupManagerService backupManagerService; - ParcelFileDescriptor mInputFile; - String mCurrentPassword; - String mDecryptPassword; - IFullBackupRestoreObserver mObserver; - AtomicBoolean mLatchObject; - IBackupAgent mAgent; - PackageManagerBackupAgent mPackageManagerBackupAgent; - String mAgentPackage; - ApplicationInfo mTargetApp; - FullBackupObbConnection mObbConnection = null; - ParcelFileDescriptor[] mPipes = null; - byte[] mWidgetData = null; + private final RefactoredBackupManagerService mBackupManagerService; + private final ParcelFileDescriptor mInputFile; + private final String mCurrentPassword; + private final String mDecryptPassword; + private final AtomicBoolean mLatchObject; + private final PackageManagerBackupAgent mPackageManagerBackupAgent; + private final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver(); + private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver(); - long mBytes; + private IFullBackupRestoreObserver mObserver; + private IBackupAgent mAgent; + private String mAgentPackage; + private ApplicationInfo mTargetApp; + private FullBackupObbConnection mObbConnection = null; + private ParcelFileDescriptor[] mPipes = null; + private byte[] mWidgetData = null; + + private long mBytes; // Runner that can be placed on a separate thread to do in-process invocation // of the "restore finished" API asynchronously. Used by adb restore. - class RestoreFinishedRunnable implements Runnable { + private static class RestoreFinishedRunnable implements Runnable { - final IBackupAgent mAgent; - final int mToken; + private final IBackupAgent mAgent; + private final int mToken; + private final RefactoredBackupManagerService mBackupManagerService; - RestoreFinishedRunnable(IBackupAgent agent, int token) { + RestoreFinishedRunnable(IBackupAgent agent, int token, + RefactoredBackupManagerService backupManagerService) { mAgent = agent; mToken = token; + mBackupManagerService = backupManagerService; } @Override public void run() { try { - mAgent.doRestoreFinished(mToken, backupManagerService.getBackupManagerBinder()); + mAgent.doRestoreFinished(mToken, mBackupManagerService.getBackupManagerBinder()); } catch (RemoteException e) { // never happens; this is used only for local binder calls } @@ -107,23 +127,23 @@ public class PerformAdbRestoreTask implements Runnable { } // possible handling states for a given package in the restore dataset - final HashMap mPackagePolicies + private final HashMap mPackagePolicies = new HashMap<>(); // installer package names for each encountered app, derived from the manifests - final HashMap mPackageInstallers = new HashMap<>(); + private final HashMap mPackageInstallers = new HashMap<>(); // Signatures for a given package found in its manifest file - final HashMap mManifestSignatures + private final HashMap mManifestSignatures = new HashMap<>(); // Packages we've already wiped data on when restoring their first file - final HashSet mClearedPackages = new HashSet<>(); + private final HashSet mClearedPackages = new HashSet<>(); public PerformAdbRestoreTask(RefactoredBackupManagerService backupManagerService, ParcelFileDescriptor fd, String curPassword, String decryptPassword, IFullBackupRestoreObserver observer, AtomicBoolean latch) { - this.backupManagerService = backupManagerService; + this.mBackupManagerService = backupManagerService; mInputFile = fd; mCurrentPassword = curPassword; mDecryptPassword = decryptPassword; @@ -139,115 +159,66 @@ public class PerformAdbRestoreTask implements Runnable { // Which packages we've already wiped data on. We prepopulate this // with a whitelist of packages known to be unclearable. mClearedPackages.add("android"); - mClearedPackages.add(RefactoredBackupManagerService.SETTINGS_PACKAGE); + mClearedPackages.add(SETTINGS_PACKAGE); } @Override public void run() { - Slog.i(RefactoredBackupManagerService.TAG, "--- Performing full-dataset restore ---"); + Slog.i(TAG, "--- Performing full-dataset restore ---"); mObbConnection.establish(); mObserver = FullBackupRestoreObserverUtils.sendStartRestore(mObserver); // Are we able to restore shared-storage data? if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - mPackagePolicies.put(RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE, + mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE, RestorePolicy.ACCEPT); } FileInputStream rawInStream = null; - DataInputStream rawDataIn = null; try { - if (!backupManagerService.backupPasswordMatches(mCurrentPassword)) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.w(RefactoredBackupManagerService.TAG, - "Backup password mismatch; aborting"); + if (!mBackupManagerService.backupPasswordMatches(mCurrentPassword)) { + if (DEBUG) { + Slog.w(TAG, "Backup password mismatch; aborting"); } return; } mBytes = 0; - byte[] buffer = new byte[32 * 1024]; + rawInStream = new FileInputStream(mInputFile.getFileDescriptor()); - rawDataIn = new DataInputStream(rawInStream); - // First, parse out the unencrypted/uncompressed header - boolean compressed = false; - InputStream preCompressStream = rawInStream; - final InputStream in; - - boolean okay = false; - final int headerLen = RefactoredBackupManagerService.BACKUP_FILE_HEADER_MAGIC.length(); - byte[] streamHeader = new byte[headerLen]; - rawDataIn.readFully(streamHeader); - byte[] magicBytes = RefactoredBackupManagerService.BACKUP_FILE_HEADER_MAGIC.getBytes( - "UTF-8"); - if (Arrays.equals(magicBytes, streamHeader)) { - // okay, header looks good. now parse out the rest of the fields. - String s = readHeaderLine(rawInStream); - final int archiveVersion = Integer.parseInt(s); - if (archiveVersion <= RefactoredBackupManagerService.BACKUP_FILE_VERSION) { - // okay, it's a version we recognize. if it's version 1, we may need - // to try two different PBKDF2 regimes to compare checksums. - final boolean pbkdf2Fallback = (archiveVersion == 1); - - s = readHeaderLine(rawInStream); - compressed = (Integer.parseInt(s) != 0); - s = readHeaderLine(rawInStream); - if (s.equals("none")) { - // no more header to parse; we're good to go - okay = true; - } else if (mDecryptPassword != null && mDecryptPassword.length() > 0) { - preCompressStream = decodeAesHeaderAndInitialize(s, pbkdf2Fallback, - rawInStream); - if (preCompressStream != null) { - okay = true; - } - } else { - Slog.w(RefactoredBackupManagerService.TAG, - "Archive is encrypted but no password given"); - } - } else { - Slog.w(RefactoredBackupManagerService.TAG, "Wrong header version: " + s); - } - } else { - Slog.w(RefactoredBackupManagerService.TAG, "Didn't read the right header magic"); - } - - if (!okay) { - Slog.w(RefactoredBackupManagerService.TAG, "Invalid restore data; aborting."); + InputStream tarInputStream = parseBackupFileHeaderAndReturnTarStream(rawInStream, + mDecryptPassword); + if (tarInputStream == null) { + // There was an error reading the backup file, which is already handled and logged. + // Just abort. return; } - // okay, use the right stream layer based on compression - in = (compressed) ? new InflaterInputStream(preCompressStream) : preCompressStream; - + byte[] buffer = new byte[32 * 1024]; boolean didRestore; do { - didRestore = restoreOneFile(in, false /* mustKillAgent */, buffer, + didRestore = restoreOneFile(tarInputStream, false /* mustKillAgent */, buffer, null /* onlyPackage */, true /* allowApks */, - backupManagerService.generateRandomIntegerToken(), null /* monitor */); + mBackupManagerService.generateRandomIntegerToken(), null /* monitor */); } while (didRestore); - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.v(RefactoredBackupManagerService.TAG, - "Done consuming input tarfile, total bytes=" + mBytes); + if (MORE_DEBUG) { + Slog.v(TAG, "Done consuming input tarfile, total bytes=" + mBytes); } } catch (IOException e) { - Slog.e(RefactoredBackupManagerService.TAG, "Unable to read restore input"); + Slog.e(TAG, "Unable to read restore input"); } finally { tearDownPipes(); tearDownAgent(mTargetApp, true); try { - if (rawDataIn != null) { - rawDataIn.close(); - } if (rawInStream != null) { rawInStream.close(); } mInputFile.close(); } catch (IOException e) { - Slog.w(RefactoredBackupManagerService.TAG, "Close of restore data pipe threw", e); + Slog.w(TAG, "Close of restore data pipe threw", e); /* nothing we can do about this */ } synchronized (mLatchObject) { @@ -256,12 +227,79 @@ public class PerformAdbRestoreTask implements Runnable { } mObbConnection.tearDown(); mObserver = FullBackupRestoreObserverUtils.sendEndRestore(mObserver); - Slog.d(RefactoredBackupManagerService.TAG, "Full restore pass complete."); - backupManagerService.getWakelock().release(); + Slog.d(TAG, "Full restore pass complete."); + mBackupManagerService.getWakelock().release(); } } - String readHeaderLine(InputStream in) throws IOException { + private static void readFullyOrThrow(InputStream in, byte[] buffer) throws IOException { + int offset = 0; + while (offset < buffer.length) { + int bytesRead = in.read(buffer, offset, buffer.length - offset); + if (bytesRead <= 0) { + throw new IOException("Couldn't fully read data"); + } + offset += bytesRead; + } + } + + @VisibleForTesting + public static InputStream parseBackupFileHeaderAndReturnTarStream( + InputStream rawInputStream, + String decryptPassword) + throws IOException { + // First, parse out the unencrypted/uncompressed header + boolean compressed = false; + InputStream preCompressStream = rawInputStream; + + boolean okay = false; + final int headerLen = BACKUP_FILE_HEADER_MAGIC.length(); + byte[] streamHeader = new byte[headerLen]; + readFullyOrThrow(rawInputStream, streamHeader); + byte[] magicBytes = BACKUP_FILE_HEADER_MAGIC.getBytes( + "UTF-8"); + if (Arrays.equals(magicBytes, streamHeader)) { + // okay, header looks good. now parse out the rest of the fields. + String s = readHeaderLine(rawInputStream); + final int archiveVersion = Integer.parseInt(s); + if (archiveVersion <= BACKUP_FILE_VERSION) { + // okay, it's a version we recognize. if it's version 1, we may need + // to try two different PBKDF2 regimes to compare checksums. + final boolean pbkdf2Fallback = (archiveVersion == 1); + + s = readHeaderLine(rawInputStream); + compressed = (Integer.parseInt(s) != 0); + s = readHeaderLine(rawInputStream); + if (s.equals("none")) { + // no more header to parse; we're good to go + okay = true; + } else if (decryptPassword != null && decryptPassword.length() > 0) { + preCompressStream = decodeAesHeaderAndInitialize( + decryptPassword, s, pbkdf2Fallback, + rawInputStream); + if (preCompressStream != null) { + okay = true; + } + } else { + Slog.w(TAG, "Archive is encrypted but no password given"); + } + } else { + Slog.w(TAG, "Wrong header version: " + s); + } + } else { + Slog.w(TAG, "Didn't read the right header magic"); + } + + if (!okay) { + Slog.w(TAG, "Invalid restore data; aborting."); + return null; + } + + // okay, use the right stream layer based on compression + return compressed ? new InflaterInputStream(preCompressStream) : preCompressStream; + } + + private static String readHeaderLine(InputStream in) throws IOException { int c; StringBuilder buffer = new StringBuilder(80); while ((c = in.read()) >= 0) { @@ -273,7 +311,8 @@ public class PerformAdbRestoreTask implements Runnable { return buffer.toString(); } - InputStream attemptMasterKeyDecryption(String algorithm, byte[] userSalt, byte[] ckSalt, + private static InputStream attemptMasterKeyDecryption(String decryptPassword, String algorithm, + byte[] userSalt, byte[] ckSalt, int rounds, String userIvHex, String masterKeyBlobHex, InputStream rawInStream, boolean doLog) { InputStream result = null; @@ -281,7 +320,7 @@ public class PerformAdbRestoreTask implements Runnable { try { Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKey userKey = PasswordUtils - .buildPasswordKey(algorithm, mDecryptPassword, userSalt, + .buildPasswordKey(algorithm, decryptPassword, userSalt, rounds); byte[] IV = PasswordUtils.hexToByteArray(userIvHex); IvParameterSpec ivSpec = new IvParameterSpec(IV); @@ -317,11 +356,11 @@ public class PerformAdbRestoreTask implements Runnable { // Only if all of the above worked properly will 'result' be assigned result = new CipherInputStream(rawInStream, c); } else if (doLog) { - Slog.w(RefactoredBackupManagerService.TAG, "Incorrect password"); + Slog.w(TAG, "Incorrect password"); } } catch (InvalidAlgorithmParameterException e) { if (doLog) { - Slog.e(RefactoredBackupManagerService.TAG, "Needed parameter spec unavailable!", e); + Slog.e(TAG, "Needed parameter spec unavailable!", e); } } catch (BadPaddingException e) { // This case frequently occurs when the wrong password is used to decrypt @@ -329,31 +368,32 @@ public class PerformAdbRestoreTask implements Runnable { // used in the checksum failure log in order to avoid providing additional // information to an attacker. if (doLog) { - Slog.w(RefactoredBackupManagerService.TAG, "Incorrect password"); + Slog.w(TAG, "Incorrect password"); } } catch (IllegalBlockSizeException e) { if (doLog) { - Slog.w(RefactoredBackupManagerService.TAG, "Invalid block size in master key"); + Slog.w(TAG, "Invalid block size in master key"); } } catch (NoSuchAlgorithmException e) { if (doLog) { - Slog.e(RefactoredBackupManagerService.TAG, - "Needed decryption algorithm unavailable!"); + Slog.e(TAG, "Needed decryption algorithm unavailable!"); } } catch (NoSuchPaddingException e) { if (doLog) { - Slog.e(RefactoredBackupManagerService.TAG, "Needed padding mechanism unavailable!"); + Slog.e(TAG, "Needed padding mechanism unavailable!"); } } catch (InvalidKeyException e) { if (doLog) { - Slog.w(RefactoredBackupManagerService.TAG, "Illegal password; aborting"); + Slog.w(TAG, "Illegal password; aborting"); } } return result; } - InputStream decodeAesHeaderAndInitialize(String encryptionName, boolean pbkdf2Fallback, + private static InputStream decodeAesHeaderAndInitialize(String decryptPassword, + String encryptionName, + boolean pbkdf2Fallback, InputStream rawInStream) { InputStream result = null; try { @@ -371,22 +411,21 @@ public class PerformAdbRestoreTask implements Runnable { String masterKeyBlobHex = readHeaderLine(rawInStream); // 9 // decrypt the master key blob - result = attemptMasterKeyDecryption(RefactoredBackupManagerService.PBKDF_CURRENT, + result = attemptMasterKeyDecryption(decryptPassword, PBKDF_CURRENT, userSalt, ckSalt, rounds, userIvHex, masterKeyBlobHex, rawInStream, false); if (result == null && pbkdf2Fallback) { result = attemptMasterKeyDecryption( - RefactoredBackupManagerService.PBKDF_FALLBACK, userSalt, ckSalt, + decryptPassword, PBKDF_FALLBACK, userSalt, ckSalt, rounds, userIvHex, masterKeyBlobHex, rawInStream, true); } } else { - Slog.w(RefactoredBackupManagerService.TAG, - "Unsupported encryption method: " + encryptionName); + Slog.w(TAG, "Unsupported encryption method: " + encryptionName); } } catch (NumberFormatException e) { - Slog.w(RefactoredBackupManagerService.TAG, "Can't parse restore data header"); + Slog.w(TAG, "Can't parse restore data header"); } catch (IOException e) { - Slog.w(RefactoredBackupManagerService.TAG, "Can't read input header"); + Slog.w(TAG, "Can't read input header"); } return result; @@ -406,7 +445,7 @@ public class PerformAdbRestoreTask implements Runnable { try { info = tarBackupReader.readTarHeaders(); if (info != null) { - if (RefactoredBackupManagerService.MORE_DEBUG) { + if (MORE_DEBUG) { info.dump(); } @@ -421,9 +460,8 @@ public class PerformAdbRestoreTask implements Runnable { // Clean up the previous agent relationship if necessary, // and let the observer know we're considering a new app. if (mAgent != null) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Saw new package; finalizing old one"); + if (DEBUG) { + Slog.d(TAG, "Saw new package; finalizing old one"); } // Now we're really done tearDownPipes(); @@ -433,19 +471,21 @@ public class PerformAdbRestoreTask implements Runnable { } } - if (info.path.equals(RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME)) { - RestorePolicy appManifest = tarBackupReader.readAppManifest( - backupManagerService.getPackageManager(), allowApks, - mManifestSignatures, info); - mPackagePolicies.put(pkg, appManifest); + if (info.path.equals(BACKUP_MANIFEST_FILENAME)) { + Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures( + info); + RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy( + mBackupManagerService.getPackageManager(), allowApks, + info, signatures); + mManifestSignatures.put(info.packageName, signatures); + mPackagePolicies.put(pkg, restorePolicy); mPackageInstallers.put(pkg, info.installerPackageName); // We've read only the manifest content itself at this point, // so consume the footer before looping around to the next // input file tarBackupReader.skipTarPadding(info.size); mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg); - } else if (info.path.equals( - RefactoredBackupManagerService.BACKUP_METADATA_FILENAME)) { + } else if (info.path.equals(BACKUP_METADATA_FILENAME)) { // Metadata blobs! tarBackupReader.readMetadata(info); @@ -472,18 +512,17 @@ public class PerformAdbRestoreTask implements Runnable { // If we're in accept-if-apk state, then the first file we // see MUST be the apk. if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "APK file; installing"); + if (DEBUG) { + Slog.d(TAG, "APK file; installing"); } // Try to install the app. String installerName = mPackageInstallers.get(pkg); boolean isSuccessfullyInstalled = RestoreUtils.installApk( - instream, backupManagerService.getPackageManager(), + instream, mBackupManagerService.getPackageManager(), mInstallObserver, mDeleteObserver, mManifestSignatures, mPackagePolicies, info, installerName, - bytesReadListener, backupManagerService.getDataDir() - ); + bytesReadListener, mBackupManagerService.getDataDir() + ); // good to go; promote to ACCEPT mPackagePolicies.put(pkg, isSuccessfullyInstalled ? RestorePolicy.ACCEPT @@ -503,9 +542,8 @@ public class PerformAdbRestoreTask implements Runnable { case ACCEPT: if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "apk present but ACCEPT"); + if (DEBUG) { + Slog.d(TAG, "apk present but ACCEPT"); } // we can take the data without the apk, so we // *want* to do so. skip the apk by declaring this @@ -519,8 +557,7 @@ public class PerformAdbRestoreTask implements Runnable { // Something has gone dreadfully wrong when determining // the restore policy from the manifest. Ignore the // rest of this package's data. - Slog.e(RefactoredBackupManagerService.TAG, - "Invalid policy from manifest"); + Slog.e(TAG, "Invalid policy from manifest"); okay = false; mPackagePolicies.put(pkg, RestorePolicy.IGNORE); break; @@ -533,19 +570,17 @@ public class PerformAdbRestoreTask implements Runnable { // If the policy is satisfied, go ahead and set up to pipe the // data to the agent. - if (RefactoredBackupManagerService.DEBUG && okay && mAgent != null) { - Slog.i(RefactoredBackupManagerService.TAG, - "Reusing existing agent instance"); + if (DEBUG && okay && mAgent != null) { + Slog.i(TAG, "Reusing existing agent instance"); } if (okay && mAgent == null) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Need to launch agent for " + pkg); + if (DEBUG) { + Slog.d(TAG, "Need to launch agent for " + pkg); } try { mTargetApp = - backupManagerService.getPackageManager().getApplicationInfo( + mBackupManagerService.getPackageManager().getApplicationInfo( pkg, 0); // If we haven't sent any data to this app yet, we probably @@ -555,29 +590,28 @@ public class PerformAdbRestoreTask implements Runnable { // responsible for coherently managing a full // restore. if (mTargetApp.backupAgentName == null) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, + if (DEBUG) { + Slog.d(TAG, "Clearing app data preparatory to full restore"); } - backupManagerService.clearApplicationDataSynchronous(pkg); + mBackupManagerService.clearApplicationDataSynchronous(pkg); } else { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, "backup agent (" + if (DEBUG) { + Slog.d(TAG, "backup agent (" + mTargetApp.backupAgentName + ") => no clear"); } } mClearedPackages.add(pkg); } else { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "We've initialized this app already; no clear " - + "required"); + if (DEBUG) { + Slog.d(TAG, "We've initialized this app already; no clear " + + "required"); } } // All set; now set up the IPC and launch the agent setUpPipes(); - mAgent = backupManagerService.bindToAgentSynchronous(mTargetApp, + mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp, ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL); mAgentPackage = pkg; } catch (IOException e) { @@ -587,9 +621,7 @@ public class PerformAdbRestoreTask implements Runnable { } if (mAgent == null) { - Slog.e( - RefactoredBackupManagerService.TAG, - "Unable to create agent for " + pkg); + Slog.e(TAG, "Unable to create agent for " + pkg); okay = false; tearDownPipes(); mPackagePolicies.put(pkg, RestorePolicy.IGNORE); @@ -599,7 +631,7 @@ public class PerformAdbRestoreTask implements Runnable { // Sanity check: make sure we never give data to the wrong app. This // should never happen but a little paranoia here won't go amiss. if (okay && !pkg.equals(mAgentPackage)) { - Slog.e(RefactoredBackupManagerService.TAG, "Restoring data for " + pkg + Slog.e(TAG, "Restoring data for " + pkg + " but agent is for " + mAgentPackage); okay = false; } @@ -612,67 +644,60 @@ public class PerformAdbRestoreTask implements Runnable { boolean agentSuccess = true; long toCopy = info.size; try { - backupManagerService + mBackupManagerService .prepareOperationTimeout(token, - RefactoredBackupManagerService.TIMEOUT_RESTORE_INTERVAL, + TIMEOUT_RESTORE_INTERVAL, null, - RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT); + OP_TYPE_RESTORE_WAIT); if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Restoring OBB file for " + pkg - + " : " + info.path); + if (DEBUG) { + Slog.d(TAG, "Restoring OBB file for " + pkg + + " : " + info.path); } mObbConnection.restoreObbFile(pkg, mPipes[0], info.size, info.type, info.path, info.mode, info.mtime, token, - backupManagerService.getBackupManagerBinder()); + mBackupManagerService.getBackupManagerBinder()); } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Restoring key-value file for " + pkg - + " : " + info.path); + if (DEBUG) { + Slog.d(TAG, "Restoring key-value file for " + pkg + + " : " + info.path); } KeyValueAdbRestoreEngine restoreEngine = new KeyValueAdbRestoreEngine( - backupManagerService, - backupManagerService.getDataDir(), info, mPipes[0], + mBackupManagerService, + mBackupManagerService.getDataDir(), info, mPipes[0], mAgent, token); new Thread(restoreEngine, "restore-key-value-runner").start(); } else { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Invoking agent to restore file " - + info.path); + if (DEBUG) { + Slog.d(TAG, "Invoking agent to restore file " + info.path); } // fire up the app's agent listening on the socket. If // the agent is running in the system process we can't // just invoke it asynchronously, so we provide a thread // for it here. if (mTargetApp.processName.equals("system")) { - Slog.d(RefactoredBackupManagerService.TAG, - "system process agent - spinning a thread"); + Slog.d(TAG, "system process agent - spinning a thread"); RestoreFileRunnable runner = new RestoreFileRunnable( - backupManagerService, mAgent, info, mPipes[0], token); + mBackupManagerService, mAgent, info, mPipes[0], token); new Thread(runner, "restore-sys-runner").start(); } else { mAgent.doRestoreFile(mPipes[0], info.size, info.type, info.domain, info.path, info.mode, info.mtime, - token, backupManagerService.getBackupManagerBinder()); + token, mBackupManagerService.getBackupManagerBinder()); } } } catch (IOException e) { // couldn't dup the socket for a process-local restore - Slog.d(RefactoredBackupManagerService.TAG, - "Couldn't establish restore"); + Slog.d(TAG, "Couldn't establish restore"); agentSuccess = false; okay = false; } catch (RemoteException e) { // whoops, remote entity went away. We'll eat the content // ourselves, then, and not copy it over. - Slog.e(RefactoredBackupManagerService.TAG, - "Agent crashed during full restore"); + Slog.e(TAG, "Agent crashed during full restore"); agentSuccess = false; okay = false; } @@ -700,8 +725,7 @@ public class PerformAdbRestoreTask implements Runnable { try { pipe.write(buffer, 0, nRead); } catch (IOException e) { - Slog.e(RefactoredBackupManagerService.TAG, - "Failed to write to restore pipe", e); + Slog.e(TAG, "Failed to write to restore pipe", e); pipeOkay = false; } } @@ -713,17 +737,16 @@ public class PerformAdbRestoreTask implements Runnable { // and now that we've sent it all, wait for the remote // side to acknowledge receipt - agentSuccess = backupManagerService.waitUntilOperationComplete(token); + agentSuccess = mBackupManagerService.waitUntilOperationComplete(token); } // okay, if the remote end failed at any point, deal with // it by ignoring the rest of the restore on it if (!agentSuccess) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "Agent failure restoring " + pkg + "; now ignoring"); + if (DEBUG) { + Slog.d(TAG, "Agent failure restoring " + pkg + "; now ignoring"); } - backupManagerService.getBackupHandler().removeMessages( + mBackupManagerService.getBackupHandler().removeMessages( MSG_RESTORE_OPERATION_TIMEOUT); tearDownPipes(); tearDownAgent(mTargetApp, false); @@ -735,8 +758,8 @@ public class PerformAdbRestoreTask implements Runnable { // ignored package: skip to the next tar stream entry by // reading and discarding this file. if (!okay) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, "[discarding file content]"); + if (DEBUG) { + Slog.d(TAG, "[discarding file content]"); } long bytesToConsume = (info.size + 511) & ~511; while (bytesToConsume > 0) { @@ -755,9 +778,8 @@ public class PerformAdbRestoreTask implements Runnable { } } } catch (IOException e) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.w(RefactoredBackupManagerService.TAG, "io exception on restore socket read", - e); + if (DEBUG) { + Slog.w(TAG, "io exception on restore socket read", e); } // treat as EOF info = null; @@ -768,8 +790,8 @@ public class PerformAdbRestoreTask implements Runnable { private static boolean isCanonicalFilePath(String path) { if (path.contains("..") || path.contains("//")) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.w(RefactoredBackupManagerService.TAG, "Dropping invalid path " + path); + if (MORE_DEBUG) { + Slog.w(TAG, "Dropping invalid path " + path); } return false; } @@ -777,11 +799,11 @@ public class PerformAdbRestoreTask implements Runnable { return true; } - void setUpPipes() throws IOException { + private void setUpPipes() throws IOException { mPipes = ParcelFileDescriptor.createPipe(); } - void tearDownPipes() { + private void tearDownPipes() { if (mPipes != null) { try { mPipes[0].close(); @@ -789,49 +811,46 @@ public class PerformAdbRestoreTask implements Runnable { mPipes[1].close(); mPipes[1] = null; } catch (IOException e) { - Slog.w(RefactoredBackupManagerService.TAG, "Couldn't close agent pipes", e); + Slog.w(TAG, "Couldn't close agent pipes", e); } mPipes = null; } } - void tearDownAgent(ApplicationInfo app, boolean doRestoreFinished) { + private void tearDownAgent(ApplicationInfo app, boolean doRestoreFinished) { if (mAgent != null) { try { // In the adb restore case, we do restore-finished here if (doRestoreFinished) { - final int token = backupManagerService.generateRandomIntegerToken(); + final int token = mBackupManagerService.generateRandomIntegerToken(); final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch( - backupManagerService, token); - backupManagerService + mBackupManagerService, token); + mBackupManagerService .prepareOperationTimeout(token, - RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL, + TIMEOUT_FULL_BACKUP_INTERVAL, latch, - RefactoredBackupManagerService.OP_TYPE_RESTORE_WAIT); + OP_TYPE_RESTORE_WAIT); if (mTargetApp.processName.equals("system")) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.d(RefactoredBackupManagerService.TAG, - "system agent - restoreFinished on thread"); + if (MORE_DEBUG) { + Slog.d(TAG, "system agent - restoreFinished on thread"); } - Runnable runner = new RestoreFinishedRunnable(mAgent, token); + Runnable runner = new RestoreFinishedRunnable(mAgent, token, + mBackupManagerService); new Thread(runner, "restore-sys-finished-runner").start(); } else { mAgent.doRestoreFinished(token, - backupManagerService.getBackupManagerBinder()); + mBackupManagerService.getBackupManagerBinder()); } latch.await(); } - backupManagerService.tearDownAgentAndKill(app); + mBackupManagerService.tearDownAgentAndKill(app); } catch (RemoteException e) { - Slog.d(RefactoredBackupManagerService.TAG, "Lost app trying to shut down"); + Slog.d(TAG, "Lost app trying to shut down"); } mAgent = null; } } - final RestoreInstallObserver mInstallObserver = new RestoreInstallObserver(); - final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver(); - } diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java index 12c1af2bbd4dd..b97b56cecc047 100644 --- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java +++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java @@ -34,6 +34,10 @@ import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH; import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER; +import static com.android.server.backup.RefactoredBackupManagerService.DEBUG; +import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBUG; +import static com.android.server.backup.RefactoredBackupManagerService.TAG; + import android.app.backup.BackupAgent; import android.app.backup.BackupManagerMonitor; import android.app.backup.FullBackup; @@ -54,7 +58,6 @@ import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.HashMap; /** * Utility methods to read backup tar file. @@ -150,23 +153,20 @@ public class TarBackupReader { case '5': { info.type = BackupAgent.TYPE_DIRECTORY; if (info.size != 0) { - Slog.w(RefactoredBackupManagerService.TAG, - "Directory entry with nonzero size in header"); + Slog.w(TAG, "Directory entry with nonzero size in header"); info.size = 0; } break; } case 0: { // presume EOF - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.w(RefactoredBackupManagerService.TAG, - "Saw type=0 in tar header block, info=" + info); + if (MORE_DEBUG) { + Slog.w(TAG, "Saw type=0 in tar header block, info=" + info); } return null; } default: { - Slog.e(RefactoredBackupManagerService.TAG, - "Unknown tar entity type: " + typeChar); + Slog.e(TAG, "Unknown tar entity type: " + typeChar); throw new IOException("Unknown entity type " + typeChar); } } @@ -180,9 +180,8 @@ public class TarBackupReader { info.path = info.path.substring(FullBackup.SHARED_PREFIX.length()); info.packageName = RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE; info.domain = FullBackup.SHARED_STORAGE_TOKEN; - if (RefactoredBackupManagerService.DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, - "File in shared storage: " + info.path); + if (DEBUG) { + Slog.i(TAG, "File in shared storage: " + info.path); } } else if (FullBackup.APPS_PREFIX.regionMatches(0, info.path, 0, FullBackup.APPS_PREFIX.length())) { @@ -214,10 +213,9 @@ public class TarBackupReader { } } } catch (IOException e) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.e(RefactoredBackupManagerService.TAG, - "Parse error in header: " + e.getMessage()); - if (RefactoredBackupManagerService.MORE_DEBUG) { + if (DEBUG) { + Slog.e(TAG, "Parse error in header: " + e.getMessage()); + if (MORE_DEBUG) { hexLog(block); } } @@ -242,41 +240,35 @@ public class TarBackupReader { if (size <= 0) { throw new IllegalArgumentException("size must be > 0"); } - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, " ... readExactly(" + size + ") called"); + if (MORE_DEBUG) { + Slog.i(TAG, " ... readExactly(" + size + ") called"); } int soFar = 0; while (soFar < size) { int nRead = in.read(buffer, offset + soFar, size - soFar); if (nRead <= 0) { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.w(RefactoredBackupManagerService.TAG, - "- wanted exactly " + size + " but got only " + soFar); + if (MORE_DEBUG) { + Slog.w(TAG, "- wanted exactly " + size + " but got only " + soFar); } break; } soFar += nRead; - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.v(RefactoredBackupManagerService.TAG, - " + got " + nRead + "; now wanting " + (size - soFar)); + if (MORE_DEBUG) { + Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soFar)); } } return soFar; } /** - * Reads app manifest and returns a policy constant. + * Reads app manifest, filling version and hasApk fields in the metadata, and returns array of + * signatures. * - * @param packageManager - PackageManager instance. - * @param allowApks - allow restore set to include apks. - * @param manifestSignatures - parsed signatures will be put here. * @param info - file metadata. - * @return a policy constant. + * @return array of signatures or null, in case of an error. * @throws IOException in case of an error. */ - public RestorePolicy readAppManifest(PackageManager packageManager, - boolean allowApks, HashMap manifestSignatures, - FileMetadata info) + public Signature[] readAppManifestAndReturnSignatures(FileMetadata info) throws IOException { // Fail on suspiciously large manifest files if (info.size > 64 * 1024) { @@ -284,9 +276,9 @@ public class TarBackupReader { } byte[] buffer = new byte[(int) info.size]; - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, - " readAppManifest() looking for " + info.size + " bytes"); + if (MORE_DEBUG) { + Slog.i(TAG, + " readAppManifestAndReturnSignatures() looking for " + info.size + " bytes"); } if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) { mBytesReadListener.onBytesRead(info.size); @@ -294,7 +286,6 @@ public class TarBackupReader { throw new IOException("Unexpected EOF in manifest"); } - RestorePolicy policy = RestorePolicy.IGNORE; String[] str = new String[1]; int offset = 0; @@ -307,7 +298,7 @@ public class TarBackupReader { // TODO: handle if (manifestPackage.equals(info.packageName)) { offset = extractLine(buffer, offset, str); - version = Integer.parseInt(str[0]); // app version + info.version = Integer.parseInt(str[0]); // app version offset = extractLine(buffer, offset, str); // This is the platform version, which we don't use, but we parse it // as a safety against corruption in the manifest. @@ -315,7 +306,7 @@ public class TarBackupReader { offset = extractLine(buffer, offset, str); info.installerPackageName = (str[0].length() > 0) ? str[0] : null; offset = extractLine(buffer, offset, str); - boolean hasApk = str[0].equals("1"); + info.hasApk = str[0].equals("1"); offset = extractLine(buffer, offset, str); int numSigs = Integer.parseInt(str[0]); if (numSigs > 0) { @@ -324,162 +315,9 @@ public class TarBackupReader { offset = extractLine(buffer, offset, str); sigs[i] = new Signature(str[0]); } - manifestSignatures.put(info.packageName, sigs); - - // Okay, got the manifest info we need... - try { - PackageInfo pkgInfo = packageManager.getPackageInfo( - info.packageName, PackageManager.GET_SIGNATURES); - // Fall through to IGNORE if the app explicitly disallows backup - final int flags = pkgInfo.applicationInfo.flags; - if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) { - // Restore system-uid-space packages only if they have - // defined a custom backup agent - if ((pkgInfo.applicationInfo.uid - >= Process.FIRST_APPLICATION_UID) - || (pkgInfo.applicationInfo.backupAgentName != null)) { - // Verify signatures against any installed version; if they - // don't match, then we fall though and ignore the data. The - // signatureMatch() method explicitly ignores the signature - // check for packages installed on the system partition, because - // such packages are signed with the platform cert instead of - // the app developer's cert, so they're different on every - // device. - if (AppBackupUtils.signaturesMatch(sigs, - pkgInfo)) { - if ((pkgInfo.applicationInfo.flags - & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) { - Slog.i(RefactoredBackupManagerService.TAG, - "Package has restoreAnyVersion; taking data"); - mMonitor = BackupManagerMonitorUtils.monitorEvent( - mMonitor, - LOG_EVENT_ID_RESTORE_ANY_VERSION, - pkgInfo, - LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - null); - policy = RestorePolicy.ACCEPT; - } else if (pkgInfo.versionCode >= version) { - Slog.i(RefactoredBackupManagerService.TAG, - "Sig + version match; taking data"); - policy = RestorePolicy.ACCEPT; - mMonitor = BackupManagerMonitorUtils.monitorEvent( - mMonitor, - LOG_EVENT_ID_VERSIONS_MATCH, - pkgInfo, - LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - null); - } else { - // The data is from a newer version of the app than - // is presently installed. That means we can only - // use it if the matching apk is also supplied. - if (allowApks) { - Slog.i(RefactoredBackupManagerService.TAG, - "Data version " + version - + " is newer than installed " - + "version " - + pkgInfo.versionCode - + " - requiring apk"); - policy = RestorePolicy.ACCEPT_IF_APK; - } else { - Slog.i(RefactoredBackupManagerService.TAG, - "Data requires newer version " - + version + "; ignoring"); - mMonitor = BackupManagerMonitorUtils - .monitorEvent(mMonitor, - LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER, - pkgInfo, - LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - BackupManagerMonitorUtils - .putMonitoringExtra( - null, - EXTRA_LOG_OLD_VERSION, - version)); - - policy = RestorePolicy.IGNORE; - } - } - } else { - Slog.w(RefactoredBackupManagerService.TAG, - "Restore manifest signatures do not match " - + "installed application for " - + info.packageName); - mMonitor = BackupManagerMonitorUtils.monitorEvent( - mMonitor, - LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH, - pkgInfo, - LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - null); - } - } else { - Slog.w(RefactoredBackupManagerService.TAG, - "Package " + info.packageName - + " is system level with no agent"); - mMonitor = BackupManagerMonitorUtils.monitorEvent( - - - mMonitor, - LOG_EVENT_ID_SYSTEM_APP_NO_AGENT, - pkgInfo, - LOG_EVENT_CATEGORY_AGENT, - null); - } - } else { - if (RefactoredBackupManagerService.DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, - "Restore manifest from " - + info.packageName + " but allowBackup=false"); - } - mMonitor = BackupManagerMonitorUtils.monitorEvent( - mMonitor, - LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE, - pkgInfo, - LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - null); - } - } catch (PackageManager.NameNotFoundException e) { - // Okay, the target app isn't installed. We can process - // the restore properly only if the dataset provides the - // apk file and we can successfully install it. - if (allowApks) { - if (RefactoredBackupManagerService.DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, - "Package " + info.packageName - + " not installed; requiring apk in dataset"); - } - policy = RestorePolicy.ACCEPT_IF_APK; - } else { - policy = RestorePolicy.IGNORE; - } - Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra( - null, - EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName); - monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra( - monitoringExtras, - EXTRA_LOG_POLICY_ALLOW_APKS, allowApks); - mMonitor = BackupManagerMonitorUtils.monitorEvent( - mMonitor, - LOG_EVENT_ID_APK_NOT_INSTALLED, - null, - LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - monitoringExtras); - } - - if (policy == RestorePolicy.ACCEPT_IF_APK && !hasApk) { - Slog.i(RefactoredBackupManagerService.TAG, - "Cannot restore package " + info.packageName - + " without the matching .apk"); - mMonitor = BackupManagerMonitorUtils.monitorEvent( - mMonitor, - LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK, - null, - LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, - BackupManagerMonitorUtils.putMonitoringExtra(null, - EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName)); - } + return sigs; } else { - Slog.i(RefactoredBackupManagerService.TAG, - "Missing signature on backed-up package " - + info.packageName); + Slog.i(TAG, "Missing signature on backed-up package " + info.packageName); mMonitor = BackupManagerMonitorUtils.monitorEvent( mMonitor, LOG_EVENT_ID_MISSING_SIGNATURE, @@ -489,9 +327,8 @@ public class TarBackupReader { EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName)); } } else { - Slog.i(RefactoredBackupManagerService.TAG, - "Expected package " + info.packageName - + " but restore manifest claims " + manifestPackage); + Slog.i(TAG, "Expected package " + info.packageName + + " but restore manifest claims " + manifestPackage); Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName); monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra( @@ -505,9 +342,8 @@ public class TarBackupReader { monitoringExtras); } } else { - Slog.i(RefactoredBackupManagerService.TAG, - "Unknown restore manifest version " + version - + " for package " + info.packageName); + Slog.i(TAG, "Unknown restore manifest version " + version + + " for package " + info.packageName); Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName); monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras, @@ -521,8 +357,7 @@ public class TarBackupReader { } } catch (NumberFormatException e) { - Slog.w(RefactoredBackupManagerService.TAG, - "Corrupt restore manifest for package " + info.packageName); + Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName); mMonitor = BackupManagerMonitorUtils.monitorEvent( mMonitor, BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST, @@ -531,7 +366,166 @@ public class TarBackupReader { BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName)); } catch (IllegalArgumentException e) { - Slog.w(RefactoredBackupManagerService.TAG, e.getMessage()); + Slog.w(TAG, e.getMessage()); + } + + return null; + } + + /** + * Chooses restore policy. + * + * @param packageManager - PackageManager instance. + * @param allowApks - allow restore set to include apks. + * @param info - file metadata. + * @param signatures - array of signatures parsed from backup file. + * @return a restore policy constant. + */ + public RestorePolicy chooseRestorePolicy(PackageManager packageManager, + boolean allowApks, FileMetadata info, Signature[] signatures) { + if (signatures == null) { + return RestorePolicy.IGNORE; + } + + RestorePolicy policy = RestorePolicy.IGNORE; + + // Okay, got the manifest info we need... + try { + PackageInfo pkgInfo = packageManager.getPackageInfo( + info.packageName, PackageManager.GET_SIGNATURES); + // Fall through to IGNORE if the app explicitly disallows backup + final int flags = pkgInfo.applicationInfo.flags; + if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) { + // Restore system-uid-space packages only if they have + // defined a custom backup agent + if ((pkgInfo.applicationInfo.uid + >= Process.FIRST_APPLICATION_UID) + || (pkgInfo.applicationInfo.backupAgentName != null)) { + // Verify signatures against any installed version; if they + // don't match, then we fall though and ignore the data. The + // signatureMatch() method explicitly ignores the signature + // check for packages installed on the system partition, because + // such packages are signed with the platform cert instead of + // the app developer's cert, so they're different on every + // device. + if (AppBackupUtils.signaturesMatch(signatures, pkgInfo)) { + if ((pkgInfo.applicationInfo.flags + & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) { + Slog.i(TAG, "Package has restoreAnyVersion; taking data"); + mMonitor = BackupManagerMonitorUtils.monitorEvent( + mMonitor, + LOG_EVENT_ID_RESTORE_ANY_VERSION, + pkgInfo, + LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + null); + policy = RestorePolicy.ACCEPT; + } else if (pkgInfo.versionCode >= info.version) { + Slog.i(TAG, "Sig + version match; taking data"); + policy = RestorePolicy.ACCEPT; + mMonitor = BackupManagerMonitorUtils.monitorEvent( + mMonitor, + LOG_EVENT_ID_VERSIONS_MATCH, + pkgInfo, + LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + null); + } else { + // The data is from a newer version of the app than + // is presently installed. That means we can only + // use it if the matching apk is also supplied. + if (allowApks) { + Slog.i(TAG, "Data version " + info.version + + " is newer than installed " + + "version " + + pkgInfo.versionCode + + " - requiring apk"); + policy = RestorePolicy.ACCEPT_IF_APK; + } else { + Slog.i(TAG, "Data requires newer version " + + info.version + "; ignoring"); + mMonitor = BackupManagerMonitorUtils + .monitorEvent(mMonitor, + LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER, + pkgInfo, + LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + BackupManagerMonitorUtils + .putMonitoringExtra( + null, + EXTRA_LOG_OLD_VERSION, + info.version)); + + policy = RestorePolicy.IGNORE; + } + } + } else { + Slog.w(TAG, "Restore manifest signatures do not match " + + "installed application for " + + info.packageName); + mMonitor = BackupManagerMonitorUtils.monitorEvent( + mMonitor, + LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH, + pkgInfo, + LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + null); + } + } else { + Slog.w(TAG, "Package " + info.packageName + + " is system level with no agent"); + mMonitor = BackupManagerMonitorUtils.monitorEvent( + mMonitor, + LOG_EVENT_ID_SYSTEM_APP_NO_AGENT, + pkgInfo, + LOG_EVENT_CATEGORY_AGENT, + null); + } + } else { + if (DEBUG) { + Slog.i(TAG, + "Restore manifest from " + info.packageName + " but allowBackup=false"); + } + mMonitor = BackupManagerMonitorUtils.monitorEvent( + mMonitor, + LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE, + pkgInfo, + LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + null); + } + } catch (PackageManager.NameNotFoundException e) { + // Okay, the target app isn't installed. We can process + // the restore properly only if the dataset provides the + // apk file and we can successfully install it. + if (allowApks) { + if (DEBUG) { + Slog.i(TAG, "Package " + info.packageName + + " not installed; requiring apk in dataset"); + } + policy = RestorePolicy.ACCEPT_IF_APK; + } else { + policy = RestorePolicy.IGNORE; + } + Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra( + null, + EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName); + monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra( + monitoringExtras, + EXTRA_LOG_POLICY_ALLOW_APKS, allowApks); + mMonitor = BackupManagerMonitorUtils.monitorEvent( + mMonitor, + LOG_EVENT_ID_APK_NOT_INSTALLED, + null, + LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + monitoringExtras); + } + + if (policy == RestorePolicy.ACCEPT_IF_APK && !info.hasApk) { + Slog.i(TAG, "Cannot restore package " + info.packageName + + " without the matching .apk"); + mMonitor = BackupManagerMonitorUtils.monitorEvent( + mMonitor, + LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK, + null, + LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, + BackupManagerMonitorUtils.putMonitoringExtra(null, + EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName)); } return policy; @@ -543,9 +537,8 @@ public class TarBackupReader { long partial = (size + 512) % 512; if (partial > 0) { final int needed = 512 - (int) partial; - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, - "Skipping tar padding: " + needed + " bytes"); + if (MORE_DEBUG) { + Slog.i(TAG, "Skipping tar padding: " + needed + " bytes"); } byte[] buffer = new byte[needed]; if (readExactly(mInputStream, buffer, 0, needed) == needed) { @@ -588,24 +581,21 @@ public class TarBackupReader { int token = in.readInt(); int size = in.readInt(); if (size > 64 * 1024) { - throw new IOException("Datum " - + Integer.toHexString(token) + throw new IOException("Datum " + Integer.toHexString(token) + " too big; corrupt? size=" + info.size); } switch (token) { case RefactoredBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN: { - if (RefactoredBackupManagerService.MORE_DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, - "Got widget metadata for " + info.packageName); + if (MORE_DEBUG) { + Slog.i(TAG, "Got widget metadata for " + info.packageName); } mWidgetData = new byte[size]; in.read(mWidgetData); break; } default: { - if (RefactoredBackupManagerService.DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, "Ignoring metadata blob " - + Integer.toHexString(token) + if (DEBUG) { + Slog.i(TAG, "Ignoring metadata blob " + Integer.toHexString(token) + " for " + info.packageName); } in.skipBytes(size); @@ -614,9 +604,9 @@ public class TarBackupReader { } } } else { - Slog.w(RefactoredBackupManagerService.TAG, - "Metadata mismatch: package " + info.packageName - + " but widget data for " + pkg); + Slog.w(TAG, + "Metadata mismatch: package " + info.packageName + " but widget data for " + + pkg); Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName); @@ -630,7 +620,7 @@ public class TarBackupReader { monitoringExtras); } } else { - Slog.w(RefactoredBackupManagerService.TAG, "Unsupported metadata version " + version); + Slog.w(TAG, "Unsupported metadata version " + version); Bundle monitoringExtras = BackupManagerMonitorUtils .putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME, @@ -692,7 +682,7 @@ public class TarBackupReader { throws IOException { // We should never see a pax extended header larger than this if (info.size > 32 * 1024) { - Slog.w(RefactoredBackupManagerService.TAG, + Slog.w(TAG, "Suspiciously large pax header size " + info.size + " - aborting"); throw new IOException("Sanity failure: pax header size " + info.size); @@ -740,8 +730,8 @@ public class TarBackupReader { } else if ("size".equals(keyStr)) { info.size = Long.parseLong(valStr); } else { - if (RefactoredBackupManagerService.DEBUG) { - Slog.i(RefactoredBackupManagerService.TAG, "Unhandled pax key: " + key); + if (DEBUG) { + Slog.i(TAG, "Unhandled pax key: " + key); } } diff --git a/services/tests/servicestests/res/raw/backup_telephony_no_password b/services/tests/servicestests/res/raw/backup_telephony_no_password new file mode 100644 index 0000000000000..12b6cbfd168fd Binary files /dev/null and b/services/tests/servicestests/res/raw/backup_telephony_no_password differ diff --git a/services/tests/servicestests/res/raw/backup_telephony_with_password b/services/tests/servicestests/res/raw/backup_telephony_with_password new file mode 100644 index 0000000000000..ce847a33210da Binary files /dev/null and b/services/tests/servicestests/res/raw/backup_telephony_with_password differ diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java new file mode 100644 index 0000000000000..05f4c13392650 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/backup/restore/PerformAdbRestoreTaskTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.restore; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.frameworks.servicestests.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.io.InputStream; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class PerformAdbRestoreTaskTest { + private Context mContext; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getContext(); + } + + @Test + public void parseBackupFileAndReturnTarStream_backupNotEncrypted_returnsNonNull() + throws Exception { + InputStream inputStream = mContext.getResources().openRawResource( + R.raw.backup_telephony_no_password); + InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream( + inputStream, null); + + assertThat(tarInputStream).isNotNull(); + } + + @Test + public void + parseBackupFileAndReturnTarStream_backupEncryptedAndPasswordProvided_returnsNonNull() + throws Exception { + InputStream inputStream = mContext.getResources().openRawResource( + R.raw.backup_telephony_with_password); + InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream( + inputStream, "123"); + + assertThat(tarInputStream).isNotNull(); + } + + @Test + public void + parseBackupFileAndReturnTarStream_backupEncryptedAndPasswordNotProvided_returnsNull() + throws Exception { + InputStream inputStream = mContext.getResources().openRawResource( + R.raw.backup_telephony_with_password); + InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream( + inputStream, null); + + assertThat(tarInputStream).isNull(); + } + + @Test + public void + parseBackupFileAndReturnTarStream_backupEncryptedAndIncorrectPassword_returnsNull() + throws Exception { + InputStream inputStream = mContext.getResources().openRawResource( + R.raw.backup_telephony_with_password); + InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream( + inputStream, "1234"); + + assertThat(tarInputStream).isNull(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java new file mode 100644 index 0000000000000..63578b0ecb49e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/backup/utils/TarBackupReaderTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.utils; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.backup.IBackupManagerMonitor; +import android.content.Context; +import android.content.pm.Signature; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.frameworks.servicestests.R; +import com.android.server.backup.FileMetadata; +import com.android.server.backup.restore.PerformAdbRestoreTask; + +import com.google.common.hash.Hashing; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.InputStream; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TarBackupReaderTest { + private static final String TELEPHONY_PACKAGE_NAME = "com.android.providers.telephony"; + private static final String TELEPHONY_PACKAGE_SIGNATURE_SHA256 = + "301aa3cb081134501c45f1422abc66c24224fd5ded5fdc8f17e697176fd866aa"; + private static final int TELEPHONY_PACKAGE_VERSION = 25; + + @Mock + private BytesReadListener mBytesReadListenerMock; + @Mock + private IBackupManagerMonitor mBackupManagerMonitorMock; + private Context mContext; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getContext(); + } + + @Test + public void readTarHeaders_backupEncrypted_correctlyParsesFileMetadata() throws Exception { + InputStream inputStream = mContext.getResources().openRawResource( + R.raw.backup_telephony_with_password); + InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream( + inputStream, "123"); + TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream, + mBytesReadListenerMock, mBackupManagerMonitorMock); + FileMetadata fileMetadata = tarBackupReader.readTarHeaders(); + + assertThat(fileMetadata.packageName).isEqualTo(TELEPHONY_PACKAGE_NAME); + assertThat(fileMetadata.mode).isEqualTo(0600); + assertThat(fileMetadata.size).isEqualTo(2438); + assertThat(fileMetadata.domain).isEqualTo(null); + assertThat(fileMetadata.path).isEqualTo("_manifest"); + } + + @Test + public void readTarHeaders_backupNotEncrypted_correctlyParsesFileMetadata() throws Exception { + InputStream inputStream = mContext.getResources().openRawResource( + R.raw.backup_telephony_no_password); + InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream( + inputStream, null); + TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream, + mBytesReadListenerMock, mBackupManagerMonitorMock); + FileMetadata fileMetadata = tarBackupReader.readTarHeaders(); + + assertThat(fileMetadata.packageName).isEqualTo(TELEPHONY_PACKAGE_NAME); + assertThat(fileMetadata.mode).isEqualTo(0600); + assertThat(fileMetadata.size).isEqualTo(2438); + assertThat(fileMetadata.domain).isEqualTo(null); + assertThat(fileMetadata.path).isEqualTo("_manifest"); + } + + @Test + public void readAppManifest_backupEncrypted_correctlyParsesAppManifest() throws Exception { + InputStream inputStream = mContext.getResources().openRawResource( + R.raw.backup_telephony_with_password); + InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream( + inputStream, "123"); + TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream, + mBytesReadListenerMock, mBackupManagerMonitorMock); + FileMetadata fileMetadata = tarBackupReader.readTarHeaders(); + + Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(fileMetadata); + + assertThat(fileMetadata.version).isEqualTo(TELEPHONY_PACKAGE_VERSION); + assertThat(fileMetadata.hasApk).isFalse(); + assertThat(signatures).isNotNull(); + assertThat(signatures).hasLength(1); + + String signatureSha256 = Hashing.sha256().hashBytes(signatures[0].toByteArray()).toString(); + assertThat(signatureSha256).isEqualTo(TELEPHONY_PACKAGE_SIGNATURE_SHA256); + } + + @Test + public void readAppManifest_backupNotEncrypted_correctlyParsesAppManifest() throws Exception { + InputStream inputStream = mContext.getResources().openRawResource( + R.raw.backup_telephony_no_password); + InputStream tarInputStream = PerformAdbRestoreTask.parseBackupFileHeaderAndReturnTarStream( + inputStream, null); + TarBackupReader tarBackupReader = new TarBackupReader(tarInputStream, + mBytesReadListenerMock, mBackupManagerMonitorMock); + FileMetadata fileMetadata = tarBackupReader.readTarHeaders(); + + Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(fileMetadata); + + assertThat(fileMetadata.version).isEqualTo(TELEPHONY_PACKAGE_VERSION); + assertThat(fileMetadata.hasApk).isFalse(); + assertThat(signatures).isNotNull(); + assertThat(signatures).hasLength(1); + + String signatureSha256 = Hashing.sha256().hashBytes(signatures[0].toByteArray()).toString(); + assertThat(signatureSha256).isEqualTo(TELEPHONY_PACKAGE_SIGNATURE_SHA256); + } +} \ No newline at end of file