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         if (signatures == null) {
393             return RestorePolicy.IGNORE;
394         }
395 
396         RestorePolicy policy = RestorePolicy.IGNORE;
397 
398         // Okay, got the manifest info we need...
399         try {
400             PackageInfo pkgInfo = packageManager.getPackageInfoAsUser(
401                     info.packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId);
402             // Fall through to IGNORE if the app explicitly disallows backup
403             final int flags = pkgInfo.applicationInfo.flags;
404             if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
405                 // Restore system-uid-space packages only if they have
406                 // defined a custom backup agent
407                 if (!UserHandle.isCore(pkgInfo.applicationInfo.uid)
408                         || (pkgInfo.applicationInfo.backupAgentName != null)) {
409                     // Verify signatures against any installed version; if they
410                     // don't match, then we fall though and ignore the data.  The
411                     // signatureMatch() method explicitly ignores the signature
412                     // check for packages installed on the system partition, because
413                     // such packages are signed with the platform cert instead of
414                     // the app developer's cert, so they're different on every
415                     // device.
416                     if (AppBackupUtils.signaturesMatch(signatures, pkgInfo, pmi)) {
417                         if ((pkgInfo.applicationInfo.flags
418                                 & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
419                             Slog.i(TAG, "Package has restoreAnyVersion; taking data");
420                             mMonitor = BackupManagerMonitorUtils.monitorEvent(
421                                     mMonitor,
422                                     LOG_EVENT_ID_RESTORE_ANY_VERSION,
423                                     pkgInfo,
424                                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
425                                     null);
426                             policy = RestorePolicy.ACCEPT;
427                         } else if (pkgInfo.getLongVersionCode() >= info.version) {
428                             Slog.i(TAG, "Sig + version match; taking data");
429                             policy = RestorePolicy.ACCEPT;
430                             mMonitor = BackupManagerMonitorUtils.monitorEvent(
431                                     mMonitor,
432                                     LOG_EVENT_ID_VERSIONS_MATCH,
433                                     pkgInfo,
434                                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
435                                     null);
436                         } else {
437                             // The data is from a newer version of the app than
438                             // is presently installed.  That means we can only
439                             // use it if the matching apk is also supplied.
440                             if (allowApks) {
441                                 Slog.i(TAG, "Data version " + info.version
442                                         + " is newer than installed "
443                                         + "version "
444                                         + pkgInfo.getLongVersionCode()
445                                         + " - requiring apk");
446                                 policy = RestorePolicy.ACCEPT_IF_APK;
447                             } else {
448                                 Slog.i(TAG, "Data requires newer version "
449                                         + info.version + "; ignoring");
450                                 mMonitor = BackupManagerMonitorUtils
451                                         .monitorEvent(mMonitor,
452                                                 LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
453                                                 pkgInfo,
454                                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
455                                                 BackupManagerMonitorUtils
456                                                         .putMonitoringExtra(
457                                                                 null,
458                                                                 EXTRA_LOG_OLD_VERSION,
459                                                                 info.version));
460 
461                                 policy = RestorePolicy.IGNORE;
462                             }
463                         }
464                     } else {
465                         Slog.w(TAG, "Restore manifest signatures do not match "
466                                 + "installed application for "
467                                 + info.packageName);
468                         mMonitor = BackupManagerMonitorUtils.monitorEvent(
469                                 mMonitor,
470                                 LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH,
471                                 pkgInfo,
472                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
473                                 null);
474                     }
475                 } else {
476                     Slog.w(TAG, "Package " + info.packageName
477                             + " is system level with no agent");
478                     mMonitor = BackupManagerMonitorUtils.monitorEvent(
479                             mMonitor,
480                             LOG_EVENT_ID_SYSTEM_APP_NO_AGENT,
481                             pkgInfo,
482                             LOG_EVENT_CATEGORY_AGENT,
483                             null);
484                 }
485             } else {
486                 if (DEBUG) {
487                     Slog.i(TAG,
488                             "Restore manifest from " + info.packageName + " but allowBackup=false");
489                 }
490                 mMonitor = BackupManagerMonitorUtils.monitorEvent(
491                         mMonitor,
492                         LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE,
493                         pkgInfo,
494                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
495                         null);
496             }
497         } catch (PackageManager.NameNotFoundException e) {
498             // Okay, the target app isn't installed.  We can process
499             // the restore properly only if the dataset provides the
500             // apk file and we can successfully install it.
501             if (allowApks) {
502                 if (DEBUG) {
503                     Slog.i(TAG, "Package " + info.packageName
504                             + " not installed; requiring apk in dataset");
505                 }
506                 policy = RestorePolicy.ACCEPT_IF_APK;
507             } else {
508                 policy = RestorePolicy.IGNORE;
509             }
510             Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
511                     null,
512                     EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
513             monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
514                     monitoringExtras,
515                     EXTRA_LOG_POLICY_ALLOW_APKS, allowApks);
516             mMonitor = BackupManagerMonitorUtils.monitorEvent(
517                     mMonitor,
518                     LOG_EVENT_ID_APK_NOT_INSTALLED,
519                     null,
520                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
521                     monitoringExtras);
522         }
523 
524         if (policy == RestorePolicy.ACCEPT_IF_APK && !info.hasApk) {
525             Slog.i(TAG, "Cannot restore package " + info.packageName
526                     + " without the matching .apk");
527             mMonitor = BackupManagerMonitorUtils.monitorEvent(
528                     mMonitor,
529                     LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK,
530                     null,
531                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
532                     BackupManagerMonitorUtils.putMonitoringExtra(null,
533                             EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
534         }
535 
536         return policy;
537     }
538 
539     // Given an actual file content size, consume the post-content padding mandated
540     // by the tar format.
skipTarPadding(long size)541     public void skipTarPadding(long size) throws IOException {
542         long partial = (size + 512) % 512;
543         if (partial > 0) {
544             final int needed = 512 - (int) partial;
545             if (MORE_DEBUG) {
546                 Slog.i(TAG, "Skipping tar padding: " + needed + " bytes");
547             }
548             byte[] buffer = new byte[needed];
549             if (readExactly(mInputStream, buffer, 0, needed) == needed) {
550                 mBytesReadListener.onBytesRead(needed);
551             } else {
552                 throw new IOException("Unexpected EOF in padding");
553             }
554         }
555     }
556 
557     /**
558      * Read a widget metadata file, returning the restored blob.
559      */
readMetadata(FileMetadata info)560     public void readMetadata(FileMetadata info) throws IOException {
561         // Fail on suspiciously large widget dump files
562         if (info.size > 64 * 1024) {
563             throw new IOException("Metadata too big; corrupt? size=" + info.size);
564         }
565 
566         byte[] buffer = new byte[(int) info.size];
567         if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
568             mBytesReadListener.onBytesRead(info.size);
569         } else {
570             throw new IOException("Unexpected EOF in widget data");
571         }
572 
573         String[] str = new String[1];
574         int offset = extractLine(buffer, 0, str);
575         int version = Integer.parseInt(str[0]);
576         if (version == BACKUP_MANIFEST_VERSION) {
577             offset = extractLine(buffer, offset, str);
578             final String pkg = str[0];
579             if (info.packageName.equals(pkg)) {
580                 // Data checks out -- the rest of the buffer is a concatenation of
581                 // binary blobs as described in the comment at writeAppWidgetData()
582                 ByteArrayInputStream bin = new ByteArrayInputStream(buffer,
583                         offset, buffer.length - offset);
584                 DataInputStream in = new DataInputStream(bin);
585                 while (bin.available() > 0) {
586                     int token = in.readInt();
587                     int size = in.readInt();
588                     if (size > 64 * 1024) {
589                         throw new IOException("Datum " + Integer.toHexString(token)
590                                 + " too big; corrupt? size=" + info.size);
591                     }
592                     switch (token) {
593                         case BACKUP_WIDGET_METADATA_TOKEN: {
594                             if (MORE_DEBUG) {
595                                 Slog.i(TAG, "Got widget metadata for " + info.packageName);
596                             }
597                             mWidgetData = new byte[size];
598                             in.read(mWidgetData);
599                             break;
600                         }
601                         default: {
602                             if (DEBUG) {
603                                 Slog.i(TAG, "Ignoring metadata blob " + Integer.toHexString(token)
604                                         + " for " + info.packageName);
605                             }
606                             in.skipBytes(size);
607                             break;
608                         }
609                     }
610                 }
611             } else {
612                 Slog.w(TAG,
613                         "Metadata mismatch: package " + info.packageName + " but widget data for "
614                                 + pkg);
615 
616                 Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
617                         EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
618                 monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
619                         BackupManagerMonitor.EXTRA_LOG_WIDGET_PACKAGE_NAME, pkg);
620                 mMonitor = BackupManagerMonitorUtils.monitorEvent(
621                         mMonitor,
622                         BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH,
623                         null,
624                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
625                         monitoringExtras);
626             }
627         } else {
628             Slog.w(TAG, "Unsupported metadata version " + version);
629 
630             Bundle monitoringExtras = BackupManagerMonitorUtils
631                     .putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
632                             info.packageName);
633             monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
634                     EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
635             mMonitor = BackupManagerMonitorUtils.monitorEvent(
636                     mMonitor,
637                     BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION,
638                     null,
639                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
640                     monitoringExtras);
641         }
642     }
643 
644     /**
645      * Builds a line from a byte buffer starting at 'offset'.
646      *
647      * @param buffer - where to read a line from.
648      * @param offset - offset in buffer to read a line from.
649      * @param outStr - an output parameter, the result will be put in outStr.
650      * @return the index of the next unconsumed data in the buffer.
651      * @throws IOException in case of an error.
652      */
extractLine(byte[] buffer, int offset, String[] outStr)653     private static int extractLine(byte[] buffer, int offset, String[] outStr) throws IOException {
654         final int end = buffer.length;
655         if (offset >= end) {
656             throw new IOException("Incomplete data");
657         }
658 
659         int pos;
660         for (pos = offset; pos < end; pos++) {
661             byte c = buffer[pos];
662             // at LF we declare end of line, and return the next char as the
663             // starting point for the next time through
664             if (c == '\n') {
665                 break;
666             }
667         }
668         outStr[0] = new String(buffer, offset, pos - offset);
669         pos++;  // may be pointing an extra byte past the end but that's okay
670         return pos;
671     }
672 
readTarHeader(byte[] block)673     private boolean readTarHeader(byte[] block) throws IOException {
674         final int got = readExactly(mInputStream, block, 0, 512);
675         if (got == 0) {
676             return false;     // Clean EOF
677         }
678         if (got < 512) {
679             throw new IOException("Unable to read full block header");
680         }
681         mBytesReadListener.onBytesRead(512);
682         return true;
683     }
684 
685     // overwrites 'info' fields based on the pax extended header
readPaxExtendedHeader(FileMetadata info)686     private boolean readPaxExtendedHeader(FileMetadata info)
687             throws IOException {
688         // We should never see a pax extended header larger than this
689         if (info.size > 32 * 1024) {
690             Slog.w(TAG, "Suspiciously large pax header size " + info.size + " - aborting");
691             throw new IOException("Sanity failure: pax header size " + info.size);
692         }
693 
694         // read whole blocks, not just the content size
695         int numBlocks = (int) ((info.size + 511) >> 9);
696         byte[] data = new byte[numBlocks * 512];
697         if (readExactly(mInputStream, data, 0, data.length) < data.length) {
698             throw new IOException("Unable to read full pax header");
699         }
700         mBytesReadListener.onBytesRead(data.length);
701 
702         final int contentSize = (int) info.size;
703         int offset = 0;
704         do {
705             // extract the line at 'offset'
706             int eol = offset + 1;
707             while (eol < contentSize && data[eol] != ' ') {
708                 eol++;
709             }
710             if (eol >= contentSize) {
711                 // error: we just hit EOD looking for the end of the size field
712                 throw new IOException("Invalid pax data");
713             }
714             // eol points to the space between the count and the key
715             int linelen = (int) extractRadix(data, offset, eol - offset, 10);
716             int key = eol + 1;  // start of key=value
717             eol = offset + linelen - 1; // trailing LF
718             int value;
719             for (value = key + 1; data[value] != '=' && value <= eol; value++) {
720                 ;
721             }
722             if (value > eol) {
723                 throw new IOException("Invalid pax declaration");
724             }
725 
726             // pax requires that key/value strings be in UTF-8
727             String keyStr = new String(data, key, value - key, "UTF-8");
728             // -1 to strip the trailing LF
729             String valStr = new String(data, value + 1, eol - value - 1, "UTF-8");
730 
731             if ("path".equals(keyStr)) {
732                 info.path = valStr;
733             } else if ("size".equals(keyStr)) {
734                 info.size = Long.parseLong(valStr);
735             } else {
736                 if (DEBUG) {
737                     Slog.i(TAG, "Unhandled pax key: " + key);
738                 }
739             }
740 
741             offset += linelen;
742         } while (offset < contentSize);
743 
744         return true;
745     }
746 
extractRadix(byte[] data, int offset, int maxChars, int radix)747     private static long extractRadix(byte[] data, int offset, int maxChars, int radix)
748             throws IOException {
749         long value = 0;
750         final int end = offset + maxChars;
751         for (int i = offset; i < end; i++) {
752             final byte b = data[i];
753             // Numeric fields in tar can terminate with either NUL or SPC
754             if (b == 0 || b == ' ') {
755                 break;
756             }
757             if (b < '0' || b > ('0' + radix - 1)) {
758                 throw new IOException("Invalid number in header: '" + (char) b
759                         + "' for radix " + radix);
760             }
761             value = radix * value + (b - '0');
762         }
763         return value;
764     }
765 
extractString(byte[] data, int offset, int maxChars)766     private static String extractString(byte[] data, int offset, int maxChars) throws IOException {
767         final int end = offset + maxChars;
768         int eos = offset;
769         // tar string fields terminate early with a NUL
770         while (eos < end && data[eos] != 0) {
771             eos++;
772         }
773         return new String(data, offset, eos - offset, "US-ASCII");
774     }
775 
hexLog(byte[] block)776     private static void hexLog(byte[] block) {
777         int offset = 0;
778         int todo = block.length;
779         StringBuilder buf = new StringBuilder(64);
780         while (todo > 0) {
781             buf.append(String.format("%04x   ", offset));
782             int numThisLine = (todo > 16) ? 16 : todo;
783             for (int i = 0; i < numThisLine; i++) {
784                 buf.append(String.format("%02x ", block[offset + i]));
785             }
786             Slog.i("hexdump", buf.toString());
787             buf.setLength(0);
788             todo -= numThisLine;
789             offset += numThisLine;
790         }
791     }
792 
getMonitor()793     public IBackupManagerMonitor getMonitor() {
794         return mMonitor;
795     }
796 
getWidgetData()797     public byte[] getWidgetData() {
798         return mWidgetData;
799     }
800 }
801