• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.utils;
18 
19 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
20 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION;
21 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_MANIFEST_PACKAGE_NAME;
22 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_OLD_VERSION;
23 import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_POLICY_ALLOW_APKS;
24 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT;
25 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY;
26 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED;
27 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK;
28 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE;
29 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE;
30 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH;
31 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE;
32 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION;
33 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT;
34 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
35 import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
36 
37 import static com.android.server.backup.BackupManagerService.DEBUG;
38 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
39 import static com.android.server.backup.BackupManagerService.TAG;
40 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
41 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_VERSION;
42 import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
43 import static com.android.server.backup.UserBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
44 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
45 
46 import android.app.backup.BackupAgent;
47 import android.app.backup.BackupManagerMonitor;
48 import android.app.backup.FullBackup;
49 import android.app.backup.IBackupManagerMonitor;
50 import android.content.pm.ApplicationInfo;
51 import android.content.pm.PackageInfo;
52 import android.content.pm.PackageManager;
53 import android.content.pm.PackageManagerInternal;
54 import android.content.pm.Signature;
55 import android.os.Bundle;
56 import android.os.UserHandle;
57 import android.util.Slog;
58 
59 import com.android.server.backup.FileMetadata;
60 import com.android.server.backup.restore.RestorePolicy;
61 
62 import java.io.ByteArrayInputStream;
63 import java.io.DataInputStream;
64 import java.io.IOException;
65 import java.io.InputStream;
66 
67 /**
68  * Utility methods to read backup tar file.
69  */
70 public class TarBackupReader {
71     private static final int TAR_HEADER_OFFSET_TYPE_CHAR = 156;
72     private static final int TAR_HEADER_LENGTH_PATH = 100;
73     private static final int TAR_HEADER_OFFSET_PATH = 0;
74     private static final int TAR_HEADER_LENGTH_PATH_PREFIX = 155;
75     private static final int TAR_HEADER_OFFSET_PATH_PREFIX = 345;
76     private static final int TAR_HEADER_LENGTH_MODE = 8;
77     private static final int TAR_HEADER_OFFSET_MODE = 100;
78     private static final int TAR_HEADER_LENGTH_MODTIME = 12;
79     private static final int TAR_HEADER_OFFSET_MODTIME = 136;
80     private static final int TAR_HEADER_LENGTH_FILESIZE = 12;
81     private static final int TAR_HEADER_OFFSET_FILESIZE = 124;
82     private static final int TAR_HEADER_LONG_RADIX = 8;
83 
84     private final InputStream mInputStream;
85     private final BytesReadListener mBytesReadListener;
86 
87     private IBackupManagerMonitor mMonitor;
88 
89     // Widget blob to be restored out-of-band.
90     private byte[] mWidgetData = null;
91 
TarBackupReader(InputStream inputStream, BytesReadListener bytesReadListener, IBackupManagerMonitor monitor)92     public TarBackupReader(InputStream inputStream, BytesReadListener bytesReadListener,
93             IBackupManagerMonitor monitor) {
94         mInputStream = inputStream;
95         mBytesReadListener = bytesReadListener;
96         mMonitor = monitor;
97     }
98 
99     /**
100      * Consumes a tar file header block [sequence] and accumulates the relevant metadata.
101      */
readTarHeaders()102     public FileMetadata readTarHeaders() throws IOException {
103         byte[] block = new byte[512];
104         FileMetadata info = null;
105 
106         boolean gotHeader = readTarHeader(block);
107         if (gotHeader) {
108             try {
109                 // okay, presume we're okay, and extract the various metadata
110                 info = new FileMetadata();
111                 info.size = extractRadix(block,
112                         TAR_HEADER_OFFSET_FILESIZE,
113                         TAR_HEADER_LENGTH_FILESIZE,
114                         TAR_HEADER_LONG_RADIX);
115                 info.mtime = extractRadix(block,
116                         TAR_HEADER_OFFSET_MODTIME,
117                         TAR_HEADER_LENGTH_MODTIME,
118                         TAR_HEADER_LONG_RADIX);
119                 info.mode = extractRadix(block,
120                         TAR_HEADER_OFFSET_MODE,
121                         TAR_HEADER_LENGTH_MODE,
122                         TAR_HEADER_LONG_RADIX);
123 
124                 info.path = extractString(block,
125                         TAR_HEADER_OFFSET_PATH_PREFIX,
126                         TAR_HEADER_LENGTH_PATH_PREFIX);
127                 String path = extractString(block,
128                         TAR_HEADER_OFFSET_PATH,
129                         TAR_HEADER_LENGTH_PATH);
130                 if (path.length() > 0) {
131                     if (info.path.length() > 0) {
132                         info.path += '/';
133                     }
134                     info.path += path;
135                 }
136 
137                 // tar link indicator field: 1 byte at offset 156 in the header.
138                 int typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
139                 if (typeChar == 'x') {
140                     // pax extended header, so we need to read that
141                     gotHeader = readPaxExtendedHeader(info);
142                     if (gotHeader) {
143                         // and after a pax extended header comes another real header -- read
144                         // that to find the real file type
145                         gotHeader = readTarHeader(block);
146                     }
147                     if (!gotHeader) {
148                         throw new IOException("Bad or missing pax header");
149                     }
150 
151                     typeChar = block[TAR_HEADER_OFFSET_TYPE_CHAR];
152                 }
153 
154                 switch (typeChar) {
155                     case '0':
156                         info.type = BackupAgent.TYPE_FILE;
157                         break;
158                     case '5': {
159                         info.type = BackupAgent.TYPE_DIRECTORY;
160                         if (info.size != 0) {
161                             Slog.w(TAG, "Directory entry with nonzero size in header");
162                             info.size = 0;
163                         }
164                         break;
165                     }
166                     case 0: {
167                         // presume EOF
168                         if (MORE_DEBUG) {
169                             Slog.w(TAG, "Saw type=0 in tar header block, info=" + info);
170                         }
171                         return null;
172                     }
173                     default: {
174                         Slog.e(TAG, "Unknown tar entity type: " + typeChar);
175                         throw new IOException("Unknown entity type " + typeChar);
176                     }
177                 }
178 
179                 // Parse out the path
180                 //
181                 // first: apps/shared/unrecognized
182                 if (FullBackup.SHARED_PREFIX.regionMatches(0,
183                         info.path, 0, FullBackup.SHARED_PREFIX.length())) {
184                     // File in shared storage.  !!! TODO: implement this.
185                     info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
186                     info.packageName = SHARED_BACKUP_AGENT_PACKAGE;
187                     info.domain = FullBackup.SHARED_STORAGE_TOKEN;
188                     if (DEBUG) {
189                         Slog.i(TAG, "File in shared storage: " + info.path);
190                     }
191                 } else if (FullBackup.APPS_PREFIX.regionMatches(0,
192                         info.path, 0, FullBackup.APPS_PREFIX.length())) {
193                     // App content!  Parse out the package name and domain
194 
195                     // strip the apps/ prefix
196                     info.path = info.path.substring(FullBackup.APPS_PREFIX.length());
197 
198                     // extract the package name
199                     int slash = info.path.indexOf('/');
200                     if (slash < 0) {
201                         throw new IOException("Illegal semantic path in " + info.path);
202                     }
203                     info.packageName = info.path.substring(0, slash);
204                     info.path = info.path.substring(slash + 1);
205 
206                     // if it's a manifest or metadata payload we're done, otherwise parse
207                     // out the domain into which the file will be restored
208                     if (!info.path.equals(BACKUP_MANIFEST_FILENAME) &&
209                             !info.path.equals(BACKUP_METADATA_FILENAME)) {
210                         slash = info.path.indexOf('/');
211                         if (slash < 0) {
212                             throw new IOException("Illegal semantic path in non-manifest "
213                                     + info.path);
214                         }
215                         info.domain = info.path.substring(0, slash);
216                         info.path = info.path.substring(slash + 1);
217                     }
218                 }
219             } catch (IOException e) {
220                 if (DEBUG) {
221                     Slog.e(TAG, "Parse error in header: " + e.getMessage());
222                     if (MORE_DEBUG) {
223                         hexLog(block);
224                     }
225                 }
226                 throw e;
227             }
228         }
229         return info;
230     }
231 
232     /**
233      * Tries to read exactly the given number of bytes into a buffer at the stated offset.
234      *
235      * @param in - input stream to read bytes from..
236      * @param buffer - where to write bytes to.
237      * @param offset - offset in buffer to write bytes to.
238      * @param size - number of bytes to read.
239      * @return number of bytes actually read.
240      * @throws IOException in case of an error.
241      */
readExactly(InputStream in, byte[] buffer, int offset, int size)242     private static int readExactly(InputStream in, byte[] buffer, int offset, int size)
243             throws IOException {
244         if (size <= 0) {
245             throw new IllegalArgumentException("size must be > 0");
246         }
247         if (MORE_DEBUG) {
248             Slog.i(TAG, "  ... readExactly(" + size + ") called");
249         }
250         int soFar = 0;
251         while (soFar < size) {
252             int nRead = in.read(buffer, offset + soFar, size - soFar);
253             if (nRead <= 0) {
254                 if (MORE_DEBUG) {
255                     Slog.w(TAG, "- wanted exactly " + size + " but got only " + soFar);
256                 }
257                 break;
258             }
259             soFar += nRead;
260             if (MORE_DEBUG) {
261                 Slog.v(TAG, "   + got " + nRead + "; now wanting " + (size - soFar));
262             }
263         }
264         return soFar;
265     }
266 
267     /**
268      * Reads app manifest, filling version and hasApk fields in the metadata, and returns array of
269      * signatures.
270      *
271      * @param info - file metadata.
272      * @return array of signatures or null, in case of an error.
273      * @throws IOException in case of an error.
274      */
readAppManifestAndReturnSignatures(FileMetadata info)275     public Signature[] readAppManifestAndReturnSignatures(FileMetadata info)
276             throws IOException {
277         // Fail on suspiciously large manifest files
278         if (info.size > 64 * 1024) {
279             throw new IOException("Restore manifest too big; corrupt? size=" + info.size);
280         }
281 
282         byte[] buffer = new byte[(int) info.size];
283         if (MORE_DEBUG) {
284             Slog.i(TAG,
285                     "   readAppManifestAndReturnSignatures() looking for " + info.size + " bytes");
286         }
287         if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
288             mBytesReadListener.onBytesRead(info.size);
289         } else {
290             throw new IOException("Unexpected EOF in manifest");
291         }
292 
293         String[] str = new String[1];
294         int offset = 0;
295 
296         try {
297             offset = extractLine(buffer, offset, str);
298             int version = Integer.parseInt(str[0]);
299             if (version == BACKUP_MANIFEST_VERSION) {
300                 offset = extractLine(buffer, offset, str);
301                 String manifestPackage = str[0];
302                 // TODO: handle <original-package>
303                 if (manifestPackage.equals(info.packageName)) {
304                     offset = extractLine(buffer, offset, str);
305                     info.version = Integer.parseInt(str[0]);  // app version
306                     offset = extractLine(buffer, offset, str);
307                     // This is the platform version, which we don't use, but we parse it
308                     // as a safety against corruption in the manifest.
309                     Integer.parseInt(str[0]);
310                     offset = extractLine(buffer, offset, str);
311                     info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
312                     offset = extractLine(buffer, offset, str);
313                     info.hasApk = str[0].equals("1");
314                     offset = extractLine(buffer, offset, str);
315                     int numSigs = Integer.parseInt(str[0]);
316                     if (numSigs > 0) {
317                         Signature[] sigs = new Signature[numSigs];
318                         for (int i = 0; i < numSigs; i++) {
319                             offset = extractLine(buffer, offset, str);
320                             sigs[i] = new Signature(str[0]);
321                         }
322                         return sigs;
323                     } else {
324                         Slog.i(TAG, "Missing signature on backed-up package " + info.packageName);
325                         mMonitor = BackupManagerMonitorUtils.monitorEvent(
326                                 mMonitor,
327                                 LOG_EVENT_ID_MISSING_SIGNATURE,
328                                 null,
329                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
330                                 BackupManagerMonitorUtils.putMonitoringExtra(null,
331                                         EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
332                     }
333                 } else {
334                     Slog.i(TAG, "Expected package " + info.packageName
335                             + " but restore manifest claims " + manifestPackage);
336                     Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
337                             EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
338                     monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
339                             monitoringExtras,
340                             EXTRA_LOG_MANIFEST_PACKAGE_NAME, manifestPackage);
341                     mMonitor = BackupManagerMonitorUtils.monitorEvent(
342                             mMonitor,
343                             LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE,
344                             null,
345                             LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
346                             monitoringExtras);
347                 }
348             } else {
349                 Slog.i(TAG, "Unknown restore manifest version " + version
350                         + " for package " + info.packageName);
351                 Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
352                         EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
353                 monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
354                         EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
355                 mMonitor = BackupManagerMonitorUtils.monitorEvent(
356                         mMonitor,
357                         BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION,
358                         null,
359                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
360                         monitoringExtras);
361 
362             }
363         } catch (NumberFormatException e) {
364             Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName);
365             mMonitor = BackupManagerMonitorUtils.monitorEvent(
366                     mMonitor,
367                     BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST,
368                     null,
369                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
370                     BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
371                             info.packageName));
372         } catch (IllegalArgumentException e) {
373             Slog.w(TAG, e.getMessage());
374         }
375 
376         return null;
377     }
378 
379     /**
380      * Chooses restore policy.
381      *
382      * @param packageManager - PackageManager instance.
383      * @param allowApks - allow restore set to include apks.
384      * @param info - file metadata.
385      * @param signatures - array of signatures parsed from backup file.
386      * @param userId - ID of the user for which restore is performed.
387      * @return a restore policy constant.
388      */
chooseRestorePolicy(PackageManager packageManager, boolean allowApks, FileMetadata info, Signature[] signatures, PackageManagerInternal pmi, int userId)389     public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
390             boolean allowApks, FileMetadata info, Signature[] signatures,
391             PackageManagerInternal pmi, int userId) {
392         return chooseRestorePolicy(packageManager, allowApks, info, signatures, pmi, userId,
393                 BackupEligibilityRules.forBackup(packageManager, pmi, userId));
394     }
395 
396     /**
397      * Chooses restore policy.
398      *
399      * @param packageManager - PackageManager instance.
400      * @param allowApks - allow restore set to include apks.
401      * @param info - file metadata.
402      * @param signatures - array of signatures parsed from backup file.
403      * @param userId - ID of the user for which restore is performed.
404      * @param eligibilityRules - {@link BackupEligibilityRules} for this operation.
405      * @return a restore policy constant.
406      */
chooseRestorePolicy(PackageManager packageManager, boolean allowApks, FileMetadata info, Signature[] signatures, PackageManagerInternal pmi, int userId, BackupEligibilityRules eligibilityRules)407     public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
408             boolean allowApks, FileMetadata info, Signature[] signatures,
409             PackageManagerInternal pmi, int userId, BackupEligibilityRules eligibilityRules) {
410         if (signatures == null) {
411             return RestorePolicy.IGNORE;
412         }
413 
414         RestorePolicy policy = RestorePolicy.IGNORE;
415         // Okay, got the manifest info we need...
416         try {
417             PackageInfo pkgInfo = packageManager.getPackageInfoAsUser(
418                     info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId);
419             // Fall through to IGNORE if the app explicitly disallows backup
420             final int flags = pkgInfo.applicationInfo.flags;
421             if (eligibilityRules.isAppBackupAllowed(pkgInfo.applicationInfo)) {
422                 // Restore system-uid-space packages only if they have
423                 // defined a custom backup agent
424                 if (!UserHandle.isCore(pkgInfo.applicationInfo.uid)
425                         || (pkgInfo.applicationInfo.backupAgentName != null)) {
426                     // Verify signatures against any installed version; if they
427                     // don't match, then we fall though and ignore the data.  The
428                     // signatureMatch() method explicitly ignores the signature
429                     // check for packages installed on the system partition, because
430                     // such packages are signed with the platform cert instead of
431                     // the app developer's cert, so they're different on every
432                     // device.
433                     if (eligibilityRules.signaturesMatch(signatures, pkgInfo)) {
434                         if ((pkgInfo.applicationInfo.flags
435                                 & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
436                             Slog.i(TAG, "Package has restoreAnyVersion; taking data");
437                             mMonitor = BackupManagerMonitorUtils.monitorEvent(
438                                     mMonitor,
439                                     LOG_EVENT_ID_RESTORE_ANY_VERSION,
440                                     pkgInfo,
441                                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
442                                     null);
443                             policy = RestorePolicy.ACCEPT;
444                         } else if (pkgInfo.getLongVersionCode() >= info.version) {
445                             Slog.i(TAG, "Sig + version match; taking data");
446                             policy = RestorePolicy.ACCEPT;
447                             mMonitor = BackupManagerMonitorUtils.monitorEvent(
448                                     mMonitor,
449                                     LOG_EVENT_ID_VERSIONS_MATCH,
450                                     pkgInfo,
451                                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
452                                     null);
453                         } else {
454                             // The data is from a newer version of the app than
455                             // is presently installed.  That means we can only
456                             // use it if the matching apk is also supplied.
457                             if (allowApks) {
458                                 Slog.i(TAG, "Data version " + info.version
459                                         + " is newer than installed "
460                                         + "version "
461                                         + pkgInfo.getLongVersionCode()
462                                         + " - requiring apk");
463                                 policy = RestorePolicy.ACCEPT_IF_APK;
464                             } else {
465                                 Slog.i(TAG, "Data requires newer version "
466                                         + info.version + "; ignoring");
467                                 mMonitor = BackupManagerMonitorUtils
468                                         .monitorEvent(mMonitor,
469                                                 LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
470                                                 pkgInfo,
471                                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
472                                                 BackupManagerMonitorUtils
473                                                         .putMonitoringExtra(
474                                                                 null,
475                                                                 EXTRA_LOG_OLD_VERSION,
476                                                                 info.version));
477 
478                                 policy = RestorePolicy.IGNORE;
479                             }
480                         }
481                     } else {
482                         Slog.w(TAG, "Restore manifest signatures do not match "
483                                 + "installed application for "
484                                 + info.packageName);
485                         mMonitor = BackupManagerMonitorUtils.monitorEvent(
486                                 mMonitor,
487                                 LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH,
488                                 pkgInfo,
489                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
490                                 null);
491                     }
492                 } else {
493                     Slog.w(TAG, "Package " + info.packageName
494                             + " is system level with no agent");
495                     mMonitor = BackupManagerMonitorUtils.monitorEvent(
496                             mMonitor,
497                             LOG_EVENT_ID_SYSTEM_APP_NO_AGENT,
498                             pkgInfo,
499                             LOG_EVENT_CATEGORY_AGENT,
500                             null);
501                 }
502             } else {
503                 if (DEBUG) {
504                     Slog.i(TAG,
505                             "Restore manifest from " + info.packageName + " but allowBackup=false");
506                 }
507                 mMonitor = BackupManagerMonitorUtils.monitorEvent(
508                         mMonitor,
509                         LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE,
510                         pkgInfo,
511                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
512                         null);
513             }
514         } catch (PackageManager.NameNotFoundException e) {
515             // Okay, the target app isn't installed.  We can process
516             // the restore properly only if the dataset provides the
517             // apk file and we can successfully install it.
518             if (allowApks) {
519                 if (DEBUG) {
520                     Slog.i(TAG, "Package " + info.packageName
521                             + " not installed; requiring apk in dataset");
522                 }
523                 policy = RestorePolicy.ACCEPT_IF_APK;
524             } else {
525                 policy = RestorePolicy.IGNORE;
526             }
527             Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
528                     null,
529                     EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
530             monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
531                     monitoringExtras,
532                     EXTRA_LOG_POLICY_ALLOW_APKS, allowApks);
533             mMonitor = BackupManagerMonitorUtils.monitorEvent(
534                     mMonitor,
535                     LOG_EVENT_ID_APK_NOT_INSTALLED,
536                     null,
537                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
538                     monitoringExtras);
539         }
540 
541         if (policy == RestorePolicy.ACCEPT_IF_APK && !info.hasApk) {
542             Slog.i(TAG, "Cannot restore package " + info.packageName
543                     + " without the matching .apk");
544             mMonitor = BackupManagerMonitorUtils.monitorEvent(
545                     mMonitor,
546                     LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK,
547                     null,
548                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
549                     BackupManagerMonitorUtils.putMonitoringExtra(null,
550                             EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
551         }
552 
553         return policy;
554     }
555 
556     // Given an actual file content size, consume the post-content padding mandated
557     // by the tar format.
skipTarPadding(long size)558     public void skipTarPadding(long size) throws IOException {
559         long partial = (size + 512) % 512;
560         if (partial > 0) {
561             final int needed = 512 - (int) partial;
562             if (MORE_DEBUG) {
563                 Slog.i(TAG, "Skipping tar padding: " + needed + " bytes");
564             }
565             byte[] buffer = new byte[needed];
566             if (readExactly(mInputStream, buffer, 0, needed) == needed) {
567                 mBytesReadListener.onBytesRead(needed);
568             } else {
569                 throw new IOException("Unexpected EOF in padding");
570             }
571         }
572     }
573 
574     /**
575      * Read a widget metadata file, returning the restored blob.
576      */
readMetadata(FileMetadata info)577     public void readMetadata(FileMetadata info) throws IOException {
578         // Fail on suspiciously large widget dump files
579         if (info.size > 64 * 1024) {
580             throw new IOException("Metadata too big; corrupt? size=" + info.size);
581         }
582 
583         byte[] buffer = new byte[(int) info.size];
584         if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
585             mBytesReadListener.onBytesRead(info.size);
586         } else {
587             throw new IOException("Unexpected EOF in widget data");
588         }
589 
590         String[] str = new String[1];
591         int offset = extractLine(buffer, 0, str);
592         int version = Integer.parseInt(str[0]);
593         if (version == BACKUP_MANIFEST_VERSION) {
594             offset = extractLine(buffer, offset, str);
595             final String pkg = str[0];
596             if (info.packageName.equals(pkg)) {
597                 // Data checks out -- the rest of the buffer is a concatenation of
598                 // binary blobs as described in the comment at writeAppWidgetData()
599                 ByteArrayInputStream bin = new ByteArrayInputStream(buffer,
600                         offset, buffer.length - offset);
601                 DataInputStream in = new DataInputStream(bin);
602                 while (bin.available() > 0) {
603                     int token = in.readInt();
604                     int size = in.readInt();
605                     if (size > 64 * 1024) {
606                         throw new IOException("Datum " + Integer.toHexString(token)
607                                 + " too big; corrupt? size=" + info.size);
608                     }
609                     switch (token) {
610                         case BACKUP_WIDGET_METADATA_TOKEN: {
611                             if (MORE_DEBUG) {
612                                 Slog.i(TAG, "Got widget metadata for " + info.packageName);
613                             }
614                             mWidgetData = new byte[size];
615                             in.read(mWidgetData);
616                             break;
617                         }
618                         default: {
619                             if (DEBUG) {
620                                 Slog.i(TAG, "Ignoring metadata blob " + Integer.toHexString(token)
621                                         + " for " + info.packageName);
622                             }
623                             in.skipBytes(size);
624                             break;
625                         }
626                     }
627                 }
628             } else {
629                 Slog.w(TAG,
630                         "Metadata mismatch: package " + info.packageName + " but widget data for "
631                                 + pkg);
632 
633                 Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
634                         EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
635                 monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
636                         BackupManagerMonitor.EXTRA_LOG_WIDGET_PACKAGE_NAME, pkg);
637                 mMonitor = BackupManagerMonitorUtils.monitorEvent(
638                         mMonitor,
639                         BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH,
640                         null,
641                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
642                         monitoringExtras);
643             }
644         } else {
645             Slog.w(TAG, "Unsupported metadata version " + version);
646 
647             Bundle monitoringExtras = BackupManagerMonitorUtils
648                     .putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
649                             info.packageName);
650             monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
651                     EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
652             mMonitor = BackupManagerMonitorUtils.monitorEvent(
653                     mMonitor,
654                     BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION,
655                     null,
656                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
657                     monitoringExtras);
658         }
659     }
660 
661     /**
662      * Builds a line from a byte buffer starting at 'offset'.
663      *
664      * @param buffer - where to read a line from.
665      * @param offset - offset in buffer to read a line from.
666      * @param outStr - an output parameter, the result will be put in outStr.
667      * @return the index of the next unconsumed data in the buffer.
668      * @throws IOException in case of an error.
669      */
extractLine(byte[] buffer, int offset, String[] outStr)670     private static int extractLine(byte[] buffer, int offset, String[] outStr) throws IOException {
671         final int end = buffer.length;
672         if (offset >= end) {
673             throw new IOException("Incomplete data");
674         }
675 
676         int pos;
677         for (pos = offset; pos < end; pos++) {
678             byte c = buffer[pos];
679             // at LF we declare end of line, and return the next char as the
680             // starting point for the next time through
681             if (c == '\n') {
682                 break;
683             }
684         }
685         outStr[0] = new String(buffer, offset, pos - offset);
686         pos++;  // may be pointing an extra byte past the end but that's okay
687         return pos;
688     }
689 
readTarHeader(byte[] block)690     private boolean readTarHeader(byte[] block) throws IOException {
691         final int got = readExactly(mInputStream, block, 0, 512);
692         if (got == 0) {
693             return false;     // Clean EOF
694         }
695         if (got < 512) {
696             throw new IOException("Unable to read full block header");
697         }
698         mBytesReadListener.onBytesRead(512);
699         return true;
700     }
701 
702     // overwrites 'info' fields based on the pax extended header
readPaxExtendedHeader(FileMetadata info)703     private boolean readPaxExtendedHeader(FileMetadata info)
704             throws IOException {
705         // We should never see a pax extended header larger than this
706         if (info.size > 32 * 1024) {
707             Slog.w(TAG, "Suspiciously large pax header size " + info.size + " - aborting");
708             throw new IOException("Sanity failure: pax header size " + info.size);
709         }
710 
711         // read whole blocks, not just the content size
712         int numBlocks = (int) ((info.size + 511) >> 9);
713         byte[] data = new byte[numBlocks * 512];
714         if (readExactly(mInputStream, data, 0, data.length) < data.length) {
715             throw new IOException("Unable to read full pax header");
716         }
717         mBytesReadListener.onBytesRead(data.length);
718 
719         final int contentSize = (int) info.size;
720         int offset = 0;
721         do {
722             // extract the line at 'offset'
723             int eol = offset + 1;
724             while (eol < contentSize && data[eol] != ' ') {
725                 eol++;
726             }
727             if (eol >= contentSize) {
728                 // error: we just hit EOD looking for the end of the size field
729                 throw new IOException("Invalid pax data");
730             }
731             // eol points to the space between the count and the key
732             int linelen = (int) extractRadix(data, offset, eol - offset, 10);
733             int key = eol + 1;  // start of key=value
734             eol = offset + linelen - 1; // trailing LF
735             int value;
736             for (value = key + 1; data[value] != '=' && value <= eol; value++) {
737                 ;
738             }
739             if (value > eol) {
740                 throw new IOException("Invalid pax declaration");
741             }
742 
743             // pax requires that key/value strings be in UTF-8
744             String keyStr = new String(data, key, value - key, "UTF-8");
745             // -1 to strip the trailing LF
746             String valStr = new String(data, value + 1, eol - value - 1, "UTF-8");
747 
748             if ("path".equals(keyStr)) {
749                 info.path = valStr;
750             } else if ("size".equals(keyStr)) {
751                 info.size = Long.parseLong(valStr);
752             } else {
753                 if (DEBUG) {
754                     Slog.i(TAG, "Unhandled pax key: " + key);
755                 }
756             }
757 
758             offset += linelen;
759         } while (offset < contentSize);
760 
761         return true;
762     }
763 
extractRadix(byte[] data, int offset, int maxChars, int radix)764     private static long extractRadix(byte[] data, int offset, int maxChars, int radix)
765             throws IOException {
766         long value = 0;
767         final int end = offset + maxChars;
768         for (int i = offset; i < end; i++) {
769             final byte b = data[i];
770             // Numeric fields in tar can terminate with either NUL or SPC
771             if (b == 0 || b == ' ') {
772                 break;
773             }
774             if (b < '0' || b > ('0' + radix - 1)) {
775                 throw new IOException("Invalid number in header: '" + (char) b
776                         + "' for radix " + radix);
777             }
778             value = radix * value + (b - '0');
779         }
780         return value;
781     }
782 
extractString(byte[] data, int offset, int maxChars)783     private static String extractString(byte[] data, int offset, int maxChars) throws IOException {
784         final int end = offset + maxChars;
785         int eos = offset;
786         // tar string fields terminate early with a NUL
787         while (eos < end && data[eos] != 0) {
788             eos++;
789         }
790         return new String(data, offset, eos - offset, "US-ASCII");
791     }
792 
hexLog(byte[] block)793     private static void hexLog(byte[] block) {
794         int offset = 0;
795         int remaining = block.length;
796         StringBuilder buf = new StringBuilder(64);
797         while (remaining > 0) {
798             buf.append(String.format("%04x   ", offset));
799             int numThisLine = (remaining > 16) ? 16 : remaining;
800             for (int i = 0; i < numThisLine; i++) {
801                 buf.append(String.format("%02x ", block[offset + i]));
802             }
803             Slog.i("hexdump", buf.toString());
804             buf.setLength(0);
805             remaining -= numThisLine;
806             offset += numThisLine;
807         }
808     }
809 
getMonitor()810     public IBackupManagerMonitor getMonitor() {
811         return mMonitor;
812     }
813 
getWidgetData()814     public byte[] getWidgetData() {
815         return mWidgetData;
816     }
817 }
818