High chrome Logic Error 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in DataTransfer
DescriptionImproper input validation in DataTransfer
ComponentDataTransfer
Bug ClassLogic Error
Tracker553117928
Fix commitdfc786d36aee (chromium/src) +42/-13
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-01

Background

`exo`
The ChromeOS Wayland compositor server (components/exo) that brokers windowing, drag-drop, and clipboard traffic between the Chrome host and guest environments such as Crostini and ARC.
ARC VM (`arc::kArcVmName`)
The Android runtime on ChromeOS running inside a virtual machine, identified as endpoint ui::EndpointType::kArc, whose file namespace is not directly mounted into the host.
`SecurityDelegate`
The exo interface (ChromeSecurityDelegate) responsible for validating and translating guest-supplied data, including turning guest VM file URIs into host FileInfo paths.
`FileInfo`
A ui::FileInfo record carrying a filesystem path (and optional display_name) that flows into the browser’s clipboard and drag-drop data as a real host file reference.

Root Cause Analysis

When a guest transferred data over exo via clipboard or drag-drop, ash::TranslateVMToHost (reached through ChromeSecurityDelegate::GetFilenames) parsed the text/uri-list payload and produced host FileInfo paths for every VM endpoint, including ui::EndpointType::kArc.

The invariant that was violated is that a guest should only be able to name paths within its own shared, host-mapped namespace: for the ARC VM there is no legitimate exo mechanism to send file paths, so any file:// URI it supplied was passed through and interpreted as a raw host path (e.g. file:///file1 became host path /file1) rather than being rejected.

The fix adds an early if (vm_name == arc::kArcVmName ...) return {}; guard, gated by the default-enabled kChromeSecurityDelegateIgnoreArcVm feature, so ARC-originated paths are dropped before any host translation occurs. This works because ARC has no supported path-transfer flow today, so returning an empty vector removes the attacker-controlled path injection surface without breaking a real feature, while the feature flag leaves a kill switch if support is added later.

Key insight
The single core mistake was translating guest-controlled file URIs into host paths for the ARC VM even though ARC has no legitimate path-transfer channel, effectively trusting the guest to name arbitrary host paths; the fix closes this by short-circuiting TranslateVMToHost to return no paths for arc::kArcVmName.

Attack Path

  1. Guest positioning A malicious or compromised Android app running in the ARC VM initiates a drag-drop or clipboard operation targeting the Chrome host through exo.
  2. Craft URI payload The app supplies a text/uri-list clipboard/drag payload containing attacker-chosen file:// URIs such as file:///file1, naming host paths outside any ARC-shared directory.
  3. Bypass translation ChromeSecurityDelegate::GetFilenames invokes TranslateVMToHost for the kArc endpoint, which (pre-fix) accepted the URIs and emitted FileInfo entries pointing at raw host paths.
  4. Deliver forged file references The resulting host FileInfo paths are handed to the browser’s clipboard/drag-drop machinery as if they were legitimately shared files, so a subsequent paste or drop references attacker-named host files.

Impact Assessment

An attacker controlling code inside the ARC VM could inject arbitrary host filesystem paths into ChromeOS clipboard or drag-drop data, causing the host to treat guest-chosen paths as real, shared file references. This occurs in the Chrome browser/host process context via the exo SecurityDelegate, and requires an already-present malicious or compromised Android app in ARC plus a user-visible clipboard/drag interaction to land the forged paths. The gain is improper input validation leading to unauthorized host file path exposure, not a direct memory-safety corruption.

Files Changed

  • chrome/browser/ash/exo/chrome_security_delegate.cc
  • chrome/browser/ash/exo/chrome_security_delegate.h
  • chrome/browser/ash/exo/chrome_security_delegate_unittest.cc

Audit Directions

  • Endpoint-specific validation
    Audit every SecurityDelegate path-translation routine (TranslateVMToHost, TranslateVMPathsToHost, GetFilenames) to confirm each ui::EndpointType only yields paths within its own host-mapped shared namespace and rejects endpoints that lack a legitimate transfer channel.
  • Guest-to-host path trust
    Review clipboard and drag-drop ingestion of text/uri-list and file:// URIs across exo for places that convert guest-supplied strings into host FileInfo/FilePath without checking against registered shared-path roots.
  • Default-on feature guards
    Verify that security-hardening feature flags like kChromeSecurityDelegateIgnoreArcVm remain FEATURE_ENABLED_BY_DEFAULT and are not silently disabled, and that disabling them cannot re-open the path-injection surface in production.
From dfc786d36aee91a37fa0b111051788d8eabf7942 Mon Sep 17 00:00:00 2001
From: Joel Hockey <joelhockey@chromium.org>
Date: Thu, 27 Aug 2026 16:28:08 -0700
Subject: [PATCH] Ignore paths from kArcVm in TranslateVMToHost()

Arc doesn't currently support sending file paths in drag-drop or
clipboard via exo.  If support is ever added we must be sure to
map paths correctly.

Bug: 553117928
Change-Id: Ifc145c0868e11ac8d1715626039a141019a4f1e1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8303173
Reviewed-by: maciek swiech <drmasquatch@google.com>
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1687650}
---

diff --git a/chrome/browser/ash/exo/chrome_security_delegate.cc b/chrome/browser/ash/exo/chrome_security_delegate.cc
index e337d2a..fb2f8eb 100644
--- a/chrome/browser/ash/exo/chrome_security_delegate.cc
+++ b/chrome/browser/ash/exo/chrome_security_delegate.cc
@@ -41,6 +41,9 @@
 
 namespace ash {
 
+BASE_FEATURE(kChromeSecurityDelegateIgnoreArcVm,
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 namespace {
 
 constexpr char kUriListSeparator[] = "\r\n";
@@ -111,6 +114,12 @@
 // Translate |vm_paths| from |source| VM to host paths.
 std::vector<FileInfo> TranslateVMToHost(const std::string& vm_name,
                                         std::vector<ui::FileInfo> vm_paths) {
+  // Arc doesn't currently support drag-drop or clipboard via exo.
+  // If Arc ever adds support, we must map paths correctly like other VMs.
+  if (vm_name == arc::kArcVmName &&
+      base::FeatureList::IsEnabled(kChromeSecurityDelegateIgnoreArcVm)) {
+    return {};
+  }
   std::vector<FileInfo> file_infos;
   Profile* primary_profile = ProfileManager::GetPrimaryUserProfile();
   bool is_crostini = vm_name == crostini::kCrostiniDefaultVmName;
diff --git a/chrome/browser/ash/exo/chrome_security_delegate.h b/chrome/browser/ash/exo/chrome_security_delegate.h
index c89a659a..78ecc654 100644
--- a/chrome/browser/ash/exo/chrome_security_delegate.h
+++ b/chrome/browser/ash/exo/chrome_security_delegate.h
@@ -5,11 +5,14 @@
 #ifndef CHROME_BROWSER_ASH_EXO_CHROME_SECURITY_DELEGATE_H_
 #define CHROME_BROWSER_ASH_EXO_CHROME_SECURITY_DELEGATE_H_
 
+#include "base/feature_list.h"
 #include "components/exo/security_delegate.h"
 #include "storage/browser/file_system/file_system_url.h"
 
 namespace ash {
 
+BASE_DECLARE_FEATURE(kChromeSecurityDelegateIgnoreArcVm);
+
 // Translate paths from |source| VM to valid paths in the host. Invalid paths
 // are ignored.
 std::vector<base::FilePath> TranslateVMPathsToHost(
diff --git a/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc b/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc
index 06e147e..fab2c5f3 100644
--- a/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc
+++ b/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/string_view_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/bruschetta/bruschetta_util.h"
 #include "chrome/browser/ash/crostini/crostini_manager.h"
 #include "chrome/browser/ash/crostini/crostini_security_delegate.h"
@@ -198,24 +199,40 @@
   guest_os_share_path->RegisterSharedPath(bruschetta::kBruschettaVmName,
                                           shared_path);
 
-  // Multiple lines should be parsed.
-  // Arc should not translate paths.
-  std::vector<ui::FileInfo> files = security_delegate.GetFilenames(
-      ui::EndpointType::kArc,
-      Data("\n\tfile:///file1\t\r\n#ignore\r\nfile:///file2\r\n"));
-  EXPECT_EQ(2u, files.size());
-  EXPECT_EQ("/file1", files[0].path.value());
-  EXPECT_EQ("", files[0].display_name.value());
-  EXPECT_EQ("/file2", files[1].path.value());
-  EXPECT_EQ("", files[1].display_name.value());
+  std::vector<ui::FileInfo> files;
+  // When kChromeSecurityDelegateIgnoreArcVm is enabled, Arc paths should be
+  // ignored.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndEnableFeature(kChromeSecurityDelegateIgnoreArcVm);
+    files = security_delegate.GetFilenames(
+        ui::EndpointType::kArc, Data("file:///file1\r\nfile:///file2"));
+    EXPECT_TRUE(files.empty());
+  }
+
+  // When feature is disabled, Arc should not translate paths.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndDisableFeature(kChromeSecurityDelegateIgnoreArcVm);
+    files = security_delegate.GetFilenames(
+        ui::EndpointType::kArc, Data("file:///file1\r\nfile:///file2"));
+    EXPECT_EQ(2u, files.size());
+    EXPECT_EQ("/file1", files[0].path.value());
+    EXPECT_EQ("", files[0].display_name.value());
+    EXPECT_EQ("/file2", files[1].path.value());
+    EXPECT_EQ("", files[1].display_name.value());
+  }
 
   // Crostini shared paths should be mapped.
+  // Multiple lines should be parsed.
   guest_os::GuestOsSecurityDelegate crostini_security_delegate("termina");
   files = crostini_security_delegate.GetFilenames(
       ui::EndpointType::kCrostini,
-      Data("file:///mnt/chromeos/MyFiles/shared/file"));
-  EXPECT_EQ(1u, files.size());
-  EXPECT_EQ(shared_path.Append("file"), files[0].path);
+      Data("\n\tfile:///mnt/chromeos/MyFiles/shared/file1\t\r\n"
+           "#ignore\r\nfile:///mnt/chromeos/MyFiles/shared/file2\r\n"));
+  EXPECT_EQ(2u, files.size());
+  EXPECT_EQ(shared_path.Append("file1"), files[0].path);
+  EXPECT_EQ(shared_path.Append("file2"), files[1].path);
 
   // Crostini homedir should be mapped.
   files = crostini_security_delegate.GetFilenames(
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc b/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc
index 06e147e..fab2c5f3 100644
--- a/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc
+++ b/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/string_view_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/bruschetta/bruschetta_util.h"
 #include "chrome/browser/ash/crostini/crostini_manager.h"
 #include "chrome/browser/ash/crostini/crostini_security_delegate.h"
@@ -198,24 +199,40 @@
   guest_os_share_path->RegisterSharedPath(bruschetta::kBruschettaVmName,
                                           shared_path);
 
-  // Multiple lines should be parsed.
-  // Arc should not translate paths.
-  std::vector<ui::FileInfo> files = security_delegate.GetFilenames(
-      ui::EndpointType::kArc,
-      Data("\n\tfile:///file1\t\r\n#ignore\r\nfile:///file2\r\n"));
-  EXPECT_EQ(2u, files.size());
-  EXPECT_EQ("/file1", files[0].path.value());
-  EXPECT_EQ("", files[0].display_name.value());
-  EXPECT_EQ("/file2", files[1].path.value());
-  EXPECT_EQ("", files[1].display_name.value());
+  std::vector<ui::FileInfo> files;
+  // When kChromeSecurityDelegateIgnoreArcVm is enabled, Arc paths should be
+  // ignored.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndEnableFeature(kChromeSecurityDelegateIgnoreArcVm);
+    files = security_delegate.GetFilenames(
+        ui::EndpointType::kArc, Data("file:///file1\r\nfile:///file2"));
+    EXPECT_TRUE(files.empty());
+  }
+
+  // When feature is disabled, Arc should not translate paths.
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndDisableFeature(kChromeSecurityDelegateIgnoreArcVm);
+    files = security_delegate.GetFilenames(
+        ui::EndpointType::kArc, Data("file:///file1\r\nfile:///file2"));
+    EXPECT_EQ(2u, files.size());
+    EXPECT_EQ("/file1", files[0].path.value());
+    EXPECT_EQ("", files[0].display_name.value());
+    EXPECT_EQ("/file2", files[1].path.value());
+    EXPECT_EQ("", files[1].display_name.value());
+  }
 
   // Crostini shared paths should be mapped.
+  // Multiple lines should be parsed.
   guest_os::GuestOsSecurityDelegate crostini_security_delegate("termina");
   files = crostini_security_delegate.GetFilenames(
       ui::EndpointType::kCrostini,
-      Data("file:///mnt/chromeos/MyFiles/shared/file"));
-  EXPECT_EQ(1u, files.size());
-  EXPECT_EQ(shared_path.Append("file"), files[0].path);
+      Data("\n\tfile:///mnt/chromeos/MyFiles/shared/file1\t\r\n"
+           "#ignore\r\nfile:///mnt/chromeos/MyFiles/shared/file2\r\n"));
+  EXPECT_EQ(2u, files.size());
+  EXPECT_EQ(shared_path.Append("file1"), files[0].path);
+  EXPECT_EQ(shared_path.Append("file2"), files[1].path);
 
   // Crostini homedir should be mapped.
   files = crostini_security_delegate.GetFilenames(
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.