1/*
2 * Copyright (C) 2009 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
17package com.android.server.backup;
18
19import android.app.backup.BackupAgent;
20import android.app.backup.BackupDataInput;
21import android.app.backup.BackupDataOutput;
22import android.content.ComponentName;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.ResolveInfo;
27import android.content.pm.PackageManager.NameNotFoundException;
28import android.content.pm.Signature;
29import android.os.Build;
30import android.os.ParcelFileDescriptor;
31import android.util.Slog;
32
33import java.io.BufferedInputStream;
34import java.io.BufferedOutputStream;
35import java.io.ByteArrayInputStream;
36import java.io.ByteArrayOutputStream;
37import java.io.DataInputStream;
38import java.io.DataOutputStream;
39import java.io.EOFException;
40import java.io.FileInputStream;
41import java.io.FileOutputStream;
42import java.io.IOException;
43import java.util.ArrayList;
44import java.util.HashMap;
45import java.util.HashSet;
46import java.util.List;
47import java.util.Set;
48
49import java.util.Objects;
50
51/**
52 * We back up the signatures of each package so that during a system restore,
53 * we can verify that the app whose data we think we have matches the app
54 * actually resident on the device.
55 *
56 * Since the Package Manager isn't a proper "application" we just provide a
57 * direct IBackupAgent implementation and hand-construct it at need.
58 */
59public class PackageManagerBackupAgent extends BackupAgent {
60    private static final String TAG = "PMBA";
61    private static final boolean DEBUG = false;
62
63    // key under which we store global metadata (individual app metadata
64    // is stored using the package name as a key)
65    private static final String GLOBAL_METADATA_KEY = "@meta@";
66
67    // key under which we store the identity of the user's chosen default home app
68    private static final String DEFAULT_HOME_KEY = "@home@";
69
70    // Sentinel: start of state file, followed by a version number
71    private static final String STATE_FILE_HEADER = "=state=";
72    private static final int STATE_FILE_VERSION = 2;
73
74    // Current version of the saved ancestral-dataset file format
75    private static final int ANCESTRAL_RECORD_VERSION = 1;
76
77    private List<PackageInfo> mAllPackages;
78    private PackageManager mPackageManager;
79    // version & signature info of each app in a restore set
80    private HashMap<String, Metadata> mRestoredSignatures;
81    // The version info of each backed-up app as read from the state file
82    private HashMap<String, Metadata> mStateVersions = new HashMap<String, Metadata>();
83
84    private final HashSet<String> mExisting = new HashSet<String>();
85    private int mStoredSdkVersion;
86    private String mStoredIncrementalVersion;
87    private ComponentName mStoredHomeComponent;
88    private long mStoredHomeVersion;
89    private ArrayList<byte[]> mStoredHomeSigHashes;
90
91    private boolean mHasMetadata;
92    private ComponentName mRestoredHome;
93    private long mRestoredHomeVersion;
94    private String mRestoredHomeInstaller;
95    private ArrayList<byte[]> mRestoredHomeSigHashes;
96
97    // For compactness we store the SHA-256 hash of each app's Signatures
98    // rather than the Signature blocks themselves.
99    public class Metadata {
100        public int versionCode;
101        public ArrayList<byte[]> sigHashes;
102
103        Metadata(int version, ArrayList<byte[]> hashes) {
104            versionCode = version;
105            sigHashes = hashes;
106        }
107    }
108
109    // We're constructed with the set of applications that are participating
110    // in backup.  This set changes as apps are installed & removed.
111    PackageManagerBackupAgent(PackageManager packageMgr, List<PackageInfo> packages) {
112        init(packageMgr, packages);
113    }
114
115    PackageManagerBackupAgent(PackageManager packageMgr) {
116        init(packageMgr, null);
117
118        evaluateStorablePackages();
119    }
120
121    private void init(PackageManager packageMgr, List<PackageInfo> packages) {
122        mPackageManager = packageMgr;
123        mAllPackages = packages;
124        mRestoredSignatures = null;
125        mHasMetadata = false;
126
127        mStoredSdkVersion = Build.VERSION.SDK_INT;
128        mStoredIncrementalVersion = Build.VERSION.INCREMENTAL;
129    }
130
131    // We will need to refresh our understanding of what is eligible for
132    // backup periodically; this entry point serves that purpose.
133    public void evaluateStorablePackages() {
134        mAllPackages = getStorableApplications(mPackageManager);
135    }
136
137    public static List<PackageInfo> getStorableApplications(PackageManager pm) {
138        List<PackageInfo> pkgs;
139        pkgs = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
140        int N = pkgs.size();
141        for (int a = N-1; a >= 0; a--) {
142            PackageInfo pkg = pkgs.get(a);
143            if (!BackupManagerService.appIsEligibleForBackup(pkg.applicationInfo)) {
144                pkgs.remove(a);
145            }
146        }
147        return pkgs;
148    }
149
150    public boolean hasMetadata() {
151        return mHasMetadata;
152    }
153
154    public Metadata getRestoredMetadata(String packageName) {
155        if (mRestoredSignatures == null) {
156            Slog.w(TAG, "getRestoredMetadata() before metadata read!");
157            return null;
158        }
159
160        return mRestoredSignatures.get(packageName);
161    }
162
163    public Set<String> getRestoredPackages() {
164        if (mRestoredSignatures == null) {
165            Slog.w(TAG, "getRestoredPackages() before metadata read!");
166            return null;
167        }
168
169        // This is technically the set of packages on the originating handset
170        // that had backup agents at all, not limited to the set of packages
171        // that had actually contributed a restore dataset, but it's a
172        // close enough approximation for our purposes and does not require any
173        // additional involvement by the transport to obtain.
174        return mRestoredSignatures.keySet();
175    }
176
177    // The backed up data is the signature block for each app, keyed by
178    // the package name.
179    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
180            ParcelFileDescriptor newState) {
181        if (DEBUG) Slog.v(TAG, "onBackup()");
182
183        ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();  // we'll reuse these
184        DataOutputStream outputBufferStream = new DataOutputStream(outputBuffer);
185        parseStateFile(oldState);
186
187        // If the stored version string differs, we need to re-backup all
188        // of the metadata.  We force this by removing everything from the
189        // "already backed up" map built by parseStateFile().
190        if (mStoredIncrementalVersion == null
191                || !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
192            Slog.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
193                    + Build.VERSION.INCREMENTAL + " - rewriting");
194            mExisting.clear();
195        }
196
197        long homeVersion = 0;
198        ArrayList<byte[]> homeSigHashes = null;
199        PackageInfo homeInfo = null;
200        String homeInstaller = null;
201        ComponentName home = getPreferredHomeComponent();
202        if (home != null) {
203            try {
204                homeInfo = mPackageManager.getPackageInfo(home.getPackageName(),
205                        PackageManager.GET_SIGNATURES);
206                homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
207                homeVersion = homeInfo.versionCode;
208                homeSigHashes = hashSignatureArray(homeInfo.signatures);
209            } catch (NameNotFoundException e) {
210                Slog.w(TAG, "Can't access preferred home info");
211                // proceed as though there were no preferred home set
212                home = null;
213            }
214        }
215
216        try {
217            // We need to push a new preferred-home-app record if:
218            //    1. the version of the home app has changed since our last backup;
219            //    2. the home app [or absence] we now use differs from the prior state,
220            // OR 3. it looks like we use the same home app + version as before, but
221            //       the signatures don't match so we treat them as different apps.
222            final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
223                    || !Objects.equals(home, mStoredHomeComponent)
224                    || (home != null
225                        && !BackupManagerService.signaturesMatch(mStoredHomeSigHashes, homeInfo));
226            if (needHomeBackup) {
227                if (DEBUG) {
228                    Slog.i(TAG, "Home preference changed; backing up new state " + home);
229                }
230                if (home != null) {
231                    outputBufferStream.writeUTF(home.flattenToString());
232                    outputBufferStream.writeLong(homeVersion);
233                    outputBufferStream.writeUTF(homeInstaller != null ? homeInstaller : "" );
234                    writeSignatureHashArray(outputBufferStream, homeSigHashes);
235                    writeEntity(data, DEFAULT_HOME_KEY, outputBuffer.toByteArray());
236                } else {
237                    data.writeEntityHeader(DEFAULT_HOME_KEY, -1);
238                }
239            }
240
241            /*
242             * Global metadata:
243             *
244             * int SDKversion -- the SDK version of the OS itself on the device
245             *                   that produced this backup set.  Used to reject
246             *                   backups from later OSes onto earlier ones.
247             * String incremental -- the incremental release name of the OS stored in
248             *                       the backup set.
249             */
250            outputBuffer.reset();
251            if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
252                if (DEBUG) Slog.v(TAG, "Storing global metadata key");
253                outputBufferStream.writeInt(Build.VERSION.SDK_INT);
254                outputBufferStream.writeUTF(Build.VERSION.INCREMENTAL);
255                writeEntity(data, GLOBAL_METADATA_KEY, outputBuffer.toByteArray());
256            } else {
257                if (DEBUG) Slog.v(TAG, "Global metadata key already stored");
258                // don't consider it to have been skipped/deleted
259                mExisting.remove(GLOBAL_METADATA_KEY);
260            }
261
262            // For each app we have on device, see if we've backed it up yet.  If not,
263            // write its signature block to the output, keyed on the package name.
264            for (PackageInfo pkg : mAllPackages) {
265                String packName = pkg.packageName;
266                if (packName.equals(GLOBAL_METADATA_KEY)) {
267                    // We've already handled the metadata key; skip it here
268                    continue;
269                } else {
270                    PackageInfo info = null;
271                    try {
272                        info = mPackageManager.getPackageInfo(packName,
273                                PackageManager.GET_SIGNATURES);
274                    } catch (NameNotFoundException e) {
275                        // Weird; we just found it, and now are told it doesn't exist.
276                        // Treat it as having been removed from the device.
277                        mExisting.add(packName);
278                        continue;
279                    }
280
281                    if (mExisting.contains(packName)) {
282                        // We have backed up this app before.  Check whether the version
283                        // of the backup matches the version of the current app; if they
284                        // don't match, the app has been updated and we need to store its
285                        // metadata again.  In either case, take it out of mExisting so that
286                        // we don't consider it deleted later.
287                        mExisting.remove(packName);
288                        if (info.versionCode == mStateVersions.get(packName).versionCode) {
289                            continue;
290                        }
291                    }
292
293                    if (info.signatures == null || info.signatures.length == 0)
294                    {
295                        Slog.w(TAG, "Not backing up package " + packName
296                                + " since it appears to have no signatures.");
297                        continue;
298                    }
299
300                    // We need to store this app's metadata
301                    /*
302                     * Metadata for each package:
303                     *
304                     * int version       -- [4] the package's versionCode
305                     * byte[] signatures -- [len] flattened signature hash array of the package
306                     */
307
308                    // marshal the version code in a canonical form
309                    outputBuffer.reset();
310                    outputBufferStream.writeInt(info.versionCode);
311                    writeSignatureHashArray(outputBufferStream,
312                            hashSignatureArray(info.signatures));
313
314                    if (DEBUG) {
315                        Slog.v(TAG, "+ writing metadata for " + packName
316                                + " version=" + info.versionCode
317                                + " entityLen=" + outputBuffer.size());
318                    }
319
320                    // Now we can write the backup entity for this package
321                    writeEntity(data, packName, outputBuffer.toByteArray());
322                }
323            }
324
325            // At this point, the only entries in 'existing' are apps that were
326            // mentioned in the saved state file, but appear to no longer be present
327            // on the device.  We want to preserve the entry for them, however,
328            // because we want the right thing to happen if the user goes through
329            // a backup / uninstall / backup / reinstall sequence.
330            if (DEBUG) {
331                if (mExisting.size() > 0) {
332                    StringBuilder sb = new StringBuilder(64);
333                    sb.append("Preserving metadata for deleted packages:");
334                    for (String app : mExisting) {
335                        sb.append(' ');
336                        sb.append(app);
337                    }
338                    Slog.v(TAG, sb.toString());
339                }
340            }
341        } catch (IOException e) {
342            // Real error writing data
343            Slog.e(TAG, "Unable to write package backup data file!");
344            return;
345        }
346
347        // Finally, write the new state blob -- just the list of all apps we handled
348        writeStateFile(mAllPackages, home, homeVersion, homeSigHashes, newState);
349    }
350
351    private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
352            throws IOException {
353        data.writeEntityHeader(key, bytes.length);
354        data.writeEntityData(bytes, bytes.length);
355    }
356
357    // "Restore" here is a misnomer.  What we're really doing is reading back the
358    // set of app signatures associated with each backed-up app in this restore
359    // image.  We'll use those later to determine what we can legitimately restore.
360    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
361            throws IOException {
362        List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
363        HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
364        if (DEBUG) Slog.v(TAG, "onRestore()");
365        int storedSystemVersion = -1;
366
367        while (data.readNextHeader()) {
368            String key = data.getKey();
369            int dataSize = data.getDataSize();
370
371            if (DEBUG) Slog.v(TAG, "   got key=" + key + " dataSize=" + dataSize);
372
373            // generic setup to parse any entity data
374            byte[] inputBytes = new byte[dataSize];
375            data.readEntityData(inputBytes, 0, dataSize);
376            ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes);
377            DataInputStream inputBufferStream = new DataInputStream(inputBuffer);
378
379            if (key.equals(GLOBAL_METADATA_KEY)) {
380                int storedSdkVersion = inputBufferStream.readInt();
381                if (DEBUG) Slog.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
382                if (storedSystemVersion > Build.VERSION.SDK_INT) {
383                    // returning before setting the sig map means we rejected the restore set
384                    Slog.w(TAG, "Restore set was from a later version of Android; not restoring");
385                    return;
386                }
387                mStoredSdkVersion = storedSdkVersion;
388                mStoredIncrementalVersion = inputBufferStream.readUTF();
389                mHasMetadata = true;
390                if (DEBUG) {
391                    Slog.i(TAG, "Restore set version " + storedSystemVersion
392                            + " is compatible with OS version " + Build.VERSION.SDK_INT
393                            + " (" + mStoredIncrementalVersion + " vs "
394                            + Build.VERSION.INCREMENTAL + ")");
395                }
396            } else if (key.equals(DEFAULT_HOME_KEY)) {
397                String cn = inputBufferStream.readUTF();
398                mRestoredHome = ComponentName.unflattenFromString(cn);
399                mRestoredHomeVersion = inputBufferStream.readLong();
400                mRestoredHomeInstaller = inputBufferStream.readUTF();
401                mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
402                if (DEBUG) {
403                    Slog.i(TAG, "   read preferred home app " + mRestoredHome
404                            + " version=" + mRestoredHomeVersion
405                            + " installer=" + mRestoredHomeInstaller
406                            + " sig=" + mRestoredHomeSigHashes);
407                }
408            } else {
409                // it's a file metadata record
410                int versionCode = inputBufferStream.readInt();
411                ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
412                if (DEBUG) {
413                    Slog.i(TAG, "   read metadata for " + key
414                            + " dataSize=" + dataSize
415                            + " versionCode=" + versionCode + " sigs=" + sigs);
416                }
417
418                if (sigs == null || sigs.size() == 0) {
419                    Slog.w(TAG, "Not restoring package " + key
420                            + " since it appears to have no signatures.");
421                    continue;
422                }
423
424                ApplicationInfo app = new ApplicationInfo();
425                app.packageName = key;
426                restoredApps.add(app);
427                sigMap.put(key, new Metadata(versionCode, sigs));
428            }
429        }
430
431        // On successful completion, cache the signature map for the Backup Manager to use
432        mRestoredSignatures = sigMap;
433    }
434
435    private static ArrayList<byte[]> hashSignatureArray(Signature[] sigs) {
436        if (sigs == null) {
437            return null;
438        }
439
440        ArrayList<byte[]> hashes = new ArrayList<byte[]>(sigs.length);
441        for (Signature s : sigs) {
442            hashes.add(BackupManagerService.hashSignature(s));
443        }
444        return hashes;
445    }
446
447    private static void writeSignatureHashArray(DataOutputStream out, ArrayList<byte[]> hashes)
448            throws IOException {
449        // the number of entries in the array
450        out.writeInt(hashes.size());
451
452        // the hash arrays themselves as length + contents
453        for (byte[] buffer : hashes) {
454            out.writeInt(buffer.length);
455            out.write(buffer);
456        }
457    }
458
459    private static ArrayList<byte[]> readSignatureHashArray(DataInputStream in) {
460        try {
461            int num;
462            try {
463                num = in.readInt();
464            } catch (EOFException e) {
465                // clean termination
466                Slog.w(TAG, "Read empty signature block");
467                return null;
468            }
469
470            if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
471
472            // Sensical?
473            if (num > 20) {
474                Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
475                throw new IllegalStateException("Bad restore state");
476            }
477
478            // This could be a "legacy" block of actual signatures rather than their hashes.
479            // If this is the case, convert them now.  We judge based on the payload size:
480            // if the blocks are all 256 bits (32 bytes) then we take them to be SHA-256 hashes;
481            // otherwise we take them to be Signatures.
482            boolean nonHashFound = false;
483            ArrayList<byte[]> sigs = new ArrayList<byte[]>(num);
484            for (int i = 0; i < num; i++) {
485                int len = in.readInt();
486                byte[] readHash = new byte[len];
487                in.read(readHash);
488                sigs.add(readHash);
489                if (len != 32) {
490                    nonHashFound = true;
491                }
492            }
493
494            if (nonHashFound) {
495                ArrayList<byte[]> hashes =
496                        new ArrayList<byte[]>(sigs.size());
497                for (int i = 0; i < sigs.size(); i++) {
498                    Signature s = new Signature(sigs.get(i));
499                    hashes.add(BackupManagerService.hashSignature(s));
500                }
501                sigs = hashes;
502            }
503
504            return sigs;
505        } catch (IOException e) {
506            Slog.e(TAG, "Unable to read signatures");
507            return null;
508        }
509    }
510
511    // Util: parse out an existing state file into a usable structure
512    private void parseStateFile(ParcelFileDescriptor stateFile) {
513        mExisting.clear();
514        mStateVersions.clear();
515        mStoredSdkVersion = 0;
516        mStoredIncrementalVersion = null;
517        mStoredHomeComponent = null;
518        mStoredHomeVersion = 0;
519        mStoredHomeSigHashes = null;
520
521        // The state file is just the list of app names we have stored signatures for
522        // with the exception of the metadata block, to which is also appended the
523        // version numbers corresponding with the last time we wrote this PM block.
524        // If they mismatch the current system, we'll re-store the metadata key.
525        FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
526        BufferedInputStream inbuffer = new BufferedInputStream(instream);
527        DataInputStream in = new DataInputStream(inbuffer);
528
529        try {
530            boolean ignoreExisting = false;
531            String pkg = in.readUTF();
532
533            // Validate the state file version is sensical to us
534            if (pkg.equals(STATE_FILE_HEADER)) {
535                int stateVersion = in.readInt();
536                if (stateVersion > STATE_FILE_VERSION) {
537                    Slog.w(TAG, "Unsupported state file version " + stateVersion
538                            + ", redoing from start");
539                    return;
540                }
541                pkg = in.readUTF();
542            } else {
543                // This is an older version of the state file in which the lead element
544                // is not a STATE_FILE_VERSION string.  If that's the case, we want to
545                // make sure to write our full backup dataset when given an opportunity.
546                // We trigger that by simply not marking the restored package metadata
547                // as known-to-exist-in-archive.
548                Slog.i(TAG, "Older version of saved state - rewriting");
549                ignoreExisting = true;
550            }
551
552            // First comes the preferred home app data, if any, headed by the DEFAULT_HOME_KEY tag
553            if (pkg.equals(DEFAULT_HOME_KEY)) {
554                // flattened component name, version, signature of the home app
555                mStoredHomeComponent = ComponentName.unflattenFromString(in.readUTF());
556                mStoredHomeVersion = in.readLong();
557                mStoredHomeSigHashes = readSignatureHashArray(in);
558
559                pkg = in.readUTF(); // set up for the next block of state
560            } else {
561                // else no preferred home app on the ancestral device - fall through to the rest
562            }
563
564            // After (possible) home app data comes the global metadata block
565            if (pkg.equals(GLOBAL_METADATA_KEY)) {
566                mStoredSdkVersion = in.readInt();
567                mStoredIncrementalVersion = in.readUTF();
568                if (!ignoreExisting) {
569                    mExisting.add(GLOBAL_METADATA_KEY);
570                }
571            } else {
572                Slog.e(TAG, "No global metadata in state file!");
573                return;
574            }
575
576            // The global metadata was last; now read all the apps
577            while (true) {
578                pkg = in.readUTF();
579                int versionCode = in.readInt();
580
581                if (!ignoreExisting) {
582                    mExisting.add(pkg);
583                }
584                mStateVersions.put(pkg, new Metadata(versionCode, null));
585            }
586        } catch (EOFException eof) {
587            // safe; we're done
588        } catch (IOException e) {
589            // whoops, bad state file.  abort.
590            Slog.e(TAG, "Unable to read Package Manager state file: " + e);
591        }
592    }
593
594    private ComponentName getPreferredHomeComponent() {
595        return mPackageManager.getHomeActivities(new ArrayList<ResolveInfo>());
596    }
597
598    // Util: write out our new backup state file
599    private void writeStateFile(List<PackageInfo> pkgs, ComponentName preferredHome,
600            long homeVersion, ArrayList<byte[]> homeSigHashes, ParcelFileDescriptor stateFile) {
601        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
602        BufferedOutputStream outbuf = new BufferedOutputStream(outstream);
603        DataOutputStream out = new DataOutputStream(outbuf);
604
605        // by the time we get here we know we've done all our backing up
606        try {
607            // state file version header
608            out.writeUTF(STATE_FILE_HEADER);
609            out.writeInt(STATE_FILE_VERSION);
610
611            // If we remembered a preferred home app, record that
612            if (preferredHome != null) {
613                out.writeUTF(DEFAULT_HOME_KEY);
614                out.writeUTF(preferredHome.flattenToString());
615                out.writeLong(homeVersion);
616                writeSignatureHashArray(out, homeSigHashes);
617            }
618
619            // Conclude with the metadata block
620            out.writeUTF(GLOBAL_METADATA_KEY);
621            out.writeInt(Build.VERSION.SDK_INT);
622            out.writeUTF(Build.VERSION.INCREMENTAL);
623
624            // now write all the app names + versions
625            for (PackageInfo pkg : pkgs) {
626                out.writeUTF(pkg.packageName);
627                out.writeInt(pkg.versionCode);
628            }
629
630            out.flush();
631        } catch (IOException e) {
632            Slog.e(TAG, "Unable to write package manager state file!");
633        }
634    }
635}
636