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

Commit 06522151 authored by Joël Stemmer's avatar Joël Stemmer
Browse files

Read and write the cross-platform manifest for cross-platform transfers

On backup we write the cross-platform manifest to the full backup tar
file so it can be sent to the target device. On restore we check whether
the locally installed app has opted-in to cross-platform transfers the
the configured bundle id and team id matches the values in the manifest
that we received from the source device.

Bug: 432673356
Test: atest FullRestoreEngineTest.java
Test: Manual testing with bmgr backupnow/restore
Flag: com.android.server.backup.enable_cross_platform_transfer
Change-Id: I60b4435828cd0fc876cbe883b23deefd72aa7117
parent 24fe7026
Loading
Loading
Loading
Loading
+42 −0
Original line number Diff line number Diff line
@@ -20,13 +20,17 @@ import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
import static com.android.server.backup.UserBackupManagerService.CROSS_PLATFORM_MANIFEST_FILENAME;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import static com.android.server.backup.crossplatform.PlatformConfigParser.PLATFORM_IOS;

import android.annotation.UserIdInt;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.BackupTransport;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -38,14 +42,17 @@ import android.util.Slog;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.Flags;
import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.crossplatform.CrossPlatformManifest;
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.utils.BackupEligibilityRules;
import com.android.server.backup.utils.BackupManagerMonitorEventSender;
import com.android.server.backup.utils.FullBackupUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
@@ -137,6 +144,12 @@ public class FullBackupEngine {
                    appMetadataBackupWriter.backupObb(mUserId, mPackage);
                }

                if (Flags.enableCrossPlatformTransfer()
                        && (mTransportFlags & BackupAgent.FLAG_CROSS_PLATFORM_DATA_TRANSFER_IOS)
                                != 0) {
                    backupCrossPlatformManifest(output, mPackage.applicationInfo);
                }

                Slog.d(TAG, "Calling doFullBackup() on " + packageName);

                long timeout =
@@ -181,6 +194,35 @@ public class FullBackupEngine {
                    && !isSharedStorage
                    && (!isSystemApp || isUpdatedSystemApp);
        }

        /** Back up the app's cross platform manifest. */
        private void backupCrossPlatformManifest(
                FullBackupDataOutput output, ApplicationInfo applicationInfo) throws IOException {
            CrossPlatformManifest manifest =
                    CrossPlatformManifest.create(
                            mPackage,
                            PLATFORM_IOS,
                            mBackupEligibilityRules.getPlatformSpecificParams(
                                    applicationInfo, PLATFORM_IOS));
            File manifestFile = new File(mFilesDir, CROSS_PLATFORM_MANIFEST_FILENAME);
            try (FileOutputStream out = new FileOutputStream(manifestFile)) {
                out.write(manifest.toByteArray());
            }

            // We want the manifest block in the archive stream to be constant each time we generate
            // a backup stream for the app. However, the underlying TAR mechanism sees it as a file
            // and will propagate its last modified time. We pin the last modified time to zero to
            // prevent the TAR header from varying.
            manifestFile.setLastModified(0);

            FullBackup.backupToTar(
                    mPackage.packageName,
                    /* domain= */ null,
                    /* linkdomain= */ null,
                    mFilesDir.getAbsolutePath(),
                    manifestFile.getAbsolutePath(),
                    output);
        }
    }

    public FullBackupEngine(
+83 −5
Original line number Diff line number Diff line
@@ -20,10 +20,13 @@ import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
import static com.android.server.backup.UserBackupManagerService.CROSS_PLATFORM_MANIFEST_FILENAME;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import static com.android.server.backup.crossplatform.PlatformConfigParser.PLATFORM_IOS;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
@@ -31,10 +34,12 @@ import android.app.backup.BackupAnnotations;
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.FullBackup;
import android.app.backup.FullBackup.BackupScheme.PlatformSpecificParams;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IFullBackupRestoreObserver;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
@@ -56,6 +61,7 @@ import com.android.server.backup.KeyValueAdbRestoreEngine;
import com.android.server.backup.OperationStorage;
import com.android.server.backup.OperationStorage.OpType;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.crossplatform.CrossPlatformManifest;
import com.android.server.backup.fullbackup.FullBackupObbConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
import com.android.server.backup.utils.BackupManagerMonitorEventSender;
@@ -129,7 +135,10 @@ public class FullRestoreEngine extends RestoreEngine {

    // Widget blob to be restored out-of-band
    private byte[] mWidgetData = null;

    private int mTransportFlags = 0;
    private long mAppVersion;
    private String mContentVersion = "";

    final int mEphemeralOpToken;

@@ -182,7 +191,9 @@ public class FullRestoreEngine extends RestoreEngine {
    }

    @VisibleForTesting
    FullRestoreEngine() {
    FullRestoreEngine(UserBackupManagerService backupManagerService) {
        mBackupManagerService = backupManagerService;

        mIsAdbRestore = false;
        mAllowApks = false;
        mEphemeralOpToken = 0;
@@ -190,7 +201,6 @@ public class FullRestoreEngine extends RestoreEngine {
        mBackupEligibilityRules = null;
        mAgentTimeoutParameters = null;
        mBuffer = null;
        mBackupManagerService = null;
        mOperationStorage = null;
        mMonitor = null;
        mMonitorTask = null;
@@ -290,6 +300,37 @@ public class FullRestoreEngine extends RestoreEngine {
                    mManifestSignatures.put(info.packageName, signatures);
                    mPackagePolicies.put(pkg, restorePolicy);
                    mPackageInstallers.put(pkg, info.installerPackageName);
                    // We've read only the manifest content itself at this point,
                    // so consume the footer before looping around to the next
                    // input file
                    tarBackupReader.skipTarPadding(info.size);
                    mObserver = FullBackupRestoreObserverUtils.sendOnRestorePackage(mObserver, pkg);
                } else if (Flags.enableCrossPlatformTransfer()
                        && info.path.equals(CROSS_PLATFORM_MANIFEST_FILENAME)) {
                    // We start by reading the manifest content so we consume the data before doing
                    // any further checks.
                    CrossPlatformManifest manifest =
                            tarBackupReader.readCrossPlatformManifest(info);
                    if (mBackupEligibilityRules.getBackupDestination()
                            != BackupDestination.CROSS_PLATFORM_TRANSFER) {
                        mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                    } else {
                        PlatformSpecificParams params =
                                findValidPlatformSpecificParams(
                                        info.packageName, manifest, mBackupEligibilityRules);
                        if (params == null) {
                            Slog.w(
                                    TAG,
                                    "No source declared platform-specific params found that match"
                                            + " the target app");
                            mPackagePolicies.put(pkg, RestorePolicy.IGNORE);
                        } else {
                            mPackagePolicies.put(pkg, RestorePolicy.ACCEPT);
                            mTransportFlags |= BackupAgent.FLAG_CROSS_PLATFORM_DATA_TRANSFER_IOS;
                            mContentVersion = params.getContentVersion();
                        }
                    }

                    // We've read only the manifest content itself at this point,
                    // so consume the footer before looping around to the next
                    // input file
@@ -541,9 +582,9 @@ public class FullRestoreEngine extends RestoreEngine {
                                            info.mtime,
                                            token,
                                            mBackupManagerService.getBackupManagerBinder(),
                                            /* appVersionCode= */ 0,
                                            /* transportFlags= */ 0,
                                            /* contentVersion= */ "");
                                            mAppVersion,
                                            mTransportFlags,
                                            mContentVersion);
                                }
                            }
                        } catch (IOException e) {
@@ -879,4 +920,41 @@ public class FullRestoreEngine extends RestoreEngine {
                                : ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL,
                        mBackupEligibilityRules.getBackupDestination());
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    PlatformSpecificParams findValidPlatformSpecificParams(
            String pkg,
            @Nullable CrossPlatformManifest manifest,
            BackupEligibilityRules backupEligibilityRules) {
        if (manifest == null) {
            Slog.d(TAG, "No cross-platform manifest found.");
            return null;
        }

        ApplicationInfo targetApp;
        try {
            targetApp = mBackupManagerService
                    .getPackageManager()
                    .getApplicationInfoAsUser(
                            pkg,
                            PackageManager.GET_SIGNING_CERTIFICATES,
                            mUserId);
        } catch (NameNotFoundException e) {
            Slog.d(TAG, "Unable to fetch cross-platform configuration for target app", e);
            return null;
        }

        // Both source and target may specify multiple platform specific params. For us to continue
        // restoring, there should be at least one combination that matches.
        for (PlatformSpecificParams sourceParams : manifest.getPlatformSpecificParams()) {
            for (PlatformSpecificParams targetParams :
                    backupEligibilityRules.getPlatformSpecificParams(targetApp, PLATFORM_IOS)) {
                if (TextUtils.equals(sourceParams.getBundleId(), targetParams.getBundleId())
                        && TextUtils.equals(sourceParams.getTeamId(), targetParams.getTeamId())) {
                    return sourceParams;
                }
            }
        }
        return null;
    }
}
+24 −13
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@ import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.Nullable;
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupTransport;
import android.app.backup.FullBackup;
import android.app.backup.FullBackup.BackupScheme.PlatformSpecificParams;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -98,9 +98,6 @@ public class BackupEligibilityRules {
    @BackupDestination private final int mBackupDestination;
    private final boolean mSkipRestoreForLaunchedApps;

    private Map<String, List<FullBackup.BackupScheme.PlatformSpecificParams>>
            mPlatformSpecificParams;

    /**
     * When this change is enabled, {@code adb backup} is automatically turned on for apps running
     * as debuggable ({@code android:debuggable} set to {@code true}) and unavailable to any other
@@ -157,7 +154,6 @@ public class BackupEligibilityRules {
        mBackupDestination = backupDestination;
        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
        mSkipRestoreForLaunchedApps = skipRestoreForLaunchedApps;
        mPlatformSpecificParams = Collections.emptyMap();
    }

    /**
@@ -363,23 +359,38 @@ public class BackupEligibilityRules {
     */
    public boolean appSupportsCrossPlatformTransfer(
            ApplicationInfo applicationInfo, String platform) {
        return !getPlatformSpecificParams(applicationInfo, platform).isEmpty();
    }

    /** Get the platform specific parameters for the given package, if it configured any. */
    public List<PlatformSpecificParams> getPlatformSpecificParams(
            ApplicationInfo applicationInfo, String platform) {
        Map<String, List<PlatformSpecificParams>> platformSpecificParams =
                loadCrossPlatformConfig(applicationInfo);
        return platformSpecificParams.getOrDefault(platform, Collections.emptyList());
    }

    private Map<String, List<PlatformSpecificParams>> loadCrossPlatformConfig(
            ApplicationInfo applicationInfo) {
        try {
            mPlatformSpecificParams =
            Map<String, List<PlatformSpecificParams>> platformSpecificParams =
                    PlatformConfigParser.parsePlatformSpecificConfig(
                            mPackageManager, applicationInfo);
            Slog.d(
                    TAG,
                    "Loaded cross-platform configuration for "
                            + applicationInfo.packageName
                            + " and found "
                            + platformSpecificParams.size()
                            + " configured platforms.");
            return platformSpecificParams;
        } catch (IOException e) {
            Slog.e(
                    TAG,
                    "Unable to parse cross-platform configuration from data-extraction-rules",
                    e);
            return false;
            return Collections.emptyMap();
        }
        Slog.d(
                TAG,
                "Found cross-platform configuration for "
                        + mPlatformSpecificParams.size()
                        + " platforms.");
        return !mPlatformSpecificParams.getOrDefault(platform, Collections.emptyList()).isEmpty();
    }

    /**
+54 −3
Original line number Diff line number Diff line
@@ -31,9 +31,9 @@ import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER;
import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE;

import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
@@ -41,8 +41,10 @@ import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST
import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_VERSION;
import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
import static com.android.server.backup.UserBackupManagerService.BACKUP_WIDGET_METADATA_TOKEN;
import static com.android.server.backup.UserBackupManagerService.CROSS_PLATFORM_MANIFEST_FILENAME;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;

import android.annotation.Nullable;
import android.app.backup.BackupAgent;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.FullBackup;
@@ -61,6 +63,7 @@ import android.util.Slog;

import com.android.server.backup.FileMetadata;
import com.android.server.backup.Flags;
import com.android.server.backup.crossplatform.CrossPlatformManifest;
import com.android.server.backup.restore.RestorePolicy;

import java.io.ByteArrayInputStream;
@@ -212,8 +215,9 @@ public class TarBackupReader {

                    // if it's a manifest or metadata payload we're done, otherwise parse
                    // out the domain into which the file will be restored
                    if (!info.path.equals(BACKUP_MANIFEST_FILENAME) &&
                            !info.path.equals(BACKUP_METADATA_FILENAME)) {
                    if (!info.path.equals(BACKUP_MANIFEST_FILENAME)
                            && !info.path.equals(CROSS_PLATFORM_MANIFEST_FILENAME)
                            && !info.path.equals(BACKUP_METADATA_FILENAME)) {
                        slash = info.path.indexOf('/');
                        if (slash < 0) {
                            throw new IOException("Illegal semantic path in non-manifest "
@@ -378,6 +382,53 @@ public class TarBackupReader {
        return null;
    }

    /**
     * Parses the cross-platform manifest from the tar file and returns it.
     * @throws IOException in case of an error.
     */
    @Nullable
    public CrossPlatformManifest readCrossPlatformManifest(FileMetadata info) throws IOException {
        // Fail on suspiciously large manifest files
        if (info.size > 64 * 1024) {
            throw new IOException(
                    "Restore cross platform manifest too big; corrupt? size=" + info.size);
        }

        byte[] buffer = new byte[(int) info.size];
        if (DEBUG) {
            Slog.i(TAG, "   readCrossPlatformManifest() looking for " + info.size + " bytes");
        }
        if (readExactly(mInputStream, buffer, 0, (int) info.size) == info.size) {
            mBytesReadListener.onBytesRead(info.size);
        } else {
            throw new IOException("Unexpected EOF in cross-platform manifest");
        }
        CrossPlatformManifest manifest = CrossPlatformManifest.parseFrom(buffer);
        if (!manifest.getPackageName().equals(info.packageName)) {
            Slog.i(
                    TAG,
                    "Expected package "
                            + info.packageName
                            + " but restore manifest claims "
                            + manifest.getPackageName());
            Bundle monitoringExtras =
                    mBackupManagerMonitorEventSender.putMonitoringExtra(
                            null, EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
            monitoringExtras =
                    mBackupManagerMonitorEventSender.putMonitoringExtra(
                            monitoringExtras,
                            EXTRA_LOG_MANIFEST_PACKAGE_NAME,
                            manifest.getPackageName());
            mBackupManagerMonitorEventSender.monitorEvent(
                    LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE,
                    null,
                    LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                    monitoringExtras);
            return null;
        }
        return manifest;
    }

    /**
     * Chooses restore policy.
     *
+111 −2

File changed.

Preview size limit exceeded, changes collapsed.