Generate CreatorToken
Collect extra intent keys on the client side and generated CreatorToken
on the system server side
Bug: 363016571
Test: ActivityManagerService#testAddCreatorToken
Flag: android.security.prevent_intent_redirect
Change-Id: Ibc6d461e74d96b12a9af990d8653f66fcfa26c14
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index d318812..3bd121a 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -1323,4 +1323,12 @@
*/
public abstract void killApplicationSync(String pkgName, int appId, int userId,
String reason, int exitInfoReason);
+
+ /**
+ * Add a creator token for all embedded intents (stored as extra) of the given intent.
+ *
+ * @param intent The given intent
+ * @hide
+ */
+ public abstract void addCreatorToken(Intent intent);
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 3714e5d..393ec8c 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -1094,6 +1094,9 @@
@Nullable String requiredPermission, @Nullable Bundle options)
throws CanceledException {
try {
+ if (intent != null) {
+ intent.collectExtraIntentKeys();
+ }
String resolvedType = intent != null ?
intent.resolveTypeIfNeeded(context.getContentResolver())
: null;
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index b421339..ff0bb25 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -1149,7 +1149,7 @@
for (int i = 0; i < size; i++) {
final Item item = mItems.get(i);
if (item.mIntent != null) {
- item.mIntent.prepareToLeaveProcess(leavingPackage);
+ item.mIntent.prepareToLeaveProcess(leavingPackage, false);
}
if (item.mUri != null && leavingPackage) {
if (StrictMode.vmFileUriExposureEnabled()) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 044178c..c23bcab 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12227,6 +12227,8 @@
/**
* Collects keys in the extra bundle whose value are intents.
+ * With these keys collected on the client side, the system server would only unparcel values
+ * of these keys and create IntentCreatorToken for them.
* @hide
*/
public void collectExtraIntentKeys() {
@@ -12589,22 +12591,29 @@
*/
@android.ravenwood.annotation.RavenwoodThrow
public void prepareToLeaveProcess(boolean leavingPackage) {
+ prepareToLeaveProcess(leavingPackage, true);
+ }
+
+ /**
+ * @hide
+ */
+ void prepareToLeaveProcess(boolean leavingPackage, boolean isTopLevel) {
setAllowFds(false);
if (mSelector != null) {
- mSelector.prepareToLeaveProcess(leavingPackage);
+ mSelector.prepareToLeaveProcess(leavingPackage, false);
}
if (mClipData != null) {
mClipData.prepareToLeaveProcess(leavingPackage, getFlags());
}
if (mOriginalIntent != null) {
- mOriginalIntent.prepareToLeaveProcess(leavingPackage);
+ mOriginalIntent.prepareToLeaveProcess(leavingPackage, false);
}
if (mExtras != null && !mExtras.isParcelled()) {
final Object intent = mExtras.get(Intent.EXTRA_INTENT);
if (intent instanceof Intent) {
- ((Intent) intent).prepareToLeaveProcess(leavingPackage);
+ ((Intent) intent).prepareToLeaveProcess(leavingPackage, false);
}
}
@@ -12678,6 +12687,10 @@
StrictMode.onUnsafeIntentLaunch(this);
}
}
+
+ if (isTopLevel) {
+ collectExtraIntentKeys();
+ }
}
/**
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index ca6d86a..f406927 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -288,6 +288,9 @@
@Nullable Executor executor, @Nullable OnFinished onFinished)
throws SendIntentException {
try {
+ if (intent != null) {
+ intent.collectExtraIntentKeys();
+ }
String resolvedType = intent != null ?
intent.resolveTypeIfNeeded(context.getContentResolver())
: null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 35323d6..5ca72ab 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -130,6 +130,7 @@
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
+import static android.security.Flags.preventIntentRedirect;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static android.view.Display.INVALID_DISPLAY;
@@ -420,6 +421,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.AlarmManagerInternal;
import com.android.server.BootReceiver;
import com.android.server.DeviceIdleInternal;
@@ -2420,6 +2422,7 @@
mBroadcastController = new BroadcastController(mContext, this, mBroadcastQueue);
mComponentAliasResolver = new ComponentAliasResolver(this);
mApplicationSharedMemoryReadOnlyFd = null;
+ sCreatorTokenCacheCleaner = new Handler(mHandlerThread.getLooper());
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2526,6 +2529,7 @@
mPendingStartActivityUids = new PendingStartActivityUids();
mTraceErrorLogger = new TraceErrorLogger();
mComponentAliasResolver = new ComponentAliasResolver(this);
+ sCreatorTokenCacheCleaner = new Handler(mHandlerThread.getLooper());
try {
mApplicationSharedMemoryReadOnlyFd =
ApplicationSharedMemory.getInstance().getReadOnlyFileDescriptor();
@@ -5532,6 +5536,7 @@
public int sendIntentSender(IApplicationThread caller, IIntentSender target,
IBinder allowlistToken, int code, Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+ addCreatorToken(intent);
if (target instanceof PendingIntentRecord) {
final PendingIntentRecord originalRecord = (PendingIntentRecord) target;
@@ -13610,6 +13615,7 @@
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
+ addCreatorToken(service);
if (service != null) {
// Refuse possible leaked file descriptors
if (service.hasFileDescriptors()) {
@@ -13871,6 +13877,7 @@
validateServiceInstanceName(instanceName);
+ addCreatorToken(service);
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
final ComponentName cn = service.getComponent();
@@ -17148,6 +17155,7 @@
Slog.v(TAG_SERVICE,
"startServiceInPackage: " + service + " type=" + resolvedType);
}
+ addCreatorToken(service);
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
@@ -17973,6 +17981,11 @@
userId, reason, exitInfoReason);
}
}
+
+ @Override
+ public void addCreatorToken(Intent intent) {
+ ActivityManagerService.this.addCreatorToken(intent);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -19105,6 +19118,7 @@
private static final Map<IntentCreatorToken.Key, WeakReference<IntentCreatorToken>>
sIntentCreatorTokenCache = new ConcurrentHashMap<>();
+ private static Handler sCreatorTokenCacheCleaner;
/**
* A binder token used to keep track of which app created the intent. This token can be used to
* defend against intent redirect attacks. It stores uid of the intent creator and key fields of
@@ -19112,13 +19126,16 @@
*
* @hide
*/
+ @VisibleForTesting
public static final class IntentCreatorToken extends Binder {
@NonNull
private final Key mKeyFields;
+ private final WeakReference<IntentCreatorToken> mRef;
public IntentCreatorToken(int creatorUid, Intent intent) {
super();
this.mKeyFields = new Key(creatorUid, intent);
+ mRef = new WeakReference<>(this);
}
public int getCreatorUid() {
@@ -19136,6 +19153,26 @@
new Key(token.mKeyFields.mCreatorUid, intent));
}
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ sCreatorTokenCacheCleaner.sendMessage(PooledLambda.obtainMessage(
+ IntentCreatorToken::completeFinalize, this));
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void completeFinalize() {
+ synchronized (sIntentCreatorTokenCache) {
+ WeakReference<IntentCreatorToken> current = sIntentCreatorTokenCache.get(
+ mKeyFields);
+ if (current == mRef) {
+ sIntentCreatorTokenCache.remove(mKeyFields);
+ }
+ }
+ }
+
private static class Key {
private Key(int creatorUid, Intent intent) {
this.mCreatorUid = creatorUid;
@@ -19178,9 +19215,57 @@
@Override
public int hashCode() {
return Objects.hash(mCreatorUid, mAction, mData, mType, mPackage, mComponent,
- mFlags,
- mClipDataUris);
+ mFlags, mClipDataUris);
}
}
}
+
+ /**
+ * Add a creator token for all embedded intents (stored as extra) of the given intent.
+ *
+ * @param intent The given intent
+ * @hide
+ */
+ public void addCreatorToken(@Nullable Intent intent) {
+ if (!preventIntentRedirect()) return;
+
+ if (intent == null || intent.getExtraIntentKeys() == null) return;
+ for (String key : intent.getExtraIntentKeys()) {
+ try {
+ Intent extraIntent = intent.getParcelableExtra(key, Intent.class);
+ if (extraIntent == null) {
+ Slog.w(TAG, "The key {" + key
+ + "} does not correspond to an intent in the extra bundle.");
+ continue;
+ }
+ Slog.wtf(TAG, "A creator token is added to an intent.");
+ IBinder creatorToken = createIntentCreatorToken(extraIntent);
+ if (creatorToken != null) {
+ extraIntent.setCreatorToken(creatorToken);
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG,
+ "Something went wrong when trying to add creator token for embedded "
+ + "intents of intent: ."
+ + intent, e);
+ }
+ }
+ }
+
+ private IBinder createIntentCreatorToken(Intent intent) {
+ if (IntentCreatorToken.isValid(intent)) return null;
+ int creatorUid = getCallingUid();
+ IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, intent);
+ IntentCreatorToken token;
+ synchronized (sIntentCreatorTokenCache) {
+ WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key);
+ if (ref == null || ref.get() == null) {
+ token = new IntentCreatorToken(creatorUid, intent);
+ sIntentCreatorTokenCache.put(key, token.mRef);
+ } else {
+ token = ref.get();
+ }
+ }
+ return token;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index c1e859d..a6b650f 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -442,6 +442,8 @@
throw new IllegalArgumentException("File descriptors passed in Intent");
}
+ mService.mAmInternal.addCreatorToken(resultData);
+
final ActivityRecord r;
synchronized (mGlobalLock) {
r = ActivityRecord.isInRootTaskLocked(token);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index de95d1b..93f428b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1228,6 +1228,7 @@
String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
Bundle bOptions) {
+ mAmInternal.addCreatorToken(intent);
return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
@@ -1240,6 +1241,11 @@
assertPackageMatchesCallingUid(callingPackage);
final String reason = "startActivities";
enforceNotIsolatedCaller(reason);
+ if (intents != null) {
+ for (Intent intent : intents) {
+ mAmInternal.addCreatorToken(intent);
+ }
+ }
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, reason);
// TODO: Switch to user app stacks here.
return getActivityStartController().startActivities(caller, -1, 0, -1, callingPackage,
@@ -1269,6 +1275,7 @@
@Nullable String callingFeatureId, Intent intent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
+ mAmInternal.addCreatorToken(intent);
final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
assertPackageMatchesCallingUid(callingPackage);
@@ -1323,6 +1330,7 @@
}
// Remove existing mismatch flag so it can be properly updated later
fillInIntent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+ mAmInternal.addCreatorToken(fillInIntent);
}
if (!(target instanceof PendingIntentRecord)) {
@@ -1352,6 +1360,9 @@
if (intent != null && intent.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
+
+ mAmInternal.addCreatorToken(intent);
+
SafeActivityOptions options = SafeActivityOptions.fromBundle(bOptions);
synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index d699af8..4d17ed2 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -160,6 +160,7 @@
Intent intent, String resolvedType, Bundle bOptions) {
checkCallerOrSystemOrRoot();
mService.assertPackageMatchesCallingUid(callingPackage);
+ mService.mAmInternal.addCreatorToken(intent);
int callingUser = UserHandle.getCallingUserId();
Task task;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 809e13c..6ccc037 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -29,6 +29,7 @@
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.content.Intent.FILL_IN_ACTION;
import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.os.UserHandle.USER_ALL;
import static android.util.DebugUtils.valueToString;
@@ -1300,6 +1301,45 @@
.containsExactly(new Pair<>(USER_ID, 42));
}
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testAddCreatorToken() {
+ Intent intent = new Intent();
+ Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
+ intent.putExtra("EXTRA_INTENT0", extraIntent);
+
+ intent.collectExtraIntentKeys();
+ mAms.addCreatorToken(intent);
+
+ ActivityManagerService.IntentCreatorToken token =
+ (ActivityManagerService.IntentCreatorToken) extraIntent.getCreatorToken();
+ assertThat(token).isNotNull();
+ assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT)
+ public void testAddCreatorTokenForFillingIntent() {
+ Intent intent = new Intent();
+ Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
+ intent.putExtra("EXTRA_INTENT0", extraIntent);
+ Intent fillinIntent = new Intent();
+ Intent fillinExtraIntent = new Intent("FILLIN_EXTRA_INTENT_ACTION");
+ fillinIntent.putExtra("FILLIN_EXTRA_INTENT0", fillinExtraIntent);
+
+ fillinIntent.collectExtraIntentKeys();
+ intent.fillIn(fillinIntent, FILL_IN_ACTION);
+
+ mAms.addCreatorToken(fillinIntent);
+
+ fillinExtraIntent = intent.getParcelableExtra("FILLIN_EXTRA_INTENT0", Intent.class);
+
+ ActivityManagerService.IntentCreatorToken token =
+ (ActivityManagerService.IntentCreatorToken) fillinExtraIntent.getCreatorToken();
+ assertThat(token).isNotNull();
+ assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+ }
+
private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
long lastNetworkUpdatedProcStateSeq,
final long procStateSeqToWait, boolean expectWait) throws Exception {