Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80b57afa authored by Nan Wu's avatar Nan Wu
Browse files

Allow refresh creator token for edge case

Provides method to allow caller to keep its intent creator token's
creatorUid but update its key fields to match its current state.

Bug: 369856138
Test: Manual test.
Flag: android.security.prevent_intent_redirect
Change-Id: Icc829427b103d08764becf4b56ee6710865d305c
parent 8f7bc91f
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -1028,4 +1028,14 @@ interface IActivityManager {
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
    void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
            boolean enabled, int reason, in String subReason, int source, long threshold);

    /**
     * Creates and returns a new IntentCreatorToken that keeps the creatorUid and refreshes key
     * fields of the intent passed in.
     *
     * @param intent The intent with key fields out of sync of the IntentCreatorToken it contains.
     * @hide
     */
    @EnforcePermission("INTERACT_ACROSS_USERS_FULL")
    IBinder refreshIntentCreatorToken(in Intent intent);
}
+28 −0
Original line number Diff line number Diff line
@@ -945,6 +945,34 @@ public class ClipData implements Parcelable {
        return mParcelItemActivityInfos;
    }

    /**
     * Make a clone of ClipData that only contains URIs. This reduces the size of data transfer over
     * IPC and only retains important information for the purpose of verifying creator token of an
     * Intent.
     * @return a copy of ClipData with only URIs remained.
     * @hide
     */
    public ClipData cloneOnlyUriItems() {
        ArrayList<Item> items = null;
        final int N = mItems.size();
        for (int i = 0; i < N; i++) {
            Item item = mItems.get(i);
            if (item.getUri() != null) {
                if (items == null) {
                    items = new ArrayList<>(N);
                }
                items.add(new Item(item.getUri()));
            } else if (item.getIntent() != null) {
                if (items == null) {
                    items = new ArrayList<>(N);
                }
                items.add(new Item(item.getIntent().cloneForCreatorToken()));
            }
        }
        if (items == null || items.isEmpty()) return null;
        return new ClipData(new ClipDescription("", new String[0]), items);
    }

    /**
     * Create a new ClipData holding data of the type
     * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}.
+59 −1
Original line number Diff line number Diff line
@@ -7969,6 +7969,24 @@ public class Intent implements Parcelable, Cloneable {
        return new Intent(this, COPY_MODE_FILTER);
    }
    /**
     * Make a copy of all members important to identify an intent with its creator token.
     * @hide
     */
    public @NonNull Intent cloneForCreatorToken() {
        Intent clone = new Intent()
                .setAction(this.mAction)
                .setDataAndType(this.mData, this.mType)
                .setPackage(this.mPackage)
                .setComponent(this.mComponent)
                .setFlags(this.mFlags & IMMUTABLE_FLAGS);
        if (this.mClipData != null) {
            clone.setClipData(this.mClipData.cloneOnlyUriItems());
        }
        clone.mCreatorTokenInfo = this.mCreatorTokenInfo;
        return clone;
    }
    /**
     * Create an intent with a given action.  All other fields (data, type,
     * class) are null.  Note that the action <em>must</em> be in a
@@ -11684,7 +11702,7 @@ public class Intent implements Parcelable, Cloneable {
                Log.w(TAG, "Failure filling in extras", e);
            }
        }
        mCreatorTokenInfo = other.mCreatorTokenInfo;
        fillInCreatorTokenInfo(other.mCreatorTokenInfo, changes);
        if (mayHaveCopiedUris && mContentUserHint == UserHandle.USER_CURRENT
                && other.mContentUserHint != UserHandle.USER_CURRENT) {
            mContentUserHint = other.mContentUserHint;
@@ -11692,6 +11710,45 @@ public class Intent implements Parcelable, Cloneable {
        return changes;
    }
    // keep original creator token and merge nested intent keys.
    private void fillInCreatorTokenInfo(CreatorTokenInfo otherCreatorTokenInfo, int changes) {
        if (otherCreatorTokenInfo != null && otherCreatorTokenInfo.mNestedIntentKeys != null) {
            if (mCreatorTokenInfo == null) {
                mCreatorTokenInfo = new CreatorTokenInfo();
            }
            ArraySet<NestedIntentKey> otherNestedIntentKeys =
                    otherCreatorTokenInfo.mNestedIntentKeys;
            if (mCreatorTokenInfo.mNestedIntentKeys == null) {
                mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(otherNestedIntentKeys);
            } else {
                ArraySet<NestedIntentKey> otherKeys;
                if ((changes & FILL_IN_CLIP_DATA) == 0) {
                    // If clip data is Not filled in from other, do not merge clip data keys.
                    otherKeys = new ArraySet<>();
                    int N = otherNestedIntentKeys.size();
                    for (int i = 0; i < N; i++) {
                        NestedIntentKey key = otherNestedIntentKeys.valueAt(i);
                        if (key.mType != NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) {
                            otherKeys.add(key);
                        }
                    }
                } else {
                    // If clip data is filled in from other, remove clip data keys from this
                    // creatorTokenInfo and then merge every key from the others.
                    int N = mCreatorTokenInfo.mNestedIntentKeys.size();
                    for (int i = N - 1; i >= 0; i--) {
                        NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
                        if (key.mType == NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA) {
                            mCreatorTokenInfo.mNestedIntentKeys.removeAt(i);
                        }
                    }
                    otherKeys = otherNestedIntentKeys;
                }
                mCreatorTokenInfo.mNestedIntentKeys.addAll(otherKeys);
            }
        }
    }
    /**
     * Merge the extras data in this intent with that of other supplied intent using the
     * strategy specified using {@code extrasMerger}.
@@ -12228,6 +12285,7 @@ public class Intent implements Parcelable, Cloneable {
        private IBinder mCreatorToken;
        // Stores all extra keys whose values are intents for a top level intent.
        private ArraySet<NestedIntentKey> mNestedIntentKeys;
    }
    /**
+111 −2
Original line number Diff line number Diff line
@@ -19,8 +19,9 @@ package android.content;
import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
@@ -29,6 +30,7 @@ import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.security.Flags;
import android.util.ArraySet;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -40,6 +42,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 *  Build/Install/Run:
@@ -51,7 +54,6 @@ import java.util.List;
public class IntentTest {
    private static final String TEST_ACTION = "android.content.IntentTest_test";
    private static final String TEST_EXTRA_NAME = "testExtraName";
    private static final Uri TEST_URI = Uri.parse("content://com.example/people");

    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -129,4 +131,111 @@ public class IntentTest {
        }
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
    public void testFillInCreatorTokenInfo() {
        // case 1: intent does not have creatorTokenInfo; fillinIntent contains creatorTokenInfo
        Intent intent = new Intent();
        Intent fillInIntent = new Intent();
        fillInIntent.setCreatorToken(new Binder());
        fillInIntent.putExtra("extraKey", new Intent());

        fillInIntent.collectExtraIntentKeys();
        intent.fillIn(fillInIntent, 0);

        // extra intent keys are merged
        assertThat(intent.getExtraIntentKeys()).isEqualTo(fillInIntent.getExtraIntentKeys());
        // but creator token is not overwritten.
        assertThat(intent.getCreatorToken()).isNull();


        // case 2: Both intent and fillInIntent contains creatorToken, intent's creatorToken is not
        // overwritten.
        intent = new Intent();
        IBinder creatorToken = new Binder();
        intent.setCreatorToken(creatorToken);
        fillInIntent = new Intent();
        fillInIntent.setCreatorToken(new Binder());

        intent.fillIn(fillInIntent, 0);

        assertThat(intent.getCreatorToken()).isEqualTo(creatorToken);


        // case 3: Contains duplicate extra keys
        intent = new Intent();
        intent.putExtra("key1", new Intent());
        intent.putExtra("key2", new Intent());
        fillInIntent = new Intent();
        fillInIntent.putExtra("key1", new Intent());
        fillInIntent.putExtra("key3", new Intent());

        intent.collectExtraIntentKeys();
        Set originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());

        fillInIntent.collectExtraIntentKeys();
        intent.fillIn(fillInIntent, 0);

        assertThat(intent.getExtraIntentKeys()).hasSize(3);
        assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
        assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));


        // case 4: Both contains a mixture of extras and clip data. NOT force to fill in clip data.
        intent = new Intent();
        ClipData clipData = ClipData.newIntent("clip", new Intent());
        clipData.addItem(new ClipData.Item(new Intent()));
        intent.setClipData(clipData);
        intent.putExtra("key1", new Intent());
        intent.putExtra("key2", new Intent());
        fillInIntent = new Intent();
        ClipData fillInClipData = ClipData.newIntent("clip", new Intent());
        fillInClipData.addItem(new ClipData.Item(new Intent()));
        fillInClipData.addItem(new ClipData.Item(new Intent()));
        fillInIntent.setClipData(fillInClipData);
        fillInIntent.putExtra("key1", new Intent());
        fillInIntent.putExtra("key3", new Intent());

        intent.collectExtraIntentKeys();
        originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
        fillInIntent.collectExtraIntentKeys();
        intent.fillIn(fillInIntent, 0);

        // size is 5 ( 3 extras merged from both + 2 clip data in the original.
        assertThat(intent.getExtraIntentKeys()).hasSize(5);
        // all keys from original are kept.
        assertTrue(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
        // Not all keys from fillInIntent are kept - clip data keys are dropped.
        assertFalse(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));


        // case 5: Both contains a mixture of extras and clip data. Force to fill in clip data.
        intent = new Intent();
        clipData = ClipData.newIntent("clip", new Intent());
        clipData.addItem(new ClipData.Item(new Intent()));
        clipData.addItem(new ClipData.Item(new Intent()));
        clipData.addItem(new ClipData.Item(new Intent()));
        intent.setClipData(clipData);
        intent.putExtra("key1", new Intent());
        intent.putExtra("key2", new Intent());
        fillInIntent = new Intent();
        fillInClipData = ClipData.newIntent("clip", new Intent());
        fillInClipData.addItem(new ClipData.Item(new Intent()));
        fillInClipData.addItem(new ClipData.Item(new Intent()));
        fillInIntent.setClipData(fillInClipData);
        fillInIntent.putExtra("key1", new Intent());
        fillInIntent.putExtra("key3", new Intent());

        intent.collectExtraIntentKeys();
        originalIntentKeys = new ArraySet<>(intent.getExtraIntentKeys());
        fillInIntent.collectExtraIntentKeys();
        intent.fillIn(fillInIntent, Intent.FILL_IN_CLIP_DATA);

        // size is 6 ( 3 extras merged from both + 3 clip data in the fillInIntent.
        assertThat(intent.getExtraIntentKeys()).hasSize(6);
        // all keys from fillInIntent are kept.
        assertTrue(intent.getExtraIntentKeys().containsAll(fillInIntent.getExtraIntentKeys()));
        // Not all keys from intent are kept - clip data keys are dropped.
        assertFalse(intent.getExtraIntentKeys().containsAll(originalIntentKeys));
    }
}
+37 −4
Original line number Diff line number Diff line
@@ -192,6 +192,7 @@ import static com.android.systemui.shared.Flags.enableHomeDelay;
import android.Manifest;
import android.Manifest.permission;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.PermissionMethod;
@@ -19228,6 +19229,11 @@ public class ActivityManagerService extends IActivityManager.Stub
            return mKeyFields.mCreatorPackage;
        }
        @VisibleForTesting
        public @NonNull Key getKeyFields() {
            return mKeyFields;
        }
        public static boolean isValid(@NonNull Intent intent) {
            IBinder binder = intent.getCreatorToken();
            IntentCreatorToken token = null;
@@ -19271,9 +19277,13 @@ public class ActivityManagerService extends IActivityManager.Stub
                this.mFlags = intent.getFlags() & Intent.IMMUTABLE_FLAGS;
                ClipData clipData = intent.getClipData();
                if (clipData != null) {
                    this.mClipDataUris = new ArrayList<>(clipData.getItemCount());
                    for (int i = 0; i < clipData.getItemCount(); i++) {
                        this.mClipDataUris.add(clipData.getItemAt(i).getUri());
                    clipData = clipData.cloneOnlyUriItems();
                    if (clipData != null) {
                        List<Uri> clipDataUris = new ArrayList<>();
                        clipData.collectUris(clipDataUris);
                        if (!clipDataUris.isEmpty()) {
                            this.mClipDataUris = clipDataUris;
                        }
                    }
                }
            }
@@ -19375,11 +19385,34 @@ public class ActivityManagerService extends IActivityManager.Stub
            String creatorPackage) {
        if (IntentCreatorToken.isValid(intent)) return null;
        IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent);
        return createOrGetIntentCreatorToken(intent, key);
    }
    /**
     * @hide
     */
    @EnforcePermission("android.permission.INTERACT_ACROSS_USERS_FULL")
    public IBinder refreshIntentCreatorToken(Intent intent) {
        refreshIntentCreatorToken_enforcePermission();
        IBinder binder = intent.getCreatorToken();
        if (binder instanceof IntentCreatorToken) {
            IntentCreatorToken token = (IntentCreatorToken) binder;
            IntentCreatorToken.Key key = new IntentCreatorToken.Key(token.getCreatorUid(),
                    token.getCreatorPackage(), intent);
            return createOrGetIntentCreatorToken(intent, key);
        } else {
            throw new IllegalArgumentException("intent does not contain a creator token.");
        }
    }
    private static IntentCreatorToken createOrGetIntentCreatorToken(Intent intent,
            IntentCreatorToken.Key key) {
        IntentCreatorToken token;
        synchronized (sIntentCreatorTokenCache) {
            WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key);
            if (ref == null || ref.get() == null) {
                token = new IntentCreatorToken(creatorUid, creatorPackage, intent);
                token = new IntentCreatorToken(key.mCreatorUid, key.mCreatorPackage, intent);
                sIntentCreatorTokenCache.put(key, token.mRef);
            } else {
                token = ref.get();
Loading