1 /*
2  * Copyright (C) 2017 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.backup.restore;
18 
19 import static com.android.server.backup.BackupManagerService.DEBUG;
20 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
21 import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
22 import static com.android.server.backup.internal.BackupHandler.MSG_RUN_GET_RESTORE_SETS;
23 import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.backup.BackupAnnotations.BackupDestination;
28 import android.app.backup.IBackupManagerMonitor;
29 import android.app.backup.IRestoreObserver;
30 import android.app.backup.IRestoreSession;
31 import android.app.backup.RestoreSet;
32 import android.content.pm.PackageInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.os.Binder;
36 import android.os.Handler;
37 import android.os.Message;
38 import android.util.Slog;
39 
40 import com.android.server.backup.TransportManager;
41 import com.android.server.backup.UserBackupManagerService;
42 import com.android.server.backup.internal.OnTaskFinishedListener;
43 import com.android.server.backup.params.RestoreGetSetsParams;
44 import com.android.server.backup.params.RestoreParams;
45 import com.android.server.backup.transport.TransportConnection;
46 import com.android.server.backup.utils.BackupEligibilityRules;
47 
48 import java.util.List;
49 import java.util.function.BiFunction;
50 
51 /**
52  * Restore session.
53  */
54 public class ActiveRestoreSession extends IRestoreSession.Stub {
55     private static final String TAG = "RestoreSession";
56     private static final String DEVICE_NAME_FOR_D2D_SET = "D2D";
57 
58     private final TransportManager mTransportManager;
59     private final String mTransportName;
60     private final UserBackupManagerService mBackupManagerService;
61     private final int mUserId;
62     private final BackupEligibilityRules mBackupEligibilityRules;
63     @Nullable private final String mPackageName;
64     public List<RestoreSet> mRestoreSets = null;
65     boolean mEnded = false;
66     boolean mTimedOut = false;
67 
ActiveRestoreSession( UserBackupManagerService backupManagerService, @Nullable String packageName, String transportName, BackupEligibilityRules backupEligibilityRules)68     public ActiveRestoreSession(
69             UserBackupManagerService backupManagerService,
70             @Nullable String packageName,
71             String transportName,
72             BackupEligibilityRules backupEligibilityRules) {
73         mBackupManagerService = backupManagerService;
74         mPackageName = packageName;
75         mTransportManager = backupManagerService.getTransportManager();
76         mTransportName = transportName;
77         mUserId = backupManagerService.getUserId();
78         mBackupEligibilityRules = backupEligibilityRules;
79     }
80 
markTimedOut()81     public void markTimedOut() {
82         mTimedOut = true;
83     }
84 
85     // --- Binder interface ---
getAvailableRestoreSets(IRestoreObserver observer, IBackupManagerMonitor monitor)86     public synchronized int getAvailableRestoreSets(IRestoreObserver observer,
87             IBackupManagerMonitor monitor) {
88         mBackupManagerService.getContext().enforceCallingOrSelfPermission(
89                 android.Manifest.permission.BACKUP,
90                 "getAvailableRestoreSets");
91         if (observer == null) {
92             throw new IllegalArgumentException("Observer must not be null");
93         }
94 
95         if (mEnded) {
96             throw new IllegalStateException("Restore session already ended");
97         }
98 
99         if (mTimedOut) {
100             Slog.i(TAG, "Session already timed out");
101             return -1;
102         }
103 
104         final long oldId = Binder.clearCallingIdentity();
105         try {
106             TransportConnection transportConnection =
107                     mTransportManager.getTransportClient(
108                                     mTransportName, "RestoreSession.getAvailableRestoreSets()");
109             if (transportConnection == null) {
110                 Slog.w(TAG, "Null transport client getting restore sets");
111                 return -1;
112             }
113 
114             // We know we're doing legit work now, so halt the timeout
115             // until we're done.  It gets started again when the result
116             // comes in.
117             mBackupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
118 
119             UserBackupManagerService.BackupWakeLock wakelock = mBackupManagerService.getWakelock();
120             wakelock.acquire();
121 
122             // Prevent lambda from leaking 'this'
123             TransportManager transportManager = mTransportManager;
124             OnTaskFinishedListener listener = caller -> {
125                     transportManager.disposeOfTransportClient(transportConnection, caller);
126                     wakelock.release();
127             };
128             Message msg = mBackupManagerService.getBackupHandler().obtainMessage(
129                     MSG_RUN_GET_RESTORE_SETS,
130                     new RestoreGetSetsParams(transportConnection, this, observer, monitor,
131                             listener));
132             mBackupManagerService.getBackupHandler().sendMessage(msg);
133             return 0;
134         } catch (Exception e) {
135             Slog.e(TAG, "Error in getAvailableRestoreSets", e);
136             return -1;
137         } finally {
138             Binder.restoreCallingIdentity(oldId);
139         }
140     }
141 
restoreAll(long token, IRestoreObserver observer, IBackupManagerMonitor monitor)142     public synchronized int restoreAll(long token, IRestoreObserver observer,
143             IBackupManagerMonitor monitor) {
144         mBackupManagerService.getContext().enforceCallingOrSelfPermission(
145                 android.Manifest.permission.BACKUP,
146                 "performRestore");
147 
148         if (DEBUG) {
149             Slog.d(TAG, "restoreAll token=" + Long.toHexString(token)
150                     + " observer=" + observer);
151         }
152 
153         if (mEnded) {
154             throw new IllegalStateException("Restore session already ended");
155         }
156 
157         if (mTimedOut) {
158             Slog.i(TAG, "Session already timed out");
159             return -1;
160         }
161 
162         if (mRestoreSets == null) {
163             Slog.e(TAG, "Ignoring restoreAll() with no restore set");
164             return -1;
165         }
166 
167         if (mPackageName != null) {
168             Slog.e(TAG, "Ignoring restoreAll() on single-package session");
169             return -1;
170         }
171 
172         if (!mTransportManager.isTransportRegistered(mTransportName)) {
173             Slog.e(TAG, "Transport " + mTransportName + " not registered");
174             return -1;
175         }
176 
177         synchronized (mBackupManagerService.getQueueLock()) {
178             for (int i = 0; i < mRestoreSets.size(); i++) {
179                 if (token == mRestoreSets.get(i).token) {
180                     final long oldId = Binder.clearCallingIdentity();
181                     RestoreSet restoreSet = mRestoreSets.get(i);
182                     try {
183                         return sendRestoreToHandlerLocked(
184                                 (transportClient, listener) ->
185                                         RestoreParams.createForRestoreAll(
186                                                 transportClient,
187                                                 observer,
188                                                 monitor,
189                                                 token,
190                                                 listener,
191                                                 getBackupEligibilityRules(restoreSet)),
192                                 "RestoreSession.restoreAll()");
193                     } finally {
194                         Binder.restoreCallingIdentity(oldId);
195                     }
196                 }
197             }
198         }
199 
200         Slog.w(TAG, "Restore token " + Long.toHexString(token) + " not found");
201         return -1;
202     }
203 
204     // Restores of more than a single package are treated as 'system' restores
restorePackages(long token, @Nullable IRestoreObserver observer, @NonNull String[] packages, @Nullable IBackupManagerMonitor monitor)205     public synchronized int restorePackages(long token, @Nullable IRestoreObserver observer,
206             @NonNull String[] packages, @Nullable IBackupManagerMonitor monitor) {
207         mBackupManagerService.getContext().enforceCallingOrSelfPermission(
208                 android.Manifest.permission.BACKUP,
209                 "performRestore");
210 
211         if (DEBUG) {
212             StringBuilder b = new StringBuilder(128);
213             b.append("restorePackages token=");
214             b.append(Long.toHexString(token));
215             b.append(" observer=");
216             if (observer == null) {
217                 b.append("null");
218             } else {
219                 b.append(observer.toString());
220             }
221             b.append(" monitor=");
222             if (monitor == null) {
223                 b.append("null");
224             } else {
225                 b.append(monitor.toString());
226             }
227             b.append(" packages=");
228             if (packages == null) {
229                 b.append("null");
230             } else {
231                 b.append('{');
232                 boolean first = true;
233                 for (String s : packages) {
234                     if (!first) {
235                         b.append(", ");
236                     } else {
237                         first = false;
238                     }
239                     b.append(s);
240                 }
241                 b.append('}');
242             }
243             Slog.d(TAG, b.toString());
244         }
245 
246         if (mEnded) {
247             throw new IllegalStateException("Restore session already ended");
248         }
249 
250         if (mTimedOut) {
251             Slog.i(TAG, "Session already timed out");
252             return -1;
253         }
254 
255         if (mRestoreSets == null) {
256             Slog.e(TAG, "Ignoring restoreAll() with no restore set");
257             return -1;
258         }
259 
260         if (mPackageName != null) {
261             Slog.e(TAG, "Ignoring restoreAll() on single-package session");
262             return -1;
263         }
264 
265         if (!mTransportManager.isTransportRegistered(mTransportName)) {
266             Slog.e(TAG, "Transport " + mTransportName + " not registered");
267             return -1;
268         }
269 
270         synchronized (mBackupManagerService.getQueueLock()) {
271             for (int i = 0; i < mRestoreSets.size(); i++) {
272                 if (token == mRestoreSets.get(i).token) {
273                     final long oldId = Binder.clearCallingIdentity();
274                     RestoreSet restoreSet = mRestoreSets.get(i);
275                     try {
276                         return sendRestoreToHandlerLocked(
277                                 (transportClient, listener) ->
278                                         RestoreParams.createForRestorePackages(
279                                                 transportClient,
280                                                 observer,
281                                                 monitor,
282                                                 token,
283                                                 packages,
284                                                 /* isSystemRestore */ packages.length > 1,
285                                                 listener,
286                                                 getBackupEligibilityRules(restoreSet)),
287                                 "RestoreSession.restorePackages(" + packages.length + " packages)");
288                     } finally {
289                         Binder.restoreCallingIdentity(oldId);
290                     }
291                 }
292             }
293         }
294 
295         Slog.w(TAG, "Restore token " + Long.toHexString(token) + " not found");
296         return -1;
297     }
298 
getBackupEligibilityRules(RestoreSet restoreSet)299     private BackupEligibilityRules getBackupEligibilityRules(RestoreSet restoreSet) {
300         // TODO(b/182986784): Remove device name comparison once a designated field for operation
301         //  type is added to RestoreSet object.
302         int backupDestination = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device)
303                 ? BackupDestination.DEVICE_TRANSFER : BackupDestination.CLOUD;
304         return mBackupManagerService.getEligibilityRulesForOperation(backupDestination);
305     }
306 
restorePackage(String packageName, IRestoreObserver observer, IBackupManagerMonitor monitor)307     public synchronized int restorePackage(String packageName, IRestoreObserver observer,
308             IBackupManagerMonitor monitor) {
309         if (DEBUG) {
310             Slog.v(TAG, "restorePackage pkg=" + packageName + " obs=" + observer
311                     + "monitor=" + monitor);
312         }
313 
314         if (mEnded) {
315             throw new IllegalStateException("Restore session already ended");
316         }
317 
318         if (mTimedOut) {
319             Slog.i(TAG, "Session already timed out");
320             return -1;
321         }
322 
323         if (mPackageName != null) {
324             if (!mPackageName.equals(packageName)) {
325                 Slog.e(TAG, "Ignoring attempt to restore pkg=" + packageName
326                         + " on session for package " + mPackageName);
327                 return -1;
328             }
329         }
330 
331         final PackageInfo app;
332         try {
333             app = mBackupManagerService.getPackageManager().getPackageInfoAsUser(
334                     packageName, 0, mUserId);
335         } catch (NameNotFoundException nnf) {
336             Slog.w(TAG, "Asked to restore nonexistent pkg " + packageName);
337             return -1;
338         }
339 
340         // If the caller is not privileged and is not coming from the target
341         // app's uid, throw a permission exception back to the caller.
342         int perm = mBackupManagerService.getContext().checkPermission(
343                 android.Manifest.permission.BACKUP,
344                 Binder.getCallingPid(), Binder.getCallingUid());
345         if ((perm == PackageManager.PERMISSION_DENIED) &&
346                 (app.applicationInfo.uid != Binder.getCallingUid())) {
347             Slog.w(TAG, "restorePackage: bad packageName=" + packageName
348                     + " or calling uid=" + Binder.getCallingUid());
349             throw new SecurityException("No permission to restore other packages");
350         }
351 
352         if (!mTransportManager.isTransportRegistered(mTransportName)) {
353             Slog.e(TAG, "Transport " + mTransportName + " not registered");
354             return -1;
355         }
356 
357         // So far so good; we're allowed to try to restore this package.
358         final long oldId = Binder.clearCallingIdentity();
359         try {
360             // Check whether there is data for it in the current dataset, falling back
361             // to the ancestral dataset if not.
362             long token = mBackupManagerService.getAvailableRestoreToken(packageName);
363             if (DEBUG) {
364                 Slog.v(TAG, "restorePackage pkg=" + packageName
365                         + " token=" + Long.toHexString(token));
366             }
367 
368             // If we didn't come up with a place to look -- no ancestral dataset and
369             // the app has never been backed up from this device -- there's nothing
370             // to do but return failure.
371             if (token == 0) {
372                 if (DEBUG) {
373                     Slog.w(TAG, "No data available for this package; not restoring");
374                 }
375                 return -1;
376             }
377 
378             return sendRestoreToHandlerLocked(
379                     (transportClient, listener) ->
380                             RestoreParams.createForSinglePackage(
381                                     transportClient,
382                                     observer,
383                                     monitor,
384                                     token,
385                                     app,
386                                     listener,
387                                     mBackupEligibilityRules),
388                     "RestoreSession.restorePackage(" + packageName + ")");
389         } finally {
390             Binder.restoreCallingIdentity(oldId);
391         }
392     }
393 
setRestoreSets(List<RestoreSet> restoreSets)394     public void setRestoreSets(List<RestoreSet> restoreSets) {
395         mRestoreSets = restoreSets;
396     }
397 
398     /**
399      * Returns 0 if operation sent or -1 otherwise.
400      */
sendRestoreToHandlerLocked( BiFunction<TransportConnection, OnTaskFinishedListener, RestoreParams> restoreParamsBuilder, String callerLogString)401     private int sendRestoreToHandlerLocked(
402             BiFunction<TransportConnection, OnTaskFinishedListener,
403                     RestoreParams> restoreParamsBuilder, String callerLogString) {
404         TransportConnection transportConnection =
405                 mTransportManager.getTransportClient(mTransportName, callerLogString);
406         if (transportConnection == null) {
407             Slog.e(TAG, "Transport " + mTransportName + " got unregistered");
408             return -1;
409         }
410 
411         // Stop the session timeout until we finalize the restore
412         Handler backupHandler = mBackupManagerService.getBackupHandler();
413         backupHandler.removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
414 
415         UserBackupManagerService.BackupWakeLock wakelock = mBackupManagerService.getWakelock();
416         wakelock.acquire();
417         if (MORE_DEBUG) {
418             Slog.d(TAG, callerLogString);
419         }
420 
421         // Prevent lambda from leaking 'this'
422         TransportManager transportManager = mTransportManager;
423         OnTaskFinishedListener listener = caller -> {
424                 transportManager.disposeOfTransportClient(transportConnection, caller);
425                 wakelock.release();
426         };
427         Message msg = backupHandler.obtainMessage(MSG_RUN_RESTORE);
428         msg.obj = restoreParamsBuilder.apply(transportConnection, listener);
429         backupHandler.sendMessage(msg);
430         return 0;
431     }
432 
433     // Posted to the handler to tear down a restore session in a cleanly synchronized way
434     public class EndRestoreRunnable implements Runnable {
435 
436         UserBackupManagerService mBackupManager;
437         ActiveRestoreSession mSession;
438 
EndRestoreRunnable(UserBackupManagerService manager, ActiveRestoreSession session)439         public EndRestoreRunnable(UserBackupManagerService manager, ActiveRestoreSession session) {
440             mBackupManager = manager;
441             mSession = session;
442         }
443 
run()444         public void run() {
445             // clean up the session's bookkeeping
446             synchronized (mSession) {
447                 mSession.mEnded = true;
448             }
449 
450             // clean up the BackupManagerImpl side of the bookkeeping
451             // and cancel any pending timeout message
452             mBackupManager.clearRestoreSession(mSession);
453         }
454     }
455 
endRestoreSession()456     public synchronized void endRestoreSession() {
457         if (DEBUG) {
458             Slog.d(TAG, "endRestoreSession");
459         }
460 
461         if (mTimedOut) {
462             Slog.i(TAG, "Session already timed out");
463             return;
464         }
465 
466         if (mEnded) {
467             throw new IllegalStateException("Restore session already ended");
468         }
469 
470         mBackupManagerService.getBackupHandler().post(
471                 new EndRestoreRunnable(mBackupManagerService, this));
472     }
473 }
474