1 /*
2  * Copyright (C) 2022 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.sdksandbox;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.pm.ServiceInfo;
28 import android.os.RemoteException;
29 import android.os.UserHandle;
30 import android.util.ArrayMap;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.GuardedBy;
34 import com.android.modules.utils.build.SdkLevel;
35 import com.android.sdksandbox.ISdkSandboxService;
36 import com.android.server.LocalManagerRegistry;
37 import com.android.server.am.ActivityManagerLocal;
38 
39 import java.io.PrintWriter;
40 import java.util.Objects;
41 
42 import javax.annotation.concurrent.ThreadSafe;
43 
44 /**
45  * Implementation of {@link SdkSandboxServiceProvider}.
46  *
47  * @hide
48  */
49 @ThreadSafe
50 class SdkSandboxServiceProviderImpl implements SdkSandboxServiceProvider {
51 
52     private static final String TAG = "SdkSandboxManager";
53 
54     private final Object mLock = new Object();
55 
56     private final Context mContext;
57     private final ActivityManagerLocal mActivityManagerLocal;
58 
59     @GuardedBy("mLock")
60     private final ArrayMap<CallingInfo, SdkSandboxConnection> mAppSdkSandboxConnections =
61             new ArrayMap<>();
62 
SdkSandboxServiceProviderImpl(Context context)63     SdkSandboxServiceProviderImpl(Context context) {
64         mContext = context;
65         mActivityManagerLocal = LocalManagerRegistry.getManager(ActivityManagerLocal.class);
66     }
67 
68     @Override
69     @Nullable
bindService(CallingInfo callingInfo, ServiceConnection serviceConnection)70     public void bindService(CallingInfo callingInfo, ServiceConnection serviceConnection) {
71         synchronized (mLock) {
72             SdkSandboxConnection sdkSandboxConnection = getSdkSandboxConnectionLocked(callingInfo);
73             if (sdkSandboxConnection != null && sdkSandboxConnection.getStatus() != NON_EXISTENT) {
74                 // The sandbox is either already created or is in the process of being
75                 // created/restarted. Do not bind again. Note that later restarts can take a while,
76                 // since retries are done exponentially.
77                 Log.i(TAG, "SDK sandbox for " + callingInfo + " is already created");
78                 return;
79             }
80 
81             Log.i(TAG, "Binding sdk sandbox for " + callingInfo);
82 
83             ComponentName componentName = getServiceComponentName();
84             if (componentName == null) {
85                 Log.e(TAG, "Failed to find sdk sandbox service");
86                 notifyFailedBinding(serviceConnection);
87                 return;
88             }
89             Intent intent = new Intent().setComponent(componentName);
90 
91             String callingPackageName = callingInfo.getPackageName();
92             String sandboxProcessName = null;
93             try {
94                 sandboxProcessName = toSandboxProcessName(callingInfo);
95             } catch (PackageManager.NameNotFoundException e) {
96                 Log.e(TAG, "bindService failed for: " + callingInfo.toString(), e);
97                 notifyFailedBinding(serviceConnection);
98                 return;
99             }
100             try {
101                 boolean bound;
102                 // For U+, we start the sandbox and then bind to it to prevent restarts. For T,
103                 // the sandbox service is directly bound to using BIND_AUTO_CREATE flag which brings
104                 // up the sandbox but also restarts it if the sandbox dies when bound.
105                 if (SdkLevel.isAtLeastU()) {
106                     ComponentName name =
107                             mActivityManagerLocal.startSdkSandboxService(
108                                     intent,
109                                     callingInfo.getUid(),
110                                     callingPackageName,
111                                     sandboxProcessName);
112                     if (name == null) {
113                         notifyFailedBinding(serviceConnection);
114                         return;
115                     }
116                     bound =
117                             mActivityManagerLocal.bindSdkSandboxService(
118                                     intent,
119                                     serviceConnection,
120                                     callingInfo.getUid(),
121                                     callingInfo.getAppProcessToken(),
122                                     callingPackageName,
123                                     sandboxProcessName,
124                                     0);
125                 } else {
126                     // Using BIND_AUTO_CREATE will create the sandbox process.
127                     bound =
128                             mActivityManagerLocal.bindSdkSandboxService(
129                                     intent,
130                                     serviceConnection,
131                                     callingInfo.getUid(),
132                                     callingPackageName,
133                                     sandboxProcessName,
134                                     Context.BIND_AUTO_CREATE);
135                 }
136                 if (!bound) {
137                     mContext.unbindService(serviceConnection);
138                     notifyFailedBinding(serviceConnection);
139                     return;
140                 }
141             } catch (RemoteException e) {
142                 notifyFailedBinding(serviceConnection);
143                 return;
144             }
145             sdkSandboxConnection = new SdkSandboxConnection(serviceConnection, sandboxProcessName);
146             mAppSdkSandboxConnections.put(callingInfo, sdkSandboxConnection);
147             Log.i(TAG, "Sdk sandbox has been bound");
148         }
149     }
150 
151     // a way to notify manager that binding never happened
notifyFailedBinding(ServiceConnection serviceConnection)152     private void notifyFailedBinding(ServiceConnection serviceConnection) {
153         serviceConnection.onNullBinding(null);
154     }
155 
156     @Override
dump(PrintWriter writer)157     public void dump(PrintWriter writer) {
158         synchronized (mLock) {
159             if (mAppSdkSandboxConnections.size() == 0) {
160                 writer.println("mAppSdkSandboxConnections is empty");
161             } else {
162                 writer.print("mAppSdkSandboxConnections size: ");
163                 writer.println(mAppSdkSandboxConnections.size());
164                 for (int i = 0; i < mAppSdkSandboxConnections.size(); i++) {
165                     CallingInfo callingInfo = mAppSdkSandboxConnections.keyAt(i);
166                     SdkSandboxConnection sdkSandboxConnection =
167                             mAppSdkSandboxConnections.get(callingInfo);
168                     writer.printf(
169                             "Sdk sandbox for UID: %s, app package: %s, isConnected: %s Status: %d",
170                             callingInfo.getUid(),
171                             callingInfo.getPackageName(),
172                             Objects.requireNonNull(sdkSandboxConnection).isConnected(),
173                             sdkSandboxConnection.getStatus());
174                     writer.println();
175                 }
176             }
177         }
178     }
179 
180     @Override
unbindService(CallingInfo callingInfo)181     public void unbindService(CallingInfo callingInfo) {
182         synchronized (mLock) {
183             SdkSandboxConnection sandbox = getSdkSandboxConnectionLocked(callingInfo);
184 
185             if (sandbox == null) {
186                 return;
187             }
188 
189             if (sandbox.isBound) {
190                 try {
191                     mContext.unbindService(sandbox.getServiceConnection());
192                 } catch (Exception e) {
193                     // Sandbox has already unbound previously.
194                 }
195                 sandbox.onUnbind();
196                 Log.i(TAG, "Sdk sandbox for " + callingInfo + " has been unbound");
197             }
198         }
199     }
200 
201     @Override
stopSandboxService(CallingInfo callingInfo)202     public void stopSandboxService(CallingInfo callingInfo) {
203         synchronized (mLock) {
204             SdkSandboxConnection sandbox = getSdkSandboxConnectionLocked(callingInfo);
205 
206             if (!SdkLevel.isAtLeastU() || sandbox == null || sandbox.getStatus() == NON_EXISTENT) {
207                 return;
208             }
209 
210             ComponentName componentName = getServiceComponentName();
211             if (componentName == null) {
212                 Log.e(TAG, "Failed to find sdk sandbox service");
213                 return;
214             }
215             Intent intent = new Intent().setComponent(componentName);
216             String callingPackageName = callingInfo.getPackageName();
217             String sandboxProcessName = sandbox.getSandboxProcessName();
218 
219             mActivityManagerLocal.stopSdkSandboxService(
220                     intent, callingInfo.getUid(), callingPackageName, sandboxProcessName);
221         }
222     }
223 
224     @Override
225     @Nullable
getSdkSandboxServiceForApp(CallingInfo callingInfo)226     public ISdkSandboxService getSdkSandboxServiceForApp(CallingInfo callingInfo) {
227         synchronized (mLock) {
228             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
229             if (connection != null && connection.getStatus() == CREATED) {
230                 return connection.getSdkSandboxService();
231             }
232         }
233         return null;
234     }
235 
236     @Override
onServiceConnected(CallingInfo callingInfo, @NonNull ISdkSandboxService service)237     public void onServiceConnected(CallingInfo callingInfo, @NonNull ISdkSandboxService service) {
238         synchronized (mLock) {
239             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
240             if (connection != null) {
241                 connection.onServiceConnected(service);
242             }
243         }
244     }
245 
246     @Override
onServiceDisconnected(CallingInfo callingInfo)247     public void onServiceDisconnected(CallingInfo callingInfo) {
248         synchronized (mLock) {
249             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
250             if (connection != null) {
251                 connection.onServiceDisconnected();
252             }
253         }
254     }
255 
256     @Override
onAppDeath(CallingInfo callingInfo)257     public void onAppDeath(CallingInfo callingInfo) {
258         synchronized (mLock) {
259             mAppSdkSandboxConnections.remove(callingInfo);
260         }
261     }
262 
263     @Override
onSandboxDeath(CallingInfo callingInfo)264     public void onSandboxDeath(CallingInfo callingInfo) {
265         synchronized (mLock) {
266             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
267             if (connection != null) {
268                 connection.onSdkSandboxDeath();
269             }
270         }
271     }
272 
273     @Override
isSandboxBoundForApp(CallingInfo callingInfo)274     public boolean isSandboxBoundForApp(CallingInfo callingInfo) {
275         synchronized (mLock) {
276             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
277             if (connection != null) {
278                 synchronized (connection.mLock) {
279                     return connection.isBound;
280                 }
281             }
282             return false;
283         }
284     }
285 
286     @Override
getSandboxStatusForApp(CallingInfo callingInfo)287     public int getSandboxStatusForApp(CallingInfo callingInfo) {
288         synchronized (mLock) {
289             SdkSandboxConnection connection = getSdkSandboxConnectionLocked(callingInfo);
290             if (connection == null) {
291                 return NON_EXISTENT;
292             } else {
293                 return connection.getStatus();
294             }
295         }
296     }
297 
298     @Override
299     @NonNull
toSandboxProcessName(@onNull CallingInfo callingInfo)300     public String toSandboxProcessName(@NonNull CallingInfo callingInfo)
301             throws PackageManager.NameNotFoundException {
302         return getProcessName(callingInfo) + SANDBOX_PROCESS_NAME_SUFFIX;
303     }
304 
305     @Override
306     @NonNull
toSandboxProcessNameForInstrumentation(@onNull CallingInfo callingInfo)307     public String toSandboxProcessNameForInstrumentation(@NonNull CallingInfo callingInfo)
308             throws PackageManager.NameNotFoundException {
309         return getProcessName(callingInfo) + SANDBOX_INSTR_PROCESS_NAME_SUFFIX;
310     }
311 
312     @Nullable
getServiceComponentName()313     private ComponentName getServiceComponentName() {
314         final Intent intent = new Intent(SdkSandboxManagerLocal.SERVICE_INTERFACE);
315         intent.setPackage(mContext.getPackageManager().getSdkSandboxPackageName());
316 
317         final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
318                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
319         if (resolveInfo == null) {
320             Log.e(TAG, "Failed to find resolveInfo for sdk sandbox service");
321             return null;
322         }
323 
324         final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
325         if (serviceInfo == null) {
326             Log.e(TAG, "Failed to find serviceInfo for sdk sandbox service");
327             return null;
328         }
329 
330         return new ComponentName(serviceInfo.packageName, serviceInfo.name);
331     }
332 
333     @GuardedBy("mLock")
334     @Nullable
getSdkSandboxConnectionLocked(CallingInfo callingInfo)335     private SdkSandboxConnection getSdkSandboxConnectionLocked(CallingInfo callingInfo) {
336         return mAppSdkSandboxConnections.get(callingInfo);
337     }
338 
getProcessName(CallingInfo callingInfo)339     private String getProcessName(CallingInfo callingInfo)
340             throws PackageManager.NameNotFoundException {
341         UserHandle userHandle = UserHandle.getUserHandleForUid(callingInfo.getUid());
342         return mContext.getPackageManager()
343                 .getApplicationInfoAsUser(callingInfo.getPackageName(), /*flags=*/ 0, userHandle)
344                 .processName;
345     }
346 
347     // Represents the connection to an SDK sandbox service.
348     private static class SdkSandboxConnection {
349 
350         private final Object mLock = new Object();
351 
352         @GuardedBy("mLock")
353         @SandboxStatus
354         private int mStatus = CREATE_PENDING;
355 
356         // The connection used to bind and unbind from the SDK sandbox service.
357         private final ServiceConnection mServiceConnection;
358 
359         // The binder returned by the SDK sandbox service on connection.
360         @GuardedBy("mLock")
361         @Nullable
362         private ISdkSandboxService mSdkSandboxService = null;
363 
364         // Set to true when requested to bind to the SDK sandbox service. It is reset back to false
365         // when unbinding the sandbox service.
366         @GuardedBy("mLock")
367         public boolean isBound = true;
368 
369         private final String mSandboxProcessName;
370 
SdkSandboxConnection(ServiceConnection serviceConnection, String sandboxProcessName)371         SdkSandboxConnection(ServiceConnection serviceConnection, String sandboxProcessName) {
372             mServiceConnection = serviceConnection;
373             mSandboxProcessName = sandboxProcessName;
374         }
375 
376         @SandboxStatus
getStatus()377         public int getStatus() {
378             synchronized (mLock) {
379                 return mStatus;
380             }
381         }
382 
onUnbind()383         public void onUnbind() {
384             synchronized (mLock) {
385                 isBound = false;
386             }
387         }
388 
onServiceConnected(ISdkSandboxService service)389         public void onServiceConnected(ISdkSandboxService service) {
390             synchronized (mLock) {
391                 mStatus = CREATED;
392                 mSdkSandboxService = service;
393             }
394         }
395 
onServiceDisconnected()396         public void onServiceDisconnected() {
397             synchronized (mLock) {
398                 mSdkSandboxService = null;
399             }
400         }
401 
onSdkSandboxDeath()402         public void onSdkSandboxDeath() {
403             synchronized (mLock) {
404                 // For U+, the sandbox does not restart after dying.
405                 if (SdkLevel.isAtLeastU()) {
406                     mStatus = NON_EXISTENT;
407                     return;
408                 }
409 
410                 if (isBound) {
411                     // If the sandbox was bound at the time of death, the system will automatically
412                     // restart it.
413                     mStatus = CREATE_PENDING;
414                 } else {
415                     // If the sandbox was not bound at the time of death, the sandbox is dead for
416                     // good.
417                     mStatus = NON_EXISTENT;
418                 }
419             }
420         }
421 
422         @Nullable
getSdkSandboxService()423         public ISdkSandboxService getSdkSandboxService() {
424             synchronized (mLock) {
425                 return mSdkSandboxService;
426             }
427         }
428 
getSandboxProcessName()429         public String getSandboxProcessName() {
430             return mSandboxProcessName;
431         }
432 
getServiceConnection()433         public ServiceConnection getServiceConnection() {
434             return mServiceConnection;
435         }
436 
isConnected()437         boolean isConnected() {
438             synchronized (mLock) {
439                 return mSdkSandboxService != null;
440             }
441         }
442     }
443 }
444