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