Home | History | Annotate | Download | only in restore
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.server.backup.restore;
     18 
     19 import static com.android.server.backup.BackupManagerService.BACKUP_FILE_HEADER_MAGIC;
     20 import static com.android.server.backup.BackupManagerService.BACKUP_FILE_VERSION;
     21 import static com.android.server.backup.BackupManagerService.BACKUP_MANIFEST_FILENAME;
     22 import static com.android.server.backup.BackupManagerService.BACKUP_METADATA_FILENAME;
     23 import static com.android.server.backup.BackupManagerService.DEBUG;
     24 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
     25 import static com.android.server.backup.BackupManagerService.OP_TYPE_RESTORE_WAIT;
     26 import static com.android.server.backup.BackupManagerService.SETTINGS_PACKAGE;
     27 import static com.android.server.backup.BackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
     28 import static com.android.server.backup.BackupManagerService.TAG;
     29 import static com.android.server.backup.BackupPasswordManager.PBKDF_CURRENT;
     30 import static com.android.server.backup.BackupPasswordManager.PBKDF_FALLBACK;
     31 import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
     32 
     33 import android.app.ApplicationThreadConstants;
     34 import android.app.IBackupAgent;
     35 import android.app.backup.FullBackup;
     36 import android.app.backup.IBackupManagerMonitor;
     37 import android.app.backup.IFullBackupRestoreObserver;
     38 import android.content.pm.ApplicationInfo;
     39 import android.content.pm.PackageInfo;
     40 import android.content.pm.PackageManager.NameNotFoundException;
     41 import android.content.pm.PackageManagerInternal;
     42 import android.content.pm.Signature;
     43 import android.os.Environment;
     44 import android.os.ParcelFileDescriptor;
     45 import android.os.RemoteException;
     46 import android.util.Slog;
     47 
     48 import com.android.internal.annotations.VisibleForTesting;
     49 import com.android.internal.util.Preconditions;
     50 import com.android.server.LocalServices;
     51 import com.android.server.backup.BackupAgentTimeoutParameters;
     52 import com.android.server.backup.BackupManagerService;
     53 import com.android.server.backup.FileMetadata;
     54 import com.android.server.backup.KeyValueAdbRestoreEngine;
     55 import com.android.server.backup.PackageManagerBackupAgent;
     56 import com.android.server.backup.fullbackup.FullBackupObbConnection;
     57 import com.android.server.backup.utils.BytesReadListener;
     58 import com.android.server.backup.utils.FullBackupRestoreObserverUtils;
     59 import com.android.server.backup.utils.PasswordUtils;
     60 import com.android.server.backup.utils.RestoreUtils;
     61 import com.android.server.backup.utils.TarBackupReader;
     62 
     63 import java.io.FileInputStream;
     64 import java.io.FileOutputStream;
     65 import java.io.IOException;
     66 import java.io.InputStream;
     67 import java.security.InvalidAlgorithmParameterException;
     68 import java.security.InvalidKeyException;
     69 import java.security.NoSuchAlgorithmException;
     70 import java.util.Arrays;
     71 import java.util.HashMap;
     72 import java.util.HashSet;
     73 import java.util.concurrent.atomic.AtomicBoolean;
     74 import java.util.zip.InflaterInputStream;
     75 
     76 import javax.crypto.BadPaddingException;
     77 import javax.crypto.Cipher;
     78 import javax.crypto.CipherInputStream;
     79 import javax.crypto.IllegalBlockSizeException;
     80 import javax.crypto.NoSuchPaddingException;
     81 import javax.crypto.SecretKey;
     82 import javax.crypto.spec.IvParameterSpec;
     83 import javax.crypto.spec.SecretKeySpec;
     84 
     85 public class PerformAdbRestoreTask implements Runnable {
     86 
     87     private final BackupManagerService mBackupManagerService;
     88     private final ParcelFileDescriptor mInputFile;
     89     private final String mCurrentPassword;
     90     private final String mDecryptPassword;
     91     private final AtomicBoolean mLatchObject;
     92     private final PackageManagerBackupAgent mPackageManagerBackupAgent;
     93     private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver();
     94 
     95     private IFullBackupRestoreObserver mObserver;
     96     private IBackupAgent mAgent;
     97     private String mAgentPackage;
     98     private ApplicationInfo mTargetApp;
     99     private FullBackupObbConnection mObbConnection = null;
    100     private ParcelFileDescriptor[] mPipes = null;
    101     private byte[] mWidgetData = null;
    102     private long mAppVersion;
    103 
    104     private long mBytes;
    105     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
    106 
    107     // Runner that can be placed on a separate thread to do in-process invocation
    108     // of the "restore finished" API asynchronously.  Used by adb restore.
    109     private static class RestoreFinishedRunnable implements Runnable {
    110 
    111         private final IBackupAgent mAgent;
    112         private final int mToken;
    113         private final BackupManagerService mBackupManagerService;
    114 
    115         RestoreFinishedRunnable(IBackupAgent agent, int token,
    116                 BackupManagerService backupManagerService) {
    117             mAgent = agent;
    118             mToken = token;
    119             mBackupManagerService = backupManagerService;
    120         }
    121 
    122         @Override
    123         public void run() {
    124             try {
    125                 mAgent.doRestoreFinished(mToken, mBackupManagerService.getBackupManagerBinder());
    126             } catch (RemoteException e) {
    127                 // never happens; this is used only for local binder calls
    128             }
    129         }
    130     }
    131 
    132     // possible handling states for a given package in the restore dataset
    133     private final HashMap<String, RestorePolicy> mPackagePolicies
    134             = new HashMap<>();
    135 
    136     // installer package names for each encountered app, derived from the manifests
    137     private final HashMap<String, String> mPackageInstallers = new HashMap<>();
    138 
    139     // Signatures for a given package found in its manifest file
    140     private final HashMap<String, Signature[]> mManifestSignatures
    141             = new HashMap<>();
    142 
    143     // Packages we've already wiped data on when restoring their first file
    144     private final HashSet<String> mClearedPackages = new HashSet<>();
    145 
    146     public PerformAdbRestoreTask(BackupManagerService backupManagerService,
    147             ParcelFileDescriptor fd, String curPassword, String decryptPassword,
    148             IFullBackupRestoreObserver observer, AtomicBoolean latch) {
    149         this.mBackupManagerService = backupManagerService;
    150         mInputFile = fd;
    151         mCurrentPassword = curPassword;
    152         mDecryptPassword = decryptPassword;
    153         mObserver = observer;
    154         mLatchObject = latch;
    155         mAgent = null;
    156         mPackageManagerBackupAgent = backupManagerService.makeMetadataAgent();
    157         mAgentPackage = null;
    158         mTargetApp = null;
    159         mObbConnection = new FullBackupObbConnection(backupManagerService);
    160         mAgentTimeoutParameters = Preconditions.checkNotNull(
    161                 backupManagerService.getAgentTimeoutParameters(),
    162                 "Timeout parameters cannot be null");
    163 
    164         // Which packages we've already wiped data on.  We prepopulate this
    165         // with a whitelist of packages known to be unclearable.
    166         mClearedPackages.add("android");
    167         mClearedPackages.add(SETTINGS_PACKAGE);
    168     }
    169 
    170     @Override
    171     public void run() {
    172         Slog.i(TAG, "--- Performing full-dataset restore ---");
    173         mObbConnection.establish();
    174         mObserver = FullBackupRestoreObserverUtils.sendStartRestore(mObserver);
    175 
    176         // Are we able to restore shared-storage data?
    177         if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
    178             mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE, RestorePolicy.ACCEPT);
    179         }
    180 
    181         FileInputStream rawInStream = null;
    182         try {
    183             if (!mBackupManagerService.backupPasswordMatches(mCurrentPassword)) {
    184                 if (DEBUG) {
    185                     Slog.w(TAG, "Backup password mismatch; aborting");
    186                 }
    187                 return;
    188             }
    189 
    190             mBytes = 0;
    191 
    192             rawInStream = new FileInputStream(mInputFile.getFileDescriptor());
    193 
    194             InputStream tarInputStream = parseBackupFileHeaderAndReturnTarStream(rawInStream,
    195                     mDecryptPassword);
    196             if (tarInputStream == null) {
    197                 // There was an error reading the backup file, which is already handled and logged.
    198                 // Just abort.
    199                 return;
    200             }
    201 
    202             byte[] buffer = new byte[32 * 1024];
    203             boolean didRestore;
    204             do {
    205                 didRestore = restoreOneFile(tarInputStream, false /* mustKillAgent */, buffer,
    206                         null /* onlyPackage */, true /* allowApks */,
    207                         mBackupManagerService.generateRandomIntegerToken(), null /* monitor */);
    208             } while (didRestore);
    209 
    210             if (MORE_DEBUG) {
    211                 Slog.v(TAG, "Done consuming input tarfile, total bytes=" + mBytes);
    212             }
    213         } catch (IOException e) {
    214             Slog.e(TAG, "Unable to read restore input");
    215         } finally {
    216             tearDownPipes();
    217             tearDownAgent(mTargetApp, true);
    218 
    219             try {
    220                 if (rawInStream != null) {
    221                     rawInStream.close();
    222                 }
    223                 mInputFile.close();
    224             } catch (IOException e) {
    225                 Slog.w(TAG, "Close of restore data pipe threw", e);
    226                 /* nothing we can do about this */
    227             }
    228             synchronized (mLatchObject) {
    229                 mLatchObject.set(true);
    230                 mLatchObject.notifyAll();
    231             }
    232             mObbConnection.tearDown();
    233             mObserver = FullBackupRestoreObserverUtils.sendEndRestore(mObserver);
    234             Slog.d(TAG, "Full restore pass complete.");
    235             mBackupManagerService.getWakelock().release();
    236         }
    237     }
    238 
    239     private static void readFullyOrThrow(InputStream in, byte[] buffer) throws IOException {
    240         int offset = 0;
    241         while (offset < buffer.length) {
    242             int bytesRead = in.read(buffer, offset, buffer.length - offset);
    243             if (bytesRead <= 0) {
    244                 throw new IOException("Couldn't fully read data");
    245             }
    246             offset += bytesRead;
    247         }
    248     }
    249 
    250     @VisibleForTesting
    251     public static InputStream parseBackupFileHeaderAndReturnTarStream(
    252             InputStream rawInputStream,
    253             String decryptPassword)
    254             throws IOException {
    255         // First, parse out the unencrypted/uncompressed header
    256         boolean compressed = false;
    257         InputStream preCompressStream = rawInputStream;
    258 
    259         boolean okay = false;
    260         final int headerLen = BACKUP_FILE_HEADER_MAGIC.length();
    261         byte[] streamHeader = new byte[headerLen];
    262         readFullyOrThrow(rawInputStream, streamHeader);
    263         byte[] magicBytes = BACKUP_FILE_HEADER_MAGIC.getBytes(
    264                 "UTF-8");
    265         if (Arrays.equals(magicBytes, streamHeader)) {
    266             // okay, header looks good.  now parse out the rest of the fields.
    267             String s = readHeaderLine(rawInputStream);
    268             final int archiveVersion = Integer.parseInt(s);
    269             if (archiveVersion <= BACKUP_FILE_VERSION) {
    270                 // okay, it's a version we recognize.  if it's version 1, we may need
    271                 // to try two different PBKDF2 regimes to compare checksums.
    272                 final boolean pbkdf2Fallback = (archiveVersion == 1);
    273 
    274                 s = readHeaderLine(rawInputStream);
    275                 compressed = (Integer.parseInt(s) != 0);
    276                 s = readHeaderLine(rawInputStream);
    277                 if (s.equals("none")) {
    278                     // no more header to parse; we're good to go
    279                     okay = true;
    280                 } else if (decryptPassword != null && decryptPassword.length() > 0) {
    281                     preCompressStream = decodeAesHeaderAndInitialize(
    282                             decryptPassword, s, pbkdf2Fallback,
    283                             rawInputStream);
    284                     if (preCompressStream != null) {
    285                         okay = true;
    286                     }
    287                 } else {
    288                     Slog.w(TAG, "Archive is encrypted but no password given");
    289                 }
    290             } else {
    291                 Slog.w(TAG, "Wrong header version: " + s);
    292             }
    293         } else {
    294             Slog.w(TAG, "Didn't read the right header magic");
    295         }
    296 
    297         if (!okay) {
    298             Slog.w(TAG, "Invalid restore data; aborting.");
    299             return null;
    300         }
    301 
    302         // okay, use the right stream layer based on compression
    303         return compressed ? new InflaterInputStream(preCompressStream) : preCompressStream;
    304     }
    305 
    306     private static String readHeaderLine(InputStream in) throws IOException {
    307         int c;
    308         StringBuilder buffer = new StringBuilder(80);
    309         while ((c = in.read()) >= 0) {
    310             if (c == '\n') {
    311                 break;   // consume and discard the newlines
    312             }
    313             buffer.append((char) c);
    314         }
    315         return buffer.toString();
    316     }
    317 
    318     private static InputStream attemptMasterKeyDecryption(String decryptPassword, String algorithm,
    319             byte[] userSalt, byte[] ckSalt,
    320             int rounds, String userIvHex, String masterKeyBlobHex, InputStream rawInStream,
    321             boolean doLog) {
    322         InputStream result = null;
    323 
    324         try {
    325             Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
    326             SecretKey userKey = PasswordUtils
    327                     .buildPasswordKey(algorithm, decryptPassword, userSalt,
    328                             rounds);
    329             byte[] IV = PasswordUtils.hexToByteArray(userIvHex);
    330             IvParameterSpec ivSpec = new IvParameterSpec(IV);
    331             c.init(Cipher.DECRYPT_MODE,
    332                     new SecretKeySpec(userKey.getEncoded(), "AES"),
    333                     ivSpec);
    334             byte[] mkCipher = PasswordUtils.hexToByteArray(masterKeyBlobHex);
    335             byte[] mkBlob = c.doFinal(mkCipher);
    336 
    337             // first, the master key IV
    338             int offset = 0;
    339             int len = mkBlob[offset++];
    340             IV = Arrays.copyOfRange(mkBlob, offset, offset + len);
    341             offset += len;
    342             // then the master key itself
    343             len = mkBlob[offset++];
    344             byte[] mk = Arrays.copyOfRange(mkBlob,
    345                     offset, offset + len);
    346             offset += len;
    347             // and finally the master key checksum hash
    348             len = mkBlob[offset++];
    349             byte[] mkChecksum = Arrays.copyOfRange(mkBlob,
    350                     offset, offset + len);
    351 
    352             // now validate the decrypted master key against the checksum
    353             byte[] calculatedCk = PasswordUtils.makeKeyChecksum(algorithm, mk, ckSalt,
    354                     rounds);
    355             if (Arrays.equals(calculatedCk, mkChecksum)) {
    356                 ivSpec = new IvParameterSpec(IV);
    357                 c.init(Cipher.DECRYPT_MODE,
    358                         new SecretKeySpec(mk, "AES"),
    359                         ivSpec);
    360                 // Only if all of the above worked properly will 'result' be assigned
    361                 result = new CipherInputStream(rawInStream, c);
    362             } else if (doLog) {
    363                 Slog.w(TAG, "Incorrect password");
    364             }
    365         } catch (InvalidAlgorithmParameterException e) {
    366             if (doLog) {
    367                 Slog.e(TAG, "Needed parameter spec unavailable!", e);
    368             }
    369         } catch (BadPaddingException e) {
    370             // This case frequently occurs when the wrong password is used to decrypt
    371             // the master key.  Use the identical "incorrect password" log text as is
    372             // used in the checksum failure log in order to avoid providing additional
    373             // information to an attacker.
    374             if (doLog) {
    375                 Slog.w(TAG, "Incorrect password");
    376             }
    377         } catch (IllegalBlockSizeException e) {
    378             if (doLog) {
    379                 Slog.w(TAG, "Invalid block size in master key");
    380             }
    381         } catch (NoSuchAlgorithmException e) {
    382             if (doLog) {
    383                 Slog.e(TAG, "Needed decryption algorithm unavailable!");
    384             }
    385         } catch (NoSuchPaddingException e) {
    386             if (doLog) {
    387                 Slog.e(TAG, "Needed padding mechanism unavailable!");
    388             }
    389         } catch (InvalidKeyException e) {
    390             if (doLog) {
    391                 Slog.w(TAG, "Illegal password; aborting");
    392             }
    393         }
    394 
    395         return result;
    396     }
    397 
    398     private static InputStream decodeAesHeaderAndInitialize(String decryptPassword,
    399             String encryptionName,
    400             boolean pbkdf2Fallback,
    401             InputStream rawInStream) {
    402         InputStream result = null;
    403         try {
    404             if (encryptionName.equals(PasswordUtils.ENCRYPTION_ALGORITHM_NAME)) {
    405 
    406                 String userSaltHex = readHeaderLine(rawInStream); // 5
    407                 byte[] userSalt = PasswordUtils.hexToByteArray(userSaltHex);
    408 
    409                 String ckSaltHex = readHeaderLine(rawInStream); // 6
    410                 byte[] ckSalt = PasswordUtils.hexToByteArray(ckSaltHex);
    411 
    412                 int rounds = Integer.parseInt(readHeaderLine(rawInStream)); // 7
    413                 String userIvHex = readHeaderLine(rawInStream); // 8
    414 
    415                 String masterKeyBlobHex = readHeaderLine(rawInStream); // 9
    416 
    417                 // decrypt the master key blob
    418                 result = attemptMasterKeyDecryption(decryptPassword, PBKDF_CURRENT,
    419                         userSalt, ckSalt, rounds, userIvHex, masterKeyBlobHex, rawInStream, false);
    420                 if (result == null && pbkdf2Fallback) {
    421                     result = attemptMasterKeyDecryption(
    422                             decryptPassword, PBKDF_FALLBACK, userSalt, ckSalt,
    423                             rounds, userIvHex, masterKeyBlobHex, rawInStream, true);
    424                 }
    425             } else {
    426                 Slog.w(TAG, "Unsupported encryption method: " + encryptionName);
    427             }
    428         } catch (NumberFormatException e) {
    429             Slog.w(TAG, "Can't parse restore data header");
    430         } catch (IOException e) {
    431             Slog.w(TAG, "Can't read input header");
    432         }
    433 
    434         return result;
    435     }
    436 
    437     boolean restoreOneFile(InputStream instream, boolean mustKillAgent, byte[] buffer,
    438             PackageInfo onlyPackage, boolean allowApks, int token, IBackupManagerMonitor monitor) {
    439         BytesReadListener bytesReadListener = new BytesReadListener() {
    440             @Override
    441             public void onBytesRead(long bytesRead) {
    442                 mBytes += bytesRead;
    443             }
    444         };
    445         TarBackupReader tarBackupReader = new TarBackupReader(instream,
    446                 bytesReadListener, monitor);
    447         FileMetadata info;
    448         try {
    449             info = tarBackupReader.readTarHeaders();
    450             if (info != null) {
    451                 if (MORE_DEBUG) {
    452                     info.dump();
    453                 }
    454 
    455                 final String pkg = info.packageName;
    456                 if (!pkg.equals(mAgentPackage)) {
    457                     // okay, change in package; set up our various
    458                     // bookkeeping if we haven't seen it yet
    459                     if (!mPackagePolicies.containsKey(pkg)) {
    460                         mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
    461                     }
    462 
    463                     // Clean up the previous agent relationship if necessary,
    464                     // and let the observer know we're considering a new app.
    465                     if (mAgent != null) {
    466                         if (DEBUG) {
    467                             Slog.d(TAG, "Saw new package; finalizing old one");
    468                         }
    469                         // Now we're really done
    470                         tearDownPipes();
    471                         tearDownAgent(mTargetApp, true);
    472                         mTargetApp = null;
    473                         mAgentPackage = null;
    474                     }
    475                 }
    476 
    477                 if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
    478                     Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
    479                             info);
    480                     // readAppManifestAndReturnSignatures() will have extracted the version from
    481                     // the manifest, so we save it to use in key-value restore later.
    482                     mAppVersion = info.version;
    483                     PackageManagerInternal pmi = LocalServices.getService(
    484                             PackageManagerInternal.class);
    485                     RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
    486                             mBackupManagerService.getPackageManager(), allowApks,
    487                             info, signatures, pmi);
    488                     mManifestSignatures.put(info.packageName, signatures);
    489                     mPackagePolicies.put(pkg, restorePolicy);
    490                     mPackageInstallers.put(pkg, info.installerPackageName);
    491                     // We've read only the manifest content itself at this point,
    492                     // so consume the footer before looping around to the next
    493                     // input file
    494                     tarBackupReader.skipTarPadding(info.size);
    495                     mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg);
    496                 } else if (info.path.equals(BACKUP_METADATA_FILENAME)) {
    497                     // Metadata blobs!
    498                     tarBackupReader.readMetadata(info);
    499 
    500                     // The following only exist because we want to keep refactoring as safe as
    501                     // possible, without changing too much.
    502                     // TODO: Refactor, so that there are no funny things like this.
    503                     // This is read during TarBackupReader.readMetadata().
    504                     mWidgetData = tarBackupReader.getWidgetData();
    505                     // This can be nulled during TarBackupReader.readMetadata().
    506                     monitor = tarBackupReader.getMonitor();
    507 
    508                     tarBackupReader.skipTarPadding(info.size);
    509                 } else {
    510                     // Non-manifest, so it's actual file data.  Is this a package
    511                     // we're ignoring?
    512                     boolean okay = true;
    513                     RestorePolicy policy = mPackagePolicies.get(pkg);
    514                     switch (policy) {
    515                         case IGNORE:
    516                             okay = false;
    517                             break;
    518 
    519                         case ACCEPT_IF_APK:
    520                             // If we're in accept-if-apk state, then the first file we
    521                             // see MUST be the apk.
    522                             if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
    523                                 if (DEBUG) {
    524                                     Slog.d(TAG, "APK file; installing");
    525                                 }
    526                                 // Try to install the app.
    527                                 String installerPackageName = mPackageInstallers.get(pkg);
    528                                 boolean isSuccessfullyInstalled = RestoreUtils.installApk(instream,
    529                                         mBackupManagerService.getContext(),
    530                                         mDeleteObserver, mManifestSignatures, mPackagePolicies,
    531                                         info, installerPackageName, bytesReadListener);
    532                                 // good to go; promote to ACCEPT
    533                                 mPackagePolicies.put(pkg, isSuccessfullyInstalled
    534                                         ? RestorePolicy.ACCEPT
    535                                         : RestorePolicy.IGNORE);
    536                                 // At this point we've consumed this file entry
    537                                 // ourselves, so just strip the tar footer and
    538                                 // go on to the next file in the input stream
    539                                 tarBackupReader.skipTarPadding(info.size);
    540                                 return true;
    541                             } else {
    542                                 // File data before (or without) the apk.  We can't
    543                                 // handle it coherently in this case so ignore it.
    544                                 mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
    545                                 okay = false;
    546                             }
    547                             break;
    548 
    549                         case ACCEPT:
    550                             if (info.domain.equals(FullBackup.APK_TREE_TOKEN)) {
    551                                 if (DEBUG) {
    552                                     Slog.d(TAG, "apk present but ACCEPT");
    553                                 }
    554                                 // we can take the data without the apk, so we
    555                                 // *want* to do so.  skip the apk by declaring this
    556                                 // one file not-okay without changing the restore
    557                                 // policy for the package.
    558                                 okay = false;
    559                             }
    560                             break;
    561 
    562                         default:
    563                             // Something has gone dreadfully wrong when determining
    564                             // the restore policy from the manifest.  Ignore the
    565                             // rest of this package's data.
    566                             Slog.e(TAG, "Invalid policy from manifest");
    567                             okay = false;
    568                             mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
    569                             break;
    570                     }
    571 
    572                     // The path needs to be canonical
    573                     if (!isCanonicalFilePath(info.path)) {
    574                         okay = false;
    575                     }
    576 
    577                     // If the policy is satisfied, go ahead and set up to pipe the
    578                     // data to the agent.
    579                     if (DEBUG && okay && mAgent != null) {
    580                         Slog.i(TAG, "Reusing existing agent instance");
    581                     }
    582                     if (okay && mAgent == null) {
    583                         if (DEBUG) {
    584                             Slog.d(TAG, "Need to launch agent for " + pkg);
    585                         }
    586 
    587                         try {
    588                             mTargetApp =
    589                                     mBackupManagerService.getPackageManager().getApplicationInfo(
    590                                             pkg, 0);
    591 
    592                             // If we haven't sent any data to this app yet, we probably
    593                             // need to clear it first.  Check that.
    594                             if (!mClearedPackages.contains(pkg)) {
    595                                 // apps with their own backup agents are
    596                                 // responsible for coherently managing a full
    597                                 // restore.
    598                                 if (mTargetApp.backupAgentName == null) {
    599                                     if (DEBUG) {
    600                                         Slog.d(TAG,
    601                                                 "Clearing app data preparatory to full restore");
    602                                     }
    603                                     mBackupManagerService.clearApplicationDataSynchronous(pkg, true);
    604                                 } else {
    605                                     if (DEBUG) {
    606                                         Slog.d(TAG, "backup agent ("
    607                                                 + mTargetApp.backupAgentName + ") => no clear");
    608                                     }
    609                                 }
    610                                 mClearedPackages.add(pkg);
    611                             } else {
    612                                 if (DEBUG) {
    613                                     Slog.d(TAG, "We've initialized this app already; no clear "
    614                                             + "required");
    615                                 }
    616                             }
    617 
    618                             // All set; now set up the IPC and launch the agent
    619                             setUpPipes();
    620                             mAgent = mBackupManagerService.bindToAgentSynchronous(mTargetApp,
    621                                     FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)
    622                                             ? ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
    623                                             : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL);
    624                             mAgentPackage = pkg;
    625                         } catch (IOException e) {
    626                             // fall through to error handling
    627                         } catch (NameNotFoundException e) {
    628                             // fall through to error handling
    629                         }
    630 
    631                         if (mAgent == null) {
    632                             Slog.e(TAG, "Unable to create agent for " + pkg);
    633                             okay = false;
    634                             tearDownPipes();
    635                             mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
    636                         }
    637                     }
    638 
    639                     // Sanity check: make sure we never give data to the wrong app.  This
    640                     // should never happen but a little paranoia here won't go amiss.
    641                     if (okay && !pkg.equals(mAgentPackage)) {
    642                         Slog.e(TAG, "Restoring data for " + pkg
    643                                 + " but agent is for " + mAgentPackage);
    644                         okay = false;
    645                     }
    646 
    647                     // At this point we have an agent ready to handle the full
    648                     // restore data as well as a pipe for sending data to
    649                     // that agent.  Tell the agent to start reading from the
    650                     // pipe.
    651                     if (okay) {
    652                         boolean agentSuccess = true;
    653                         long toCopy = info.size;
    654                         long restoreAgentTimeoutMillis =
    655                                 mAgentTimeoutParameters.getRestoreAgentTimeoutMillis();
    656                         try {
    657                             mBackupManagerService.prepareOperationTimeout(
    658                                     token, restoreAgentTimeoutMillis, null, OP_TYPE_RESTORE_WAIT);
    659 
    660                             if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
    661                                 if (DEBUG) {
    662                                     Slog.d(TAG, "Restoring OBB file for " + pkg
    663                                             + " : " + info.path);
    664                                 }
    665                                 mObbConnection.restoreObbFile(pkg, mPipes[0],
    666                                         info.size, info.type, info.path, info.mode,
    667                                         info.mtime, token,
    668                                         mBackupManagerService.getBackupManagerBinder());
    669                             } else if (FullBackup.KEY_VALUE_DATA_TOKEN.equals(info.domain)) {
    670                                 if (DEBUG) {
    671                                     Slog.d(TAG, "Restoring key-value file for " + pkg
    672                                             + " : " + info.path);
    673                                 }
    674                                 // Set the version saved from manifest entry.
    675                                 info.version = mAppVersion;
    676                                 KeyValueAdbRestoreEngine restoreEngine =
    677                                         new KeyValueAdbRestoreEngine(
    678                                                 mBackupManagerService,
    679                                                 mBackupManagerService.getDataDir(), info, mPipes[0],
    680                                                 mAgent, token);
    681                                 new Thread(restoreEngine, "restore-key-value-runner").start();
    682                             } else {
    683                                 if (DEBUG) {
    684                                     Slog.d(TAG, "Invoking agent to restore file " + info.path);
    685                                 }
    686                                 // fire up the app's agent listening on the socket.  If
    687                                 // the agent is running in the system process we can't
    688                                 // just invoke it asynchronously, so we provide a thread
    689                                 // for it here.
    690                                 if (mTargetApp.processName.equals("system")) {
    691                                     Slog.d(TAG, "system process agent - spinning a thread");
    692                                     RestoreFileRunnable runner = new RestoreFileRunnable(
    693                                             mBackupManagerService, mAgent, info, mPipes[0], token);
    694                                     new Thread(runner, "restore-sys-runner").start();
    695                                 } else {
    696                                     mAgent.doRestoreFile(mPipes[0], info.size, info.type,
    697                                             info.domain, info.path, info.mode, info.mtime,
    698                                             token, mBackupManagerService.getBackupManagerBinder());
    699                                 }
    700                             }
    701                         } catch (IOException e) {
    702                             // couldn't dup the socket for a process-local restore
    703                             Slog.d(TAG, "Couldn't establish restore");
    704                             agentSuccess = false;
    705                             okay = false;
    706                         } catch (RemoteException e) {
    707                             // whoops, remote entity went away.  We'll eat the content
    708                             // ourselves, then, and not copy it over.
    709                             Slog.e(TAG, "Agent crashed during full restore");
    710                             agentSuccess = false;
    711                             okay = false;
    712                         }
    713 
    714                         // Copy over the data if the agent is still good
    715                         if (okay) {
    716                             boolean pipeOkay = true;
    717                             FileOutputStream pipe = new FileOutputStream(
    718                                     mPipes[1].getFileDescriptor());
    719                             while (toCopy > 0) {
    720                                 int toRead = (toCopy > buffer.length)
    721                                         ? buffer.length : (int) toCopy;
    722                                 int nRead = instream.read(buffer, 0, toRead);
    723                                 if (nRead >= 0) {
    724                                     mBytes += nRead;
    725                                 }
    726                                 if (nRead <= 0) {
    727                                     break;
    728                                 }
    729                                 toCopy -= nRead;
    730 
    731                                 // send it to the output pipe as long as things
    732                                 // are still good
    733                                 if (pipeOkay) {
    734                                     try {
    735                                         pipe.write(buffer, 0, nRead);
    736                                     } catch (IOException e) {
    737                                         Slog.e(TAG, "Failed to write to restore pipe", e);
    738                                         pipeOkay = false;
    739                                     }
    740                                 }
    741                             }
    742 
    743                             // done sending that file!  Now we just need to consume
    744                             // the delta from info.size to the end of block.
    745                             tarBackupReader.skipTarPadding(info.size);
    746 
    747                             // and now that we've sent it all, wait for the remote
    748                             // side to acknowledge receipt
    749                             agentSuccess = mBackupManagerService.waitUntilOperationComplete(token);
    750                         }
    751 
    752                         // okay, if the remote end failed at any point, deal with
    753                         // it by ignoring the rest of the restore on it
    754                         if (!agentSuccess) {
    755                             if (DEBUG) {
    756                                 Slog.d(TAG, "Agent failure restoring " + pkg + "; now ignoring");
    757                             }
    758                             mBackupManagerService.getBackupHandler().removeMessages(
    759                                     MSG_RESTORE_OPERATION_TIMEOUT);
    760                             tearDownPipes();
    761                             tearDownAgent(mTargetApp, false);
    762                             mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
    763                         }
    764                     }
    765 
    766                     // Problems setting up the agent communication, or an already-
    767                     // ignored package: skip to the next tar stream entry by
    768                     // reading and discarding this file.
    769                     if (!okay) {
    770                         if (DEBUG) {
    771                             Slog.d(TAG, "[discarding file content]");
    772                         }
    773                         long bytesToConsume = (info.size + 511) & ~511;
    774                         while (bytesToConsume > 0) {
    775                             int toRead = (bytesToConsume > buffer.length)
    776                                     ? buffer.length : (int) bytesToConsume;
    777                             long nRead = instream.read(buffer, 0, toRead);
    778                             if (nRead >= 0) {
    779                                 mBytes += nRead;
    780                             }
    781                             if (nRead <= 0) {
    782                                 break;
    783                             }
    784                             bytesToConsume -= nRead;
    785                         }
    786                     }
    787                 }
    788             }
    789         } catch (IOException e) {
    790             if (DEBUG) {
    791                 Slog.w(TAG, "io exception on restore socket read", e);
    792             }
    793             // treat as EOF
    794             info = null;
    795         }
    796 
    797         return (info != null);
    798     }
    799 
    800     private static boolean isCanonicalFilePath(String path) {
    801         if (path.contains("..") || path.contains("//")) {
    802             if (MORE_DEBUG) {
    803                 Slog.w(TAG, "Dropping invalid path " + path);
    804             }
    805             return false;
    806         }
    807 
    808         return true;
    809     }
    810 
    811     private void setUpPipes() throws IOException {
    812         mPipes = ParcelFileDescriptor.createPipe();
    813     }
    814 
    815     private void tearDownPipes() {
    816         if (mPipes != null) {
    817             try {
    818                 mPipes[0].close();
    819                 mPipes[0] = null;
    820                 mPipes[1].close();
    821                 mPipes[1] = null;
    822             } catch (IOException e) {
    823                 Slog.w(TAG, "Couldn't close agent pipes", e);
    824             }
    825             mPipes = null;
    826         }
    827     }
    828 
    829     private void tearDownAgent(ApplicationInfo app, boolean doRestoreFinished) {
    830         if (mAgent != null) {
    831             try {
    832                 // In the adb restore case, we do restore-finished here
    833                 if (doRestoreFinished) {
    834                     final int token = mBackupManagerService.generateRandomIntegerToken();
    835                     long fullBackupAgentTimeoutMillis =
    836                             mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
    837                     final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(
    838                             mBackupManagerService, token);
    839                     mBackupManagerService.prepareOperationTimeout(
    840                             token, fullBackupAgentTimeoutMillis, latch, OP_TYPE_RESTORE_WAIT);
    841                     if (mTargetApp.processName.equals("system")) {
    842                         if (MORE_DEBUG) {
    843                             Slog.d(TAG, "system agent - restoreFinished on thread");
    844                         }
    845                         Runnable runner = new RestoreFinishedRunnable(mAgent, token,
    846                                 mBackupManagerService);
    847                         new Thread(runner, "restore-sys-finished-runner").start();
    848                     } else {
    849                         mAgent.doRestoreFinished(token,
    850                                 mBackupManagerService.getBackupManagerBinder());
    851                     }
    852 
    853                     latch.await();
    854                 }
    855 
    856                 mBackupManagerService.tearDownAgentAndKill(app);
    857             } catch (RemoteException e) {
    858                 Slog.d(TAG, "Lost app trying to shut down");
    859             }
    860             mAgent = null;
    861         }
    862     }
    863 
    864 }
    865