Chrome · Actor › Actor
CVE-2026-113083
Memory Corruption in Actor (Actor)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.javachrome/browser/actor/android/java/src/org/chromium/chrome/browser/actor/ActorNotificationClickIntegrationTest.javachrome/android/junit/src/org/chromium/chrome/browser/IntentHandlerRobolectricTest.java
Patch
From a15d652f920d385b56d147999fa9074cae08a1b3 Mon Sep 17 00:00:00 2001 From: Bhuvana Betini <[email protected]> Date: Mon, 28 Sep 2026 18:44:58 -0700 Subject: [PATCH] [Actor] Require first-party sender in isActorNotificationIntent IntentHandler.isActorNotificationIntent() previously classified intents based solely on EXTRA_SHOW_ACTOR_CONTROL or EXTRA_ACTOR_TASK_ID extras without verifying that the intent originated from Chrome. Because ChromeTabbedActivity is exported, an untrusted external intent carrying these extras could trigger ActorNotificationService .maybeDismissNotificationFromIntent() and cancel tagless notifications. Gate isActorNotificationIntent() on wasIntentSenderChrome(intent) to match sibling accessors in IntentHandler. Legitimate notification click intents are created via ActorForegroundServiceControllerImpl .createTrustedBringTabToFrontIntent(), which already stamps trusted intent extras via IntentUtils.addTrustedIntentExtras(). Bug: 563337773, 567165855 Change-Id: If003025af2ea0456296479e8f2d67f623df460f7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8477091 Reviewed-by: Calder Kitagawa <[email protected]> Commit-Queue: Bhuvana Betini <[email protected]> Cr-Commit-Position: refs/heads/main@{#1706589} --- diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java index fe688d1..ea0c016 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java @@ -1897,7 +1897,7 @@ * @return Whether the intent originated from an Actor notification. */ public static boolean isActorNotificationIntent(@Nullable Intent intent) { - if (intent == null) return false; + if (!wasIntentSenderChrome(intent)) return false; return IntentUtils.safeGetBooleanExtra( intent, ActorNotificationFactory.EXTRA_SHOW_ACTOR_CONTROL, false) || IntentUtils.safeGetIntExtra( diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/IntentHandlerRobolectricTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/IntentHandlerRobolectricTest.java index 17280d0..15fd1f90 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/IntentHandlerRobolectricTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/IntentHandlerRobolectricTest.java @@ -974,16 +974,29 @@ @Test public void testIsActorNotificationIntent() { + Context context = ContextUtils.getApplicationContext(); + assertFalse(IntentHandler.isActorNotificationIntent(null)); Intent intent = new Intent(Intent.ACTION_VIEW); assertFalse(IntentHandler.isActorNotificationIntent(intent)); + // Untrusted intents with Actor extras should be rejected. intent.putExtra(ActorNotificationFactory.EXTRA_SHOW_ACTOR_CONTROL, true); + assertFalse(IntentHandler.isActorNotificationIntent(intent)); + intent.setPackage(context.getPackageName()); + assertFalse(IntentHandler.isActorNotificationIntent(intent)); + + IntentUtils.addTrustedIntentExtras(intent); assertTrue(IntentHandler.isActorNotificationIntent(intent)); intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_ID, 123); + assertFalse(IntentHandler.isActorNotificationIntent(intent)); + intent.setPackage(context.getPackageName()); + assertFalse(IntentHandler.isActorNotificationIntent(intent)); + + IntentUtils.addTrustedIntentExtras(intent); assertTrue(IntentHandler.isActorNotificationIntent(intent)); } diff --git a/chrome/browser/actor/android/java/src/org/chromium/chrome/browser/actor/ActorNotificationClickIntegrationTest.java b/chrome/browser/actor/android/java/src/org/chromium/chrome/browser/actor/ActorNotificationClickIntegrationTest.java index 556c4f40..ccc8c57 100644 --- a/chrome/browser/actor/android/java/src/org/chromium/chrome/browser/actor/ActorNotificationClickIntegrationTest.java +++ b/chrome/browser/actor/android/java/src/org/chromium/chrome/browser/actor/ActorNotificationClickIntegrationTest.java @@ -24,6 +24,7 @@ import org.mockito.junit.MockitoRule; import org.chromium.base.ContextUtils; +import org.chromium.base.IntentUtils; import org.chromium.base.ThreadUtils; import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CriteriaHelper; @@ -34,6 +35,7 @@ import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.notifications.NotificationConstants; +import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeTabbedActivityTestRule; import org.chromium.components.browser_ui.notifications.BaseNotificationManagerProxyFactory; @@ -78,6 +80,7 @@ intent.putExtra(ActorNotificationFactory.EXTRA_SHOW_ACTOR_CONTROL, true); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_ID, taskId); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_STATE, state); + IntentUtils.addTrustedIntentExtras(intent); var watcher = HistogramWatcher.newSingleRecordWatcher("Actor.Notification.ClickTaskState", state); @@ -98,6 +101,7 @@ Intent intent = new Intent(mContext, ChromeTabbedActivity.class); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_ID, taskId); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_STATE, state); + IntentUtils.addTrustedIntentExtras(intent); var watcher = HistogramWatcher.newSingleRecordWatcher("Actor.Notification.ClickTaskState", state); @@ -117,6 +121,7 @@ ActorTask task = mock(ActorTask.class); when(task.getId()).thenReturn(taskId); when(task.getState()).thenReturn(state); + when(task.getTargetTabId()).thenReturn(Tab.INVALID_TAB_ID); ActorForegroundServiceController controller = mock(ActorForegroundServiceController.class); ActorForegroundServiceController.setInstanceForTesting(controller); @@ -126,6 +131,7 @@ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_ID, taskId); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_STATE, state); + IntentUtils.addTrustedIntentExtras(intent); when(controller.createTrustedBringTabToFrontIntent(task)).thenReturn(intent); // Verify that building a notification for this task produces a PendingIntent. @@ -151,6 +157,7 @@ ActorTask liveTask = mock(ActorTask.class); when(mActorKeyedService.getTask(taskId)).thenReturn(liveTask); when(liveTask.getState()).thenReturn(liveState); + when(liveTask.getTargetTabId()).thenReturn(Tab.INVALID_TAB_ID); // The intent contains an older state, but the live state should be logged. Intent intent = new Intent(mContext, ChromeTabbedActivity.class); @@ -158,6 +165,7 @@ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_ID, taskId); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_STATE, intentState); + IntentUtils.addTrustedIntentExtras(intent); var watcher = HistogramWatcher.newSingleRecordWatcher( @@ -186,6 +194,7 @@ intent.putExtra(ActorNotificationFactory.EXTRA_SHOW_ACTOR_CONTROL, true); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_ID, taskId); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_STATE, state); + IntentUtils.addTrustedIntentExtras(intent); mActivityTestRule.startMainActivityFromIntent(intent, null); @@ -210,6 +219,7 @@ intent.putExtra(ActorNotificationFactory.EXTRA_SHOW_ACTOR_CONTROL, true); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_ID, taskId); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_STATE, state); + IntentUtils.addTrustedIntentExtras(intent); ThreadUtils.runOnUiThreadBlocking( () -> mActivityTestRule.getActivity().onNewIntent(intent)); @@ -219,6 +229,33 @@ @Test @MediumTest + public void testNotificationClick_UntrustedIntent_DoesNotDismissNotification() + throws Exception { + int taskId = 108; + int state = ActorTaskState.FINISHED; + + MockNotificationManagerProxy notificationManager = new MockNotificationManagerProxy(); + BaseNotificationManagerProxyFactory.setInstanceForTesting(notificationManager); + notificationManager.notify(taskId, new Notification()); + assertEquals(1, notificationManager.getNotifications().size()); + + Intent intent = new Intent(mContext, ChromeTabbedActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(ActorNotificationFactory.EXTRA_SHOW_ACTOR_CONTROL, true); + intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_ID, taskId); + intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_STATE, state); + + mActivityTestRule.startMainActivityFromIntent(intent, null); + + ThreadUtils.runOnUiThreadBlocking( + () -> mActivityTestRule.getActivity().onNewIntent(intent)); + + assertEquals(1, notificationManager.getNotifications().size()); + } + + @Test + @MediumTest public void testNotificationClick_ActiveTask_DoesNotDismissNotification() throws Exception { int taskId = 107; int state = ActorTaskState.ACTING; @@ -234,6 +271,7 @@ intent.putExtra(ActorNotificationFactory.EXTRA_SHOW_ACTOR_CONTROL, true); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_ID, taskId); intent.putExtra(NotificationConstants.EXTRA_ACTOR_TASK_STATE, state); + IntentUtils.addTrustedIntentExtras(intent); mActivityTestRule.startMainActivityFromIntent(intent, null);
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page