Remove permission token from startActivityAsCaller

The call is protected by the signature permission
START_ACTIVITY_AS_CALLER. This will remove the need for additional data
to be passed from system server.

Bug: 222082547
Bug: 226354782
Test: atest ActivityTaskManagerServiceTests, atest ChooserActivityTest,
      manual testing
Change-Id: I94f34a68e390569ec14dfebb1902a7c30558864f
This commit is contained in:
Mark Renouf
2022-03-02 10:12:42 -05:00
parent 501913df26
commit 05a2ef2647
11 changed files with 33 additions and 160 deletions

View File

@@ -5707,7 +5707,6 @@ public class Activity extends ContextThemeWrapper
* their launch had come from the original activity.
* @param intent The Intent to start.
* @param options ActivityOptions or null.
* @param permissionToken Token received from the system that permits this call to be made.
* @param ignoreTargetSecurity If true, the activity manager will not check whether the
* caller it is doing the start is, is actually allowed to start the target activity.
* If you set this to true, you must set an explicit component in the Intent and do any
@@ -5716,18 +5715,18 @@ public class Activity extends ContextThemeWrapper
* @hide
*/
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
startActivityAsCaller(intent, options, permissionToken, ignoreTargetSecurity, userId, -1);
boolean ignoreTargetSecurity, int userId) {
startActivityAsCaller(intent, options, ignoreTargetSecurity, userId, -1);
}
/**
* @see #startActivityAsCaller(Intent, Bundle, IBinder, boolean, int)
* @see #startActivityAsCaller(Intent, Bundle, boolean, int)
* @param requestCode The request code used for returning a result or -1 if no result should be
* returned.
* @hide
*/
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
IBinder permissionToken, boolean ignoreTargetSecurity, int userId, int requestCode) {
boolean ignoreTargetSecurity, int userId, int requestCode) {
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
@@ -5735,8 +5734,7 @@ public class Activity extends ContextThemeWrapper
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivityAsCaller(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options, permissionToken, ignoreTargetSecurity,
userId);
intent, requestCode, options, ignoreTargetSecurity, userId);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());

View File

@@ -105,14 +105,6 @@ public class ActivityTaskManager {
public static final int RESIZE_MODE_USER_FORCED =
RESIZE_MODE_PRESERVE_WINDOW | RESIZE_MODE_FORCED;
/**
* Extra included on intents that are delegating the call to
* ActivityManager#startActivityAsCaller to another app. This token is necessary for that call
* to succeed. Type is IBinder.
* @hide
*/
public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN";
/**
* Extra included on intents that contain an EXTRA_INTENT, with options that the contained
* intent may want to be started with. Type is Bundle.

View File

@@ -141,7 +141,7 @@ interface IActivityTaskManager {
int startActivityAsCaller(in IApplicationThread caller, in String callingPackage,
in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options,
IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
boolean ignoreTargetSecurity, int userId);
boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType,
int userId);
@@ -182,18 +182,6 @@ interface IActivityTaskManager {
int addAppTask(in IBinder activityToken, in Intent intent,
in ActivityManager.TaskDescription description, in Bitmap thumbnail);
Point getAppTaskThumbnailSize();
/**
* Only callable from the system. This token grants a temporary permission to call
* #startActivityAsCaller. The token will time out after START_AS_CALLER_TOKEN_TIMEOUT
* if it is not used.
*
* @param componentName The component name of the delegated component that is allowed to
* call #startActivityAsCaller with the returned token.
*
* @return Returns a token that can be given to a "delegate" app that may call
* #startActivityAsCaller
*/
IBinder requestStartActivityPermissionToken(in ComponentName componentName);
oneway void releaseSomeActivities(in IApplicationThread app);
Bitmap getTaskDescriptionIcon(in String filename, int userId);

View File

@@ -1986,7 +1986,7 @@ public class Instrumentation {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public ActivityResult execStartActivityAsCaller(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options, IBinder permissionToken,
Intent intent, int requestCode, Bundle options,
boolean ignoreTargetSecurity, int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
@@ -2021,7 +2021,7 @@ public class Instrumentation {
.startActivityAsCaller(whoThread, who.getOpPackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options, permissionToken,
requestCode, 0, null, options,
ignoreTargetSecurity, userId);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {

View File

@@ -51,7 +51,6 @@ public final class GameSessionTrampolineActivity extends Activity {
startActivityAsCaller(
getIntent().getParcelableExtra(INTENT_KEY),
getIntent().getBundleExtra(OPTIONS_KEY),
null,
false,
getUserId(),
REQUEST_CODE);

View File

@@ -28,7 +28,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.SharedElementCallback;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
@@ -71,11 +70,9 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Parcelable;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.os.UserManager;
@@ -762,25 +759,17 @@ public class ChooserActivity extends ResolverActivity implements
return false;
}
try {
Intent delegationIntent = new Intent();
final ComponentName delegateActivity = ComponentName.unflattenFromString(
Resources.getSystem().getString(R.string.config_chooserActivity));
IBinder permissionToken = ActivityTaskManager.getService()
.requestStartActivityPermissionToken(delegateActivity);
delegationIntent.setComponent(delegateActivity);
delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent());
delegationIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
delegationIntent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
Intent delegationIntent = new Intent();
final ComponentName delegateActivity = ComponentName.unflattenFromString(
Resources.getSystem().getString(R.string.config_chooserActivity));
delegationIntent.setComponent(delegateActivity);
delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent());
delegationIntent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
// Don't close until the delegate finishes, or the token will be invalidated.
mAwaitingDelegateResponse = true;
startActivityForResult(delegationIntent, REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER);
return true;
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
return false;
// Don't close until the delegate finishes, or the token will be invalidated.
mAwaitingDelegateResponse = true;
startActivityForResult(delegationIntent, REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER);
return true;
}
@Override

View File

@@ -207,7 +207,6 @@ public class IntentForwarderActivity extends Activity {
startActivityAsCaller(
newIntent,
/* options= */ null,
/* permissionToken= */ null,
/* ignoreTargetSecurity= */ false,
userId);
} catch (RuntimeException e) {
@@ -231,7 +230,7 @@ public class IntentForwarderActivity extends Activity {
return;
}
sanitizeIntent(innerIntent);
startActivityAsCaller(intentReceived, null, null, false, getUserId());
startActivityAsCaller(intentReceived, null, false, getUserId());
finish();
}
@@ -251,7 +250,7 @@ public class IntentForwarderActivity extends Activity {
sanitizeIntent(intentReceived);
intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile);
intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId));
startActivityAsCaller(intentReceived, null, null, false, userId);
startActivityAsCaller(intentReceived, null, false, userId);
finish();
}

View File

@@ -1463,7 +1463,7 @@ public class ResolverActivity extends Activity implements
int userId) {
// Note: this method will be overridden in the delegate implementation to use the passed-in
// permission token.
startActivityAsCaller(intent, options, null, false, userId);
startActivityAsCaller(intent, options, false, userId);
return true;
}

View File

@@ -44,7 +44,6 @@ import android.content.pm.UserInfo;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -649,7 +648,7 @@ public class IntentForwarderActivityTest {
@Override
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
boolean ignoreTargetSecurity, int userId) {
mStartActivityIntent = intent;
mUserIdActivityLaunchedIn = userId;
}

View File

@@ -21,14 +21,11 @@ import static com.android.server.wm.ActivityInterceptorCallback.INTENT_RESOLVER_
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.RemoteException;
import android.provider.DeviceConfig;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -39,10 +36,8 @@ import com.android.server.wm.ActivityTaskManagerInternal;
/**
* Service to register an {@code ActivityInterceptorCallback} that modifies any {@code Intent}
* that's being used to launch a user-space {@code ChooserActivity}, by adding
* EXTRA_PERMISSION_TOKEN, a Binder representing a single-use-only permission to invoke the
* #startActivityAsCaller() API (which normally isn't available in user-space); and setting the
* destination component to the delegated component when appropriate.
* that's being used to launch a user-space {@code ChooserActivity} by setting the destination
* component to the delegated component when appropriate.
*/
public final class IntentResolverInterceptor {
private static final String TAG = "IntentResolverIntercept";
@@ -92,7 +87,6 @@ public final class IntentResolverInterceptor {
private Intent modifyChooserIntent(Intent intent) {
intent.setComponent(getUnbundledChooserComponentName());
addStartActivityPermissionTokenToIntent(intent, getUnbundledChooserComponentName());
return intent;
}
@@ -103,18 +97,6 @@ public final class IntentResolverInterceptor {
|| targetComponent.equals(getUnbundledChooserComponentName());
}
private static Intent addStartActivityPermissionTokenToIntent(
Intent intent, ComponentName grantee) {
try {
intent.putExtra(
ActivityTaskManager.EXTRA_PERMISSION_TOKEN,
ActivityTaskManager.getService().requestStartActivityPermissionToken(grantee));
} catch (RemoteException e) {
Slog.w(TAG, "Failed to add permission token to chooser intent");
}
return intent;
}
private static ComponentName getSystemChooserComponentName() {
return new ComponentName("android", "com.android.internal.app.ChooserActivity");
}

View File

@@ -421,30 +421,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// How long to wait in getAutofillAssistStructure() for the activity to respond with the result.
private static final int PENDING_AUTOFILL_ASSIST_STRUCTURE_TIMEOUT = 2000;
// Permission tokens are used to temporarily granted a trusted app the ability to call
// #startActivityAsCaller. A client is expected to dump its token after this time has elapsed,
// showing any appropriate error messages to the user.
private static final long START_AS_CALLER_TOKEN_TIMEOUT =
10 * MINUTE_IN_MILLIS;
// How long before the service actually expires a token. This is slightly longer than
// START_AS_CALLER_TOKEN_TIMEOUT, to provide a buffer so clients will rarely encounter the
// expiration exception.
private static final long START_AS_CALLER_TOKEN_TIMEOUT_IMPL =
START_AS_CALLER_TOKEN_TIMEOUT + 2 * 1000;
// How long the service will remember expired tokens, for the purpose of providing error
// messaging when a client uses an expired token.
private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT =
START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * MINUTE_IN_MILLIS;
// The component name of the delegated activities that are allowed to call
// #startActivityAsCaller with the one-time used permission token.
final HashMap<IBinder, ComponentName> mStartActivitySources = new HashMap<>();
// Permission tokens that have expired, but we remember for error reporting.
final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>();
private final ArrayList<PendingAssistExtras> mPendingAssistExtras = new ArrayList<>();
// Keeps track of the active voice interaction service component, notified from
@@ -1546,42 +1522,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.execute();
}
@Override
public IBinder requestStartActivityPermissionToken(ComponentName componentName) {
int callingUid = Binder.getCallingUid();
if (UserHandle.getAppId(callingUid) != SYSTEM_UID) {
throw new SecurityException("Only the system process can request a permission token, "
+ "received request from uid: " + callingUid);
}
IBinder permissionToken = new Binder();
synchronized (mGlobalLock) {
mStartActivitySources.put(permissionToken, componentName);
}
Message expireMsg = PooledLambda.obtainMessage(
ActivityTaskManagerService::expireStartAsCallerTokenMsg, this, permissionToken);
mUiHandler.sendMessageDelayed(expireMsg, START_AS_CALLER_TOKEN_TIMEOUT_IMPL);
Message forgetMsg = PooledLambda.obtainMessage(
ActivityTaskManagerService::forgetStartAsCallerTokenMsg, this, permissionToken);
mUiHandler.sendMessageDelayed(forgetMsg, START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT);
return permissionToken;
}
@Override
public final int startActivityAsCaller(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, IBinder permissionToken,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions,
boolean ignoreTargetSecurity, int userId) {
// This is very dangerous -- it allows you to perform a start activity (including
// permission grants) as any app that may launch one of your own activities. So we only
// allow this in two cases:
// 1) The caller is an activity that is part of the core framework, and then only when it
// is running as the system.
// 2) The caller provides a valid permissionToken. Permission tokens are one-time use and
// can only be requested from system uid, which may then delegate this call to
// another app.
// 1) The calling process holds the signature permission START_ACTIVITY_AS_CALLER
//
// 2) The calling process is an activity belonging to the package "android" which is
// running as UID_SYSTEM or as the target UID (the activity which started the activity
// calling this method).
final ActivityRecord sourceRecord;
final int targetUid;
final String targetPackage;
@@ -1600,26 +1554,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
throw new SecurityException("Called without a process attached to activity");
}
final ComponentName componentName;
if (permissionToken != null) {
// To even attempt to use a permissionToken, an app must also have this signature
// permission.
mAmInternal.enforceCallingPermission(
android.Manifest.permission.START_ACTIVITY_AS_CALLER,
"startActivityAsCaller");
// If called with a permissionToken, the caller must be the same component that
// was allowed to use the permissionToken.
componentName = mStartActivitySources.remove(permissionToken);
if (!sourceRecord.mActivityComponent.equals(componentName)) {
if (mExpiredStartAsCallerTokens.contains(permissionToken)) {
throw new SecurityException("Called with expired permission token: "
+ permissionToken);
} else {
throw new SecurityException("Called with invalid permission token: "
+ permissionToken);
}
}
} else {
if (checkCallingPermission(Manifest.permission.START_ACTIVITY_AS_CALLER)
!= PERMISSION_GRANTED) {
// Whether called directly or from a delegate, the source activity must be from the
// android package.
if (!sourceRecord.info.packageName.equals("android")) {
@@ -4451,15 +4387,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
Settings.System.putConfigurationForUser(resolver, config, userId);
}
private void expireStartAsCallerTokenMsg(IBinder permissionToken) {
mStartActivitySources.remove(permissionToken);
mExpiredStartAsCallerTokens.add(permissionToken);
}
private void forgetStartAsCallerTokenMsg(IBinder permissionToken) {
mExpiredStartAsCallerTokens.remove(permissionToken);
}
boolean isActivityStartsLoggingEnabled() {
return mAmInternal.isActivityStartsLoggingEnabled();
}