CVE-2026-11035
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
android_resourceschrome/browser/ui/android/night_mode/BUILD.gn |
modified | |
robolectric_librarychrome/browser/ui/android/night_mode/BUILD.gn |
modified |
Files Changed
chrome/browser/ui/android/night_mode/BUILD.gnchrome/browser/ui/android/night_mode/java/res_junit/layout/remote_views_test_layout.xmlchrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflater.javachrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflaterUnitTest.java
Patch
From b7e05db9db6ab7fe27c1567507980a331eef7dd5 Mon Sep 17 00:00:00 2001
From: Jinsuk Kim <jinsukkim@chromium.org>
Date: Fri, 17 Apr 2026 15:09:16 -0700
Subject: [PATCH] [CCT] Apply RemoteView inflation filter for night mode
This CL ensures that the manual LayoutInflater created in
RemoteViewsWithNightModeInflater.inflateWithEnforcedDarkMode()
enforces the same security restrictions as the standard Android
framework. Specifically, a LayoutInflater.Filter is applied to
the inflater instance before calling inflater.inflate(). This filter
should explicitly verify that any requested class is annotated with
android.widget.RemoteViews.RemoteView.
Bug: 497936421
Change-Id: I2af5dfcaa674ecd0e2059c202868f32cf5140827
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7773489
Reviewed-by: Sinan Sahin <sinansahin@google.com>
Commit-Queue: Jinsuk Kim <jinsukkim@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1616890}
---
diff --git a/chrome/browser/ui/android/night_mode/BUILD.gn b/chrome/browser/ui/android/night_mode/BUILD.gn
index fb463a7..38e6ff91f 100644
--- a/chrome/browser/ui/android/night_mode/BUILD.gn
+++ b/chrome/browser/ui/android/night_mode/BUILD.gn
@@ -119,9 +119,15 @@
resources_package = "org.chromium.chrome.browser.night_mode"
}
+android_resources("junit_resources") {
+ testonly = true
+ sources = [ "java/res_junit/layout/remote_views_test_layout.xml" ]
+}
+
robolectric_library("junit") {
sources = [
"java/src/org/chromium/chrome/browser/night_mode/AutoDarkFeedbackSourceUnitTest.java",
+ "java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflaterUnitTest.java",
"java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeControllerUnitTest.java",
"java/src/org/chromium/chrome/browser/night_mode/WebContentsDarkModeMessageControllerUnitTest.java",
"java/src/org/chromium/chrome/browser/night_mode/WebContentsThemeClientUnitTest.java",
@@ -129,6 +135,7 @@
deps = [
":java",
+ ":junit_resources",
":night_mode_java_test_support",
"//base:base_java",
"//base:base_java_test_support",
diff --git a/chrome/browser/ui/android/night_mode/java/res_junit/layout/remote_views_test_layout.xml b/chrome/browser/ui/android/night_mode/java/res_junit/layout/remote_views_test_layout.xml
new file mode 100644
index 0000000..b100278
--- /dev/null
+++ b/chrome/browser/ui/android/night_mode/java/res_junit/layout/remote_views_test_layout.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2026 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+</ScrollView>
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflater.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflater.java
index 147d28b..fcf868e 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflater.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflater.java
@@ -90,6 +90,7 @@
LayoutInflater inflater =
LayoutInflater.from(appContext).cloneInContext(contextForRemoteViews);
+ inflater.setFilter(clazz -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class));
View view = inflater.inflate(remoteViews.getLayoutId(), parent, false);
remoteViews.reapply(appContext, view);
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflaterUnitTest.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflaterUnitTest.java
new file mode 100644
index 0000000..cbaca4f
--- /dev/null
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflaterUnitTest.java
@@ -0,0 +1,44 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.night_mode;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.ScrollView;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Unit tests for {@link RemoteViewsWithNightModeInflater}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class RemoteViewsWithNightModeInflaterUnitTest {
+
+ @Test
+ public void testInflate_NonRemoteView_Fails() throws Exception {
+ Context context = ContextUtils.getApplicationContext();
+ RemoteViews remoteViews =
+ new RemoteViews(context.getPackageName(), R.layout.remote_views_test_layout);
+
+ // Pass true for isInLocalNightMode and false for isInSystemNightMode to trigger
+ // inflateWithEnforcedDarkMode.
+ View view =
+ RemoteViewsWithNightModeInflater.inflate(
+ remoteViews,
+ /* parent= */ null,
+ /* isInLocalNightMode= */ true,
+ /* isInSystemNightMode= */ false);
+
+ // The returned view cannot be ScrollView since it is not annotated with
+ // @RemoteView.RemoteView. So inflation through RemoteViews should fail and return null.
+ Assert.assertFalse("The returned view cannot be ScrollView", view instanceof ScrollView);
+ }
+}
Original Bug Report
Potential Sandbox Escape via RemoteViews Inflation Filter Bypass in Custom Tabs
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: Chrome’s manual RemoteViews inflation for Custom Tabs night mode omits a critical security filter, allowing arbitrary View instantiation. An untrusted Android application can supply a malicious RemoteViews payload and force this vulnerable code path via a CustomTabIntent color scheme mismatch. By chaining this with internal reflection gadgets like BaseOrdinalLegend, an attacker can potentially instantiate arbitrary classes and execute code within Chrome’s highly privileged browser process.
Affected files:
chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/RemoteViewsWithNightModeInflater.javachrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabBottomBarDelegate.javachrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.javachrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
Estimated timestamp from git blame: 2019-05-29
Description
When a Custom Tab is launched with an EXTRA_REMOTEVIEWS payload to customize the bottom bar, Chrome attempts to inflate the provided layout. To address an Android bug related to night mode configuration overrides, Chrome implements a custom inflation fallback in RemoteViewsWithNightModeInflater.inflateWithEnforcedDarkMode().
However, this manual fallback omits a critical security mechanism present in standard Android RemoteViews.apply(): it fails to set a LayoutInflater.Filter. The standard filter ensures that only View classes explicitly annotated with @RemoteView (e.g., TextView, LinearLayout) can be instantiated from the untrusted layout XML. Because Chrome’s implementation lacks this filter, any class extending View present in Chrome’s classpath can be instantiated if it is referenced in an attacker-provided layout XML.
An unprivileged, malicious Android app can reliably trigger this vulnerable path by providing an EXTRA_COLOR_SCHEME in the CustomTabsIntent that intentionally mismatches the device’s system-wide night mode setting (e.g., requesting dark mode when the system is in light mode). Because the CustomTabIntentDataProvider parses these extras without signature validation or first-party trust checks, any app can force Chrome’s browser process to evaluate isInLocalNightMode == isInSystemNightMode as false, triggering the inflateWithEnforcedDarkMode() fallback.
Potential Impact
By supplying a crafted XML layout, an attacker can instantiate arbitrary View subclasses within Chrome’s highly privileged browser process. This can lead to a sandbox escape or local privilege escalation. For instance, instantiating <android.webkit.WebView> could allow cross-origin requests or local file access.
More critically, the attacker can leverage internal Chrome classes as gadgets. For example, <org.chromium.chrome.browser.chart.common.legend.BaseOrdinalLegend> contains a highly dangerous reflection primitive in its constructor:
String rowProviderClassName = a.getString(R.styleable.AplosLegend_aplosRowProviderClass);
// ...
Class<?> clazz = Class.forName(rowProviderClassName);
// ... searches for constructors and invokes newInstance()
By setting the app:aplosRowProviderClass XML attribute to an arbitrary fully qualified class name, the attacker can force the browser process to call Class.forName().newInstance() on that class. This allows the execution of arbitrary static initializers and constructors for any class in Chrome’s extensive classpath.
Hypothetical Attack Scenario
Note: These are suggested steps based on static code analysis; our tooling agent does not yet have the capability to execute this code to verify a full exploit chain.
- Attacker Setup: A malicious Android application is installed on the victim’s device alongside Chrome.
- Payload Creation: The malicious app creates an XML layout resource containing a gadget tag, such as
<org.chromium.chrome.browser.chart.common.legend.BaseOrdinalLegend aplosRowProviderClass="com.example.DangerousClass" />. - Intent Construction: The app constructs an
Intentto launch a Custom Tab. - Forcing Mismatch: The app adds the
CustomTabsIntent.EXTRA_COLOR_SCHEMEextra, explicitly setting it to a value that opposes the current system night mode (e.g.,COLOR_SCHEME_DARKif the system is light). - Attaching Payload: The app creates a
RemoteViewsobject pointing to its malicious layout and attaches it viaCustomTabsIntent.EXTRA_REMOTEVIEWS. - Trigger: The app fires the Intent.
- Exploitation: Chrome parses the Intent, detects the night mode mismatch, and routes the
RemoteViewsinflation to the unfilteredRemoteViewsWithNightModeInflater. TheLayoutInflaterparses the attacker’s XML, instantiatesBaseOrdinalLegendusing Chrome’s ClassLoader, and triggers the reflection gadget, executing the attacker’s chosen constructor in the browser process.
Suggested Fix
Ensure that the manual LayoutInflater created in RemoteViewsWithNightModeInflater.inflateWithEnforcedDarkMode() enforces the same security restrictions as the standard Android framework. Specifically, a LayoutInflater.Filter must be applied to the inflater instance before calling inflater.inflate(). This filter should explicitly verify that any requested class is annotated with android.widget.RemoteViews.RemoteView. Alternatively, consider restricting the EXTRA_REMOTEVIEWS functionality exclusively to trusted, first-party applications.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.