diff options
| author | 2024-09-26 16:47:25 +0000 | |
|---|---|---|
| committer | 2024-09-26 16:47:25 +0000 | |
| commit | ba8701ff7fcb7daa5dd47d4e85bf0bc99d68db78 (patch) | |
| tree | 07dec2492c53b254b5c618e519c5324635abb7e0 | |
| parent | dc8b0cf6bf11e699e3ca2d8b2443835536c08035 (diff) | |
Revert^2 "Add CreatorToken and CreatorTokenInfo"
This reverts commit dc8b0cf6bf11e699e3ca2d8b2443835536c08035.
Reason for revert:
Fixed b/369445340 with ag/29583770
Change-Id: Ided6194949b92cafa26deb5fe60340692300905d
5 files changed, 284 insertions, 1 deletions
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 031380dc1962..044178c4f6aa 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -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); + } + } } /** diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig index 56d3669ac50c..45e9def2c15f 100644 --- a/core/java/android/security/responsible_apis_flags.aconfig +++ b/core/java/android/security/responsible_apis_flags.aconfig @@ -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 diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index d98836f8ce20..e9ce71230a82 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -104,6 +104,7 @@ android_test { "mockito-target-extended-minus-junit4", "TestParameterInjector", "android.content.res.flags-aconfig-java", + "android.security.flags-aconfig-java", ], libs: [ diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java new file mode 100644 index 000000000000..d169ce3c07d0 --- /dev/null +++ b/core/tests/coretests/src/android/content/IntentTest.java @@ -0,0 +1,99 @@ +/* + * 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); + } + +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3c574769eaec..7ff6832762c9 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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; @@ -19078,4 +19081,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); + } + } + } } |