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
This commit is contained in:
Nikita Ioffe 2022-03-09 15:26:54 +00:00
parent 44318af94f
commit 99ba880e55
4 changed files with 198 additions and 20 deletions

View File

@ -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;

View File

@ -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());

View File

@ -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;

View File

@ -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,8 +6618,14 @@ 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));
updateLruProcessLocked(app, false, null);
@ -6606,6 +6637,8 @@ public class ActivityManagerService extends IActivityManager.Stub
Event.APP_COMPONENT_USED);
// This package really, really can not be stopped.
// TODO: how set package stopped state should work for sdk sandboxes?
if (!isSdkSandbox) {
try {
AppGlobals.getPackageManager().setPackageStoppedState(
info.packageName, false, UserHandle.getUserId(app.uid));
@ -6614,6 +6647,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, "Failed trying to unstop package "
+ info.packageName + ": " + e);
}
}
if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
app.setPersistent(true);
@ -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");