CVE-2026-14122
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fchrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc |
modified |
Files Changed
chrome/browser/web_applications/os_integration/web_app_shortcut_win.ccchrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc
Patch
From 1981552b415b088fe02e08cc7b98f5820461637c Mon Sep 17 00:00:00 2001
From: Dan Murphy <dmurph@chromium.org>
Date: Tue, 19 May 2026 14:47:38 -0700
Subject: [PATCH] [PWA] Guard against Windows reserved device names
During PWA installation on Windows, the app name is used to create
shortcut (.lnk) and icon files. If the name is a Windows reserved device
name (e.g. COM1, PRN), Windows path normalization redirects file
operations to the physical hardware device or blocks indefinitely. This
allows potential unauthorized device I/O or denial of service via
ThreadPool worker exhaustion.
This CL updates GetSanitizedFileName to check if the file name is a
reserved name on Windows using net::IsReservedNameOnWindows, and
prepends an underscore if it is. This ensures they are treated as
regular files. Unit tests are added to cover these cases.
TAG=agy
CONV=0ebc5617-ab1c-4ea4-b856-1fbbf4177ecd
Bug: 513824891, b:514441287
Change-Id: I812433b58bde9620b04c18707778f4e8bec39e8b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7858093
Auto-Submit: Daniel Murphy <dmurph@chromium.org>
Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
Reviewed-by: David Bienvenu <davidbienvenu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1633131}
---
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc b/chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc
index 8309cd5..8d30580 100644
--- a/chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc
+++ b/chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc
@@ -46,6 +46,7 @@
#include "chrome/installer/util/util_constants.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/obsolete/md5.h"
+#include "net/base/filename_util.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/win/shell.h"
#include "ui/gfx/image/image.h"
@@ -654,6 +655,9 @@
base::FilePath GetSanitizedFileName(const std::u16string& name) {
std::wstring file_name = base::AsWString(name);
base::i18n::ReplaceIllegalCharactersInPath(&file_name, ' ');
+ if (net::IsReservedNameOnWindows(file_name)) {
+ file_name.insert(0, 1, FILE_PATH_LITERAL('_'));
+ }
return base::FilePath(file_name);
}
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc b/chrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc
index e67cebd0..e2c4d7b 100644
--- a/chrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc
+++ b/chrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc
@@ -103,6 +103,18 @@
GetSanitizedFileName(u"path/separator"));
EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_ _")),
GetSanitizedFileName(u"***"));
+ // Test reserved names on Windows.
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_COM1")),
+ GetSanitizedFileName(u"COM1"));
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_PRN")),
+ GetSanitizedFileName(u"PRN"));
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_CON")),
+ GetSanitizedFileName(u"CON"));
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_LPT9")),
+ GetSanitizedFileName(u"LPT9"));
+ // Test reserved names with extension (only base filename is checked).
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_COM1.lnk")),
+ GetSanitizedFileName(u"COM1.lnk"));
}
TEST_F(WebAppShortcutWinTest, GetShortcutPaths) {
@@ -451,6 +463,10 @@
EXPECT_EQ(
GetIconFilePath(web_app_path, u"***"),
base::FilePath(FILE_PATH_LITERAL("test\\web\\app\\dir\\_ _.ico")));
+ // Test reserved names on Windows.
+ EXPECT_EQ(
+ GetIconFilePath(web_app_path, u"COM1"),
+ base::FilePath(FILE_PATH_LITERAL("test\\web\\app\\dir\\_COM1.ico")));
}
} // namespace web_app::internals
Regression Test / PoC
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc b/chrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc
index e67cebd0..e2c4d7b 100644
--- a/chrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc
+++ b/chrome/browser/web_applications/os_integration/web_app_shortcut_win_unittest.cc
@@ -103,6 +103,18 @@
GetSanitizedFileName(u"path/separator"));
EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_ _")),
GetSanitizedFileName(u"***"));
+ // Test reserved names on Windows.
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_COM1")),
+ GetSanitizedFileName(u"COM1"));
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_PRN")),
+ GetSanitizedFileName(u"PRN"));
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_CON")),
+ GetSanitizedFileName(u"CON"));
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_LPT9")),
+ GetSanitizedFileName(u"LPT9"));
+ // Test reserved names with extension (only base filename is checked).
+ EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("_COM1.lnk")),
+ GetSanitizedFileName(u"COM1.lnk"));
}
TEST_F(WebAppShortcutWinTest, GetShortcutPaths) {
@@ -451,6 +463,10 @@
EXPECT_EQ(
GetIconFilePath(web_app_path, u"***"),
base::FilePath(FILE_PATH_LITERAL("test\\web\\app\\dir\\_ _.ico")));
+ // Test reserved names on Windows.
+ EXPECT_EQ(
+ GetIconFilePath(web_app_path, u"COM1"),
+ base::FilePath(FILE_PATH_LITERAL("test\\web\\app\\dir\\_COM1.ico")));
}
} // namespace web_app::internals
Original Bug Report
Potential hijack of Windows reserved device names via PWA installation
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: The PWA installation process on Windows fails to sanitize reserved device names (e.g., COM1, PRN) in the web app name. This allows an attacker to potentially perform unauthorized hardware I/O or cause a denial of service in the browser process.
Affected files:
chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc
Estimated timestamp from git blame: 2012-02-11
Summary
The PWA shortcut and icon creation logic on Windows does not check for reserved device names when converting a web app manifest’s name into a filename. While it strips illegal characters (like * or ?), it fails to reject or transform names such as CON, PRN, AUX, NUL, COM1-9, and LPT1-9. This can be leveraged by a malicious website to interact with hardware devices via the browser process or cause a persistent denial of service (DoS) by hanging background worker threads.
Root Cause Analysis
The issue is located in the GetSanitizedFileName function within chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc.
// chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc:654
base::FilePath GetSanitizedFileName(const std::u16string& name) {
std::wstring file_name = base::AsWString(name);
base::i18n::ReplaceIllegalCharactersInPath(&file_name, ' ');
return base::FilePath(file_name);
}
The call to base::i18n::ReplaceIllegalCharactersInPath removes common illegal characters but does not account for reserved names. Other components in the PWA subsystem correctly use net::IsReservedNameOnWindows to mitigate this risk (e.g., in chrome/browser/web_applications/os_integration/web_app_handler_registration_utils_win.cc), but this check is missing from the primary shortcut and icon generation paths.
Potential Impact
- Unauthorized Device I/O: During PWA installation, the browser attempts to create a shortcut (e.g.,
Desktop\COM1.lnk). Due to legacy Windows path normalization, writing toCOM1.lnkredirects the binary data of the shortcut to theCOM1serial port. An attacker can control the manifest’sdescriptionfield, which is embedded in the shortcut file, potentially allowing them to send arbitrary commands to a serial device (e.g., a printer or industrial controller) without the user’s consent, bypassing WebSerial permissions. - ThreadPool Denial of Service: The browser also manages icon files (e.g.,
COM1.ico) and their associated checksums (e.g.,COM1.ico.md5). During icon update checks, the browser callsbase::ReadFileon the checksum file. IfCOM1refers to a blocking serial device, the::ReadFilecall on the backgroundThreadPoolworker thread will hang indefinitely. Repeated triggers can exhaust the browser’s thread pool, leading to a complete hang of background tasks.
Suggested Potential Steps to Reproduce
- On a Windows 10 system, host a web manifest where the
"name"field is set to a reserved name like"COM1"and the"description"field contains a payload. - In Chrome, navigate to the site and install the PWA.
- Observe that the browser process attempts to create a file at
Desktop\COM1.lnk. If a serial listener is active onCOM1, it may receive binary data during the installation. - Trigger a PWA update or icon refresh. Observe the browser’s background thread pool for worker threads hanging in a
base::ReadFileoperation on theCOM1.ico.md5file.
Recommended Fix
Update GetSanitizedFileName in chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc to include a check for reserved names using net::IsReservedNameOnWindows. If a reserved name is detected, it should be transformed (e.g., by prepending an underscore) to ensure it is treated as a regular file rather than a device handle.
Note: These findings are based on code analysis; our tooling does not currently have the capability to execute code or provide a working proof-of-concept.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
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.