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; instrument.noRestart = true;
} else if (opt.equals("--always-check-signature")) { } else if (opt.equals("--always-check-signature")) {
instrument.alwaysCheckSignature = true; instrument.alwaysCheckSignature = true;
} else if (opt.equals("--instrument-sdk-sandbox")) {
instrument.instrumentSdkSandbox = true;
} else { } else {
System.err.println("Error: Unknown option: " + opt); System.err.println("Error: Unknown option: " + opt);
return; 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_HIDDEN_API_CHECKS;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE; 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_DISABLE_TEST_API_CHECKS;
import static android.app.ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_SANDBOX;
import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART; import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
import android.app.IActivityManager; import android.app.IActivityManager;
@ -97,6 +98,7 @@ public class Instrument {
// Required // Required
public String componentNameArg; public String componentNameArg;
public boolean alwaysCheckSignature = false; public boolean alwaysCheckSignature = false;
public boolean instrumentSdkSandbox = false;
/** /**
* Construct the instrument command runner. * Construct the instrument command runner.
@ -524,6 +526,9 @@ public class Instrument {
if (alwaysCheckSignature) { if (alwaysCheckSignature) {
flags |= INSTR_FLAG_ALWAYS_CHECK_SIGNATURE; flags |= INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
} }
if (instrumentSdkSandbox) {
flags |= INSTR_FLAG_INSTRUMENT_SDK_SANDBOX;
}
if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId, if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId,
abi)) { abi)) {
throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString()); throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());

View File

@ -185,6 +185,11 @@ public class ActivityManager {
* @hide * @hide
*/ */
public static final int INSTR_FLAG_ALWAYS_CHECK_SIGNATURE = 1 << 4; 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 { static final class UidObserver extends IUidObserver.Stub {
final OnUidImportanceListener mListener; 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.SELinuxUtil;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.snapshot.PackageDataSnapshot; import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
import com.android.server.uri.GrantUri; import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants; import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal; 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, final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
boolean disableHiddenApiChecks, boolean disableTestApiChecks, boolean disableHiddenApiChecks, boolean disableTestApiChecks,
String abiOverride, int zygotePolicyFlags) { 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; ProcessRecord app;
if (!isolated) { if (!isolated) {
app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName, app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
@ -6593,10 +6618,16 @@ public class ActivityManagerService extends IActivityManager.Stub
} }
if (app == null) { if (app == null) {
app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0, app = mProcessList.newProcessRecordLocked(
false, 0, null, info,
customProcess,
isolated,
/* isolatedUid= */0,
isSdkSandbox,
sdkSandboxUid,
sdkSandboxClientAppPackage,
new HostingRecord("added application", new HostingRecord("added application",
customProcess != null ? customProcess : info.processName)); customProcess != null ? customProcess : info.processName));
updateLruProcessLocked(app, false, null); updateLruProcessLocked(app, false, null);
updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
} }
@ -6606,13 +6637,16 @@ public class ActivityManagerService extends IActivityManager.Stub
Event.APP_COMPONENT_USED); Event.APP_COMPONENT_USED);
// This package really, really can not be stopped. // This package really, really can not be stopped.
try { // TODO: how set package stopped state should work for sdk sandboxes?
AppGlobals.getPackageManager().setPackageStoppedState( if (!isSdkSandbox) {
info.packageName, false, UserHandle.getUserId(app.uid)); try {
} catch (RemoteException e) { AppGlobals.getPackageManager().setPackageStoppedState(
} catch (IllegalArgumentException e) { info.packageName, false, UserHandle.getUserId(app.uid));
Slog.w(TAG, "Failed trying to unstop package " } catch (RemoteException e) {
+ info.packageName + ": " + e); } catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ info.packageName + ": " + e);
}
} }
if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) { 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); ActiveInstrumentation activeInstr = new ActiveInstrumentation(this);
activeInstr.mClass = className; activeInstr.mClass = className;
String defProcess = ai.processName;; String defProcess = ai.processName;;
@ -14434,15 +14494,6 @@ public class ActivityManagerService extends IActivityManager.Stub
START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid, callingUid) START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid, callingUid)
== PackageManager.PERMISSION_GRANTED; == PackageManager.PERMISSION_GRANTED;
activeInstr.mNoRestart = noRestart; 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(); final long origId = Binder.clearCallingIdentity();
@ -14488,6 +14539,111 @@ public class ActivityManagerService extends IActivityManager.Stub
return true; 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, private void instrumentWithoutRestart(ActiveInstrumentation activeInstr,
ApplicationInfo targetInfo) { ApplicationInfo targetInfo) {
ProcessRecord pr; ProcessRecord pr;
@ -14610,7 +14766,17 @@ public class ActivityManagerService extends IActivityManager.Stub
app.setActiveInstrumentation(null); 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, forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false,
app.userId, app.userId,
"finished inst"); "finished inst");