Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Drag and Drop
DescriptionInsufficient validation of untrusted input in Drag and Drop
ComponentDrag and Drop
Bug ClassLogic Error
Tracker497651688
Fix commit5ef49809a501 (chromium/src) +23/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
content/browser/renderer_host/data_transfer_util.cc
modified
TEST_F
content/browser/renderer_host/render_widget_host_unittest.cc
modified

Files Changed

  • content/browser/renderer_host/data_transfer_util.cc
  • content/browser/renderer_host/render_widget_host_unittest.cc
From 5ef49809a501c2d5144127f62896e4557567b538 Mon Sep 17 00:00:00 2001
From: Shu Yang <shuyng@google.com>
Date: Thu, 16 Apr 2026 12:36:22 -0700
Subject: [PATCH] Sanitize file extension in DragDataToDropData to prevent path traversal

A compromised renderer could supply a malicious `filename_extension`
containing path traversal characters (e.g., `../../`) during a
drag-and-drop operation. This could potentially allow a sandbox escape
by writing arbitrary files to a vulnerable third-party app.

To fix this, we now use `BaseName()` on the `filename_extension` in
`DragDataToDropData` to strip out any directory components.

We also added a regression test in `render_widget_host_unittest.cc` to
verify that malicious paths are correctly sanitized.

Bug: 497651688
Change-Id: Ic6fcfd598c3bd4a45a88f5218a93e0d80add22f8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7762535
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Commit-Queue: Shu Yang <shuyng@google.com>
Cr-Commit-Position: refs/heads/main@{#1616053}
---

diff --git a/content/browser/renderer_host/data_transfer_util.cc b/content/browser/renderer_host/data_transfer_util.cc
index 38929861..42236d3d 100644
--- a/content/browser/renderer_host/data_transfer_util.cc
+++ b/content/browser/renderer_host/data_transfer_util.cc
@@ -398,7 +398,7 @@
             binary_item->is_image_accessible;
         result.file_contents_source_url = binary_item->source_url;
         result.file_contents_filename_extension =
-            binary_item->filename_extension.value();
+            binary_item->filename_extension.BaseName().value();
         if (binary_item->content_disposition) {
           result.file_contents_content_disposition =
               *binary_item->content_disposition;
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index bef04fb..fc7fe932 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -2411,6 +2411,28 @@
   EXPECT_TRUE(drop_data().download_metadata.has_value());
 }
 
+TEST_F(RenderWidgetHostDragTest, SanitizeFilenameExtensionOnDrag) {
+  NavigateAndCommit(GURL("https://example.com"));
+  EXPECT_EQ(start_dragging_count(), 0);
+
+  auto drag_data = blink::mojom::DragData::New();
+  blink::mojom::DragItemBinaryPtr item = blink::mojom::DragItemBinary::New();
+  item->data = mojo_base::BigBuffer(std::vector<uint8_t>{1, 2, 3});
+  item->is_image_accessible = true;
+  item->source_url = GURL("http://example.com/image.png");
+  item->filename_extension =
+      base::FilePath(FILE_PATH_LITERAL("png/../../payload.so"));
+  drag_data->items.push_back(
+      blink::mojom::DragItem::NewBinary(std::move(item)));
+
+  StartDragWithDragData(std::move(drag_data));
+
+  EXPECT_EQ(start_dragging_count(), 1);
+  // BaseName() should strip the path traversal components.
+  EXPECT_EQ(drop_data().file_contents_filename_extension,
+            FILE_PATH_LITERAL("payload.so"));
+}
+
 // Hiding the RenderWidgetHostImpl instance via a call to WasHidden should
 // not reject a pending pointer lock, if the operation is waiting for the
 // user to make a selection on the permission prompt.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index bef04fb..fc7fe932 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -2411,6 +2411,28 @@
   EXPECT_TRUE(drop_data().download_metadata.has_value());
 }
 
+TEST_F(RenderWidgetHostDragTest, SanitizeFilenameExtensionOnDrag) {
+  NavigateAndCommit(GURL("https://example.com"));
+  EXPECT_EQ(start_dragging_count(), 0);
+
+  auto drag_data = blink::mojom::DragData::New();
+  blink::mojom::DragItemBinaryPtr item = blink::mojom::DragItemBinary::New();
+  item->data = mojo_base::BigBuffer(std::vector<uint8_t>{1, 2, 3});
+  item->is_image_accessible = true;
+  item->source_url = GURL("http://example.com/image.png");
+  item->filename_extension =
+      base::FilePath(FILE_PATH_LITERAL("png/../../payload.so"));
+  drag_data->items.push_back(
+      blink::mojom::DragItem::NewBinary(std::move(item)));
+
+  StartDragWithDragData(std::move(drag_data));
+
+  EXPECT_EQ(start_dragging_count(), 1);
+  // BaseName() should strip the path traversal components.
+  EXPECT_EQ(drop_data().file_contents_filename_extension,
+            FILE_PATH_LITERAL("payload.so"));
+}
+
 // Hiding the RenderWidgetHostImpl instance via a call to WasHidden should
 // not reject a pending pointer lock, if the operation is waiting for the
 // user to make a selection on the permission prompt.
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential path traversal in DropDataProvider via unsanitized filename_extension

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A compromised renderer can supply a malicious filename_extension containing path traversal characters during a drag-and-drop operation. Chrome’s Android implementation fails to sanitize this extension in its fallback logic, exposing it to drop-target applications. This could allow a sandbox escape by writing arbitrary files to a vulnerable third-party app.

Affected files:

  • content/browser/android/drop_data_android.cc
  • content/browser/renderer_host/render_widget_host_impl.cc
  • ui/android/java/src/org/chromium/ui/dragdrop/DropDataProviderImpl.java
  • content/browser/renderer_host/data_transfer_util.cc
  • ui/android/java/src/org/chromium/ui/dragdrop/DropDataProviderUtils.java
  • mojo/public/cpp/base/file_path_mojom_traits.cc

Estimated timestamp from git blame: 2022-06-21

Overview

There is a potential path traversal vulnerability in Chrome’s Android drag-and-drop implementation (DropDataProvider). A compromised renderer can control the filename_extension field of a dragged image. By injecting path traversal sequences (e.g., ../../../), an attacker can control the OpenableColumns.DISPLAY_NAME exposed to a target application. If the target application naively uses this display name to save the dropped file, it can lead to an arbitrary file write within the target application’s private data directory, effectively acting as a sandbox escape.

Note: This is a potential vulnerability identified via static code analysis. Our tooling agent does not yet have the ability to run code to produce a live proof-of-concept.

Vulnerability Details

  1. Mojo IPC: A compromised renderer can construct a blink.mojom.DragItemBinary struct and set the filename_extension field to a path traversal string (e.g., png/../../../../data/data/com.victim/files/payload).
  2. Missing Validation: In the browser process, RenderWidgetHostImpl::StartDragging converts this IPC data into a DropData object. Security checks are applied to file paths, but the file_contents_filename_extension field is not sanitized because it is assumed to be a short, benign extension.
  3. Unsanitized Fallback in Android JNI: In content/browser/android/drop_data_android.cc, the browser attempts to generate a safe filename using GetSafeFilenameForImageFileContents(). Because the malicious extension is not a recognized MIME type, this fails. The code then relies on fallback logic that blindly concatenates the extension to a timestamp: base::NumberToString(...) + "." + drop_data.file_contents_filename_extension
  4. Confused Deputy: This tainted string is passed to the Android Java layer and cached in DropDataProviderImpl. When a target application receives the drop and queries the ContentProvider URI for file metadata, Chrome returns the tainted string as OpenableColumns.DISPLAY_NAME.

Potential Attack Steps

  1. An attacker compromises a Chrome renderer process (e.g., via a v8 exploit).
  2. The attacker triggers a LocalFrameHost::StartDragging IPC, embedding a DragItemBinary payload with a malicious filename_extension (e.g., png/../../../../data/data/com.victim/files/libmalicious.so) and custom file bytes.
  3. The attacker tricks the user into dragging the malicious element from Chrome into a vulnerable target application (e.g., in split-screen mode).
  4. The target application resolves the dropped ContentProvider URI, queries the DISPLAY_NAME, and receives the traversal string.
  5. If the target app uses this DISPLAY_NAME to construct a file path (e.g., new File(targetDir, displayName)), the payload is written outside the intended directory, overwriting sensitive files or native libraries to achieve code execution in the victim app.

Suggested Fix

The browser process must aggressively sanitize the file_contents_filename_extension before using it or passing it to the OS.

  • In content/browser/android/drop_data_android.cc (or earlier in DragDataToDropData), ensure that any path separators (/, \) or directory traversal sequences (..) are stripped from the extension.
  • A simple fix in ToJavaDropData could be utilizing base::FilePath(drop_data.file_contents_filename_extension).BaseName().value() or explicitly checking that the extension only contains alphanumeric characters.

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.

View on issue tracker