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