High watch CVSS 9.8 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
9.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in DataTransfer
DescriptionImproper input validation in DataTransfer
ComponentDataTransfer
Sub-componentBlink>DataTransfer
Bug ClassInput Validation
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker553117928
Fix commitdfc786d36aee (chromium/src) +42/-13
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-08-26 (per vendor advisory)
Fix landed2026-08-27 (public git)
Disclosed2026-09-01
Patch gap5 days — fix public in source before the advisory

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

reported by vm...@google.com

Host path spoofing in ChromeOS exo allows ARCVM to expose host files on paste

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: In ChromeOS exo, TranslateVMToHost() explicitly exempts ARCVM from VM-to-host path translation, IsPathShared() validation, and vmfile: anti-spoofing prefixes. An ARCVM guest could potentially inject arbitrary host file paths into Ash’s clipboard or drag-and-drop operations as FilenamesType. A subsequent paste or drop gesture on a web page grants the renderer process read access to chronos-readable host profile files.

Affected files:

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

Estimated timestamp from git blame: 2024-02-29

Summary

TranslateVMToHost() in chrome/browser/ash/exo/chrome_security_delegate.cc is responsible for translating guest VM file paths to valid host paths and preventing VMs from spoofing host filesystem paths. However, an explicit exemption for ARCVM bypasses all path translation, path-sharing verification, and anti-spoofing prefixing. Consequently, a compromised ARCVM guest or malicious Android app could potentially plant arbitrary host file paths (e.g., /home/chronos/user/Login Data or /home/chronos/user/Cookies) into Ash’s system clipboard as FilenamesType. When a user pastes (Ctrl+V) or drops onto a web page, the browser grants the renderer ChildProcessSecurityPolicy read permissions and FileSystemAccess tokens for those host files.

Root Cause Analysis

In chrome/browser/ash/exo/chrome_security_delegate.cc:112-143:

std::vector<FileInfo> TranslateVMToHost(const std::string& vm_name,
                                        std::vector<ui::FileInfo> vm_paths) {
  std::vector<FileInfo> file_infos;
  Profile* primary_profile = ProfileManager::GetPrimaryUserProfile();
  bool is_crostini = vm_name == crostini::kCrostiniDefaultVmName;

  for (ui::FileInfo& info : vm_paths) {
    base::FilePath path = std::move(info.path);
    storage::FileSystemURL url;

    // Convert the VM path to a path in the host if possible. Otherwise, prefix
    // it with 'vmfile:<vm_name>:' to avoid VMs spoofing host paths.
    // E.g. crostini /etc/mime.types => vmfile:termina:/etc/mime.types.
    if (!vm_name.empty() && vm_name != arc::kArcVmName) {
      if (file_manager::util::ConvertPathInsideVMToFileSystemURL(
              primary_profile, path, GetVmMount(vm_name),
              /*map_crostini_home=*/is_crostini, &url)) {
        path = url.path();
        // Only allow write to clipboard for paths that are shared.
        if (!IsPathShared(primary_profile, vm_name, is_crostini, path)) {
          LOG(WARNING) << "Unshared file path: " << path;
          continue;
        }
      } else {
        path = base::FilePath(
            base::StrCat({kVmFileScheme, ":", vm_name, ":", path.value()}));
      }
    }
    file_infos.push_back({std::move(path), std::move(url)});
  }
  return file_infos;
}
  1. ChromeSecurityDelegate::GetVmName(ui::EndpointType::kArc) (chrome_security_delegate.cc:376-381) returns arc::kArcVmName ("arcvm").
  2. When clipboard data originating from ARC is processed, TranslateVMToHost() checks if (!vm_name.empty() && vm_name != arc::kArcVmName). Because vm_name == arc::kArcVmName, the entire security check is skipped.
  3. The raw path parsed from the guest-supplied text/uri-list payload via ui::URIListToFileInfos() (ui/base/clipboard/file_info.cc:91-103) is pushed directly to file_infos at line 140.
  4. Seat::OnFilenamesRead() (components/exo/seat.cc:334) commits these paths to the Ash clipboard via writer->WriteFilenames().

Potential Impact via Web Paste or Drag & Drop

When a user pastes into a web page:

  1. Blink calls SystemClipboard::ReadFiles() (third_party/blink/renderer/core/clipboard/system_clipboard.cc:350-365), sending mojom::ClipboardHost::ReadFiles to the browser.
  2. In ClipboardHostImpl::OnReadFilesPolicyResult() (content/browser/renderer_host/clipboard_host_impl.cc:514-561), PrepareDataTransferFilenamesForChildProcess() (content/browser/file_system/browser_file_system_helper.cc:279-333) calls security_policy->GrantReadFile(child_id, filename.path) and registers an isolated file system.
  3. FileSystemAccessManagerImpl::ResolveDataTransferTokenWithFileType() (content/browser/file_system_access/file_system_access_manager_impl.cc:990-996) explicitly bypasses sensitive directory checks for files (HandleType::kFile).
  4. The renderer is granted read access to the specified host files (such as /home/chronos/user/Login Data and /home/chronos/user/Cookies), allowing an attacker’s website to read sensitive profile information upon receiving a paste or drop event.

Potential Steps to Trigger

(Note: These are suggested/potential steps based on static code analysis; our tooling agent does not have the ability to run live code in the target environment.)

  1. From code running within the ARCVM guest (e.g., an untrusted Android application with a focused Wayland surface), create a wl_data_source advertising text/uri-list.
  2. Set the clipboard selection by calling wl_data_device.set_selection.
  3. When Exo requests the data, supply file URIs targeting host files: file:///home/chronos/user/Login%20Data\r\nfile:///home/chronos/user/Cookies\r\n.
  4. Due to the exemption in TranslateVMToHost, Ash stores these literal paths on the clipboard as FilenamesType.
  5. Navigate the user’s browser to https://attacker.example (e.g., via an ACTION_VIEW intent).
  6. When the user pastes (Ctrl+V) on the page, the browser grants ChildProcessSecurityPolicy read access, and the page’s paste event handler reads the file contents via standard File / FileSystemAccess APIs.

Suggested Fix

Ensure ARCVM cannot pass unvalidated host paths to the clipboard or drag-and-drop operations. ARCVM paths should either be validated, converted using appropriate ARC URL translation mechanisms, or prefixed with vmfile:arcvm: / blocked if they do not correspond to legitimately shared directories.

Evaluated with Chrome root at commit: c6718ebbf2c677c6f63691e28c7801c563f6b19c


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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.

Raised in root component due to access or custom field issues on 658562

View on issue tracker