Security model for moving sharesheet to systemui

ResolverActivity (still in frameworks) now requests a "permission token"
that it hands to a stubbed system ui activity ChooserActivity.

This permission token allows an app (SysUI) with the signed permission
"START_ACTIVITY_AS_CALLER" to call
ActivityManagerService#startActivityAsCaller. Permission tokens are a
one-time use, limited-time offer.

Test: runtest systemui && manual testing
Bug: 69850752
Change-Id: I3600e1a8ff9eea7397f5f59853423c79b6401f98
This commit is contained in:
Geoffrey Pitsch
2017-11-22 13:31:11 -05:00
parent e437a074fa
commit 88f3d4dbe1
17 changed files with 376 additions and 31 deletions

View File

@@ -17,6 +17,7 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static java.lang.Character.MIN_VALUE;
import android.annotation.CallSuper;
@@ -4671,6 +4672,7 @@ 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
@@ -4679,7 +4681,7 @@ public class Activity extends ContextThemeWrapper
* @hide
*/
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
boolean ignoreTargetSecurity, int userId) {
IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
if (mParent != null) {
throw new RuntimeException("Can't be called from a child");
}
@@ -4687,7 +4689,7 @@ public class Activity extends ContextThemeWrapper
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivityAsCaller(
this, mMainThread.getApplicationThread(), mToken, this,
intent, -1, options, ignoreTargetSecurity, userId);
intent, -1, options, permissionToken, ignoreTargetSecurity, userId);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, -1, ar.getResultCode(),

View File

@@ -443,6 +443,31 @@ public class ActivityManager {
*/
public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5;
/**
* 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.
* TODO: remove once the ChooserActivity moves to systemui
* @hide
*/
public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS";
/**
* Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the
* parameter of the same name when starting the contained intent.
* TODO: remove once the ChooserActivity moves to systemui
* @hide
*/
public static final String EXTRA_IGNORE_TARGET_SECURITY =
"android.app.extra.EXTRA_IGNORE_TARGET_SECURITY";
/** @hide User operation call: success! */
public static final int USER_OP_SUCCESS = 0;

View File

@@ -438,10 +438,11 @@ interface IActivityManager {
boolean isTopOfTask(in IBinder token);
void notifyLaunchTaskBehindComplete(in IBinder token);
void notifyEnterAnimationComplete(in IBinder token);
IBinder requestStartActivityPermissionToken(in IBinder delegatorToken);
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,
boolean ignoreTargetSecurity, int userId);
in IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
int addAppTask(in IBinder activityToken, in Intent intent,
in ActivityManager.TaskDescription description, in Bitmap thumbnail);
Point getAppTaskThumbnailSize();

View File

@@ -1872,8 +1872,8 @@ public class Instrumentation {
*/
public ActivityResult execStartActivityAsCaller(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity,
int userId) {
Intent intent, int requestCode, Bundle options, IBinder permissionToken,
boolean ignoreTargetSecurity, int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
@@ -1904,7 +1904,8 @@ public class Instrumentation {
.startActivityAsCaller(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options, ignoreTargetSecurity, userId);
requestCode, 0, null, options, permissionToken,
ignoreTargetSecurity, userId);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);

View File

@@ -841,7 +841,7 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
public boolean startAsCaller(Activity activity, Bundle options, int userId) {
public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
final Intent intent = getBaseIntentToSend();
if (intent == null) {
return false;
@@ -860,8 +860,7 @@ public class ChooserActivity extends ResolverActivity {
final boolean ignoreTargetSecurity = mSourceInfo != null
&& mSourceInfo.getResolvedComponentName().getPackageName()
.equals(mChooserTarget.getComponentName().getPackageName());
activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId);
return true;
return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
}
@Override

View File

@@ -107,7 +107,7 @@ public class IntentForwarderActivity extends Activity {
|| ChooserActivity.class.getName().equals(ri.activityInfo.name));
try {
startActivityAsCaller(newIntent, null, false, targetUserId);
startActivityAsCaller(newIntent, null, null, false, targetUserId);
} catch (RuntimeException e) {
int launchedFromUid = -1;
String launchedFromPackage = "?";

View File

@@ -43,6 +43,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.StrictMode;
@@ -857,6 +858,36 @@ public class ResolverActivity extends Activity {
}
}
public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
int userId) {
// Pass intent to delegate chooser activity with permission token.
// TODO: This should move to a trampoline Activity in the system when the ChooserActivity
// moves into systemui
try {
// TODO: Once this is a small springboard activity, it can move off the UI process
// and we can move the request method to ActivityManagerInternal.
IBinder permissionToken = ActivityManager.getService()
.requestStartActivityPermissionToken(getActivityToken());
final Intent chooserIntent = new Intent();
final ComponentName delegateActivity = ComponentName.unflattenFromString(
Resources.getSystem().getString(R.string.config_chooserActivity));
chooserIntent.setClassName(delegateActivity.getPackageName(),
delegateActivity.getClassName());
chooserIntent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, permissionToken);
// TODO: These extras will change as chooser activity moves into systemui
chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
chooserIntent.putExtra(ActivityManager.EXTRA_OPTIONS, options);
chooserIntent.putExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY,
ignoreTargetSecurity);
chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId);
startActivity(chooserIntent);
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
return true;
}
public void onActivityStarted(TargetInfo cti) {
// Do nothing
}
@@ -1181,9 +1212,8 @@ public class ResolverActivity extends Activity {
}
@Override
public boolean startAsCaller(Activity activity, Bundle options, int userId) {
activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
return true;
public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
}
@Override
@@ -1242,7 +1272,7 @@ public class ResolverActivity extends Activity {
* @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
* @return true if the start completed successfully
*/
boolean startAsCaller(Activity activity, Bundle options, int userId);
boolean startAsCaller(ResolverActivity activity, Bundle options, int userId);
/**
* Start the activity referenced by this target as a given user.

View File

@@ -1929,6 +1929,12 @@
<permission android:name="android.permission.START_ANY_ACTIVITY"
android:protectionLevel="signature" />
<!-- Allows an application to start an activity as another app, provided that app has been
granted a permissionToken from the ActivityManagerService.
@hide -->
<permission android:name="android.permission.START_ACTIVITY_AS_CALLER"
android:protectionLevel="signature" />
<!-- @deprecated The {@link android.app.ActivityManager#restartPackage}
API is no longer supported. -->
<permission android:name="android.permission.RESTART_PACKAGES"

View File

@@ -2295,7 +2295,10 @@
Can be customized for other product types -->
<string name="config_chooseTypeAndAccountActivity" translatable="false"
>android/android.accounts.ChooseTypeAndAccountActivity</string>
<!-- Name of the activity that will handle requests to the system to choose an activity for
the purposes of resolving an intent. -->
<string name="config_chooserActivity" translatable="false"
>com.android.systemui/com.android.systemui.chooser.ChooserActivity</string>
<!-- Component name of a custom ResolverActivity (Intent resolver) to be used instead of
the default framework version. If left empty, then the framework version will be used.
Example: com.google.android.myapp/.resolver.MyResolverActivity -->

View File

@@ -1071,6 +1071,7 @@
<java-symbol type="string" name="owner_name" />
<java-symbol type="string" name="config_chooseAccountActivity" />
<java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
<java-symbol type="string" name="config_chooserActivity" />
<java-symbol type="string" name="config_customResolverActivity" />
<java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" />
<java-symbol type="string" name="error_message_title" />

View File

@@ -24,6 +24,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
import android.os.UserManager;
import android.support.test.InstrumentationRegistry;
@@ -269,8 +270,8 @@ public class IntentForwarderActivityTest {
}
@Override
public void startActivityAsCaller(Intent intent, @Nullable Bundle options, boolean
ignoreTargetSecurity, int userId) {
public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
mStartActivityIntent = intent;
mUserIdActivityLaunchedIn = userId;
}
@@ -293,4 +294,4 @@ public class IntentForwarderActivityTest {
return mPm;
}
}
}
}

View File

@@ -368,6 +368,7 @@ applications that come with the platform
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
<permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
<permission name="android.permission.START_TASKS_FROM_RECENTS"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.STOP_APP_SWITCHES"/>

View File

@@ -89,6 +89,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" />
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<uses-permission android:name="android.permission.START_ACTIVITY_AS_CALLER" />
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
@@ -555,6 +556,22 @@
</intent-filter>
</activity>
<activity android:name=".chooser.ChooserActivity"
android:theme="@*android:style/Theme.NoDisplay"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:documentLaunchMode="never"
android:relinquishTaskIdentity="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:process=":ui"
android:visibleToInstantApps="true">
<intent-filter>
<action android:name="android.intent.action.CHOOSER_UI" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.VOICE" />
</intent-filter>
</activity>
<!-- Doze with notifications, run in main sysui process for every user -->
<service
android:name=".doze.DozeService"

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2017 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.systemui.chooser;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import com.android.systemui.R;
import java.lang.Thread;
import java.util.ArrayList;
public final class ChooserActivity extends Activity {
private static final String TAG = "ChooserActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ChooserHelper.onChoose(this);
finish();
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2017 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.systemui.chooser;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import com.android.systemui.R;
public class ChooserHelper {
private static final String TAG = "ChooserHelper";
static void onChoose(Activity activity) {
final Intent thisIntent = activity.getIntent();
final Bundle thisExtras = thisIntent.getExtras();
final Intent chosenIntent = thisIntent.getParcelableExtra(Intent.EXTRA_INTENT);
final Bundle options = thisIntent.getParcelableExtra(ActivityManager.EXTRA_OPTIONS);
final IBinder permissionToken =
thisExtras.getBinder(ActivityManager.EXTRA_PERMISSION_TOKEN);
final boolean ignoreTargetSecurity =
thisIntent.getBooleanExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY, false);
final int userId = thisIntent.getIntExtra(Intent.EXTRA_USER_ID, -1);
activity.startActivityAsCaller(
chosenIntent, options, permissionToken, ignoreTargetSecurity, userId);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2016 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.systemui.chooser;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Intent;
import android.os.Binder;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import com.android.systemui.chooser.ChooserHelper;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ChooserHelperTest extends SysuiTestCase {
@Test
public void testOnChoose_CallsStartActivityAsCallerWithToken() {
final Intent intent = new Intent();
final Binder token = new Binder();
intent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, token);
final Activity mockActivity = mock(Activity.class);
when(mockActivity.getIntent()).thenReturn(intent);
ChooserHelper.onChoose(mockActivity);
verify(mockActivity, times(1)).startActivityAsCaller(
any(), any(), eq(token), anyBoolean(), anyInt());
}
}

View File

@@ -26,6 +26,7 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REMOVE_TASKS;
import static android.Manifest.permission.START_ACTIVITY_AS_CALLER;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
@@ -554,6 +555,23 @@ public class ActivityManagerService extends IActivityManager.Stub
// could take much longer than usual.
static final int PROC_START_TIMEOUT_WITH_WRAPPER = 1200*1000;
// 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 * DateUtils.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 * DateUtils.MINUTE_IN_MILLIS;
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;
@@ -662,6 +680,13 @@ public class ActivityManagerService extends IActivityManager.Stub
final ArrayList<ActiveInstrumentation> mActiveInstrumentation = new ArrayList<>();
// Activity tokens of system activities that are delegating their call to
// #startActivityByCaller, keyed by the permissionToken granted to the delegate.
final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>();
// Permission tokens that have expired, but we remember for error reporting.
final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>();
public final IntentFirewall mIntentFirewall;
// Whether we should show our dialogs (ANR, crash, etc) or just perform their
@@ -1781,6 +1806,8 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
static final int EXPIRE_START_AS_CALLER_TOKEN_MSG = 75;
static final int FORGET_START_AS_CALLER_TOKEN_MSG = 76;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -2445,6 +2472,19 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
} break;
case EXPIRE_START_AS_CALLER_TOKEN_MSG: {
synchronized (ActivityManagerService.this) {
final IBinder permissionToken = (IBinder)msg.obj;
mStartActivitySources.remove(permissionToken);
mExpiredStartAsCallerTokens.add(permissionToken);
}
} break;
case FORGET_START_AS_CALLER_TOKEN_MSG: {
synchronized (ActivityManagerService.this) {
final IBinder permissionToken = (IBinder)msg.obj;
mExpiredStartAsCallerTokens.remove(permissionToken);
}
} break;
}
}
};
@@ -4712,16 +4752,54 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
* Only callable from the system. This token grants a temporary permission to call
* #startActivityAsCallerWithToken. The token will time out after
* START_AS_CALLER_TOKEN_TIMEOUT if it is not used.
*
* @param delegatorToken The Binder token referencing the system Activity that wants to delegate
* the #startActivityAsCaller to another app. The "caller" will be the caller of this
* activity's token, not the delegate's caller (which is probably the delegator itself).
*
* @return Returns a token that can be given to a "delegate" app that may call
* #startActivityAsCaller
*/
@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, boolean ignoreTargetSecurity,
int userId) {
public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
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 (this) {
mStartActivitySources.put(permissionToken, delegatorToken);
}
Message expireMsg = mHandler.obtainMessage(EXPIRE_START_AS_CALLER_TOKEN_MSG,
permissionToken);
mHandler.sendMessageDelayed(expireMsg, START_AS_CALLER_TOKEN_TIMEOUT_IMPL);
Message forgetMsg = mHandler.obtainMessage(FORGET_START_AS_CALLER_TOKEN_MSG,
permissionToken);
mHandler.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, 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 will only allow this to be done from activities that are part of the core framework,
// and then only when they are running as the system.
// 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 by a system activity, which may then delegate this call to
// another app.
final ActivityRecord sourceRecord;
final int targetUid;
final String targetPackage;
@@ -4729,17 +4807,47 @@ public class ActivityManagerService extends IActivityManager.Stub
if (resultTo == null) {
throw new SecurityException("Must be called from an activity");
}
sourceRecord = mStackSupervisor.isInAnyStackLocked(resultTo);
if (sourceRecord == null) {
throw new SecurityException("Called with bad activity token: " + resultTo);
final IBinder sourceToken;
if (permissionToken != null) {
// To even attempt to use a permissionToken, an app must also have this signature
// permission.
enforceCallingPermission(android.Manifest.permission.START_ACTIVITY_AS_CALLER,
"startActivityAsCaller");
// If called with a permissionToken, we want the sourceRecord from the delegator
// activity that requested this token.
sourceToken =
mStartActivitySources.remove(permissionToken);
if (sourceToken == null) {
// Invalid permissionToken, check if it recently expired.
if (mExpiredStartAsCallerTokens.contains(permissionToken)) {
throw new SecurityException("Called with expired permission token: "
+ permissionToken);
} else {
throw new SecurityException("Called with invalid permission token: "
+ permissionToken);
}
}
} else {
// This method was called directly by the source.
sourceToken = resultTo;
}
if (!sourceRecord.info.packageName.equals("android")) {
throw new SecurityException(
"Must be called from an activity that is declared in the android package");
sourceRecord = mStackSupervisor.isInAnyStackLocked(sourceToken);
if (sourceRecord == null) {
throw new SecurityException("Called with bad activity token: " + sourceToken);
}
if (sourceRecord.app == null) {
throw new SecurityException("Called without a process attached to activity");
}
// Whether called directly or from a delegate, the source activity must be from the
// android package.
if (!sourceRecord.info.packageName.equals("android")) {
throw new SecurityException("Must be called from an activity that is " +
"declared in the android package");
}
if (UserHandle.getAppId(sourceRecord.app.uid) != SYSTEM_UID) {
// This is still okay, as long as this activity is running under the
// uid of the original calling activity.
@@ -4750,6 +4858,7 @@ public class ActivityManagerService extends IActivityManager.Stub
+ sourceRecord.launchedFromUid);
}
}
if (ignoreTargetSecurity) {
if (intent.getComponent() == null) {
throw new SecurityException(