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

Commit bdb8848a authored by Annie Meng's avatar Annie Meng
Browse files

[Multi-user] Disable backup by default in non-system users

Key changes in this CL:
- Backup is now disabled by default in non-system users unless DPM
activates backup for this user AND the system user is activated. This
provides gating for the multi-user B&R feature.
- Activation is done via an 'activate' file that is per-user (but lives
in the system user directory to account for locked users).
- isBackupServiceActive() handles both locked and unlocked users.
- Added a bmgr command to expose isBackupServiceActive() for testing
purposes and enforce appropriate permissions.

Future CLs:
- Handle future migration to backup on by default for non-system users
- Change CTS tests to use the new bmgr command

Bug: 121306407
Test: 1) atest TrampolineTest
2) Start system user -> service started; run backup and restore
successfully
3) Start non-system user -> ignored;
4) adb shell bmgr --user 0 activate true -> security exception;
adb shell bmgr --user 10 activate true -> security exception (work
profile);
adb shell bmgr --user 11 activate true/false -> creates/deletes activate
file and starts/stops the service
Change-Id: Ic77db9b8b2e5170dcf89bef863dac4713730797a
parent 25b54058
Loading
Loading
Loading
Loading
+39 −7
Original line number Diff line number Diff line
@@ -102,7 +102,17 @@ public class Bmgr {
        String op = nextArg();
        Slog.v(TAG, "Running " + op + " for user:" + userId);

        if (!isBmgrActive(userId)) {
        if (mBmgr == null) {
            System.err.println(BMGR_NOT_RUNNING_ERR);
            return;
        }

        if ("activate".equals(op)) {
            doActivateService(userId);
            return;
        }

        if (!isBackupActive(userId)) {
            return;
        }

@@ -175,12 +185,7 @@ public class Bmgr {
        showUsage();
    }

    boolean isBmgrActive(@UserIdInt int userId) {
        if (mBmgr == null) {
            System.err.println(BMGR_NOT_RUNNING_ERR);
            return false;
        }

    boolean isBackupActive(@UserIdInt int userId) {
        try {
            if (!mBmgr.isBackupServiceActive(userId)) {
                System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -845,6 +850,27 @@ public class Bmgr {
        }
    }

    private void doActivateService(int userId) {
        String arg = nextArg();
        if (arg == null) {
            showUsage();
            return;
        }

        try {
            boolean activate = Boolean.parseBoolean(arg);
            mBmgr.setBackupServiceActive(userId, activate);
            System.out.println(
                    "Backup service now "
                            + (activate ? "activated" : "deactivated")
                            + " for user "
                            + userId);
        } catch (RemoteException e) {
            System.err.println(e.toString());
            System.err.println(BMGR_NOT_RUNNING_ERR);
        }
    }

    private String nextArg() {
        if (mNextArg >= mArgs.length) {
            return null;
@@ -880,6 +906,7 @@ public class Bmgr {
        System.err.println("       bmgr backupnow [--monitor|--monitor-verbose] --all|PACKAGE...");
        System.err.println("       bmgr cancel backups");
        System.err.println("       bmgr init TRANSPORT...");
        System.err.println("       bmgr activate BOOL");
        System.err.println("");
        System.err.println("The '--user' option specifies the user on which the operation is run.");
        System.err.println("It must be the first argument before the operation.");
@@ -946,6 +973,11 @@ public class Bmgr {
        System.err.println("");
        System.err.println("The 'init' command initializes the given transports, wiping all data");
        System.err.println("from their backing data stores.");
        System.err.println("");
        System.err.println("The 'activate' command activates or deactivates the backup service.");
        System.err.println("If the argument is 'true' it will be activated, otherwise it will be");
        System.err.println("deactivated. When deactivated, the service will not be running and no");
        System.err.println("operations can be performed until activation.");
    }

    private static class BackupMonitor extends IBackupManagerMonitor.Stub {
+127 −68
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.backup;

import static com.android.server.backup.BackupManagerService.TAG;

import android.Manifest;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager;
@@ -41,9 +42,10 @@ import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.os.UserManager;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;

import java.io.File;
@@ -75,19 +77,25 @@ import java.io.PrintWriter;
 * system user is unlocked before any other users.
 */
public class Trampoline extends IBackupManager.Stub {
    // When this file is present, the backup service is inactive.
    /**
     * Name of file that disables the backup service. If this file exists, then backup is disabled
     * for all users.
     */
    private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress";

    /**
     * Name of file for non-system users that enables the backup service for the user. Backup is
     * disabled by default in non-system users.
     */
    private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated";

    // Product-level suppression of backup/restore.
    private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";

    private static final String BACKUP_THREAD = "backup";

    /** Values for setting {@link Settings.Global#BACKUP_MULTI_USER_ENABLED} */
    private static final int MULTI_USER_DISABLED = 0;
    private static final int MULTI_USER_ENABLED = 1;

    private final Context mContext;
    private final UserManager mUserManager;

    private final boolean mGlobalDisable;
    // Lock to write backup suppress files.
@@ -104,20 +112,13 @@ public class Trampoline extends IBackupManager.Stub {
        mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());
        mUserManager = UserManager.get(context);
    }

    protected boolean isBackupDisabled() {
        return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
    }

    private boolean isMultiUserEnabled() {
        return Settings.Global.getInt(
                mContext.getContentResolver(),
                Settings.Global.BACKUP_MULTI_USER_ENABLED,
                MULTI_USER_DISABLED)
                == MULTI_USER_ENABLED;
    }

    protected int binderGetCallingUserId() {
        return Binder.getCallingUserHandle().getIdentifier();
    }
@@ -126,21 +127,65 @@ public class Trampoline extends IBackupManager.Stub {
        return Binder.getCallingUid();
    }

    protected File getSuppressFileForUser(int userId) {
        return new File(UserBackupManagerFiles.getBaseStateDir(userId),
    /** Stored in the system user's directory. */
    protected File getSuppressFileForSystemUser() {
        return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
                BACKUP_SUPPRESS_FILENAME);
    }

    protected void createBackupSuppressFileForUser(int userId) throws IOException {
        synchronized (mStateLock) {
            getSuppressFileForUser(userId).getParentFile().mkdirs();
            getSuppressFileForUser(userId).createNewFile();
    /** Stored in the system user's directory and the file is indexed by the user it refers to. */
    protected File getActivatedFileForNonSystemUser(int userId) {
        return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM),
                BACKUP_ACTIVATED_FILENAME + "-" + userId);
    }

    private void createFile(File file) throws IOException {
        if (file.exists()) {
            return;
        }

        file.getParentFile().mkdirs();
        if (!file.createNewFile()) {
            Slog.w(TAG, "Failed to create file " + file.getPath());
        }
    }

    private void deleteFile(File file) {
        if (!file.exists()) {
            return;
        }

        if (!file.delete()) {
            Slog.w(TAG, "Failed to delete file " + file.getPath());
        }
    }

    /**
     * Deactivates the backup service for user {@code userId}. If this is the system user, it
     * creates a suppress file which disables backup for all users. If this is a non-system user, it
     * only deactivates backup for that user by deleting its activate file.
     */
    @GuardedBy("mStateLock")
    private void deactivateBackupForUserLocked(int userId) throws IOException {
        if (userId == UserHandle.USER_SYSTEM) {
            createFile(getSuppressFileForSystemUser());
        } else {
            deleteFile(getActivatedFileForNonSystemUser(userId));
        }
    }

    private void deleteBackupSuppressFileForUser(int userId) {
        if (!getSuppressFileForUser(userId).delete()) {
            Slog.w(TAG, "Failed deleting backup suppressed file for user: " + userId);
    /**
     * Enables the backup service for user {@code userId}. If this is the system user, it deletes
     * the suppress file. If this is a non-system user, it creates the user's activate file. Note,
     * deleting the suppress file does not automatically enable backup for non-system users, they
     * need their own activate file in order to participate in the service.
     */
    @GuardedBy("mStateLock")
    private void activateBackupForUserLocked(int userId) throws IOException {
        if (userId == UserHandle.USER_SYSTEM) {
            deleteFile(getSuppressFileForSystemUser());
        } else {
            createFile(getActivatedFileForNonSystemUser(userId));
        }
    }

@@ -148,24 +193,31 @@ public class Trampoline extends IBackupManager.Stub {
    // admin (device owner or profile owner).
    private boolean isUserReadyForBackup(int userId) {
        return mService != null && mService.getServiceUsers().get(userId) != null
                && !isBackupSuppressedForUser(userId);
                && isBackupActivatedForUser(userId);
    }

    private boolean isBackupSuppressedForUser(int userId) {
        // If backup is disabled for system user, it's disabled for all other users on device.
        if (getSuppressFileForUser(UserHandle.USER_SYSTEM).exists()) {
            return true;
        }
        if (userId != UserHandle.USER_SYSTEM) {
            return getSuppressFileForUser(userId).exists();
        }
    /**
     * Backup is activated for the system user if the suppress file does not exist. Backup is
     * activated for non-system users if the suppress file does not exist AND the user's activated
     * file exists.
     */
    private boolean isBackupActivatedForUser(int userId) {
        if (getSuppressFileForSystemUser().exists()) {
            return false;
        }

        return userId == UserHandle.USER_SYSTEM
                || getActivatedFileForNonSystemUser(userId).exists();
    }

    protected Context getContext() {
        return mContext;
    }

    protected UserManager getUserManager() {
        return mUserManager;
    }

    protected BackupManagerService createBackupManagerService() {
        return new BackupManagerService(mContext, this, mHandlerThread);
    }
@@ -198,23 +250,17 @@ public class Trampoline extends IBackupManager.Stub {

    /**
     * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked.
     * Starts the backup service for this user if it's the system user or if the service supports
     * multi-user. Offloads work onto the handler thread {@link #mHandlerThread} to keep unlock time
     * low.
     * Starts the backup service for this user if backup is active for this user. Offloads work onto
     * the handler thread {@link #mHandlerThread} to keep unlock time low.
     */
    void unlockUser(int userId) {
        if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) {
            Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId);
            return;
        }

        postToHandler(() -> startServiceForUser(userId));
    }

    private void startServiceForUser(int userId) {
        // We know that the user is unlocked here because it is called from setBackupServiceActive
        // and unlockUser which have these guarantees. So we can check if the file exists.
        if (mService != null && !isBackupSuppressedForUser(userId)) {
        if (mService != null && isBackupActivatedForUser(userId)) {
            Slog.i(TAG, "Starting service for user: " + userId);
            mService.startServiceForUser(userId);
        }
@@ -225,11 +271,6 @@ public class Trampoline extends IBackupManager.Stub {
     * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low.
     */
    void stopUser(int userId) {
        if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) {
            Slog.i(TAG, "Multi-user disabled, cannot stop service for user: " + userId);
            return;
        }

        postToHandler(
                () -> {
                    if (mService != null) {
@@ -240,30 +281,39 @@ public class Trampoline extends IBackupManager.Stub {
    }

    /**
     * Only privileged callers should be changing the backup state. This method only acts on {@link
     * UserHandle#USER_SYSTEM} and is a no-op if passed non-system users. Deactivating backup in the
     * system user also deactivates backup in all users.
     *
     * This call will only work if the calling {@code userID} is unlocked.
     * The system user and managed profiles can only be acted on by callers in the system or root
     * processes. Other users can be acted on by callers who have both android.permission.BACKUP and
     * android.permission.INTERACT_ACROSS_USERS_FULL permissions.
     */
    public void setBackupServiceActive(int userId, boolean makeActive) {
    private void enforcePermissionsOnUser(int userId) throws SecurityException {
        boolean isRestrictedUser =
                userId == UserHandle.USER_SYSTEM
                        || getUserManager().getUserInfo(userId).isManagedProfile();

        if (isRestrictedUser) {
            int caller = binderGetCallingUid();
            if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) {
                throw new SecurityException("No permission to configure backup activity");
            }

        if (mGlobalDisable) {
            Slog.i(TAG, "Backup service not supported");
            return;
        } else {
            mContext.enforceCallingOrSelfPermission(
                    Manifest.permission.BACKUP, "No permission to configure backup activity");
            mContext.enforceCallingOrSelfPermission(
                    Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                    "No permission to configure backup activity");
        }

        if (userId != UserHandle.USER_SYSTEM) {
            Slog.i(TAG, "Cannot set backup service activity for non-system user: " + userId);
            return;
    }

        if (makeActive == isBackupServiceActive(userId)) {
            Slog.i(TAG, "No change in backup service activity");
    /**
     * Only privileged callers should be changing the backup state. Deactivating backup in the
     * system user also deactivates backup in all users. We are not guaranteed that {@code userId}
     * is unlocked at this point yet, so handle both cases.
     */
    public void setBackupServiceActive(int userId, boolean makeActive) {
        enforcePermissionsOnUser(userId);

        if (mGlobalDisable) {
            Slog.i(TAG, "Backup service not supported");
            return;
        }

@@ -273,12 +323,21 @@ public class Trampoline extends IBackupManager.Stub {
                if (mService == null) {
                    mService = createBackupManagerService();
                }
                deleteBackupSuppressFileForUser(userId);
                try {
                    activateBackupForUserLocked(userId);
                } catch (IOException e) {
                    Slog.e(TAG, "Unable to persist backup service activity");
                }

                // If the user is unlocked, we can start the backup service for it. Otherwise we
                // will start the service when the user is unlocked as part of its unlock callback.
                if (getUserManager().isUserUnlocked(userId)) {
                    startServiceForUser(userId);
                }
            } else {
                try {
                    //TODO(b/121198006): what if this throws an exception?
                    createBackupSuppressFileForUser(userId);
                    deactivateBackupForUserLocked(userId);
                } catch (IOException e) {
                    Slog.e(TAG, "Unable to persist backup service inactivity");
                }
+165 −125

File changed.

Preview size limit exceeded, changes collapsed.