From 99ba880e55591b3bf2d96dd6f9520d1d37177925 Mon Sep 17 00:00:00 2001 From: Nikita Ioffe Date: Wed, 9 Mar 2022 15:26:54 +0000 Subject: [PATCH] Add a way to instrument sdk sandbox processes The following restrictions applies to the instrumentation of the sdk sandbox processes: * Instrumentation must be signed with the same certificate as the client app the instrumented sdk sandbox belongs to. * If there is a running instance of to-be-instrumented sdk sandbox process, then it will be killed before the instrumentation starts. * While instrumentation is running the client app won't be allowed to connect to the instrumented sdk sandbox process. * The --no-restart instrumentation of the sdk sandbox processes is not supported. Bug: 209061624 Test: atest SdkSandboxInprocessTests Change-Id: Ia4b145c091bf8da600a77ea82fc9e3cd97757275 --- cmds/am/src/com/android/commands/am/Am.java | 2 + .../com/android/commands/am/Instrument.java | 5 + core/java/android/app/ActivityManager.java | 5 + .../server/am/ActivityManagerService.java | 206 ++++++++++++++++-- 4 files changed, 198 insertions(+), 20 deletions(-) diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index c5410a082322..b8d24e388d67 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -191,6 +191,8 @@ public class Am extends BaseCommand { instrument.noRestart = true; } else if (opt.equals("--always-check-signature")) { instrument.alwaysCheckSignature = true; + } else if (opt.equals("--instrument-sdk-sandbox")) { + instrument.instrumentSdkSandbox = true; } else { System.err.println("Error: Unknown option: " + opt); return; diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java index a0562d964954..7ff4bc4bcf76 100644 --- a/cmds/am/src/com/android/commands/am/Instrument.java +++ b/cmds/am/src/com/android/commands/am/Instrument.java @@ -20,6 +20,7 @@ import static android.app.ActivityManager.INSTR_FLAG_ALWAYS_CHECK_SIGNATURE; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS; +import static android.app.ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_SANDBOX; import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART; import android.app.IActivityManager; @@ -97,6 +98,7 @@ public class Instrument { // Required public String componentNameArg; public boolean alwaysCheckSignature = false; + public boolean instrumentSdkSandbox = false; /** * Construct the instrument command runner. @@ -524,6 +526,9 @@ public class Instrument { if (alwaysCheckSignature) { flags |= INSTR_FLAG_ALWAYS_CHECK_SIGNATURE; } + if (instrumentSdkSandbox) { + flags |= INSTR_FLAG_INSTRUMENT_SDK_SANDBOX; + } if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId, abi)) { throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString()); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 9f1510526bae..c8ecfaa60107 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -185,6 +185,11 @@ public class ActivityManager { * @hide */ public static final int INSTR_FLAG_ALWAYS_CHECK_SIGNATURE = 1 << 4; + /** + * Instrument Sdk Sandbox process that corresponds to the target package. + * @hide + */ + public static final int INSTR_FLAG_INSTRUMENT_SDK_SANDBOX = 1 << 5; static final class UidObserver extends IUidObserver.Stub { final OnUidImportanceListener mListener; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9d9ee8c7a03b..f92fb06e4fc4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -406,6 +406,7 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.snapshot.PackageDataSnapshot; +import com.android.server.sdksandbox.SdkSandboxManagerLocal; import com.android.server.uri.GrantUri; import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriGrantsManagerInternal; @@ -6584,6 +6585,30 @@ public class ActivityManagerService extends IActivityManager.Stub final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, boolean disableHiddenApiChecks, boolean disableTestApiChecks, String abiOverride, int zygotePolicyFlags) { + return addAppLocked( + info, + customProcess, + isolated, + /* isSdkSandbox= */ false, + /* sdkSandboxUid= */ 0, + /* sdkSandboxClientAppPackage= */ null, + disableHiddenApiChecks, + disableTestApiChecks, + abiOverride, + zygotePolicyFlags); + } + + final ProcessRecord addAppLocked( + ApplicationInfo info, + String customProcess, + boolean isolated, + boolean isSdkSandbox, + int sdkSandboxUid, + @Nullable String sdkSandboxClientAppPackage, + boolean disableHiddenApiChecks, + boolean disableTestApiChecks, + String abiOverride, + int zygotePolicyFlags) { ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName, @@ -6593,10 +6618,16 @@ public class ActivityManagerService extends IActivityManager.Stub } if (app == null) { - app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0, - false, 0, null, + app = mProcessList.newProcessRecordLocked( + info, + customProcess, + isolated, + /* isolatedUid= */0, + isSdkSandbox, + sdkSandboxUid, + sdkSandboxClientAppPackage, new HostingRecord("added application", - customProcess != null ? customProcess : info.processName)); + customProcess != null ? customProcess : info.processName)); updateLruProcessLocked(app, false, null); updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); } @@ -6606,13 +6637,16 @@ public class ActivityManagerService extends IActivityManager.Stub Event.APP_COMPONENT_USED); // This package really, really can not be stopped. - try { - AppGlobals.getPackageManager().setPackageStoppedState( - info.packageName, false, UserHandle.getUserId(app.uid)); - } catch (RemoteException e) { - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Failed trying to unstop package " - + info.packageName + ": " + e); + // TODO: how set package stopped state should work for sdk sandboxes? + if (!isSdkSandbox) { + try { + AppGlobals.getPackageManager().setPackageStoppedState( + info.packageName, false, UserHandle.getUserId(app.uid)); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + info.packageName + ": " + e); + } } if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) { @@ -14410,6 +14444,32 @@ public class ActivityManagerService extends IActivityManager.Stub } } + boolean disableHiddenApiChecks = ai.usesNonSdkApi() + || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0; + boolean disableTestApiChecks = disableHiddenApiChecks + || (flags & INSTR_FLAG_DISABLE_TEST_API_CHECKS) != 0; + + if (disableHiddenApiChecks || disableTestApiChecks) { + enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS, + "disable hidden API checks"); + } + + if ((flags & ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_SANDBOX) != 0) { + return startInstrumentationOfSdkSandbox( + className, + profileFile, + arguments, + watcher, + uiAutomationConnection, + userId, + abiOverride, + ii, + ai, + noRestart, + disableHiddenApiChecks, + disableTestApiChecks); + } + ActiveInstrumentation activeInstr = new ActiveInstrumentation(this); activeInstr.mClass = className; String defProcess = ai.processName;; @@ -14434,15 +14494,6 @@ public class ActivityManagerService extends IActivityManager.Stub START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; activeInstr.mNoRestart = noRestart; - boolean disableHiddenApiChecks = ai.usesNonSdkApi() - || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0; - boolean disableTestApiChecks = disableHiddenApiChecks - || (flags & INSTR_FLAG_DISABLE_TEST_API_CHECKS) != 0; - - if (disableHiddenApiChecks || disableTestApiChecks) { - enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS, - "disable hidden API checks"); - } final long origId = Binder.clearCallingIdentity(); @@ -14488,6 +14539,111 @@ public class ActivityManagerService extends IActivityManager.Stub return true; } + @GuardedBy("this") + private boolean startInstrumentationOfSdkSandbox( + ComponentName className, + String profileFile, + Bundle arguments, + IInstrumentationWatcher watcher, + IUiAutomationConnection uiAutomationConnection, + int userId, + String abiOverride, + InstrumentationInfo instrumentationInfo, + ApplicationInfo sdkSandboxClientAppInfo, + boolean noRestart, + boolean disableHiddenApiChecks, + boolean disableTestApiChecks) { + + if (noRestart) { + reportStartInstrumentationFailureLocked( + watcher, + className, + "Instrumenting sdk sandbox with --no-restart flag is not supported"); + return false; + } + + final ApplicationInfo sdkSandboxInfo; + try { + final PackageManager pm = mContext.getPackageManager(); + sdkSandboxInfo = pm.getApplicationInfoAsUser(pm.getSdkSandboxPackageName(), 0, userId); + } catch (NameNotFoundException e) { + reportStartInstrumentationFailureLocked( + watcher, className, "Can't find SdkSandbox package"); + return false; + } + + final SdkSandboxManagerLocal sandboxManagerLocal = + LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class); + if (sandboxManagerLocal == null) { + reportStartInstrumentationFailureLocked( + watcher, className, "Can't locate SdkSandboxManagerLocal"); + return false; + } + + final String processName = sandboxManagerLocal.getSdkSandboxProcessNameForInstrumentation( + sdkSandboxClientAppInfo); + + ActiveInstrumentation activeInstr = new ActiveInstrumentation(this); + activeInstr.mClass = className; + activeInstr.mTargetProcesses = new String[]{processName}; + activeInstr.mTargetInfo = sdkSandboxInfo; + activeInstr.mProfileFile = profileFile; + activeInstr.mArguments = arguments; + activeInstr.mWatcher = watcher; + activeInstr.mUiAutomationConnection = uiAutomationConnection; + activeInstr.mResultClass = className; + activeInstr.mHasBackgroundActivityStartsPermission = false; + activeInstr.mHasBackgroundForegroundServiceStartsPermission = false; + // Instrumenting sdk sandbox without a restart is not supported + activeInstr.mNoRestart = false; + + final int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + sandboxManagerLocal.notifyInstrumentationStarted( + sdkSandboxClientAppInfo.packageName, sdkSandboxClientAppInfo.uid); + synchronized (mProcLock) { + int sdkSandboxUid = Process.toSdkSandboxUid(sdkSandboxClientAppInfo.uid); + // Kill the package sdk sandbox process belong to. At this point sdk sandbox is + // already killed. + forceStopPackageLocked( + instrumentationInfo.targetPackage, + /* appId= */ -1, + /* callerWillRestart= */ true, + /* purgeCache= */ false, + /* doIt= */ true, + /* evenPersistent= */ true, + /* uninstalling= */ false, + userId, + "start instr"); + + ProcessRecord app = addAppLocked( + sdkSandboxInfo, + processName, + /* isolated= */ false, + /* isSdkSandbox= */ true, + sdkSandboxUid, + sdkSandboxClientAppInfo.packageName, + disableHiddenApiChecks, + disableTestApiChecks, + abiOverride, + ZYGOTE_POLICY_FLAG_EMPTY); + + app.setActiveInstrumentation(activeInstr); + activeInstr.mFinished = false; + activeInstr.mSourceUid = callingUid; + activeInstr.mRunningProcesses.add(app); + if (!mActiveInstrumentation.contains(activeInstr)) { + mActiveInstrumentation.add(activeInstr); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + + return true; + } + private void instrumentWithoutRestart(ActiveInstrumentation activeInstr, ApplicationInfo targetInfo) { ProcessRecord pr; @@ -14610,7 +14766,17 @@ public class ActivityManagerService extends IActivityManager.Stub app.setActiveInstrumentation(null); } - if (!instr.mNoRestart) { + if (app.isSdkSandbox) { + // For sharedUid apps this will kill all sdk sandbox processes, which is not ideal. + // TODO(b/209061624): should we call ProcessList.removeProcessLocked instead? + killUid(UserHandle.getAppId(app.uid), UserHandle.getUserId(app.uid), "finished instr"); + final SdkSandboxManagerLocal sandboxManagerLocal = + LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class); + if (sandboxManagerLocal != null) { + sandboxManagerLocal.notifyInstrumentationFinished( + app.sdkSandboxClientAppPackage, Process.getAppUidForSdkSandboxUid(app.uid)); + } + } else if (!instr.mNoRestart) { forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false, app.userId, "finished inst");