Low watch CVSS 8.3 chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect reference resolution in Storage
DescriptionIncorrect reference resolution in Storage
ComponentStorage
Bug ClassLogic Error
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.
Tracker497203958
Fix commit27bb7c8fa525 (chromium/src) +140/-19
CWECWE-706
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-28 (per vendor advisory)
Fix landed2026-08-26 (public git)
Disclosed2026-09-08
Patch gap13 days — fix public in source before the advisory

Background

`storage.mojom.StrictRelativePath`
a Mojo type that carries a base::FilePath over IPC to the sandboxed Storage Service, meant to guarantee the value is a relative, non-escaping path before it is joined to a service-controlled root directory.
`StructTraits<...>::Read`
the deserialization hook (strict_relative_path_mojom_traits.cc) that validates an incoming base::FilePath and rejects the message when the value fails its invariants.
Windows reserved device names
legacy DOS device names (CON, NUL, AUX, PRN, COM1–COM9, LPT1–LPT9) plus console devices (CONIN$, CONOUT$) that Win32 path APIs resolve to real devices (\\.\CON, etc.) regardless of the containing directory.
`IsReservedNameOnWindows`
a base helper (file_util.cc) that reports whether a single file-name component is one of these reserved names, accounting for extensions and trailing whitespace/dots.

Root Cause Analysis

The pre-patch StructTraits<storage::mojom::StrictRelativePathDataView, base::FilePath>::Read only rejected a deserialized path when path.IsAbsolute() or path.ReferencesParent() were true. A relative path component such as con, nul, con.zip, or con (trailing space) satisfies neither predicate, so it passed validation and was handed to FilesystemImpl, which appended it to the service’s root directory. On Windows, Win32 canonicalization strips trailing spaces/dots and resolves these names to system DOS devices, so the resulting file operation targeted a device (\\.\CON, \\.\NUL, …) instead of a file confined to the sandboxed directory, violating the invariant that a StrictRelativePath stays inside the root.

The fix TIGHTENS Read by adding an #if BUILDFLAG(IS_WIN) guard that runs std::ranges::any_of(path.GetComponents(), &base::IsReservedNameOnWindows) and rejects the message if any component is a reserved name. It also hardens IsReservedNameOnWindows to trim trailing spaces and dots (TrimString(filename_lower, " .", base::TRIM_TRAILING)) and to recognize conin$/conout$, closing the canonicalization tricks while still allowing benign names like conin$.txt.

Key insight
The single core mistake was equating “relative and non-parent-referencing” with “stays inside the sandbox root,” ignoring that Windows resolves reserved device names to out-of-tree devices; the fix adds an explicit per-component reserved-name rejection on Windows so such paths never reach FilesystemImpl.

Attack Path

  1. Obtain a pipe to the Storage Service A compromised or malicious renderer (or other Mojo client) holds a message pipe that delivers StrictRelativePath values into FilesystemImpl in the Storage Service process.
  2. Craft a reserved-name path The attacker sends a path whose component is a Windows reserved device name, e.g. con, nul, con.zip, con (trailing space), or conin$, which is relative and does not reference a parent.
  3. Bypass the old validation In the pre-patch Read, path.IsAbsolute() and path.ReferencesParent() both return false, so the path is accepted and stored into out.
  4. Trigger device resolution FilesystemImpl joins the path to its root; Win32 canonicalization strips trailing spaces/dots and resolves the component to a DOS device, so the file operation escapes the intended directory sandbox and targets a system device (inferred beyond-sandbox escalation such as further sandbox escape or code execution is not demonstrated by the patch).

Impact Assessment

An attacker able to send Mojo messages to the Storage Service can direct filesystem operations at Windows reserved devices rather than files inside the service’s sandboxed root, defeating the directory confinement the Storage Service relies on; the advisory classifies this as a LogicError / “Incorrect reference resolution in Storage” and the platform precondition is Windows. The vulnerable operation runs in the Storage Service (utility) process and the preconditions are an attacker-controlled Mojo client plus a Windows host. The vendor advisory rates this low severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.3. The advisory does not report exploitation in the wild or assign a stated severity here.

Changed Functions

FunctionChangeNotes
if
base/files/file_util.cc
modified
Read
components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits.cc
modified
TEST_F
base/files/file_util_unittest.cc
modified
for
base/files/file_util_unittest.cc
modified
TEST
components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits_unittest.cc
modified
for
components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits_unittest.cc
modified
source_set
components/services/storage/public/cpp/filesystem/BUILD.gn
modified

Files Changed

  • components/services/storage/public/cpp/filesystem/BUILD.gn
  • base/files/file_util.cc
  • components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits_unittest.cc
  • components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits.cc
  • base/files/file_util.h
  • base/files/file_util_unittest.cc

Audit Directions

  • Mojo path deserialization
    Audit every StructTraits::Read and typemap that accepts a base::FilePath from IPC and only checks IsAbsolute/ReferencesParent, since relative-but-special names (reserved devices, symlinks, alternate data streams) can still escape a root.
  • OS-specific name canonicalization
    Look for validation that compares file-name strings without accounting for platform normalization (trailing spaces/dots, case folding, extension stripping) before the name is passed to a native filesystem API.
  • Sandboxed-directory join points
    Review services that append a “trusted relative” path to a confinement root (e.g. FilesystemImpl) and confirm the reserved-name/reference-resolution check runs before the native call, not after.
From 27bb7c8fa525405fe3ca40edf7f56d369cb25052 Mon Sep 17 00:00:00 2001
From: S Ganesh <[email protected]>
Date: Wed, 26 Aug 2026 15:45:48 -0700
Subject: [PATCH] Reland "Reject Windows reserved device names in Storage Service"

This is a reland of https://crrev.com/c/8221662 which was reverted in
https://crrev.com/c/8283070 because the Windows reserved name validation
was checked on all platforms. On non-Windows platforms such as macOS,
typical database or file names (like "aux") are valid and their
validation triggered Mojo deserialization failures.

This reland wraps the validation check and its tests under a
BUILDFLAG(IS_WIN) preprocessor conditional so that the check is only
executed on Windows where the sandbox bypass is relevant.

To address previous performance and code review feedback:
- SplitStringOnce is extracted out of the loop in IsReservedNameOnWindows
  to avoid redundant string splitting up to 23 times per path component.
- Added explanatory comments around the optimized prefix check.
- Passed IsReservedNameOnWindows by function pointer to std::ranges::any_of
  to prevent potential compiler/toolchain overload ambiguities.

Original change's description:
> storage.mojom.StrictRelativePath deserializes paths passed over
> Mojo IPC to FilesystemImpl. Previously, it only checked
> path.IsAbsolute() and path.ReferencesParent().
>
> On Windows, DOS reserved device names (such as CON, PRN, AUX,
> NUL, COM1-COM9, LPT1-LPT9, CLOCK$, CONIN$, CONOUT$) or names with
> trailing spaces/dots (which Win32 API canonicalization strips to
> the underlying device, e.g., "con ") are not considered absolute
> or parent-referencing by base::FilePath. When FilesystemImpl
> appends such a relative path to its root directory, Win32 APIs
> resolve the path to system DOS devices (\\.\\CON, \\.\\NUL, etc.),
> bypassing the directory sandbox.

Bug: 497203958, 545283003
Change-Id: Ieee521f20a134767a4a523d0b317cf58bf698ebb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8291418
Reviewed-by: Daniel Cheng <[email protected]>
Reviewed-by: Evan Stade <[email protected]>
Commit-Queue: S Ganesh <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1686747}
---
diff --git a/base/files/file_util.cc b/base/files/file_util.cc
index 6b1a6cb..fe72e5f 100644
--- a/base/files/file_util.cc
+++ b/base/files/file_util.cc
@@ -534,7 +534,6 @@
   return FilePath();
 }
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 bool IsReservedNameOnWindows(const base::FilePath::StringType& filename) {
   // This list is taken from the MSDN article "Naming a file"
   // http://msdn2.microsoft.com/en-us/library/aa365247(VS.85).aspx
@@ -549,26 +548,38 @@
       // shell.
       "desktop.ini",
       "thumbs.db",
+      // Windows console input/output devices. Unlike legacy DOS devices (e.g.
+      // CON), Windows does not strip extensions for CONIN$/CONOUT$.
+      "conin$",
+      "conout$",
   });
 
 #if BUILDFLAG(IS_WIN)
   std::string filename_lower = base::ToLowerASCII(base::WideToUTF8(filename));
-#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
+#else
   std::string filename_lower = base::ToLowerASCII(filename);
 #endif
 
-  return std::ranges::any_of(kKnownDevices,
-                             [&filename_lower](std::string_view device) {
-                               if (filename_lower == device) {
-                                 return true;
-                               }
-                               auto parts =
-                                   SplitStringOnce(filename_lower, '.');
-                               return parts && parts->first == device;
-                             }) ||
-         kMagicNames.contains(filename_lower);
+  // On Windows, trailing spaces and dots are stripped by Win32 API path
+  // canonicalization (e.g., "con " or "con. " resolves to device "\\.\CON").
+  std::string_view trimmed_filename =
+      base::TrimString(filename_lower, " .", base::TRIM_TRAILING);
+
+  // Extract the part of the filename before the first dot to check against
+  // DOS device names (e.g. "CON.zip" -> "CON" and "CON.tar.gz" -> "CON").
+  // Doing this once here avoids redundant string splitting inside the loop.
+  std::string_view prefix = trimmed_filename;
+  if (auto parts = SplitStringOnce(trimmed_filename, '.')) {
+    prefix = parts->first;
+  }
+
+  return std::ranges::any_of(
+             kKnownDevices,
+             [prefix, trimmed_filename](std::string_view device) {
+               return trimmed_filename == device || prefix == device;
+             }) ||
+         kMagicNames.contains(trimmed_filename);
 }
-#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 
 std::optional<FilePath> GetLatestTemporaryFileWithNamePrefix(
     const FilePath& dir,
diff --git a/base/files/file_util.h b/base/files/file_util.h
index 32e7134..e71ab46 100644
--- a/base/files/file_util.h
+++ b/base/files/file_util.h
@@ -811,7 +811,6 @@
 
 #endif
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 // Returns whether the specified file name is a reserved name on Windows.
 // This includes names like "com2.zip" (which correspond to devices) and
 // desktop.ini and thumbs.db which have special meaning to the Windows shell.
@@ -819,7 +818,6 @@
 // reserved on Windows.
 BASE_EXPORT bool IsReservedNameOnWindows(
     const base::FilePath::StringType& filename);
-#endif
 
 // Internal --------------------------------------------------------------------
 
diff --git a/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits.cc b/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits.cc
index 6b41b94e..d264d9e 100644
--- a/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits.cc
+++ b/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits.cc
@@ -4,9 +4,12 @@
 
 #include "components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits.h"
 
+#include <algorithm>
 #include <utility>
 
+#include "base/files/file_util.h"
 #include "base/logging.h"
+#include "build/build_config.h"
 #include "mojo/public/cpp/base/file_path_mojom_traits.h"
 
 namespace mojo {
@@ -14,14 +17,24 @@
 bool StructTraits<storage::mojom::StrictRelativePathDataView, base::FilePath>::
     Read(storage::mojom::StrictRelativePathDataView data, base::FilePath* out) {
   base::FilePath path;
-  if (!data.ReadPath(&path))
+  if (!data.ReadPath(&path)) {
     return false;
+  }
   if (path.IsAbsolute() || path.ReferencesParent()) {
     DLOG(ERROR) << "Rejecting non-relative or non-descending path: "
                 << path.value();
     return false;
   }
 
+#if BUILDFLAG(IS_WIN)
+  if (std::ranges::any_of(path.GetComponents(),
+                          &base::IsReservedNameOnWindows)) {
+    DLOG(ERROR) << "Rejecting path containing reserved Windows device name: "
+                << path.value();
+    return false;
+  }
+#endif
+
   *out = std::move(path);
   return true;
 }
diff --git a/base/files/file_util_unittest.cc b/base/files/file_util_unittest.cc
index 591de4bf..af06c8e 100644
--- a/base/files/file_util_unittest.cc
+++ b/base/files/file_util_unittest.cc
@@ -5631,7 +5631,6 @@
   ASSERT_EQ(second_file.GetLength(), 0);
 }
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 TEST_F(FileUtilTest, IsReservedNameOnWindows) {
   static constexpr auto kAllowedBasenames =
       std::to_array<const base::FilePath::CharType*>({
@@ -5640,15 +5639,24 @@
           FILE_PATH_LITERAL("a b.txt"),
           FILE_PATH_LITERAL("a-b.txt"),
           FILE_PATH_LITERAL("My Computer"),
+          FILE_PATH_LITERAL("conin$.txt"),
+          FILE_PATH_LITERAL("conout$.log"),
       });
 
   static constexpr auto kDisallowedBasenames =
       std::to_array<const base::FilePath::CharType*>({
           FILE_PATH_LITERAL("con"),
+          FILE_PATH_LITERAL("con "),
+          FILE_PATH_LITERAL("con. "),
           FILE_PATH_LITERAL("con.zip"),
+          FILE_PATH_LITERAL("conin$"),
+          FILE_PATH_LITERAL("conin$ "),
+          FILE_PATH_LITERAL("conout$"),
+          FILE_PATH_LITERAL("aux . "),
           FILE_PATH_LITERAL("NUL"),
           FILE_PATH_LITERAL("NUL.zip"),
           FILE_PATH_LITERAL("desktop.ini"),
+          FILE_PATH_LITERAL("desktop.ini "),
       });
 
   for (const base::FilePath::CharType* basename : kAllowedBasenames) {
@@ -5659,7 +5667,6 @@
     EXPECT_TRUE(IsReservedNameOnWindows(basename)) << basename;
   }
 }
-#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 
 }  // namespace
 
diff --git a/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits_unittest.cc b/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits_unittest.cc
new file mode 100644
index 0000000..7ee73c3
--- /dev/null
+++ b/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits.h"
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "components/services/storage/public/mojom/filesystem/directory.mojom.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace storage {
+namespace {
+
+TEST(StrictRelativePathTraitsTest, ValidRelativePaths) {
+  const base::FilePath test_paths[] = {
+      base::FilePath(FILE_PATH_LITERAL("file.txt")),
+      base::FilePath(FILE_PATH_LITERAL("dir/file.txt")),
+      base::FilePath(FILE_PATH_LITERAL("dir/subdir/file.txt")),
+  };
+
+  for (const auto& original : test_paths) {
+    base::FilePath deserialized;
+    EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+    EXPECT_EQ(original, deserialized);
+  }
+}
+
+TEST(StrictRelativePathTraitsTest, RejectsInvalidPaths) {
+  const base::FilePath test_paths[] = {
+      base::FilePath(FILE_PATH_LITERAL("../file.txt")),
+      base::FilePath(FILE_PATH_LITERAL("dir/../file.txt")),
+  };
+
+  for (const auto& original : test_paths) {
+    base::FilePath deserialized;
+    EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+  }
+}
+
+TEST(StrictRelativePathTraitsTest, RejectsReservedWindowsNames) {
+  const base::FilePath test_paths[] = {
+      base::FilePath(FILE_PATH_LITERAL("con")),
+      base::FilePath(FILE_PATH_LITERAL("con ")),
+      base::FilePath(FILE_PATH_LITERAL("con. ")),
+      base::FilePath(FILE_PATH_LITERAL("con.txt")),
+      base::FilePath(FILE_PATH_LITERAL("conin$")),
+      base::FilePath(FILE_PATH_LITERAL("conin$ ")),
+      base::FilePath(FILE_PATH_LITERAL("conout$")),
+      base::FilePath(FILE_PATH_LITERAL("nul")),
+      base::FilePath(FILE_PATH_LITERAL("dir1/nul")),
+      base::FilePath(FILE_PATH_LITERAL("dir1/aux/file.txt")),
+      base::FilePath(FILE_PATH_LITERAL("dir1/aux . /file.txt")),
+  };
+
+  for (const auto& original : test_paths) {
+    base::FilePath deserialized;
+#if BUILDFLAG(IS_WIN)
+    EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+#else
+    EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+    EXPECT_EQ(original, deserialized);
+#endif
+  }
+}
+
+TEST(StrictRelativePathTraitsTest, AllowsHarmlessExtensionsOnSpecialDevices) {
+  const base::FilePath test_paths[] = {
+      base::FilePath(FILE_PATH_LITERAL("conin$.txt")),
+      base::FilePath(FILE_PATH_LITERAL("conout$.log")),
+  };
+
+  for (const auto& original : test_paths) {
+    base::FilePath deserialized;
+    EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+    EXPECT_EQ(original, deserialized);
+  }
+}
+
+}  // namespace
+}  // namespace storage
diff --git a/components/services/storage/public/cpp/filesystem/BUILD.gn b/components/services/storage/public/cpp/filesystem/BUILD.gn
index 84608110..4aec4e5 100644
--- a/components/services/storage/public/cpp/filesystem/BUILD.gn
+++ b/components/services/storage/public/cpp/filesystem/BUILD.gn
@@ -41,12 +41,17 @@
 
 source_set("tests") {
   testonly = true
-  sources = [ "filesystem_proxy_unittest.cc" ]
+  sources = [
+    "filesystem_proxy_unittest.cc",
+    "strict_relative_path_mojom_traits_unittest.cc",
+  ]
   deps = [
     ":filesystem",
+    ":typemap_traits",
     "//base",
     "//base/test:test_support",
     "//components/services/storage/public/mojom/filesystem",
+    "//mojo/public/cpp/test_support:test_utils",
     "//testing/gmock",
     "//testing/gtest",
   ]
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/base/files/file_util_unittest.cc b/base/files/file_util_unittest.cc
index 591de4bf..af06c8e 100644
--- a/base/files/file_util_unittest.cc
+++ b/base/files/file_util_unittest.cc
@@ -5631,7 +5631,6 @@
   ASSERT_EQ(second_file.GetLength(), 0);
 }
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 TEST_F(FileUtilTest, IsReservedNameOnWindows) {
   static constexpr auto kAllowedBasenames =
       std::to_array<const base::FilePath::CharType*>({
@@ -5640,15 +5639,24 @@
           FILE_PATH_LITERAL("a b.txt"),
           FILE_PATH_LITERAL("a-b.txt"),
           FILE_PATH_LITERAL("My Computer"),
+          FILE_PATH_LITERAL("conin$.txt"),
+          FILE_PATH_LITERAL("conout$.log"),
       });
 
   static constexpr auto kDisallowedBasenames =
       std::to_array<const base::FilePath::CharType*>({
           FILE_PATH_LITERAL("con"),
+          FILE_PATH_LITERAL("con "),
+          FILE_PATH_LITERAL("con. "),
           FILE_PATH_LITERAL("con.zip"),
+          FILE_PATH_LITERAL("conin$"),
+          FILE_PATH_LITERAL("conin$ "),
+          FILE_PATH_LITERAL("conout$"),
+          FILE_PATH_LITERAL("aux . "),
           FILE_PATH_LITERAL("NUL"),
           FILE_PATH_LITERAL("NUL.zip"),
           FILE_PATH_LITERAL("desktop.ini"),
+          FILE_PATH_LITERAL("desktop.ini "),
       });
 
   for (const base::FilePath::CharType* basename : kAllowedBasenames) {
@@ -5659,7 +5667,6 @@
     EXPECT_TRUE(IsReservedNameOnWindows(basename)) << basename;
   }
 }
-#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
 
 }  // namespace
diff --git a/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits_unittest.cc b/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits_unittest.cc
new file mode 100644
index 0000000..7ee73c3
--- /dev/null
+++ b/components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/services/storage/public/cpp/filesystem/strict_relative_path_mojom_traits.h"
+
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+#include "components/services/storage/public/mojom/filesystem/directory.mojom.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace storage {
+namespace {
+
+TEST(StrictRelativePathTraitsTest, ValidRelativePaths) {
+  const base::FilePath test_paths[] = {
+      base::FilePath(FILE_PATH_LITERAL("file.txt")),
+      base::FilePath(FILE_PATH_LITERAL("dir/file.txt")),
+      base::FilePath(FILE_PATH_LITERAL("dir/subdir/file.txt")),
+  };
+
+  for (const auto& original : test_paths) {
+    base::FilePath deserialized;
+    EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+    EXPECT_EQ(original, deserialized);
+  }
+}
+
+TEST(StrictRelativePathTraitsTest, RejectsInvalidPaths) {
+  const base::FilePath test_paths[] = {
+      base::FilePath(FILE_PATH_LITERAL("../file.txt")),
+      base::FilePath(FILE_PATH_LITERAL("dir/../file.txt")),
+  };
+
+  for (const auto& original : test_paths) {
+    base::FilePath deserialized;
+    EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+  }
+}
+
+TEST(StrictRelativePathTraitsTest, RejectsReservedWindowsNames) {
+  const base::FilePath test_paths[] = {
+      base::FilePath(FILE_PATH_LITERAL("con")),
+      base::FilePath(FILE_PATH_LITERAL("con ")),
+      base::FilePath(FILE_PATH_LITERAL("con. ")),
+      base::FilePath(FILE_PATH_LITERAL("con.txt")),
+      base::FilePath(FILE_PATH_LITERAL("conin$")),
+      base::FilePath(FILE_PATH_LITERAL("conin$ ")),
+      base::FilePath(FILE_PATH_LITERAL("conout$")),
+      base::FilePath(FILE_PATH_LITERAL("nul")),
+      base::FilePath(FILE_PATH_LITERAL("dir1/nul")),
+      base::FilePath(FILE_PATH_LITERAL("dir1/aux/file.txt")),
+      base::FilePath(FILE_PATH_LITERAL("dir1/aux . /file.txt")),
+  };
+
+  for (const auto& original : test_paths) {
+    base::FilePath deserialized;
+#if BUILDFLAG(IS_WIN)
+    EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+#else
+    EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+    EXPECT_EQ(original, deserialized);
+#endif
+  }
+}
+
+TEST(StrictRelativePathTraitsTest, AllowsHarmlessExtensionsOnSpecialDevices) {
+  const base::FilePath test_paths[] = {
+      base::FilePath(FILE_PATH_LITERAL("conin$.txt")),
+      base::FilePath(FILE_PATH_LITERAL("conout$.log")),
+  };
+
+  for (const auto& original : test_paths) {
+    base::FilePath deserialized;
+    EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::StrictRelativePath>(
+        original, deserialized));
+    EXPECT_EQ(original, deserialized);
+  }
+}
+
+}  // namespace
+}  // namespace storage
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.