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

Commit 892351db authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add CreatorToken and CreatorTokenInfo" into main

parents 9ee3054e f17fccbe
Loading
Loading
Loading
Loading
+89 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_AC
import static android.content.ContentProvider.maybeAddUserId;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.security.Flags.FLAG_FRP_ENFORCEMENT;
import static android.security.Flags.preventIntentRedirect;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -7687,9 +7688,17 @@ public class Intent implements Parcelable, Cloneable {
    /** @hide */
    public static final int LOCAL_FLAG_FROM_SYSTEM = 1 << 5;
    /**
     * This flag indicates the creator token of this intent has been verified.
     *
     * @hide
     */
    public static final int LOCAL_FLAG_CREATOR_TOKEN_VERIFIED = 1 << 6;
    /** @hide */
    @IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = {
            EXTENDED_FLAG_FILTER_MISMATCH,
            EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ExtendedFlags {}
@@ -7703,6 +7712,13 @@ public class Intent implements Parcelable, Cloneable {
    @TestApi
    public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1 << 0;
    /**
     * This flag indicates the creator token of this intent is either missing or invalid.
     *
     * @hide
     */
    public static final int EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN = 1 << 1;
    // ---------------------------------------------------------------------
    // ---------------------------------------------------------------------
    // toUri() and parseUri() options.
@@ -7870,6 +7886,7 @@ public class Intent implements Parcelable, Cloneable {
        this.mPackage = o.mPackage;
        this.mComponent = o.mComponent;
        this.mOriginalIntent = o.mOriginalIntent;
        this.mCreatorTokenInfo = o.mCreatorTokenInfo;
        if (o.mCategories != null) {
            this.mCategories = new ArraySet<>(o.mCategories);
@@ -12176,6 +12193,60 @@ public class Intent implements Parcelable, Cloneable {
        return (mExtras != null) ? mExtras.describeContents() : 0;
    }
    private static class CreatorTokenInfo {
        // Stores a creator token for an intent embedded as an extra intent in a top level intent,
        private IBinder mCreatorToken;
        // Stores all extra keys whose values are intents for a top level intent.
        private ArraySet<String> mExtraIntentKeys;
    }
    private @Nullable CreatorTokenInfo mCreatorTokenInfo;
    /** @hide */
    public void removeCreatorTokenInfo() {
        mCreatorTokenInfo = null;
    }
    /** @hide */
    public @Nullable IBinder getCreatorToken() {
        return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mCreatorToken;
    }
    /** @hide */
    public Set<String> getExtraIntentKeys() {
        return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys;
    }
    /** @hide */
    public void setCreatorToken(@NonNull IBinder creatorToken) {
        if (mCreatorTokenInfo == null) {
            mCreatorTokenInfo = new CreatorTokenInfo();
        }
        mCreatorTokenInfo.mCreatorToken = creatorToken;
    }
    /**
     * Collects keys in the extra bundle whose value are intents.
     * @hide
     */
    public void collectExtraIntentKeys() {
        if (!preventIntentRedirect()) return;
        if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
            for (String key : mExtras.keySet()) {
                if (mExtras.get(key) instanceof Intent) {
                    if (mCreatorTokenInfo == null) {
                        mCreatorTokenInfo = new CreatorTokenInfo();
                    }
                    if (mCreatorTokenInfo.mExtraIntentKeys == null) {
                        mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>();
                    }
                    mCreatorTokenInfo.mExtraIntentKeys.add(key);
                }
            }
        }
    }
    public void writeToParcel(Parcel out, int flags) {
        out.writeString8(mAction);
        Uri.writeToParcel(out, mData);
@@ -12225,6 +12296,16 @@ public class Intent implements Parcelable, Cloneable {
        } else {
            out.writeInt(0);
        }
        if (preventIntentRedirect()) {
            if (mCreatorTokenInfo == null) {
                out.writeInt(0);
            } else {
                out.writeInt(1);
                out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken);
                out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys);
            }
        }
    }
    public static final @android.annotation.NonNull Parcelable.Creator<Intent> CREATOR
@@ -12282,6 +12363,14 @@ public class Intent implements Parcelable, Cloneable {
        if (in.readInt() != 0) {
            mOriginalIntent = new Intent(in);
        }
        if (preventIntentRedirect()) {
            if (in.readInt() != 0) {
                mCreatorTokenInfo = new CreatorTokenInfo();
                mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
                mCreatorTokenInfo.mExtraIntentKeys = (ArraySet<String>) in.readArraySet(null);
            }
        }
    }
    /**
+8 −0
Original line number Diff line number Diff line
@@ -52,3 +52,11 @@ flag {
    description: "Opt the system into enforcement of BAL"
    bug: "339403750"
}

flag {
    name: "prevent_intent_redirect"
    namespace: "responsible_apis"
    description: "Prevent intent redirect attacks"
    bug: "361143368"
    is_fixed_read_only: true
}
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
@@ -104,6 +104,7 @@ android_test {
        "mockito-target-extended-minus-junit4",
        "TestParameterInjector",
        "android.content.res.flags-aconfig-java",
        "android.security.flags-aconfig-java",
    ],

    libs: [
+99 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.content;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;

import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
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 androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 *  Build/Install/Run:
 *   atest FrameworksCoreTests:IntentTest
 */
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
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();

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
    public void testReadFromParcelWithExtraIntentKeys() {
        Intent intent = new Intent("TEST_ACTION");
        intent.putExtra(TEST_EXTRA_NAME, new Intent(TEST_ACTION));
        intent.putExtra(TEST_EXTRA_NAME + "2", 1);

        intent.collectExtraIntentKeys();
        final Parcel parcel = Parcel.obtain();
        intent.writeToParcel(parcel, 0);
        parcel.setDataPosition(0);
        final Intent target = new Intent();
        target.readFromParcel(parcel);

        assertEquals(intent.getAction(), target.getAction());
        assertEquals(intent.getExtraIntentKeys(), target.getExtraIntentKeys());
        assertThat(intent.getExtraIntentKeys()).hasSize(1);
    }

    @Test
    public void testCreatorTokenInfo() {
        Intent intent = new Intent(TEST_ACTION);
        IBinder creatorToken = new Binder();

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

        intent.removeCreatorTokenInfo();
        assertThat(intent.getCreatorToken()).isNull();
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
    public void testCollectExtraIntentKeys() {
        Intent intent = new Intent(TEST_ACTION);
        Intent extraIntent = new Intent(TEST_ACTION, TEST_URI);
        intent.putExtra(TEST_EXTRA_NAME, extraIntent);

        intent.collectExtraIntentKeys();

        assertThat(intent.getExtraIntentKeys()).hasSize(1);
        assertThat(intent.getExtraIntentKeys()).contains(TEST_EXTRA_NAME);
    }

}
+87 −1
Original line number Diff line number Diff line
@@ -262,6 +262,7 @@ import android.appwidget.AppWidgetManagerInternal;
import android.content.AttributionSource;
import android.content.AutofillOptions;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
@@ -418,7 +419,6 @@ import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.MemInfoReader;
import com.android.internal.util.Preconditions;
import com.android.server.crashrecovery.CrashRecoveryHelper;
import com.android.server.AlarmManagerInternal;
import com.android.server.BootReceiver;
import com.android.server.DeviceIdleInternal;
@@ -438,6 +438,7 @@ import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.appop.AppOpsService;
import com.android.server.compat.PlatformCompat;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.crashrecovery.CrashRecoveryHelper;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.firewall.IntentFirewall;
import com.android.server.graphics.fonts.FontManagerInternal;
@@ -482,6 +483,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@@ -499,6 +501,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -19085,4 +19088,87 @@ public class ActivityManagerService extends IActivityManager.Stub
    Freezer getFreezer() {
        return mFreezer;
    }
    // Set of IntentCreatorToken objects that are currently active.
    private static final Map<IntentCreatorToken.Key, WeakReference<IntentCreatorToken>>
            sIntentCreatorTokenCache = new ConcurrentHashMap<>();
    /**
     * 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
     * the intent to make it impossible for attacker to fake uid with a malicious intent.
     *
     * @hide
     */
    public static final class IntentCreatorToken extends Binder {
        @NonNull
        private final Key mKeyFields;
        public IntentCreatorToken(int creatorUid, Intent intent) {
            super();
            this.mKeyFields = new Key(creatorUid, intent);
        }
        public int getCreatorUid() {
            return mKeyFields.mCreatorUid;
        }
        /** {@hide} */
        public static boolean isValid(@NonNull Intent intent) {
            IBinder binder = intent.getCreatorToken();
            IntentCreatorToken token = null;
            if (binder instanceof IntentCreatorToken) {
                token = (IntentCreatorToken) binder;
            }
            return token != null && token.mKeyFields.equals(
                    new Key(token.mKeyFields.mCreatorUid, intent));
        }
        private static class Key {
            private Key(int creatorUid, Intent intent) {
                this.mCreatorUid = creatorUid;
                this.mAction = intent.getAction();
                this.mData = intent.getData();
                this.mType = intent.getType();
                this.mPackage = intent.getPackage();
                this.mComponent = intent.getComponent();
                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());
                    }
                }
            }
            private final int mCreatorUid;
            private final String mAction;
            private final Uri mData;
            private final String mType;
            private final String mPackage;
            private final ComponentName mComponent;
            private final int mFlags;
            private List<Uri> mClipDataUris;
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                Key key = (Key) o;
                return mCreatorUid == key.mCreatorUid && mFlags == key.mFlags && Objects.equals(
                        mAction, key.mAction) && Objects.equals(mData, key.mData)
                        && Objects.equals(mType, key.mType) && Objects.equals(mPackage,
                        key.mPackage) && Objects.equals(mComponent, key.mComponent)
                        && Objects.equals(mClipDataUris, key.mClipDataUris);
            }
            @Override
            public int hashCode() {
                return Objects.hash(mCreatorUid, mAction, mData, mType, mPackage, mComponent,
                        mFlags,
                        mClipDataUris);
            }
        }
    }
}