blob: 018caebf4c1ab05036f386e564eca30d62a5106e [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.sdksandbox;
import static android.app.sdksandbox.SdkSandboxManager.SDK_SANDBOX_SERVICE;
import static com.android.server.sdksandbox.SdkSandboxStorageManager.SdkDataDirInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.sdksandbox.ILoadSdkCallback;
import android.app.sdksandbox.IRequestSurfacePackageCallback;
import android.app.sdksandbox.ISdkSandboxLifecycleCallback;
import android.app.sdksandbox.ISdkSandboxManager;
import android.app.sdksandbox.ISendDataCallback;
import android.app.sdksandbox.SdkSandboxManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.view.SurfaceControlViewHost;
import android.webkit.WebViewUpdateService;
import com.android.adservices.AdServicesCommon;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.sdksandbox.IDataReceivedCallback;
import com.android.sdksandbox.ILoadSdkInSandboxCallback;
import com.android.sdksandbox.IRequestSurfacePackageFromSdkCallback;
import com.android.sdksandbox.ISdkSandboxManagerToSdkSandboxCallback;
import com.android.sdksandbox.ISdkSandboxService;
import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
import com.android.server.pm.PackageManagerLocal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.concurrent.ThreadSafe;
/**
* Implementation of {@link SdkSandboxManager}.
*
* @hide
*/
public class SdkSandboxManagerService extends ISdkSandboxManager.Stub {
private static final String TAG = "SdkSandboxManager";
private static final String PROPERTY_SDK_PROVIDER_CLASS_NAME =
"android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME";
private static final String STOP_SDK_SANDBOX_PERMISSION =
"com.android.app.sdksandbox.permission.STOP_SDK_SANDBOX";
private final Context mContext;
private final SdkTokenManager mSdkTokenManager = new SdkTokenManager();
private final ActivityManager mActivityManager;
private final Handler mHandler;
private final SdkSandboxStorageManager mSdkSandboxStorageManager;
private final SdkSandboxServiceProvider mServiceProvider;
private final Object mLock = new Object();
// For communication between app<-ManagerService->RemoteCode for each codeToken
@GuardedBy("mLock")
private final ArrayMap<IBinder, AppAndRemoteSdkLink> mAppAndRemoteSdkLinks = new ArrayMap<>();
@GuardedBy("mLock")
private final ArraySet<CallingInfo> mCallingInfosWithDeathRecipients = new ArraySet<>();
@GuardedBy("mLock")
private final Set<CallingInfo> mRunningInstrumentations = new ArraySet<>();
@GuardedBy("mLock")
private final ArrayMap<CallingInfo, RemoteCallbackList<ISdkSandboxLifecycleCallback>>
mSandboxLifecycleCallbacks = new ArrayMap<>();
private final SdkSandboxManagerLocal mLocalManager;
private final String mAdServicesPackageName;
SdkSandboxManagerService(Context context, SdkSandboxServiceProvider provider) {
mContext = context;
mServiceProvider = provider;
mActivityManager = mContext.getSystemService(ActivityManager.class);
mLocalManager = new LocalImpl();
PackageManagerLocal packageManagerLocal =
LocalManagerRegistry.getManager(PackageManagerLocal.class);
mSdkSandboxStorageManager =
new SdkSandboxStorageManager(mContext, mLocalManager, packageManagerLocal);
// Start the handler thread.
HandlerThread handlerThread = new HandlerThread("SdkSandboxManagerServiceHandler");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
registerBroadcastReceivers();
mAdServicesPackageName = resolveAdServicesPackage();
}
private void registerBroadcastReceivers() {
// Register for package addition and update
final IntentFilter packageAddedIntentFilter = new IntentFilter();
packageAddedIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageAddedIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
packageAddedIntentFilter.addDataScheme("package");
BroadcastReceiver packageAddedIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String packageName = intent.getData().getSchemeSpecificPart();
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
final CallingInfo callingInfo = new CallingInfo(uid, packageName);
// TODO(b/223386213): We could miss broadcast or app might be started before we
// handle broadcast.
mHandler.post(() -> mSdkSandboxStorageManager.onPackageAddedOrUpdated(callingInfo));
}
};
mContext.registerReceiver(packageAddedIntentReceiver, packageAddedIntentFilter,
/*broadcastPermission=*/null, mHandler);
}
@Override
public List<SharedLibraryInfo> getLoadedSdkLibrariesInfo(String callingPackageName) {
final int callingUid = Binder.getCallingUid();
final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
enforceCallingPackageBelongsToUid(callingInfo);
List<SharedLibraryInfo> sharedLibraryInfos = new ArrayList<>();
synchronized (mLock) {
for (int i = mAppAndRemoteSdkLinks.size() - 1; i >= 0; i--) {
AppAndRemoteSdkLink link = mAppAndRemoteSdkLinks.valueAt(i);
if (link.mCallingInfo.equals(callingInfo) && link.mSdkProviderInfo != null) {
sharedLibraryInfos.add(link.mSdkProviderInfo.mSdkInfo);
}
}
return sharedLibraryInfos;
}
}
@Override
public void addSdkSandboxLifecycleCallback(
String callingPackageName, ISdkSandboxLifecycleCallback callback) {
final int callingUid = Binder.getCallingUid();
final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
enforceCallingPackageBelongsToUid(callingInfo);
synchronized (mLock) {
if (mSandboxLifecycleCallbacks.containsKey(callingInfo)) {
mSandboxLifecycleCallbacks.get(callingInfo).register(callback);
} else {
RemoteCallbackList<ISdkSandboxLifecycleCallback> sandboxLifecycleCallbacks =
new RemoteCallbackList<>();
sandboxLifecycleCallbacks.register(callback);
mSandboxLifecycleCallbacks.put(callingInfo, sandboxLifecycleCallbacks);
}
}
}
@Override
public void removeSdkSandboxLifecycleCallback(
String callingPackageName, ISdkSandboxLifecycleCallback callback) {
final int callingUid = Binder.getCallingUid();
final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
enforceCallingPackageBelongsToUid(callingInfo);
synchronized (mLock) {
RemoteCallbackList<ISdkSandboxLifecycleCallback> sandboxLifecycleCallbacks =
mSandboxLifecycleCallbacks.get(callingInfo);
if (sandboxLifecycleCallbacks != null) {
sandboxLifecycleCallbacks.unregister(callback);
}
}
}
@Override
public void loadSdk(
String callingPackageName, String sdkName, Bundle params, ILoadSdkCallback callback) {
final int callingUid = Binder.getCallingUid();
final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
synchronized (mLock) {
if (mRunningInstrumentations.contains(callingInfo)) {
throw new SecurityException(
"Currently running instrumentation of this sdk sandbox process");
}
}
enforceCallingPackageBelongsToUid(callingInfo);
enforceCallerHasNetworkAccess(callingPackageName);
//TODO(b/232924025): Sdk data should be prepared once per sandbox instantiation
mSdkSandboxStorageManager.prepareSdkDataOnLoad(callingInfo);
final long token = Binder.clearCallingIdentity();
try {
loadSdkWithClearIdentity(callingInfo, sdkName, params, callback);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void loadSdkWithClearIdentity(
CallingInfo callingInfo, String sdkName, Bundle params, ILoadSdkCallback callback) {
// Step 1: create unique identity for the {callingUid, sdkName} pair
final IBinder sdkToken = mSdkTokenManager.createOrGetSdkToken(callingInfo, sdkName);
// Step 2: fetch the installed code in device
SdkProviderInfo sdkProviderInfo =
createSdkProviderInfo(sdkName, callingInfo.getPackageName());
String errorMsg = "";
if (sdkProviderInfo == null) {
errorMsg = sdkName + " not found for loading";
} else if (TextUtils.isEmpty(sdkProviderInfo.getSdkProviderClassName())) {
errorMsg = sdkName + " did not set " + PROPERTY_SDK_PROVIDER_CLASS_NAME;
}
// Ensure we are not already loading sdk for this sdkToken. That's determined by
// checking if we already have an AppAndRemoteCodeLink for the sdkToken.
final AppAndRemoteSdkLink link =
new AppAndRemoteSdkLink(callingInfo, sdkToken, callback, sdkProviderInfo);
synchronized (mLock) {
if (mAppAndRemoteSdkLinks.putIfAbsent(sdkToken, link) != null) {
link.handleLoadSdkError(
SdkSandboxManager.LOAD_SDK_ALREADY_LOADED,
sdkName + " is being loaded or has been loaded already",
/*cleanUpInternalState=*/ false);
return;
}
}
if (!TextUtils.isEmpty(errorMsg)) {
Log.w(TAG, errorMsg);
link.handleLoadSdkError(
SdkSandboxManager.LOAD_SDK_NOT_FOUND, errorMsg, /*cleanUpInternalState=*/ true);
return;
}
// Register a death recipient to clean up sdkToken and unbind its service after app dies.
try {
synchronized (mLock) {
if (!mCallingInfosWithDeathRecipients.contains(callingInfo)) {
callback.asBinder().linkToDeath(() -> onAppDeath(callingInfo), 0);
mCallingInfosWithDeathRecipients.add(callingInfo);
}
}
} catch (RemoteException re) {
// App has already died, cleanup sdk token and link, and unbind its service
onAppDeath(callingInfo);
return;
}
invokeSdkSandboxServiceToLoadSdk(callingInfo, sdkToken, sdkProviderInfo, params, link);
}
@Override
public void unloadSdk(String callingPackageName, String sdkName) {
final CallingInfo callingInfo = new CallingInfo(Binder.getCallingUid(), callingPackageName);
enforceCallingPackageBelongsToUid(callingInfo);
final long token = Binder.clearCallingIdentity();
try {
IBinder sdkToken = mSdkTokenManager.getSdkToken(callingInfo, sdkName);
if (sdkToken == null) {
throw new IllegalArgumentException(
"SDK " + sdkName + " is not loaded for " + callingInfo);
}
unloadSdkWithClearIdentity(callingInfo, sdkName);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void unloadSdkWithClearIdentity(CallingInfo callingInfo, String sdkName) {
boolean shouldStopSandbox = removeLinksToSdk(callingInfo, sdkName);
if (shouldStopSandbox) {
stopSdkSandboxService(
callingInfo, "Caller " + callingInfo + " has no remaining SDKS loaded.");
}
}
private void enforceCallingPackageBelongsToUid(CallingInfo callingInfo) {
int callingUid = callingInfo.getUid();
String callingPackage = callingInfo.getPackageName();
int packageUid;
PackageManager pm = mContext.createContextAsUser(
UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
try {
packageUid = pm.getPackageUid(callingPackage, 0);
} catch (PackageManager.NameNotFoundException e) {
throw new SecurityException(callingPackage + " not found");
}
if (packageUid != callingUid) {
throw new SecurityException(callingPackage + " does not belong to uid " + callingUid);
}
}
private void enforceCallerHasNetworkAccess(String callingPackage) {
mContext.enforceCallingPermission(android.Manifest.permission.INTERNET,
callingPackage + " does not hold INTERNET permission");
mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_NETWORK_STATE,
callingPackage + " does not hold ACCESS_NETWORK_STATE permission");
}
private void onAppDeath(CallingInfo callingInfo) {
synchronized (mLock) {
mSandboxLifecycleCallbacks.remove(callingInfo);
mCallingInfosWithDeathRecipients.remove(callingInfo);
removeAllSdkTokensAndLinks(callingInfo);
stopSdkSandboxService(callingInfo, "Caller " + callingInfo + " has died");
}
}
@Override
public void requestSurfacePackage(
String callingPackageName,
String sdkName,
IBinder hostToken,
int displayId,
int width,
int height,
Bundle params,
IRequestSurfacePackageCallback callback) {
final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
enforceCallingPackageBelongsToUid(callingInfo);
try {
final IBinder sdkToken = mSdkTokenManager.getSdkToken(callingInfo, sdkName);
if (sdkToken == null) {
throw new IllegalArgumentException("Sdk " + sdkName + " is not loaded");
}
requestSurfacePackageWithClearIdentity(
sdkToken, hostToken, displayId, width, height, params, callback);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void requestSurfacePackageWithClearIdentity(
IBinder sdkToken,
IBinder hostToken,
int displayId,
int width,
int height,
Bundle params,
IRequestSurfacePackageCallback callback) {
final AppAndRemoteSdkLink link;
synchronized (mLock) {
link = mAppAndRemoteSdkLinks.get(sdkToken);
}
link.requestSurfacePackageFromSdk(hostToken, displayId, width, height, params, callback);
}
@Override
public void sendData(
String callingPackageName, String sdkName, Bundle data, ISendDataCallback callback) {
final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
enforceCallingPackageBelongsToUid(callingInfo);
try {
final IBinder sdkToken = mSdkTokenManager.getSdkToken(callingInfo, sdkName);
if (sdkToken == null) {
throw new IllegalArgumentException("Sdk " + sdkName + " is not loaded");
}
final AppAndRemoteSdkLink link;
synchronized (mLock) {
link = mAppAndRemoteSdkLinks.get(sdkToken);
}
link.sendDataToSdk(data, callback);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void onUserUnlocking(int userId) {
Log.i(TAG, "onUserUnlocking " + userId);
// using postDelayed to wait for other volumes to mount
mHandler.postDelayed(() -> mSdkSandboxStorageManager.onUserUnlocking(userId), 20000);
}
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingPermission(android.Manifest.permission.DUMP,
"Can't dump " + TAG);
// TODO(b/211575098): Use IndentingPrintWriter for better formatting
synchronized (mLock) {
writer.println("mAppAndRemoteSdkLinks size: " + mAppAndRemoteSdkLinks.size());
}
writer.println("mSdkTokenManager:");
mSdkTokenManager.dump(writer);
writer.println();
writer.println("mServiceProvider:");
mServiceProvider.dump(writer);
writer.println();
}
@Override
public void syncDataFromClient(String callingPackageName, Bundle data) {
final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
enforceCallingPackageBelongsToUid(callingInfo);
try {
syncDataFromClientInternal(callingInfo, data);
} finally {
Binder.restoreCallingIdentity(token);
}
}
private void syncDataFromClientInternal(CallingInfo callingInfo, Bundle data) {
// check first if service already bound
ISdkSandboxService service = mServiceProvider.getBoundServiceForApp(callingInfo);
if (service != null) {
try {
service.syncDataFromClient(data);
} catch (RemoteException ignore) {
// TODO(b/239403323): Sandbox has died. Register lifecycle callback to retry.
}
}
}
interface SandboxBindingCallback {
void onBindingSuccessful(ISdkSandboxService service);
void onBindingFailed();
}
class SandboxServiceConnection implements ServiceConnection {
private final SdkSandboxServiceProvider mServiceProvider;
private final CallingInfo mCallingInfo;
private boolean mServiceBound = false;
private final SandboxBindingCallback mCallback;
SandboxServiceConnection(
SdkSandboxServiceProvider serviceProvider,
CallingInfo callingInfo,
SandboxBindingCallback callback) {
mServiceProvider = serviceProvider;
mCallingInfo = callingInfo;
mCallback = callback;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
final ISdkSandboxService mService =
ISdkSandboxService.Stub.asInterface(service);
Log.d(
TAG,
String.format(
"Sdk sandbox has been bound for app package %s with uid %d",
mCallingInfo.getPackageName(), mCallingInfo.getUid()));
mServiceProvider.setBoundServiceForApp(mCallingInfo, mService);
try {
service.linkToDeath(() -> removeAllSdkTokensAndLinks(mCallingInfo), 0);
} catch (RemoteException re) {
// Sandbox had already died, cleanup sdk tokens and links.
removeAllSdkTokensAndLinks(mCallingInfo);
}
if (!mServiceBound) {
mCallback.onBindingSuccessful(mService);
mServiceBound = true;
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
// Sdk sandbox crashed or killed, system will start it again.
// TODO(b/204991850): Handle restarts differently
// (e.g. Exponential backoff retry strategy)
mServiceProvider.setBoundServiceForApp(mCallingInfo, null);
}
@Override
public void onBindingDied(ComponentName name) {
mServiceProvider.setBoundServiceForApp(mCallingInfo, null);
mServiceProvider.unbindService(mCallingInfo);
mServiceProvider.bindService(mCallingInfo, this);
}
@Override
public void onNullBinding(ComponentName name) {
mCallback.onBindingFailed();
}
}
void startSdkSandbox(CallingInfo callingInfo, SandboxBindingCallback callback) {
mServiceProvider.bindService(
callingInfo, new SandboxServiceConnection(mServiceProvider, callingInfo, callback));
}
private void invokeSdkSandboxServiceToLoadSdk(CallingInfo callingInfo, IBinder sdkToken,
SdkProviderInfo info, Bundle params, AppAndRemoteSdkLink link) {
// check first if service already bound
ISdkSandboxService service = mServiceProvider.getBoundServiceForApp(callingInfo);
if (service != null) {
loadSdkForService(callingInfo, sdkToken, info, params, link, service);
return;
}
startSdkSandbox(
callingInfo,
new SandboxBindingCallback() {
@Override
public void onBindingSuccessful(ISdkSandboxService service) {
try {
service.asBinder()
.linkToDeath(
() -> handleSandboxLifecycleCallbacks(callingInfo),
0);
} catch (RemoteException re) {
handleSandboxLifecycleCallbacks(callingInfo);
return;
}
loadSdkForService(callingInfo, sdkToken, info, params, link, service);
}
@Override
public void onBindingFailed() {
link.handleLoadSdkError(
SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR,
"Failed to bind the service",
/*cleanUpInternalState=*/ true);
}
});
}
private void handleSandboxLifecycleCallbacks(CallingInfo callingInfo) {
RemoteCallbackList<ISdkSandboxLifecycleCallback> sandboxLifecycleCallbacks;
synchronized (mLock) {
sandboxLifecycleCallbacks = mSandboxLifecycleCallbacks.get(callingInfo);
if (sandboxLifecycleCallbacks == null) {
return;
}
int size = sandboxLifecycleCallbacks.beginBroadcast();
for (int i = 0; i < size; ++i) {
try {
sandboxLifecycleCallbacks.getBroadcastItem(i).onSdkSandboxDied();
} catch (RemoteException e) {
Log.w(TAG, "Unable to send sdk sandbox death event to app", e);
}
}
sandboxLifecycleCallbacks.finishBroadcast();
mSandboxLifecycleCallbacks.remove(callingInfo);
}
}
@Override
public void stopSdkSandbox(String callingPackageName) {
final int callingUid = Binder.getCallingUid();
final CallingInfo callingInfo = new CallingInfo(callingUid, callingPackageName);
enforceCallingPackageBelongsToUid(callingInfo);
mContext.enforceCallingPermission(
STOP_SDK_SANDBOX_PERMISSION,
callingPackageName + " does not have permission to stop their sandbox");
final long token = Binder.clearCallingIdentity();
try {
stopSdkSandboxService(callingInfo, "App requesting sandbox kill");
} finally {
Binder.restoreCallingIdentity(token);
}
}
void stopSdkSandboxService(CallingInfo callingInfo, String reason) {
if (!isSdkSandboxServiceRunning(callingInfo)) {
return;
}
mServiceProvider.unbindService(callingInfo);
final int sdkSandboxUid = Process.toSdkSandboxUid(callingInfo.getUid());
Log.i(TAG, "Killing sdk sandbox/s with uid " + sdkSandboxUid);
// TODO(b/230839879): Avoid killing by uid
mActivityManager.killUid(sdkSandboxUid, reason);
}
boolean isSdkSandboxServiceRunning(CallingInfo callingInfo) {
return mServiceProvider.getBoundServiceForApp(callingInfo) != null;
}
private void loadSdkForService(CallingInfo callingInfo, IBinder sdkToken,
SdkProviderInfo sdkProviderInfo, Bundle params, AppAndRemoteSdkLink link,
ISdkSandboxService service) {
// Gather sdk storage information
SdkDataDirInfo sdkDataInfo =
mSdkSandboxStorageManager.getSdkDataDirInfo(
callingInfo, sdkProviderInfo.getSdkInfo().getName());
try {
service.loadSdk(
callingInfo.getPackageName(),
sdkToken,
sdkProviderInfo.getApplicationInfo(),
sdkProviderInfo.getSdkInfo().getName(),
sdkProviderInfo.getSdkProviderClassName(),
sdkDataInfo.getCeDataDir(),
sdkDataInfo.getDeDataDir(),
params,
link);
} catch (RemoteException e) {
String errorMsg = "Failed to load code";
Log.w(TAG, errorMsg, e);
link.handleLoadSdkError(SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR, errorMsg,
/*cleanupInternalState=*/ true);
}
}
/**
* Clean up all internal data structures related to {@code sdkToken}
*/
private void cleanUp(IBinder sdkToken) {
// Destroy the sdkToken first, to free up the {callingUid, name} pair
mSdkTokenManager.destroy(sdkToken);
// Now clean up rest of the state which is using an obsolete sdkToken
synchronized (mLock) {
mAppAndRemoteSdkLinks.remove(sdkToken);
}
}
/** Clean up all internal data structures related to {@code callingInfo} of the app */
private void removeAllSdkTokensAndLinks(CallingInfo callingInfo) {
removeLinksToSdk(callingInfo, null);
}
/**
* Removes {@link AppAndRemoteSdkLink} objects associated with the {@code callingInfo}. If
* {@code sdkName} is specified, only the object associated with that SDK name will be removed.
* Otherwise, all objects for the caller will be removed.
*
* <p>Returns {@code true} if there are no more SDKs associated with the caller after cleanup,
* {@code false} otherwise.
*/
private boolean removeLinksToSdk(CallingInfo callingInfo, @Nullable String sdkName) {
synchronized (mLock) {
ISdkSandboxService boundSandbox = mServiceProvider.getBoundServiceForApp(callingInfo);
boolean shouldStopSandbox = true;
ArrayList<IBinder> linksToDelete = new ArrayList<>();
for (int i = 0; i < mAppAndRemoteSdkLinks.size(); i++) {
AppAndRemoteSdkLink link = mAppAndRemoteSdkLinks.valueAt(i);
if (link.mCallingInfo.equals(callingInfo)) {
IBinder sdkToken = mAppAndRemoteSdkLinks.keyAt(i);
if (TextUtils.isEmpty(sdkName)
|| link.mSdkProviderInfo.getSdkInfo().getName().equals(sdkName)) {
if (boundSandbox != null) {
try {
boundSandbox.unloadSdk(sdkToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to unload SDK: ", e);
}
}
linksToDelete.add(sdkToken);
} else {
shouldStopSandbox = false;
}
}
}
for (int i = 0; i < linksToDelete.size(); i++) {
IBinder sdkToken = linksToDelete.get(i);
mSdkTokenManager.destroy(sdkToken);
mAppAndRemoteSdkLinks.remove(sdkToken);
}
return shouldStopSandbox;
}
}
private void enforceAllowedToStartOrBindService(Intent intent) {
ComponentName component = intent.getComponent();
String errorMsg = "SDK sandbox uid may not bind to or start a service: ";
if (component == null) {
throw new SecurityException(errorMsg + "intent component must be non-null.");
}
String componentPackageName = component.getPackageName();
if (componentPackageName != null) {
if (!componentPackageName.equals(WebViewUpdateService.getCurrentWebViewPackageName())
&& !componentPackageName.equals(getAdServicesPackageName())) {
throw new SecurityException(errorMsg + "component package name "
+ componentPackageName + " is not allowlisted.");
}
} else {
throw new SecurityException(errorMsg
+ "the intent's component package name must be non-null.");
}
}
@Override
public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out,
ParcelFileDescriptor err, String[] args) {
return new SdkSandboxShellCommand(this, mContext).exec(this,
in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
}
private SdkProviderInfo createSdkProviderInfo(
String sharedLibraryName, String callingPackageName) {
try {
PackageManager pm = mContext.getPackageManager();
ApplicationInfo info = pm.getApplicationInfo(
callingPackageName, PackageManager.GET_SHARED_LIBRARY_FILES);
List<SharedLibraryInfo> sharedLibraries = info.getSharedLibraryInfos();
for (int j = 0; j < sharedLibraries.size(); j++) {
SharedLibraryInfo sharedLibrary = sharedLibraries.get(j);
if (sharedLibrary.getType() != SharedLibraryInfo.TYPE_SDK_PACKAGE) {
continue;
}
if (!sharedLibraryName.equals(sharedLibrary.getName())) {
continue;
}
String sdkProviderClassName = pm.getProperty(PROPERTY_SDK_PROVIDER_CLASS_NAME,
sharedLibrary.getDeclaringPackage().getPackageName()).getString();
ApplicationInfo applicationInfo =
pm.getPackageInfo(
sharedLibrary.getDeclaringPackage(),
PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES)
.applicationInfo;
return new SdkProviderInfo(applicationInfo, sharedLibrary, sdkProviderClassName);
}
return null;
} catch (PackageManager.NameNotFoundException ignored) {
return null;
}
}
private String resolveAdServicesPackage() {
PackageManager pm = mContext.getPackageManager();
Intent serviceIntent = new Intent(AdServicesCommon.ACTION_TOPICS_SERVICE);
List<ResolveInfo> resolveInfos =
pm.queryIntentServicesAsUser(
serviceIntent,
PackageManager.GET_SERVICES
| PackageManager.MATCH_SYSTEM_ONLY
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
UserHandle.SYSTEM);
if (resolveInfos == null || resolveInfos.size() == 0) {
Log.e(TAG, "AdServices package could not be resolved");
} else if (resolveInfos.size() > 1) {
Log.e(TAG, "More than one service matched intent " + serviceIntent.getAction());
} else {
return resolveInfos.get(0).serviceInfo.packageName;
}
return null;
}
@VisibleForTesting
String getAdServicesPackageName() {
return mAdServicesPackageName;
}
@ThreadSafe
private static class SdkTokenManager {
// Keep track of codeToken for each unique pair of {callingUid, sdkName}
@GuardedBy("mSdkTokens")
final ArrayMap<Pair<CallingInfo, String>, IBinder> mSdkTokens = new ArrayMap<>();
@GuardedBy("mSdkTokens")
final ArrayMap<IBinder, Pair<CallingInfo, String>> mReverseSdkTokens =
new ArrayMap<>();
/**
* For the given {callingUid, name} pair, create unique {@code sdkToken} or
* return existing one.
*/
public IBinder createOrGetSdkToken(CallingInfo callingInfo, String sdkName) {
final Pair<CallingInfo, String> pair = Pair.create(callingInfo, sdkName);
synchronized (mSdkTokens) {
if (mSdkTokens.containsKey(pair)) {
return mSdkTokens.get(pair);
}
final IBinder sdkToken = new Binder();
mSdkTokens.put(pair, sdkToken);
mReverseSdkTokens.put(sdkToken, pair);
return sdkToken;
}
}
@Nullable
public IBinder getSdkToken(CallingInfo callingInfo, String sdkName) {
final Pair<CallingInfo, String> pair = Pair.create(callingInfo, sdkName);
synchronized (mSdkTokens) {
return mSdkTokens.get(pair);
}
}
public void destroy(IBinder sdkToken) {
synchronized (mSdkTokens) {
mSdkTokens.remove(mReverseSdkTokens.get(sdkToken));
mReverseSdkTokens.remove(sdkToken);
}
}
void dump(PrintWriter writer) {
synchronized (mSdkTokens) {
if (mSdkTokens.isEmpty()) {
writer.println("mSdkTokens is empty");
} else {
writer.print("mSdkTokens size: ");
writer.println(mSdkTokens.size());
for (Pair<CallingInfo, String> pair : mSdkTokens.keySet()) {
writer.printf("caller: %s, sdkName: %s",
pair.first,
pair.second);
writer.println();
}
}
}
}
}
/**
* A callback object to establish a link between the app calling into manager service and the
* remote SDK being loaded in SdkSandbox.
*
* <p>Overview of communication:
*
* <ol>
* <li>App to ManagerService: App calls into this service via app context.
* <li>ManagerService to App: {@link AppAndRemoteSdkLink} holds reference to {@link
* ILoadSdkCallback} object which provides a call back into the app for providing the
* status of loading an SDK.
* <li>RemoteSdk to ManagerService: {@link AppAndRemoteSdkLink} extends {@link
* ILoadSdkInSandboxCallback} interface. We pass on this object to {@link
* ISdkSandboxService} so that remote SDK can call back into ManagerService.
* <li>ManagerService to RemoteSdk: When the SDK is loaded for the first time and remote SDK
* calls back with successful result, it also sends reference to {@link
* ISdkSandboxManagerToSdkSandboxCallback} callback object. ManagerService uses this to
* callback into the remote SDK.
* </ol>
*
* <p>We maintain a link for each unique {app, remoteCode} pair, which is identified with {@code
* codeToken}.
*/
private class AppAndRemoteSdkLink extends ILoadSdkInSandboxCallback.Stub {
private final CallingInfo mCallingInfo;
private final SdkProviderInfo mSdkProviderInfo;
// The codeToken for which this channel has been created
private final IBinder mSdkToken;
private final ILoadSdkCallback mManagerToAppCallback;
@GuardedBy("this")
private ISdkSandboxManagerToSdkSandboxCallback mManagerToCodeCallback;
AppAndRemoteSdkLink(
CallingInfo callingInfo,
IBinder sdkToken,
ILoadSdkCallback managerToAppCallback,
SdkProviderInfo sdkProviderInfo) {
mSdkToken = sdkToken;
mSdkProviderInfo = sdkProviderInfo;
mCallingInfo = callingInfo;
mManagerToAppCallback = managerToAppCallback;
}
@Override
public void onLoadSdkSuccess(
Bundle params, ISdkSandboxManagerToSdkSandboxCallback callback) {
// Keep reference to callback so that manager service can
// callback to remote code loaded.
synchronized (this) {
mManagerToCodeCallback = callback;
}
sendLoadSdkSuccessToApp(params);
}
@Override
public void onLoadSdkError(int errorCode, String errorMsg) {
handleLoadSdkError(
toSdkSandboxManagerLoadSdkErrorCode(errorCode),
errorMsg,
/*cleanUpInternalState=*/ true);
}
private void sendLoadSdkSuccessToApp(Bundle params) {
try {
mManagerToAppCallback.onLoadSdkSuccess(params);
} catch (RemoteException e) {
Log.w(TAG, "Failed to send onLoadCodeSuccess", e);
}
}
void handleLoadSdkError(int errorCode, String errorMsg, boolean cleanUpInternalState) {
if (cleanUpInternalState) {
// If an SDK fails to load entirely and does not exist in the sandbox, cleanup
// might need to occur so that the manager has to no longer concern itself with
// communication between the app and a non-existing remote code.
cleanUp(mSdkToken);
}
try {
mManagerToAppCallback.onLoadSdkFailure(errorCode, errorMsg);
} catch (RemoteException e) {
Log.w(TAG, "Failed to send onLoadCodeFailure", e);
}
}
void sendSurfacePackageErrorToApp(
int errorCode, String errorMsg, IRequestSurfacePackageCallback callback) {
try {
callback.onSurfacePackageError(errorCode, errorMsg);
} catch (RemoteException e) {
Log.w(TAG, "Failed to send onSurfacePackageError", e);
}
}
private void sendSurfacePackageReadyToApp(
SurfaceControlViewHost.SurfacePackage surfacePackage,
int surfacePackageId,
Bundle params,
IRequestSurfacePackageCallback callback) {
try {
callback.onSurfacePackageReady(surfacePackage, surfacePackageId, params);
} catch (RemoteException e) {
Log.w(TAG, "Failed to send onSurfacePackageReady callback", e);
}
}
void requestSurfacePackageFromSdk(
IBinder hostToken,
int displayId,
int width,
int height,
Bundle params,
IRequestSurfacePackageCallback callback) {
try {
synchronized (this) {
mManagerToCodeCallback.onSurfacePackageRequested(
hostToken,
displayId,
width,
height,
params,
new IRequestSurfacePackageFromSdkCallback.Stub() {
@Override
public void onSurfacePackageReady(
SurfaceControlViewHost.SurfacePackage surfacePackage,
int surfacePackageId,
Bundle params) {
sendSurfacePackageReadyToApp(
surfacePackage, surfacePackageId, params, callback);
}
@Override
public void onSurfacePackageError(int errorCode, String errorMsg) {
int sdkSandboxManagerErrorCode =
toSdkSandboxManagerRequestSurfacePackageErrorCode(
errorCode);
sendSurfacePackageErrorToApp(
sdkSandboxManagerErrorCode, errorMsg, callback);
}
});
}
} catch (RemoteException e) {
String errorMsg = "Failed to requestSurfacePackage";
Log.w(TAG, errorMsg, e);
sendSurfacePackageErrorToApp(
SdkSandboxManager.REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR,
errorMsg + ": " + e,
callback);
}
}
private void sendSendDataSuccessToApp(Bundle params, ISendDataCallback callback) {
try {
callback.onSendDataSuccess(params);
} catch (RemoteException e) {
Log.w(TAG, "Failed to send onSendDataSuccess", e);
}
}
private void sendSendDataErrorToApp(
int errorCode, String errorMsg, ISendDataCallback callback) {
try {
callback.onSendDataError(errorCode, errorMsg);
} catch (RemoteException e) {
Log.w(TAG, "Failed to send onSendDataError", e);
}
}
void sendDataToSdk(Bundle data, ISendDataCallback callback) {
try {
synchronized (this) {
mManagerToCodeCallback.onDataReceived(
data,
new IDataReceivedCallback.Stub() {
@Override
public void onDataReceivedSuccess(Bundle params) {
sendSendDataSuccessToApp(params, callback);
}
@Override
public void onDataReceivedError(int errorCode, String errorMsg) {
sendSendDataErrorToApp(
toSdkSandboxManagerSendDataErrorCode(errorCode),
errorMsg,
callback);
}
});
}
} catch (RemoteException e) {
String errorMsg = "Failed to sendData";
Log.w(TAG, errorMsg, e);
sendSendDataErrorToApp(
SdkSandboxManager.SEND_DATA_INTERNAL_ERROR, errorMsg + ": " + e, callback);
}
}
@SdkSandboxManager.LoadSdkErrorCode
private int toSdkSandboxManagerLoadSdkErrorCode(int sdkSandboxErrorCode) {
switch (sdkSandboxErrorCode) {
case ILoadSdkInSandboxCallback.LOAD_SDK_ALREADY_LOADED:
return SdkSandboxManager.LOAD_SDK_ALREADY_LOADED;
case ILoadSdkInSandboxCallback.LOAD_SDK_NOT_FOUND:
return SdkSandboxManager.LOAD_SDK_NOT_FOUND;
case ILoadSdkInSandboxCallback.LOAD_SDK_PROVIDER_INIT_ERROR:
return SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR;
case ILoadSdkInSandboxCallback.LOAD_SDK_INSTANTIATION_ERROR:
return SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR;
default:
Log.e(TAG, "Error code" + sdkSandboxErrorCode
+ "has no mapping to the SdkSandboxManager error codes");
return SdkSandboxManager.LOAD_SDK_INTERNAL_ERROR;
}
}
@SdkSandboxManager.RequestSurfacePackageErrorCode
private int toSdkSandboxManagerRequestSurfacePackageErrorCode(int sdkSandboxErrorCode) {
if (sdkSandboxErrorCode
== IRequestSurfacePackageFromSdkCallback.SURFACE_PACKAGE_INTERNAL_ERROR) {
return SdkSandboxManager.REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR;
}
Log.e(
TAG,
"Error code"
+ sdkSandboxErrorCode
+ "has no mapping to the SdkSandboxManager error codes");
return SdkSandboxManager.REQUEST_SURFACE_PACKAGE_INTERNAL_ERROR;
}
@SdkSandboxManager.SendDataErrorCode
private int toSdkSandboxManagerSendDataErrorCode(int sdkSandboxErrorCode) {
if (sdkSandboxErrorCode == IDataReceivedCallback.DATA_RECEIVED_INTERNAL_ERROR) {
return SdkSandboxManager.SEND_DATA_INTERNAL_ERROR;
}
Log.e(
TAG,
"Error code"
+ sdkSandboxErrorCode
+ "has no mapping to the SdkSandboxManager error codes");
return SdkSandboxManager.SEND_DATA_INTERNAL_ERROR;
}
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
SdkSandboxManagerLocal getLocalManager() {
return mLocalManager;
}
private void notifyInstrumentationStarted(CallingInfo callingInfo) {
Log.d(TAG, "notifyInstrumentationStarted: clientApp = " + callingInfo.getPackageName()
+ " clientAppUid = " + callingInfo.getUid());
synchronized (mLock) {
mServiceProvider.unbindService(callingInfo);
int sdkSandboxUid = Process.toSdkSandboxUid(callingInfo.getUid());
mActivityManager.killUid(sdkSandboxUid, "instrumentation started");
mRunningInstrumentations.add(callingInfo);
}
// TODO(b/223386213): we need to check if there is reconcileSdkData task already enqueued
// because the instrumented client app was just installed.
mSdkSandboxStorageManager.notifyInstrumentationStarted(callingInfo);
}
private void notifyInstrumentationFinished(CallingInfo callingInfo) {
Log.d(TAG, "notifyInstrumentationFinished: clientApp = " + callingInfo.getPackageName()
+ " clientAppUid = " + callingInfo.getUid());
synchronized (mLock) {
mRunningInstrumentations.remove(callingInfo);
}
}
private boolean isInstrumentationRunning(CallingInfo callingInfo) {
synchronized (mLock) {
return mRunningInstrumentations.contains(callingInfo);
}
}
/** @hide */
public static class Lifecycle extends SystemService {
private final SdkSandboxManagerService mService;
public Lifecycle(Context context) {
super(context);
SdkSandboxServiceProvider provider = new SdkSandboxServiceProviderImpl(getContext());
mService = new SdkSandboxManagerService(getContext(), provider);
}
@Override
public void onStart() {
publishBinderService(SDK_SANDBOX_SERVICE, mService);
LocalManagerRegistry.addManager(
SdkSandboxManagerLocal.class, mService.getLocalManager());
}
@Override
public void onUserUnlocking(TargetUser user) {
final int userId = user.getUserHandle().getIdentifier();
mService.onUserUnlocking(userId);
}
}
/** Class which retrieves and stores the sdkName, sdkProviderClassName, and ApplicationInfo */
private static class SdkProviderInfo {
private final ApplicationInfo mApplicationInfo;
private final SharedLibraryInfo mSdkInfo;
private final String mSdkProviderClassName;
private SdkProviderInfo(
ApplicationInfo applicationInfo,
SharedLibraryInfo sdkInfo,
String sdkProviderClassName) {
mApplicationInfo = applicationInfo;
mSdkInfo = sdkInfo;
mSdkProviderClassName = sdkProviderClassName;
}
public SharedLibraryInfo getSdkInfo() {
return mSdkInfo;
}
public String getSdkProviderClassName() {
return mSdkProviderClassName;
}
public ApplicationInfo getApplicationInfo() {
return mApplicationInfo;
}
}
private class LocalImpl implements SdkSandboxManagerLocal {
@NonNull
@Override
public String getSdkSandboxProcessNameForInstrumentation(
@NonNull ApplicationInfo clientAppInfo) {
return clientAppInfo.processName + "_sdk_sandbox_instr";
}
@Override
public void notifyInstrumentationStarted(
@NonNull String clientAppPackageName, int clientAppUid) {
SdkSandboxManagerService.this.notifyInstrumentationStarted(
new CallingInfo(clientAppUid, clientAppPackageName));
}
@Override
public void notifyInstrumentationFinished(
@NonNull String clientAppPackageName, int clientAppUid) {
SdkSandboxManagerService.this.notifyInstrumentationFinished(
new CallingInfo(clientAppUid, clientAppPackageName));
}
@Override
public boolean isInstrumentationRunning(
@NonNull String clientAppPackageName, int clientAppUid) {
return SdkSandboxManagerService.this.isInstrumentationRunning(
new CallingInfo(clientAppUid, clientAppPackageName));
}
@Override
public void enforceAllowedToSendBroadcast(@NonNull Intent intent) {
if (intent.getAction() != null) {
throw new SecurityException(
"Intent "
+ intent.getAction()
+ " may not be broadcast from an SDK sandbox uid");
}
}
@Override
public void enforceAllowedToStartActivity(@NonNull Intent intent) {
if (intent.getAction() != null) {
if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
throw new SecurityException(
"Intent "
+ intent.getAction()
+ " may not be broadcast from an SDK sandbox uid.");
}
if (intent.getPackage() != null || intent.getComponent() != null) {
throw new SecurityException(
"Intent "
+ intent.getAction()
+ " broadcast from an SDK sandbox uid may not specify a"
+ " package name or component.");
}
}
}
@Override
public void enforceAllowedToStartOrBindService(@NonNull Intent intent) {
SdkSandboxManagerService.this.enforceAllowedToStartOrBindService(intent);
}
}
}