CVE-2026-7345
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fcomponents/feedback/feedback_util_unittest.cc |
modified |
Files Changed
components/feedback/feedback_util.cccomponents/feedback/feedback_util_unittest.cc
Patch
From 30d636a452dfa0076ffdfe2826c358b3744bf902 Mon Sep 17 00:00:00 2001 From: Mike West <[email protected]> Date: Tue, 14 Apr 2026 12:52:43 -0700 Subject: [PATCH] Prevent path traversal in ZipString. This change uses base::SafeBaseName to ensure that the filename passed to feedback_util::ZipString is a safe, single-component base name. This prevents a potential path traversal vulnerability where a compromised renderer could attempt to write files outside of the temporary directory. Bug: 502248774 Change-Id: I948b3ce274a2469ab30e0fb043473b6acfeffdfc Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7758483 Reviewed-by: Xiangdong Kong <[email protected]> Commit-Queue: Mike West <[email protected]> Cr-Commit-Position: refs/heads/main@{#1614676} --- diff --git a/components/feedback/feedback_util.cc b/components/feedback/feedback_util.cc index 767389e..5787f98 100644 --- a/components/feedback/feedback_util.cc +++ b/components/feedback/feedback_util.cc @@ -10,6 +10,7 @@ #include "base/compiler_specific.h" #include "base/files/file_util.h" +#include "base/files/safe_base_name.h" #include "base/files/scoped_temp_dir.h" #include "base/functional/bind.h" #include "base/json/json_reader.h" @@ -32,6 +33,12 @@ std::optional<std::string> ZipString(const base::FilePath& filename, std::string_view data) { + std::optional<base::SafeBaseName> safe_name = + base::SafeBaseName::Create(filename); + if (!safe_name || safe_name->path() != filename) { + return std::nullopt; + } + base::ScopedTempDir temp_dir; base::FilePath zip_file; @@ -40,7 +47,7 @@ if (!temp_dir.CreateUniqueTempDir()) { return std::nullopt; } - if (!base::WriteFile(temp_dir.GetPath().Append(filename), data)) { + if (!base::WriteFile(temp_dir.GetPath().Append(safe_name->path()), data)) { return std::nullopt; } if (!base::CreateTemporaryFile(&zip_file)) { diff --git a/components/feedback/feedback_util_unittest.cc b/components/feedback/feedback_util_unittest.cc index 95f30b2..5e066524 100644 --- a/components/feedback/feedback_util_unittest.cc +++ b/components/feedback/feedback_util_unittest.cc @@ -153,4 +153,43 @@ EXPECT_EQ(autofill_data_str, expected_autofill_data_str); } +TEST_F(FeedbackUtilTest, ZipStringTraversal) { + // Create a temp directory, and target a file within it: + base::ScopedTempDir root_dir; + ASSERT_TRUE(root_dir.CreateUniqueTempDir()); + base::FilePath sensitive_file = + root_dir.GetPath().AppendASCII("sensitive.txt"); + + // Construct a traversal back to that file in a platform-dependent way: + std::string sensitive_path_str = sensitive_file.AsUTF8Unsafe(); +#if BUILDFLAG(IS_WIN) + // Remove "C:" if present + if (sensitive_path_str.size() >= 2 && sensitive_path_str[1] == ':') { + sensitive_path_str = sensitive_path_str.substr(2); + } + // Remove leading backslash + if (!sensitive_path_str.empty() && sensitive_path_str[0] == '\\') { + sensitive_path_str = sensitive_path_str.substr(1); + } + base::FilePath traversal( + FILE_PATH_LITERAL("..\\..\\..\\..\\..\\..\\..\\..\\")); +#else + // Remove leading slash + if (!sensitive_path_str.empty() && sensitive_path_str[0] == '/') { + sensitive_path_str = sensitive_path_str.substr(1); + } + base::FilePath traversal(FILE_PATH_LITERAL("../../../../../../../../")); +#endif + + base::FilePath malicious_filename = + traversal.Append(base::FilePath::FromUTF8Unsafe(sensitive_path_str)); + + // Call ZipString. + std::optional<std::string> result = + feedback_util::ZipString(malicious_filename, "maliciousness"); + + EXPECT_FALSE(result.has_value()); + EXPECT_FALSE(base::PathExists(sensitive_file)); +} + } // namespace feedback_util
Regression Test / PoC
diff --git a/components/feedback/feedback_util_unittest.cc b/components/feedback/feedback_util_unittest.cc
index 95f30b2..5e066524 100644
--- a/components/feedback/feedback_util_unittest.cc
+++ b/components/feedback/feedback_util_unittest.cc
@@ -153,4 +153,43 @@
EXPECT_EQ(autofill_data_str, expected_autofill_data_str);
}
+TEST_F(FeedbackUtilTest, ZipStringTraversal) {
+ // Create a temp directory, and target a file within it:
+ base::ScopedTempDir root_dir;
+ ASSERT_TRUE(root_dir.CreateUniqueTempDir());
+ base::FilePath sensitive_file =
+ root_dir.GetPath().AppendASCII("sensitive.txt");
+
+ // Construct a traversal back to that file in a platform-dependent way:
+ std::string sensitive_path_str = sensitive_file.AsUTF8Unsafe();
+#if BUILDFLAG(IS_WIN)
+ // Remove "C:" if present
+ if (sensitive_path_str.size() >= 2 && sensitive_path_str[1] == ':') {
+ sensitive_path_str = sensitive_path_str.substr(2);
+ }
+ // Remove leading backslash
+ if (!sensitive_path_str.empty() && sensitive_path_str[0] == '\\') {
+ sensitive_path_str = sensitive_path_str.substr(1);
+ }
+ base::FilePath traversal(
+ FILE_PATH_LITERAL("..\\..\\..\\..\\..\\..\\..\\..\\"));
+#else
+ // Remove leading slash
+ if (!sensitive_path_str.empty() && sensitive_path_str[0] == '/') {
+ sensitive_path_str = sensitive_path_str.substr(1);
+ }
+ base::FilePath traversal(FILE_PATH_LITERAL("../../../../../../../../"));
+#endif
+
+ base::FilePath malicious_filename =
+ traversal.Append(base::FilePath::FromUTF8Unsafe(sensitive_path_str));
+
+ // Call ZipString.
+ std::optional<std::string> result =
+ feedback_util::ZipString(malicious_filename, "maliciousness");
+
+ EXPECT_FALSE(result.has_value());
+ EXPECT_FALSE(base::PathExists(sensitive_file));
+}
+
} // namespace feedback_util
Original Bug Report
Arbitrary file write via path traversal in feedback_util::ZipString
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 without the Chrome Security team.
Overview: A path traversal vulnerability exists in the chrome.feedbackPrivate.sendFeedback API that could allow a compromised WebUI renderer to perform an arbitrary file write. The filename provided in the attachedFile object is inadequately sanitized and appended to a temporary directory path before being written to disk uncompressed. This allows an attacker to escape the temporary directory and overwrite sensitive files in the context of the unsandboxed Browser process.
Affected files:
components/feedback/feedback_util.ccextensions/browser/api/feedback_private/feedback_private_api.cccomponents/feedback/feedback_common.ccextensions/browser/api/feedback_private/feedback_service.cccomponents/feedback/feedback_data.cc
Estimated timestamp from git blame: 2024-08-22
Summary
A potential path traversal vulnerability exists in the implementation of the chrome.feedbackPrivate.sendFeedback extension API. The vulnerability could allow a compromised renderer process—specifically one hosting an authorized WebUI (like chrome://feedback)—to perform arbitrary file writes in the context of the browser process. This can lead to a sandbox escape and persistent code execution by overwriting sensitive files such as ~/.bashrc on Linux or adding files to the Startup folder on Windows.
Vulnerability Details
When a renderer calls chrome.feedbackPrivate.sendFeedback, it can optionally provide an AttachedFile object containing a name (the filename) and a blob UUID representing the file’s data. The handling of this filename allows for a path traversal attack:
- Entry Point & Weak Sanitization: In
FeedbackPrivateSendFeedbackFunction::Run(extensions/browser/api/feedback_private/feedback_private_api.cc), the providednameis passed toStripFakepath. This function only removes a literalC:\fakepath\prefix and does not perform any validation for path traversal sequences like..or..\. - Data Flow: The unsanitized string is stored in
FeedbackData. Later,FeedbackData::AttachAndCompressFileDataconverts this string into abase::FilePathusingFilePath::FromUTF8Unsafe, which performs no path normalization or traversal checks. - Vulnerable Sink: The data flows to
FeedbackCommon::CompressFileand then tofeedback_util::ZipString(filename, data)(components/feedback/feedback_util.cc). InsideZipString, a temporary directory is created. - Path Traversal: The code attempts to write the uncompressed blob data to a file inside the temporary directory using:
base::WriteFile(temp_dir.GetPath().Append(filename), data)base::FilePath::Appendperforms raw string concatenation and does not prevent the use of..relative path components. Iffilenameis set to../../../../../../home/user/.bashrc, the resulting path evaluates to that exact string. - Arbitrary Write:
base::WriteFilepasses this traversed path directly to underlying OS APIs (likecreatorCreateFile). The operating system resolves the..sequences, breaking out of the temporary directory, and writes the attacker’s raw, uncompressed payload data to the resolved path. Because the Browser process runs unsandboxed with user privileges, the write succeeds. - Persistence: The
base::ScopedTempDirdestructor only deletes the temporary directory it created. The maliciously written file resides outside this directory and persists.
Impact
An attacker who has compromised a WebUI renderer (e.g., via a V8 bug or XSS in chrome://feedback) can escalate their privileges to the browser process context. By writing to sensitive filesystem locations, the attacker can achieve persistent, unsandboxed code execution.
Suggested Reproduction Steps
Note: These are suggested steps based on code analysis; our tooling agent cannot execute code to verify them.
- Compromise a renderer process hosting an authorized WebUI (e.g.,
chrome://feedback). - Construct a JavaScript
Blobcontaining the malicious payload (e.g., a reverse shell script or malware executable) and retrieve its UUID. - Invoke the
chrome.feedbackPrivate.sendFeedbackAPI with anattachedFileobject. - Set the
attachedFile.nameto a traversal string targeting a sensitive file (e.g.,../../../../../../home/user/.bashrc). - The browser process will read the blob and write its contents to the specified path outside the intended temporary directory.
Suggested Fix
To prevent path traversal, the filename should be strictly validated before being appended to the temporary directory path.
- In
feedback_util::ZipString(or earlier in the pipeline when theFilePathis created), verify that the path does not contain traversal components by checkingfilename.ReferencesParent(). - Alternatively, use
base::SafeBaseNameto ensure the provided filename only contains a single, safe path component without any directory separators or parent references.
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results 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.
Raised in root component due to access or custom field issues on 1033360