Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3a31a93b authored by Christopher Tate's avatar Christopher Tate
Browse files

Add some global metadata to the restore set

In addition to the signatures of each participating application, we now also
store the versionCode of each backed-up package, plus the OS version running on
the device that contributed the backup set.  We also refuse to process a backup
from a later OS revision to an earlier one, or from a later app version to an
earlier.

LocalTransport has been modified as well to be more resilient to changes in the
system's use of metadata pseudopackages.
parent e146d824
Loading
Loading
Loading
Loading
+14 −7
Original line number Diff line number Diff line
@@ -170,18 +170,13 @@ public class LocalTransport extends IBackupTransport.Stub {
        // The restore set is the concatenation of the individual record blobs,
        // each of which is a file in the package's directory
        File[] blobs = packageDir.listFiles();
        if (DEBUG) Log.v(TAG, "   found " + blobs.length + " key files");
        int err = 0;
        if (blobs != null && blobs.length > 0) {
            BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
            try {
                for (File f : blobs) {
                    FileInputStream in = new FileInputStream(f);
                    int size = (int) f.length();
                    byte[] buf = new byte[size];
                    in.read(buf);
                    String key = new String(Base64.decode(f.getName()));
                    out.writeEntityHeader(key, size);
                    out.writeEntityData(buf, size);
                    copyToRestoreData(f, out);
                }
            } catch (Exception e) {
                Log.e(TAG, "Unable to read backup records");
@@ -190,4 +185,16 @@ public class LocalTransport extends IBackupTransport.Stub {
        }
        return err;
    }

    private void copyToRestoreData(File f, BackupDataOutput out) throws IOException {
        FileInputStream in = new FileInputStream(f);
        int size = (int) f.length();
        byte[] buf = new byte[size];
        in.read(buf);
        String key = new String(Base64.decode(f.getName()));
        if (DEBUG) Log.v(TAG, "   ... copy to stream: key=" + key
                + " size=" + size);
        out.writeEntityHeader(key, size);
        out.writeEntityData(buf, size);
    }
}
+19 −9
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@@ -850,18 +851,27 @@ class BackupManagerService extends IBackupManager.Stub {
                            if (app != null) {
                                // Validate against the backed-up signature block, too
                                Metadata info = pmAgent.getRestoredMetadata(app.packageName);
                                if (info != null) {
                                    if (app.versionCode >= info.versionCode) {
                                    if (DEBUG) Log.v(TAG, "Restore version " + info.versionCode
                                            + " compatible with app version " + app.versionCode);
                                        if (DEBUG) Log.v(TAG, "Restore version "
                                                + info.versionCode
                                                + " compatible with app version "
                                                + app.versionCode);
                                        if (signaturesMatch(info.signatures, app.signatures)) {
                                            appsToRestore.add(app);
                                        } else {
                                        Log.w(TAG, "Sig mismatch restoring " + app.packageName);
                                            Log.w(TAG, "Sig mismatch restoring "
                                                    + app.packageName);
                                        }
                                    } else {
                                        Log.i(TAG, "Restore set for " + app.packageName
                                                + " is too new [" + info.versionCode
                                            + "] for installed app version " + app.versionCode);
                                                + "] for installed app version "
                                                + app.versionCode);
                                    }
                                } else {
                                    Log.d(TAG, "Unable to get metadata for "
                                            + app.packageName);
                                }
                            }
                        }
+143 −84
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Log;

@@ -52,6 +53,10 @@ public class PackageManagerBackupAgent extends BackupAgent {
    private static final String TAG = "PMBA";
    private static final boolean DEBUG = true;

    // key under which we store global metadata (individual app metadata
    // is stored using the package name as a key)
    private static final String GLOBAL_METADATA_KEY = "@meta@";

    private List<ApplicationInfo> mAllApps;
    private PackageManager mPackageManager;
    private HashMap<String, Metadata> mRestoredSignatures;
@@ -76,6 +81,7 @@ public class PackageManagerBackupAgent extends BackupAgent {

    public Metadata getRestoredMetadata(String packageName) {
        if (mRestoredSignatures == null) {
            Log.w(TAG, "getRestoredMetadata() before metadata read!");
            return null;
        }

@@ -86,13 +92,32 @@ public class PackageManagerBackupAgent extends BackupAgent {
    // the package name.
    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
            ParcelFileDescriptor newState) {
        if (DEBUG) Log.v(TAG, "onBackup()");

        ByteArrayOutputStream bufStream = new ByteArrayOutputStream();  // we'll reuse these
        DataOutputStream outWriter = new DataOutputStream(bufStream);
        HashSet<String> existing = parseStateFile(oldState);

        try {
            /*
             * Global metadata:
             *
             * int version -- the SDK version of the OS itself on the device
             *                that produced this backup set.  Used to reject
             *                backups from later OSes onto earlier ones.
             */
            if (!existing.contains(GLOBAL_METADATA_KEY)) {
                if (DEBUG) Log.v(TAG, "Storing global metadata key");
                outWriter.writeInt(Build.VERSION.SDK_INT);
                byte[] metadata = bufStream.toByteArray();
                data.writeEntityHeader(GLOBAL_METADATA_KEY, metadata.length);
                data.writeEntityData(metadata, metadata.length);
            } else {
                if (DEBUG) Log.v(TAG, "Global metadata key already stored");
            }

            // For each app we have on device, see if we've backed it up yet.  If not,
            // write its signature block to the output, keyed on the package name.
        if (DEBUG) Log.v(TAG, "onBackup()");
        ByteArrayOutputStream bufStream = new ByteArrayOutputStream();  // we'll reuse these
        DataOutputStream outWriter = new DataOutputStream(bufStream);
            for (ApplicationInfo app : mAllApps) {
                String packName = app.packageName;
                if (!existing.contains(packName)) {
@@ -116,7 +141,10 @@ public class PackageManagerBackupAgent extends BackupAgent {

                        // !!! TODO: take out this debugging
                        if (DEBUG) {
                        Log.v(TAG, "+ metadata for " + packName + " version=" + info.versionCode);
                            Log.v(TAG, "+ metadata for " + packName
                                    + " version=" + info.versionCode
                                    + " versionLen=" + versionBuf.length
                                    + " sigsLen=" + sigs.length);
                        }
                        // Now we can write the backup entity for this package
                        data.writeEntityHeader(packName, versionBuf.length + sigs.length);
@@ -126,10 +154,6 @@ public class PackageManagerBackupAgent extends BackupAgent {
                        // Weird; we just found it, and now are told it doesn't exist.
                        // Treat it as having been removed from the device.
                        existing.add(packName);
                } catch (IOException e) {
                    // Real error writing data
                    Log.e(TAG, "Unable to write package backup data file!");
                    return;
                    }
                } else {
                    // We've already backed up this app.  Remove it from the set so
@@ -155,6 +179,11 @@ public class PackageManagerBackupAgent extends BackupAgent {
                    return;
                }
            }
        } catch (IOException e) {
            // Real error writing data
            Log.e(TAG, "Unable to write package backup data file!");
            return;
        }

        // Finally, write the new state blob -- just the list of all apps we handled
        writeStateFile(mAllApps, newState);
@@ -167,30 +196,53 @@ public class PackageManagerBackupAgent extends BackupAgent {
            throws IOException {
        List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>();
        HashMap<String, Metadata> sigMap = new HashMap<String, Metadata>();
        if (DEBUG) Log.v(TAG, "onRestore()");
        int storedSystemVersion = -1;

        while (data.readNextHeader()) {
            String key = data.getKey();
            int dataSize = data.getDataSize();
            byte[] buf = new byte[dataSize];
            data.readEntityData(buf, 0, dataSize);

            ByteArrayInputStream bufStream = new ByteArrayInputStream(buf);
            DataInputStream in = new DataInputStream(bufStream);
            int versionCode = in.readInt();
            if (DEBUG) Log.v(TAG, "   got key=" + key + " dataSize=" + dataSize);

            // generic setup to parse any entity data
            byte[] dataBuf = new byte[dataSize];
            data.readEntityData(dataBuf, 0, dataSize);
            ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
            DataInputStream in = new DataInputStream(baStream);

            if (key.equals(GLOBAL_METADATA_KEY)) {
                storedSystemVersion = in.readInt();
                if (DEBUG) Log.v(TAG, "   storedSystemVersion = " + storedSystemVersion);
                if (storedSystemVersion > Build.VERSION.SDK_INT) {
                    // returning before setting the sig map means we rejected the restore set
                    Log.w(TAG, "Restore set was from a later version of Android; not restoring");
                    return;
                }
                // !!! TODO: remove this debugging output
                if (DEBUG) {
                    Log.i(TAG, "Restore set version " + storedSystemVersion
                            + " is compatible with OS version " + Build.VERSION.SDK_INT);
                }
            } else {
                // it's a file metadata record
                int versionCode = in.readInt();
                Signature[] sigs = unflattenSignatureArray(in);
            String pkg = data.getKey();
//              !!! TODO: take out this debugging
                if (DEBUG) {
                Log.i(TAG, "+ restored metadata for " + pkg
                    Log.i(TAG, "   restored metadata for " + key
                            + " dataSize=" + dataSize
                            + " versionCode=" + versionCode + " sigs=" + sigs);
                }

                ApplicationInfo app = new ApplicationInfo();
            app.packageName = pkg;
                app.packageName = key;
                restoredApps.add(app);
            sigMap.put(pkg, new Metadata(versionCode, sigs));
                sigMap.put(key, new Metadata(versionCode, sigs));
            }
        }

        // On successful completion, cache the signature map for the Backup Manager to use
        mRestoredSignatures = sigMap;
    }

@@ -225,6 +277,7 @@ public class PackageManagerBackupAgent extends BackupAgent {

        try {
            int num = in.readInt();
            Log.v(TAG, " ... unflatten read " + num);
            sigs = new Signature[num];
            for (int i = 0; i < num; i++) {
                int len = in.readInt();
@@ -273,20 +326,26 @@ public class PackageManagerBackupAgent extends BackupAgent {
        return set;
    }

    // Util: write a set of names into a new state file
    // Util: write out our new backup state file
    private void writeStateFile(List<ApplicationInfo> apps, ParcelFileDescriptor stateFile) {
        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
        DataOutputStream out = new DataOutputStream(outstream);

        for (ApplicationInfo app : apps) {
        try {
            // by the time we get here we know we've stored the global metadata record
            byte[] metaNameBuf = GLOBAL_METADATA_KEY.getBytes();
            out.writeInt(metaNameBuf.length);
            out.write(metaNameBuf);

            // now write all the app names too
            for (ApplicationInfo app : apps) {
                byte[] pkgNameBuf = app.packageName.getBytes();
                out.writeInt(pkgNameBuf.length);
                out.write(pkgNameBuf);
            }
        } catch (IOException e) {
            Log.e(TAG, "Unable to write package manager state file!");
            return;
        }
    }
}
}