1 /* 2 * Copyright (C) 2019 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.rollback; 18 19 import android.content.pm.PackageManager; 20 import android.content.rollback.PackageRollbackInfo; 21 import android.content.rollback.PackageRollbackInfo.RestoreInfo; 22 import android.os.storage.StorageManager; 23 import android.util.Slog; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.server.pm.ApexManager; 27 import com.android.server.pm.Installer; 28 import com.android.server.pm.Installer.InstallerException; 29 30 import java.util.List; 31 32 /** 33 * Encapsulates the logic for initiating userdata snapshots and rollbacks via installd. 34 */ 35 @VisibleForTesting 36 // TODO(narayan): Reason about the failure scenarios that involve one or more IPCs to installd 37 // failing. We need to decide what course of action to take if calls to snapshotAppData or 38 // restoreAppDataSnapshot fail. 39 public class AppDataRollbackHelper { 40 private static final String TAG = "RollbackManager"; 41 42 private final Installer mInstaller; 43 private final ApexManager mApexManager; 44 AppDataRollbackHelper(Installer installer)45 AppDataRollbackHelper(Installer installer) { 46 mInstaller = installer; 47 mApexManager = ApexManager.getInstance(); 48 } 49 50 @VisibleForTesting AppDataRollbackHelper(Installer installer, ApexManager apexManager)51 AppDataRollbackHelper(Installer installer, ApexManager apexManager) { 52 mInstaller = installer; 53 mApexManager = apexManager; 54 } 55 56 /** 57 * Creates an app data snapshot for a specified {@code packageRollbackInfo} and the specified 58 * {@code userIds}. Updates said {@code packageRollbackInfo} with the inodes of the CE user data 59 * snapshot folders. 60 */ snapshotAppData( int rollbackId, PackageRollbackInfo packageRollbackInfo, int[] userIds)61 public void snapshotAppData( 62 int rollbackId, PackageRollbackInfo packageRollbackInfo, int[] userIds) { 63 for (int user : userIds) { 64 final int storageFlags; 65 if (isUserCredentialLocked(user)) { 66 // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy 67 // across app user data until the user unlocks their device. 68 Slog.v(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup."); 69 storageFlags = Installer.FLAG_STORAGE_DE; 70 packageRollbackInfo.addPendingBackup(user); 71 } else { 72 storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE; 73 } 74 75 doSnapshot(packageRollbackInfo, user, rollbackId, storageFlags); 76 } 77 } 78 79 /** 80 * Restores an app data snapshot for a specified {@code packageRollbackInfo}, for a specified 81 * {@code userId}. 82 * 83 * @return {@code true} iff. a change to the {@code packageRollbackInfo} has been made. Changes 84 * to {@code packageRollbackInfo} are restricted to the removal or addition of {@code 85 * userId} to the list of pending backups or restores. 86 */ restoreAppData(int rollbackId, PackageRollbackInfo packageRollbackInfo, int userId, int appId, String seInfo)87 public boolean restoreAppData(int rollbackId, PackageRollbackInfo packageRollbackInfo, 88 int userId, int appId, String seInfo) { 89 int storageFlags = Installer.FLAG_STORAGE_DE; 90 91 final List<Integer> pendingBackups = packageRollbackInfo.getPendingBackups(); 92 final List<RestoreInfo> pendingRestores = packageRollbackInfo.getPendingRestores(); 93 boolean changedRollback = false; 94 95 // If we still have a userdata backup pending for this user, it implies that the user 96 // hasn't unlocked their device between the point of backup and the point of restore, 97 // so the data cannot have changed. We simply skip restoring CE data in this case. 98 if (pendingBackups != null && pendingBackups.indexOf(userId) != -1) { 99 pendingBackups.remove(pendingBackups.indexOf(userId)); 100 changedRollback = true; 101 } else { 102 // There's no pending CE backup for this user, which means that we successfully 103 // managed to backup data for the user, which means we seek to restore it 104 if (isUserCredentialLocked(userId)) { 105 // We've encountered a user that hasn't unlocked on a FBE device, so we can't 106 // copy across app user data until the user unlocks their device. 107 pendingRestores.add(new RestoreInfo(userId, appId, seInfo)); 108 changedRollback = true; 109 } else { 110 // This user has unlocked, we can proceed to restore both CE and DE data. 111 storageFlags = storageFlags | Installer.FLAG_STORAGE_CE; 112 } 113 } 114 115 doRestoreOrWipe(packageRollbackInfo, userId, rollbackId, appId, seInfo, storageFlags); 116 117 return changedRollback; 118 } 119 doSnapshot( PackageRollbackInfo packageRollbackInfo, int userId, int rollbackId, int flags)120 private boolean doSnapshot( 121 PackageRollbackInfo packageRollbackInfo, int userId, int rollbackId, int flags) { 122 if (packageRollbackInfo.isApex()) { 123 // For APEX, only snapshot CE here 124 if ((flags & Installer.FLAG_STORAGE_CE) != 0) { 125 return mApexManager.snapshotCeData( 126 userId, rollbackId, packageRollbackInfo.getPackageName()); 127 } 128 } else { 129 // APK 130 try { 131 return mInstaller.snapshotAppData( 132 packageRollbackInfo.getPackageName(), userId, rollbackId, flags); 133 } catch (InstallerException ie) { 134 Slog.e(TAG, "Unable to create app data snapshot for: " 135 + packageRollbackInfo.getPackageName() + ", userId: " + userId, ie); 136 return false; 137 } 138 } 139 return true; 140 } 141 doRestoreOrWipe(PackageRollbackInfo packageRollbackInfo, int userId, int rollbackId, int appId, String seInfo, int flags)142 private boolean doRestoreOrWipe(PackageRollbackInfo packageRollbackInfo, int userId, 143 int rollbackId, int appId, String seInfo, int flags) { 144 if (packageRollbackInfo.isApex()) { 145 switch (packageRollbackInfo.getRollbackDataPolicy()) { 146 case PackageManager.ROLLBACK_DATA_POLICY_WIPE: 147 // TODO: Implement WIPE for apex CE data 148 break; 149 case PackageManager.ROLLBACK_DATA_POLICY_RESTORE: 150 // For APEX, only restore of CE may be done here. 151 if ((flags & Installer.FLAG_STORAGE_CE) != 0) { 152 mApexManager.restoreCeData( 153 userId, rollbackId, packageRollbackInfo.getPackageName()); 154 } 155 break; 156 default: 157 break; 158 } 159 } else { 160 // APK 161 try { 162 switch (packageRollbackInfo.getRollbackDataPolicy()) { 163 case PackageManager.ROLLBACK_DATA_POLICY_WIPE: 164 mInstaller.clearAppData(null, packageRollbackInfo.getPackageName(), 165 userId, flags, 0); 166 break; 167 case PackageManager.ROLLBACK_DATA_POLICY_RESTORE: 168 169 mInstaller.restoreAppDataSnapshot(packageRollbackInfo.getPackageName(), 170 appId, seInfo, userId, rollbackId, flags); 171 break; 172 default: 173 break; 174 } 175 } catch (InstallerException ie) { 176 Slog.e(TAG, "Unable to restore/wipe app data: " 177 + packageRollbackInfo.getPackageName() + " policy=" 178 + packageRollbackInfo.getRollbackDataPolicy(), ie); 179 return false; 180 } 181 } 182 return true; 183 } 184 185 /** 186 * Deletes an app data snapshot with a given {@code rollbackId} for a specified package 187 * {@code packageName} for a given {@code user}. 188 */ destroyAppDataSnapshot(int rollbackId, PackageRollbackInfo packageRollbackInfo, int user)189 public void destroyAppDataSnapshot(int rollbackId, PackageRollbackInfo packageRollbackInfo, 190 int user) { 191 try { 192 // Delete both DE and CE snapshots if any 193 mInstaller.destroyAppDataSnapshot(packageRollbackInfo.getPackageName(), user, 194 rollbackId, Installer.FLAG_STORAGE_DE | Installer.FLAG_STORAGE_CE); 195 } catch (InstallerException ie) { 196 Slog.e(TAG, "Unable to delete app data snapshot for " 197 + packageRollbackInfo.getPackageName(), ie); 198 } 199 } 200 201 /** 202 * Deletes all device-encrypted apex data snapshots for the given rollback id. 203 */ destroyApexDeSnapshots(int rollbackId)204 public void destroyApexDeSnapshots(int rollbackId) { 205 mApexManager.destroyDeSnapshots(rollbackId); 206 } 207 208 /** 209 * Deletes snapshots of the credential encrypted apex data directories for the specified user, 210 * for the given rollback id. This method will be a no-op if the user is not unlocked. 211 */ destroyApexCeSnapshots(int userId, int rollbackId)212 public void destroyApexCeSnapshots(int userId, int rollbackId) { 213 if (!isUserCredentialLocked(userId)) { 214 mApexManager.destroyCeSnapshots(userId, rollbackId); 215 } 216 } 217 218 /** 219 * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If 220 * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode 221 * of the CE user data snapshot. 222 * 223 * @return true if any backups or restores were found for the userId 224 */ commitPendingBackupAndRestoreForUser(int userId, Rollback rollback)225 boolean commitPendingBackupAndRestoreForUser(int userId, Rollback rollback) { 226 boolean foundBackupOrRestore = false; 227 for (PackageRollbackInfo info : rollback.info.getPackages()) { 228 boolean hasPendingBackup = false; 229 boolean hasPendingRestore = false; 230 final List<Integer> pendingBackupUsers = info.getPendingBackups(); 231 if (pendingBackupUsers != null) { 232 if (pendingBackupUsers.indexOf(userId) != -1) { 233 hasPendingBackup = true; 234 foundBackupOrRestore = true; 235 } 236 } 237 238 RestoreInfo ri = info.getRestoreInfo(userId); 239 if (ri != null) { 240 hasPendingRestore = true; 241 foundBackupOrRestore = true; 242 } 243 244 if (hasPendingBackup && hasPendingRestore) { 245 // Remove unnecessary backup, i.e. when user did not unlock their phone between the 246 // request to backup data and the request to restore it. 247 info.removePendingBackup(userId); 248 info.removePendingRestoreInfo(userId); 249 continue; 250 } 251 252 if (hasPendingBackup) { 253 int idx = pendingBackupUsers.indexOf(userId); 254 if (doSnapshot( 255 info, userId, rollback.info.getRollbackId(), Installer.FLAG_STORAGE_CE)) { 256 pendingBackupUsers.remove(idx); 257 } 258 } 259 260 if (hasPendingRestore && doRestoreOrWipe(info, userId, rollback.info.getRollbackId(), 261 ri.appId, ri.seInfo, Installer.FLAG_STORAGE_CE)) { 262 info.removeRestoreInfo(ri); 263 } 264 } 265 return foundBackupOrRestore; 266 } 267 268 /** 269 * @return {@code true} iff the credential-encrypted storage for {@code userId} is locked. 270 */ 271 @VisibleForTesting isUserCredentialLocked(int userId)272 public boolean isUserCredentialLocked(int userId) { 273 return StorageManager.isFileEncrypted() && !StorageManager.isCeStorageUnlocked(userId); 274 } 275 } 276