Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Network
DescriptionImproper input validation in Network
ComponentNetwork
Bug ClassLogic Error
Tracker533511921
Fix commita94fdc22a1e1 (chromium/src) +59/-40
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • chrome/browser/download/download_target_determiner_unittest.cc
  • content/browser/file_system_access/file_system_chooser.cc
  • content/browser/web_contents/web_contents_view_aura_unittest.cc
  • net/base/filename_util_internal.cc
  • net/base/filename_util_unittest.cc
From a94fdc22a1e11467b08cbc4bc5b8e062ae8bc358 Mon Sep 17 00:00:00 2001
From: Etienne Bergeron <etienneb@google.com>
Date: Fri, 17 Jul 2026 07:38:20 -0700
Subject: [PATCH] [net/base] Block .scf and .url files in IsShellIntegratedExtension

A divergence between net::IsShellIntegratedExtension() and
FileSystemChooser::IsShellIntegratedExtension() allowed .url and .scf
files to bypass extension sanitization during standard downloads.

Windows Explorer automatically parses .url and .scf files upon folder
view (without the user double-clicking them) to retrieve metadata
or render icons. If a downloaded .url contains an IconFile pointing
to a remote UNC path, simply viewing the Downloads directory triggers
an automatic outbound SMB connection, leading to a disclosure of the
victim's NetNTLMv2 credential hash.

This CL adds "scf" and "url" to net::IsShellIntegratedExtension(),
ensuring they are correctly renamed to ".download" on Windows during
standard downloads.

Bug: 533511921
Test: net_unittests --gtest_filter=FilenameUtilTest.*
Change-Id: Iff138ff80003999fd3f447dffec1647d45ab2b1d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8086934
Reviewed-by: Kenichi Ishibashi <bashi@chromium.org>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Commit-Queue: Etienne Bergeron <etienneb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1663888}
---

diff --git a/chrome/browser/download/download_target_determiner_unittest.cc b/chrome/browser/download/download_target_determiner_unittest.cc
index 07a025c..b113eb7 100644
--- a/chrome/browser/download/download_target_determiner_unittest.cc
+++ b/chrome/browser/download/download_target_determiner_unittest.cc
@@ -2839,7 +2839,8 @@
        DownloadItem::TARGET_DISPOSITION_PROMPT,
 
        EXPECT_CRDOWNLOAD},
-      {// 2: File name falling back to dangerous extensions after removing env var.
+      {// 2: File name falling back to dangerous extensions after removing env
+       // var.
        SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
        DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk.%%",
        "application/octet-stream", FILE_PATH_LITERAL(""),
@@ -2848,7 +2849,8 @@
        DownloadItem::TARGET_DISPOSITION_PROMPT,
 
        EXPECT_CRDOWNLOAD},
-      {// 3: Double extension bug leading to dangerous extensions after removing env var.
+      {// 3: Double extension bug leading to dangerous extensions after removing
+       // env var.
        SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
        DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk %%",
        "application/octet-stream", FILE_PATH_LITERAL(""),
@@ -2857,8 +2859,9 @@
        DownloadItem::TARGET_DISPOSITION_PROMPT,
 
        EXPECT_CRDOWNLOAD},
-      {// 4: Unicode char bug leading to dangerous extensions after removing env var.
-       // NOTE: The space before "%%" is a non-breaking space (U+00A0), not a normal space.
+      {// 4: Unicode char bug leading to dangerous extensions after removing env
+       // var. NOTE: The space before "%%" is a non-breaking space (U+00A0), not
+       // a normal space.
        SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
        DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk %%",
        "application/octet-stream", FILE_PATH_LITERAL(""),
@@ -2891,7 +2894,7 @@
        "http://example.com/photo.jpg%20%25%25.url", "text/plain",
        FILE_PATH_LITERAL(""),
 
-       FILE_PATH_LITERAL("photo.jpg.url"),
+       FILE_PATH_LITERAL("photo.jpg.download"),
        DownloadItem::TARGET_DISPOSITION_PROMPT,
 
        EXPECT_CRDOWNLOAD},
diff --git a/content/browser/file_system_access/file_system_chooser.cc b/content/browser/file_system_access/file_system_chooser.cc
index 897ae7f..d2bf686 100644
--- a/content/browser/file_system_access/file_system_chooser.cc
+++ b/content/browser/file_system_access/file_system_chooser.cc
@@ -388,12 +388,14 @@
   // https://crbug.com/1227995, respectively). '.local' files are used by
   // Windows to determine which DLLs to load for an application. '.url' files
   // can be used to read arbirtary files (see https://crbug.com/1307930).
+  // LINT.IfChange(ShellIntegratedExtensions)
   if ((extension_lower == FILE_PATH_LITERAL("lnk")) ||
       (extension_lower == FILE_PATH_LITERAL("local")) ||
       (extension_lower == FILE_PATH_LITERAL("scf")) ||
       (extension_lower == FILE_PATH_LITERAL("url"))) {
     return true;
   }
+  // LINT.ThenChange(//net/base/filename_util_internal.cc:ShellIntegratedExtensions)
 
   // Setting a file's extension to a CLSID may conceal its actual file type on
   // some Windows versions (see https://nvd.nist.gov/vuln/detail/CVE-2004-0420).
diff --git a/content/browser/web_contents/web_contents_view_aura_unittest.cc b/content/browser/web_contents/web_contents_view_aura_unittest.cc
index 5eb6c0c..ce85bc8d 100644
--- a/content/browser/web_contents/web_contents_view_aura_unittest.cc
+++ b/content/browser/web_contents/web_contents_view_aura_unittest.cc
@@ -711,7 +711,7 @@
       data->GetVirtualFilenames();
   ASSERT_TRUE(file_infos.has_value());
   ASSERT_EQ(1ULL, file_infos.value().size());
-  EXPECT_EQ(base::FilePath(base::UTF16ToWide(url_title) + L".url"),
+  EXPECT_EQ(base::FilePath(base::UTF16ToWide(url_title) + L".download"),
             file_infos.value()[0].display_name);
 
   ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
diff --git a/net/base/filename_util_internal.cc b/net/base/filename_util_internal.cc
index d5799604..1d3c9e6 100644
--- a/net/base/filename_util_internal.cc
+++ b/net/base/filename_util_internal.cc
@@ -155,12 +155,20 @@
 bool IsShellIntegratedExtension(const base::FilePath::StringType& extension) {
   base::FilePath::StringType extension_lower = base::ToLowerASCII(extension);
 
-  // .lnk files may be used to execute arbitrary code (see
-  // https://nvd.nist.gov/vuln/detail/CVE-2010-2568). .local files are used by
-  // Windows to determine which DLLs to load for an application.
+  // .lnk and .scf files may be used to execute arbitrary code (see
+  // https://nvd.nist.gov/vuln/detail/CVE-2010-2568 and
+  // https://crbug.com/1227995, respectively). .local files are used by
+  // Windows to determine which DLLs to load for an application. .url files
+  // can be used to leak credentials or read arbitrary files (see
+  // https://crbug.com/1307930).
+  // LINT.IfChange(ShellIntegratedExtensions)
   if ((extension_lower == FILE_PATH_LITERAL("local")) ||
-      (extension_lower == FILE_PATH_LITERAL("lnk")))
+      (extension_lower == FILE_PATH_LITERAL("lnk")) ||
+      (extension_lower == FILE_PATH_LITERAL("scf")) ||
+      (extension_lower == FILE_PATH_LITERAL("url"))) {
     return true;
+  }
+  // LINT.ThenChange(//content/browser/file_system_access/file_system_chooser.cc:ShellIntegratedExtensions)
 
   // Setting a file's extension to a CLSID may conceal its actual file type on
   // some Windows versions (see https://nvd.nist.gov/vuln/detail/CVE-2004-0420).
diff --git a/net/base/filename_util_unittest.cc b/net/base/filename_util_unittest.cc
index ca470af..6f06b6c0 100644
--- a/net/base/filename_util_unittest.cc
+++ b/net/base/filename_util_unittest.cc
@@ -109,6 +109,8 @@
     FILE_PATH_LITERAL(" Computer"),
     FILE_PATH_LITERAL("My Computer.{a}"),
     FILE_PATH_LITERAL("My Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}"),
+    FILE_PATH_LITERAL("harmless.scf"),
+    FILE_PATH_LITERAL("harmless.url"),
 #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
     FILE_PATH_LITERAL("a\\a"),
 #endif
@@ -355,41 +357,45 @@
     const char* filename;
     const char* expected_filename;
   } safe_tests[] = {
-    {__LINE__, "text/html", "bar.htm", "bar.htm"},
-    {__LINE__, "text/html", "bar.html", "bar.html"},
-    {__LINE__, "application/x-chrome-extension", "bar", "bar.crx"},
-    {__LINE__, "image/png", "bar.html", "bar.html"},
-    {__LINE__, "text/html", "bar.exe", "bar.exe"},
-    {__LINE__, "image/gif", "bar.exe", "bar.exe"},
-    {__LINE__, "text/html", "google.com", "google.com"},
-    // Allow extension synonyms.
-    {__LINE__, "image/jpeg", "bar.jpg", "bar.jpg"},
-    {__LINE__, "image/jpeg", "bar.jpeg", "bar.jpeg"},
+      {__LINE__, "text/html", "bar.htm", "bar.htm"},
+      {__LINE__, "text/html", "bar.html", "bar.html"},
+      {__LINE__, "application/x-chrome-extension", "bar", "bar.crx"},
+      {__LINE__, "image/png", "bar.html", "bar.html"},
+      {__LINE__, "text/html", "bar.exe", "bar.exe"},
+      {__LINE__, "image/gif", "bar.exe", "bar.exe"},
+      {__LINE__, "text/html", "google.com", "google.com"},
+      // Allow extension synonyms.
+      {__LINE__, "image/jpeg", "bar.jpg", "bar.jpg"},
+      {__LINE__, "image/jpeg", "bar.jpeg", "bar.jpeg"},
 
 #if BUILDFLAG(IS_WIN)
-    // Device names
-    {__LINE__, "text/html", "con.htm", "_con.htm"},
-    {__LINE__, "text/html", "lpt1.htm", "_lpt1.htm"},
-    {__LINE__, "application/x-chrome-extension", "con", "_con.crx"},
+      // Device names
+      {__LINE__, "text/html", "con.htm", "_con.htm"},
+      {__LINE__, "text/html", "lpt1.htm", "_lpt1.htm"},
+      {__LINE__, "application/x-chrome-extension", "con", "_con.crx"},
 
-    // Looks like foo.{GUID} which get treated as namespace mounts on Windows.
-    {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
-     "harmless.download"},
-    {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
+      // Looks like foo.{GUID} which get treated as namespace mounts on Windows.
+      {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
+       "harmless.download"},
+      {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
 
-    // Dangerous extensions
-    {__LINE__, "text/html", "harmless.local", "harmless.download"},
-    {__LINE__, "text/html", "harmless.lnk", "harmless.download"},
+      // Dangerous extensions
+      {__LINE__, "text/html", "harmless.local", "harmless.download"},
+      {__LINE__, "text/html", "harmless.lnk", "harmless.download"},
+      {__LINE__, "text/html", "harmless.scf", "harmless.download"},
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/download/download_target_determiner_unittest.cc b/chrome/browser/download/download_target_determiner_unittest.cc
index 07a025c..b113eb7 100644
--- a/chrome/browser/download/download_target_determiner_unittest.cc
+++ b/chrome/browser/download/download_target_determiner_unittest.cc
@@ -2839,7 +2839,8 @@
        DownloadItem::TARGET_DISPOSITION_PROMPT,
 
        EXPECT_CRDOWNLOAD},
-      {// 2: File name falling back to dangerous extensions after removing env var.
+      {// 2: File name falling back to dangerous extensions after removing env
+       // var.
        SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
        DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk.%%",
        "application/octet-stream", FILE_PATH_LITERAL(""),
@@ -2848,7 +2849,8 @@
        DownloadItem::TARGET_DISPOSITION_PROMPT,
 
        EXPECT_CRDOWNLOAD},
-      {// 3: Double extension bug leading to dangerous extensions after removing env var.
+      {// 3: Double extension bug leading to dangerous extensions after removing
+       // env var.
        SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
        DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk %%",
        "application/octet-stream", FILE_PATH_LITERAL(""),
@@ -2857,8 +2859,9 @@
        DownloadItem::TARGET_DISPOSITION_PROMPT,
 
        EXPECT_CRDOWNLOAD},
-      {// 4: Unicode char bug leading to dangerous extensions after removing env var.
-       // NOTE: The space before "%%" is a non-breaking space (U+00A0), not a normal space.
+      {// 4: Unicode char bug leading to dangerous extensions after removing env
+       // var. NOTE: The space before "%%" is a non-breaking space (U+00A0), not
+       // a normal space.
        SAVE_AS, download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
        DownloadFileType::NOT_DANGEROUS, "http://example.com/foo2.lnk %%",
        "application/octet-stream", FILE_PATH_LITERAL(""),
@@ -2891,7 +2894,7 @@
        "http://example.com/photo.jpg%20%25%25.url", "text/plain",
        FILE_PATH_LITERAL(""),
 
-       FILE_PATH_LITERAL("photo.jpg.url"),
+       FILE_PATH_LITERAL("photo.jpg.download"),
        DownloadItem::TARGET_DISPOSITION_PROMPT,
 
        EXPECT_CRDOWNLOAD},
diff --git a/content/browser/web_contents/web_contents_view_aura_unittest.cc b/content/browser/web_contents/web_contents_view_aura_unittest.cc
index 5eb6c0c..ce85bc8d 100644
--- a/content/browser/web_contents/web_contents_view_aura_unittest.cc
+++ b/content/browser/web_contents/web_contents_view_aura_unittest.cc
@@ -711,7 +711,7 @@
       data->GetVirtualFilenames();
   ASSERT_TRUE(file_infos.has_value());
   ASSERT_EQ(1ULL, file_infos.value().size());
-  EXPECT_EQ(base::FilePath(base::UTF16ToWide(url_title) + L".url"),
+  EXPECT_EQ(base::FilePath(base::UTF16ToWide(url_title) + L".download"),
             file_infos.value()[0].display_name);
 
   ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
diff --git a/net/base/filename_util_unittest.cc b/net/base/filename_util_unittest.cc
index ca470af..6f06b6c0 100644
--- a/net/base/filename_util_unittest.cc
+++ b/net/base/filename_util_unittest.cc
@@ -109,6 +109,8 @@
     FILE_PATH_LITERAL(" Computer"),
     FILE_PATH_LITERAL("My Computer.{a}"),
     FILE_PATH_LITERAL("My Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}"),
+    FILE_PATH_LITERAL("harmless.scf"),
+    FILE_PATH_LITERAL("harmless.url"),
 #if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
     FILE_PATH_LITERAL("a\\a"),
 #endif
@@ -355,41 +357,45 @@
     const char* filename;
     const char* expected_filename;
   } safe_tests[] = {
-    {__LINE__, "text/html", "bar.htm", "bar.htm"},
-    {__LINE__, "text/html", "bar.html", "bar.html"},
-    {__LINE__, "application/x-chrome-extension", "bar", "bar.crx"},
-    {__LINE__, "image/png", "bar.html", "bar.html"},
-    {__LINE__, "text/html", "bar.exe", "bar.exe"},
-    {__LINE__, "image/gif", "bar.exe", "bar.exe"},
-    {__LINE__, "text/html", "google.com", "google.com"},
-    // Allow extension synonyms.
-    {__LINE__, "image/jpeg", "bar.jpg", "bar.jpg"},
-    {__LINE__, "image/jpeg", "bar.jpeg", "bar.jpeg"},
+      {__LINE__, "text/html", "bar.htm", "bar.htm"},
+      {__LINE__, "text/html", "bar.html", "bar.html"},
+      {__LINE__, "application/x-chrome-extension", "bar", "bar.crx"},
+      {__LINE__, "image/png", "bar.html", "bar.html"},
+      {__LINE__, "text/html", "bar.exe", "bar.exe"},
+      {__LINE__, "image/gif", "bar.exe", "bar.exe"},
+      {__LINE__, "text/html", "google.com", "google.com"},
+      // Allow extension synonyms.
+      {__LINE__, "image/jpeg", "bar.jpg", "bar.jpg"},
+      {__LINE__, "image/jpeg", "bar.jpeg", "bar.jpeg"},
 
 #if BUILDFLAG(IS_WIN)
-    // Device names
-    {__LINE__, "text/html", "con.htm", "_con.htm"},
-    {__LINE__, "text/html", "lpt1.htm", "_lpt1.htm"},
-    {__LINE__, "application/x-chrome-extension", "con", "_con.crx"},
+      // Device names
+      {__LINE__, "text/html", "con.htm", "_con.htm"},
+      {__LINE__, "text/html", "lpt1.htm", "_lpt1.htm"},
+      {__LINE__, "application/x-chrome-extension", "con", "_con.crx"},
 
-    // Looks like foo.{GUID} which get treated as namespace mounts on Windows.
-    {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
-     "harmless.download"},
-    {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
+      // Looks like foo.{GUID} which get treated as namespace mounts on Windows.
+      {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
+       "harmless.download"},
+      {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
 
-    // Dangerous extensions
-    {__LINE__, "text/html", "harmless.local", "harmless.download"},
-    {__LINE__, "text/html", "harmless.lnk", "harmless.download"},
+      // Dangerous extensions
+      {__LINE__, "text/html", "harmless.local", "harmless.download"},
+      {__LINE__, "text/html", "harmless.lnk", "harmless.download"},
+      {__LINE__, "text/html", "harmless.scf", "harmless.download"},
+      {__LINE__, "text/html", "harmless.url", "harmless.download"},
 #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
-    // On Posix, none of the above set is particularly dangerous.
-    {__LINE__, "text/html", "con.htm", "con.htm"},
-    {__LINE__, "text/html", "lpt1.htm", "lpt1.htm"},
-    {__LINE__, "application/x-chrome-extension", "con", "con.crx"},
-    {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
-     "harmless.{not-really-this-may-be-a-guid}"},
-    {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
-    {__LINE__, "text/html", "harmless.local", "harmless.local"},
-    {__LINE__, "text/html", "harmless.lnk", "harmless.lnk"},
+      // On Posix, none of the above set is particularly dangerous.
+      {__LINE__, "text/html", "con.htm", "con.htm"},
+      {__LINE__, "text/html", "lpt1.htm", "lpt1.htm"},
+      {__LINE__, "application/x-chrome-extension", "con", "con.crx"},
+      {__LINE__, "text/html", "harmless.{not-really-this-may-be-a-guid}",
+       "harmless.{not-really-this-may-be-a-guid}"},
+      {__LINE__, "text/html", "harmless.{mismatched-", "harmless.{mismatched-"},
+      {__LINE__, "text/html", "harmless.local", "harmless.local"},
+      {__LINE__, "text/html", "harmless.lnk", "harmless.lnk"},
+      {__LINE__, "text/html", "harmless.scf", "harmless.scf"},
+      {__LINE__, "text/html", "harmless.url", "harmless.url"},
 #endif  // BUILDFLAG(IS_WIN)
   };
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.