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 <bbetini@google.com>
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 <ckitagawa@chromium.org>
Commit-Queue: Bhuvana Betini <bbetini@google.com>
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