[clang] [clang-tools-extra] [llvm] [llvm][clang] Sandbox filesystem reads (PR #162151)

Jan Svoboda via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 16 10:55:54 PDT 2025


https://github.com/jansvoboda11 updated https://github.com/llvm/llvm-project/pull/162151

>From 0e29083706a92cb06f2a3fa564390dbb799104ee Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Wed, 16 Jul 2025 11:24:41 -0700
Subject: [PATCH 01/19] [llvm][clang] Sandbox IO operations (infrastructure)

---
 clang/lib/Driver/Job.cpp              |  5 ++++
 llvm/include/llvm/Support/IOSandbox.h | 20 +++++++++++++++
 llvm/lib/Support/IOSandbox.cpp        | 19 ++++++++++++++
 llvm/lib/Support/IOSandboxInternal.h  | 36 +++++++++++++++++++++++++++
 llvm/lib/Support/Signals.cpp          |  4 +++
 5 files changed, 84 insertions(+)
 create mode 100644 llvm/include/llvm/Support/IOSandbox.h
 create mode 100644 llvm/lib/Support/IOSandbox.cpp
 create mode 100644 llvm/lib/Support/IOSandboxInternal.h

diff --git a/clang/lib/Driver/Job.cpp b/clang/lib/Driver/Job.cpp
index 715429bcd2096..ab6cd2973dd6e 100644
--- a/clang/lib/Driver/Job.cpp
+++ b/clang/lib/Driver/Job.cpp
@@ -21,6 +21,7 @@
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/Support/CrashRecoveryContext.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/PrettyStackTrace.h"
 #include "llvm/Support/Program.h"
@@ -426,6 +427,10 @@ int CC1Command::Execute(ArrayRef<std::optional<StringRef>> Redirects,
   if (ExecutionFailed)
     *ExecutionFailed = false;
 
+  // Enabling the sandbox here allows us to restore its previous state even when
+  // this cc1 invocation crashes.
+  auto EnableSandbox = llvm::sys::sandbox_scoped_enable();
+
   llvm::CrashRecoveryContext CRC;
   CRC.DumpStackAndCleanupOnFailure = true;
 
diff --git a/llvm/include/llvm/Support/IOSandbox.h b/llvm/include/llvm/Support/IOSandbox.h
new file mode 100644
index 0000000000000..1a8fb9788e3f6
--- /dev/null
+++ b/llvm/include/llvm/Support/IOSandbox.h
@@ -0,0 +1,20 @@
+//===- IOSandbox.h ----------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_IOSANDBOX_H
+#define LLVM_SUPPORT_IOSANDBOX_H
+
+#include "llvm/Support/SaveAndRestore.h"
+
+namespace llvm::sys {
+SaveAndRestore<bool> sandbox_scoped_enable();
+SaveAndRestore<bool> sandbox_scoped_disable();
+void sandbox_violation_if_enabled();
+} // namespace llvm::sys
+
+#endif
diff --git a/llvm/lib/Support/IOSandbox.cpp b/llvm/lib/Support/IOSandbox.cpp
new file mode 100644
index 0000000000000..a046d38b48fc3
--- /dev/null
+++ b/llvm/lib/Support/IOSandbox.cpp
@@ -0,0 +1,19 @@
+#include "llvm/Support/IOSandbox.h"
+
+#include <cassert>
+
+using namespace llvm;
+
+thread_local bool IOSandboxEnabled = false;
+
+SaveAndRestore<bool> sys::sandbox_scoped_enable() {
+  return {IOSandboxEnabled, true};
+}
+
+SaveAndRestore<bool> sys::sandbox_scoped_disable() {
+  return {IOSandboxEnabled, false};
+}
+
+void sys::sandbox_violation_if_enabled() {
+  assert(!IOSandboxEnabled && "sandbox violation");
+}
diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
new file mode 100644
index 0000000000000..184b220417fff
--- /dev/null
+++ b/llvm/lib/Support/IOSandboxInternal.h
@@ -0,0 +1,36 @@
+#ifndef LLVM_SUPPORT_IOSANDBOXINTERNAL_H
+#define LLVM_SUPPORT_IOSANDBOXINTERNAL_H
+
+#include "llvm/Support/IOSandbox.h"
+
+namespace llvm {
+namespace detail {
+template <class FnTy> struct Interposed;
+
+template <class RetTy, class... ArgTy> struct Interposed<RetTy (*)(ArgTy...)> {
+  RetTy (*Fn)(ArgTy...);
+
+  RetTy operator()(ArgTy... Arg) const {
+    sys::sandbox_violation_if_enabled();
+    return Fn(std::forward<ArgTy>(Arg)...);
+  }
+};
+
+template <class RetTy, class... ArgTy>
+struct Interposed<RetTy (*)(ArgTy..., ...)> {
+  RetTy (*Fn)(ArgTy..., ...);
+
+  template <class... CVarArgTy>
+  RetTy operator()(ArgTy... Arg, CVarArgTy... CVarArg) const {
+    sys::sandbox_violation_if_enabled();
+    return Fn(std::forward<ArgTy>(Arg)..., std::forward<CVarArgTy>(CVarArg)...);
+  }
+};
+
+template <class FnTy> constexpr auto interpose(FnTy Fn) {
+  return Interposed<FnTy>{Fn};
+}
+} // namespace detail
+} // namespace llvm
+
+#endif
diff --git a/llvm/lib/Support/Signals.cpp b/llvm/lib/Support/Signals.cpp
index f8a14a45ddc3e..076ec293750af 100644
--- a/llvm/lib/Support/Signals.cpp
+++ b/llvm/lib/Support/Signals.cpp
@@ -23,6 +23,7 @@
 #include "llvm/Support/FileUtilities.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/ManagedStatic.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
@@ -95,6 +96,9 @@ CallBacksToRun() {
 
 // Signal-safe.
 void sys::RunSignalHandlers() {
+  // Let's not interfere with stack trace symbolication and friends.
+  auto BypassSandbox = sandbox_scoped_disable();
+
   for (CallbackAndCookie &RunMe : CallBacksToRun()) {
     auto Expected = CallbackAndCookie::Status::Initialized;
     auto Desired = CallbackAndCookie::Status::Executing;

>From be3530feb1f1d6f2798fae379a322ee77d09c566 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Wed, 16 Jul 2025 11:20:25 -0700
Subject: [PATCH 02/19] [llvm][clang] Sandbox IO operations (getting
 `vfs::RealFileSystem`)

---
 clang/lib/CodeGen/BackendUtil.cpp      | 3 +++
 clang/tools/driver/cc1_main.cpp        | 7 ++++++-
 clang/tools/driver/cc1as_main.cpp      | 6 +++++-
 llvm/lib/Support/VirtualFileSystem.cpp | 3 +++
 4 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 602068436101b..3066bd8313777 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -46,6 +46,7 @@
 #include "llvm/Support/BuryPointer.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/PrettyStackTrace.h"
 #include "llvm/Support/Program.h"
@@ -1434,6 +1435,8 @@ void clang::emitBackendOutput(CompilerInstance &CI, CodeGenOptions &CGOpts,
 
   std::unique_ptr<llvm::Module> EmptyModule;
   if (!CGOpts.ThinLTOIndexFile.empty()) {
+    // FIXME(sandboxing): Figure out how to support distributed indexing.
+    auto BypassSandbox = sys::sandbox_scoped_disable();
     // If we are performing a ThinLTO importing compile, load the function index
     // into memory and pass it into runThinLTOBackend, which will run the
     // function importer and invoke LTO passes.
diff --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp
index 49f8843515a35..f8588d0ac4549 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -38,6 +38,7 @@
 #include "llvm/Support/BuryPointer.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/ManagedStatic.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Process.h"
@@ -272,7 +273,11 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
       CompilerInvocation::GetResourcesPath(Argv0, MainAddr);
 
   /// Create the actual file system.
-  Clang->createVirtualFileSystem(llvm::vfs::getRealFileSystem(), DiagsBuffer);
+  auto VFS = [] {
+    auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+    return llvm::vfs::getRealFileSystem();
+  }();
+  Clang->createVirtualFileSystem(std::move(VFS), DiagsBuffer);
 
   // Create the actual diagnostics engine.
   Clang->createDiagnostics();
diff --git a/clang/tools/driver/cc1as_main.cpp b/clang/tools/driver/cc1as_main.cpp
index 50da2f8449a22..4210267664f5b 100644
--- a/clang/tools/driver/cc1as_main.cpp
+++ b/clang/tools/driver/cc1as_main.cpp
@@ -45,6 +45,7 @@
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FormattedStream.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Process.h"
@@ -672,7 +673,10 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   DiagClient->setPrefix("clang -cc1as");
   DiagnosticsEngine Diags(DiagnosticIDs::create(), DiagOpts, DiagClient);
 
-  auto VFS = vfs::getRealFileSystem();
+  auto VFS = [] {
+    auto BypassSandbox = sys::sandbox_scoped_disable();
+    return vfs::getRealFileSystem();
+  }();
 
   // Set an error handler, so that any LLVM backend diagnostics go through our
   // error handler.
diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp
index c754b30d8de4a..1d27d02a27f47 100644
--- a/llvm/lib/Support/VirtualFileSystem.cpp
+++ b/llvm/lib/Support/VirtualFileSystem.cpp
@@ -31,6 +31,7 @@
 #include "llvm/Support/ErrorOr.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FileSystem/UniqueID.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/SMLoc.h"
@@ -399,10 +400,12 @@ void RealFileSystem::printImpl(raw_ostream &OS, PrintType Type,
 IntrusiveRefCntPtr<FileSystem> vfs::getRealFileSystem() {
   static IntrusiveRefCntPtr<FileSystem> FS =
       makeIntrusiveRefCnt<RealFileSystem>(true);
+  sys::sandbox_violation_if_enabled();
   return FS;
 }
 
 std::unique_ptr<FileSystem> vfs::createPhysicalFileSystem() {
+  sys::sandbox_violation_if_enabled();
   return std::make_unique<RealFileSystem>(false);
 }
 

>From 9c944d688bab8e8ca3f776547021b1f564e8b00c Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Thu, 10 Jul 2025 08:01:31 -0700
Subject: [PATCH 03/19] [llvm][clang] Sandbox IO operations (read, pread)

---
 clang/lib/Serialization/GlobalModuleIndex.cpp |  4 ++
 clang/tools/driver/cc1_main.cpp               | 20 +++---
 clang/tools/driver/cc1as_main.cpp             |  7 +-
 llvm/lib/Support/IOSandboxInternal.h          | 10 +++
 llvm/lib/Support/Unix/Path.inc                | 68 ++++++++++---------
 llvm/lib/Support/VirtualFileSystem.cpp        | 11 ++-
 6 files changed, 76 insertions(+), 44 deletions(-)

diff --git a/clang/lib/Serialization/GlobalModuleIndex.cpp b/clang/lib/Serialization/GlobalModuleIndex.cpp
index 1e2272c48bd04..613121a9596fd 100644
--- a/clang/lib/Serialization/GlobalModuleIndex.cpp
+++ b/clang/lib/Serialization/GlobalModuleIndex.cpp
@@ -24,6 +24,7 @@
 #include "llvm/Bitstream/BitstreamWriter.h"
 #include "llvm/Support/DJB.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/LockFileManager.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/OnDiskHashTable.h"
@@ -250,6 +251,9 @@ GlobalModuleIndex::~GlobalModuleIndex() {
 
 std::pair<GlobalModuleIndex *, llvm::Error>
 GlobalModuleIndex::readIndex(StringRef Path) {
+  // This is a compiler-internal input/output, let's bypass the sandbox.
+  auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+
   // Load the index file, if it's there.
   llvm::SmallString<128> IndexPath;
   IndexPath += Path;
diff --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp
index f8588d0ac4549..af30ba68dfde4 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -307,15 +307,19 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
 
   // If any timers were active but haven't been destroyed yet, print their
   // results now.  This happens in -disable-free mode.
-  std::unique_ptr<raw_ostream> IOFile = llvm::CreateInfoOutputFile();
-  if (Clang->getCodeGenOpts().TimePassesJson) {
-    *IOFile << "{\n";
-    llvm::TimerGroup::printAllJSONValues(*IOFile, "");
-    *IOFile << "\n}\n";
-  } else if (!Clang->getCodeGenOpts().TimePassesStatsFile) {
-    llvm::TimerGroup::printAll(*IOFile);
+  {
+    // This isn't a formal input or output of the compiler.
+    auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+    std::unique_ptr<raw_ostream> IOFile = llvm::CreateInfoOutputFile();
+    if (Clang->getCodeGenOpts().TimePassesJson) {
+      *IOFile << "{\n";
+      llvm::TimerGroup::printAllJSONValues(*IOFile, "");
+      *IOFile << "\n}\n";
+    } else if (!Clang->getCodeGenOpts().TimePassesStatsFile) {
+      llvm::TimerGroup::printAll(*IOFile);
+    }
+    llvm::TimerGroup::clearAll();
   }
-  llvm::TimerGroup::clearAll();
 
   if (llvm::timeTraceProfilerEnabled()) {
     // It is possible that the compiler instance doesn't own a file manager here
diff --git a/clang/tools/driver/cc1as_main.cpp b/clang/tools/driver/cc1as_main.cpp
index 4210267664f5b..285a128add1e1 100644
--- a/clang/tools/driver/cc1as_main.cpp
+++ b/clang/tools/driver/cc1as_main.cpp
@@ -426,8 +426,11 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
   if (!TheTarget)
     return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str();
 
-  ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
-      MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
+  ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = [=] {
+    // FIXME(sandboxing): Make this a proper input file.
+    auto BypassSandbox = sys::sandbox_scoped_disable();
+    return MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
+  }();
 
   if (std::error_code EC = Buffer.getError()) {
     return Diags.Report(diag::err_fe_error_reading)
diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
index 184b220417fff..72845966d5eb3 100644
--- a/llvm/lib/Support/IOSandboxInternal.h
+++ b/llvm/lib/Support/IOSandboxInternal.h
@@ -3,6 +3,13 @@
 
 #include "llvm/Support/IOSandbox.h"
 
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+extern thread_local bool IOSandboxEnabled;
+
 namespace llvm {
 namespace detail {
 template <class FnTy> struct Interposed;
@@ -31,6 +38,9 @@ template <class FnTy> constexpr auto interpose(FnTy Fn) {
   return Interposed<FnTy>{Fn};
 }
 } // namespace detail
+
+static constexpr auto read = detail::interpose(::read);
+static constexpr auto pread = detail::interpose(::pread);
 } // namespace llvm
 
 #endif
diff --git a/llvm/lib/Support/Unix/Path.inc b/llvm/lib/Support/Unix/Path.inc
index 0d991ead72416..fa27f86241b7d 100644
--- a/llvm/lib/Support/Unix/Path.inc
+++ b/llvm/lib/Support/Unix/Path.inc
@@ -114,6 +114,8 @@ typedef uint_t uint;
 #define STATVFS_F_FLAG(vfs) (vfs).f_flags
 #endif
 
+#include "IOSandboxInternal.h"
+
 using namespace llvm;
 
 namespace llvm {
@@ -197,7 +199,7 @@ std::string getMainExecutable(const char *argv0, void *MainAddr) {
   uint32_t size = sizeof(exe_path);
   if (_NSGetExecutablePath(exe_path, &size) == 0) {
     char link_path[PATH_MAX];
-    if (realpath(exe_path, link_path))
+    if (::realpath(exe_path, link_path))
       return link_path;
   }
 #elif defined(__FreeBSD__)
@@ -368,7 +370,7 @@ ErrorOr<space_info> disk_space(const Twine &Path) {
 std::error_code current_path(SmallVectorImpl<char> &result) {
   result.clear();
 
-  const char *pwd = ::getenv("PWD");
+  const char *pwd = getenv("PWD");
   llvm::sys::fs::file_status PWDStatus, DotStatus;
   if (pwd && llvm::sys::path::is_absolute(pwd) &&
       !llvm::sys::fs::status(pwd, PWDStatus) &&
@@ -381,7 +383,7 @@ std::error_code current_path(SmallVectorImpl<char> &result) {
   result.resize_for_overwrite(PATH_MAX);
 
   while (true) {
-    if (::getcwd(result.data(), result.size()) == nullptr) {
+    if (getcwd(result.data(), result.size()) == nullptr) {
       // See if there was a real error.
       if (errno != ENOMEM) {
         result.clear();
@@ -402,7 +404,7 @@ std::error_code set_current_path(const Twine &path) {
   SmallString<128> path_storage;
   StringRef p = path.toNullTerminatedStringRef(path_storage);
 
-  if (::chdir(p.begin()) == -1)
+  if (chdir(p.begin()) == -1)
     return errnoAsErrorCode();
 
   return std::error_code();
@@ -413,7 +415,7 @@ std::error_code create_directory(const Twine &path, bool IgnoreExisting,
   SmallString<128> path_storage;
   StringRef p = path.toNullTerminatedStringRef(path_storage);
 
-  if (::mkdir(p.begin(), Perms) == -1) {
+  if (mkdir(p.begin(), Perms) == -1) {
     if (errno != EEXIST || !IgnoreExisting)
       return errnoAsErrorCode();
   }
@@ -430,7 +432,7 @@ std::error_code create_link(const Twine &to, const Twine &from) {
   StringRef f = from.toNullTerminatedStringRef(from_storage);
   StringRef t = to.toNullTerminatedStringRef(to_storage);
 
-  if (::symlink(t.begin(), f.begin()) == -1)
+  if (symlink(t.begin(), f.begin()) == -1)
     return errnoAsErrorCode();
 
   return std::error_code();
@@ -443,7 +445,7 @@ std::error_code create_hard_link(const Twine &to, const Twine &from) {
   StringRef f = from.toNullTerminatedStringRef(from_storage);
   StringRef t = to.toNullTerminatedStringRef(to_storage);
 
-  if (::link(t.begin(), f.begin()) == -1)
+  if (link(t.begin(), f.begin()) == -1)
     return errnoAsErrorCode();
 
   return std::error_code();
@@ -594,7 +596,7 @@ std::error_code rename(const Twine &from, const Twine &to) {
 std::error_code resize_file(int FD, uint64_t Size) {
   // Use ftruncate as a fallback. It may or may not allocate space. At least on
   // OS X with HFS+ it does.
-  if (::ftruncate(FD, Size) == -1)
+  if (ftruncate(FD, Size) == -1)
     return errnoAsErrorCode();
 
   return std::error_code();
@@ -764,13 +766,13 @@ std::error_code status(const Twine &Path, file_status &Result, bool Follow) {
   StringRef P = Path.toNullTerminatedStringRef(PathStorage);
 
   struct stat Status;
-  int StatRet = (Follow ? ::stat : ::lstat)(P.begin(), &Status);
+  int StatRet = (Follow ? stat : lstat)(P.begin(), &Status);
   return fillStatus(StatRet, Status, Result);
 }
 
 std::error_code status(int FD, file_status &Result) {
   struct stat Status;
-  int StatRet = ::fstat(FD, &Status);
+  int StatRet = fstat(FD, &Status);
   return fillStatus(StatRet, Status, Result);
 }
 
@@ -786,13 +788,13 @@ std::error_code setPermissions(const Twine &Path, perms Permissions) {
   SmallString<128> PathStorage;
   StringRef P = Path.toNullTerminatedStringRef(PathStorage);
 
-  if (::chmod(P.begin(), Permissions))
+  if (chmod(P.begin(), Permissions))
     return errnoAsErrorCode();
   return std::error_code();
 }
 
 std::error_code setPermissions(int FD, perms Permissions) {
-  if (::fchmod(FD, Permissions))
+  if (fchmod(FD, Permissions))
     return errnoAsErrorCode();
   return std::error_code();
 }
@@ -803,7 +805,7 @@ std::error_code setLastAccessAndModificationTime(int FD, TimePoint<> AccessTime,
   timespec Times[2];
   Times[0] = sys::toTimeSpec(AccessTime);
   Times[1] = sys::toTimeSpec(ModificationTime);
-  if (::futimens(FD, Times))
+  if (futimens(FD, Times))
     return errnoAsErrorCode();
   return std::error_code();
 #elif defined(HAVE_FUTIMES)
@@ -813,7 +815,7 @@ std::error_code setLastAccessAndModificationTime(int FD, TimePoint<> AccessTime,
   Times[1] =
       sys::toTimeVal(std::chrono::time_point_cast<std::chrono::microseconds>(
           ModificationTime));
-  if (::futimes(FD, Times))
+  if (futimes(FD, Times))
     return errnoAsErrorCode();
   return std::error_code();
 #elif defined(__MVS__)
@@ -861,7 +863,7 @@ std::error_code mapped_file_region::init(int FD, uint64_t Offset,
   }
 #endif // #if defined (__APPLE__)
 
-  Mapping = ::mmap(nullptr, Size, prot, flags, FD, Offset);
+  Mapping = mmap(nullptr, Size, prot, flags, FD, Offset);
   if (Mapping == MAP_FAILED)
     return errnoAsErrorCode();
   return std::error_code();
@@ -878,7 +880,7 @@ mapped_file_region::mapped_file_region(int fd, mapmode mode, size_t length,
 
 void mapped_file_region::unmapImpl() {
   if (Mapping)
-    ::munmap(Mapping, Size);
+    munmap(Mapping, Size);
 }
 
 std::error_code mapped_file_region::sync() const {
@@ -906,7 +908,7 @@ std::error_code detail::directory_iterator_construct(detail::DirIterState &it,
                                                      StringRef path,
                                                      bool follow_symlinks) {
   SmallString<128> path_null(path);
-  DIR *directory = ::opendir(path_null.c_str());
+  DIR *directory = opendir(path_null.c_str());
   if (!directory)
     return errnoAsErrorCode();
 
@@ -919,7 +921,7 @@ std::error_code detail::directory_iterator_construct(detail::DirIterState &it,
 
 std::error_code detail::directory_iterator_destruct(detail::DirIterState &it) {
   if (it.IterationHandle)
-    ::closedir(reinterpret_cast<DIR *>(it.IterationHandle));
+    closedir(reinterpret_cast<DIR *>(it.IterationHandle));
   it.IterationHandle = 0;
   it.CurrentEntry = directory_entry();
   return std::error_code();
@@ -941,7 +943,7 @@ static file_type direntType(dirent *Entry) {
 
 std::error_code detail::directory_iterator_increment(detail::DirIterState &It) {
   errno = 0;
-  dirent *CurDir = ::readdir(reinterpret_cast<DIR *>(It.IterationHandle));
+  dirent *CurDir = readdir(reinterpret_cast<DIR *>(It.IterationHandle));
   if (CurDir == nullptr && errno != 0) {
     return errnoAsErrorCode();
   } else if (CurDir != nullptr) {
@@ -976,7 +978,7 @@ ErrorOr<basic_file_status> directory_entry::status() const {
 static bool hasProcSelfFD() {
   // If we have a /proc filesystem mounted, we can quickly establish the
   // real name of the file with readlink
-  static const bool Result = (::access("/proc/self/fd", R_OK) == 0);
+  static const bool Result = (access("/proc/self/fd", R_OK) == 0);
   return Result;
 }
 #endif
@@ -1031,9 +1033,9 @@ std::error_code openFile(const Twine &Name, int &ResultFD,
 
   SmallString<128> Storage;
   StringRef P = Name.toNullTerminatedStringRef(Storage);
-  // Call ::open in a lambda to avoid overload resolution in RetryAfterSignal
+  // Call open in a lambda to avoid overload resolution in RetryAfterSignal
   // when open is overloaded, such as in Bionic.
-  auto Open = [&]() { return ::open(P.begin(), OpenFlags, Mode); };
+  auto Open = [&]() { return open(P.begin(), OpenFlags, Mode); };
   if ((ResultFD = sys::RetryAfterSignal(-1, Open)) < 0)
     return errnoAsErrorCode();
 #ifndef O_CLOEXEC
@@ -1155,7 +1157,7 @@ std::error_code openFileForRead(const Twine &Name, int &ResultFD,
   // When F_GETPATH is availble, it is the quickest way to get
   // the real path name.
   char Buffer[PATH_MAX];
-  if (::fcntl(ResultFD, F_GETPATH, Buffer) != -1)
+  if (fcntl(ResultFD, F_GETPATH, Buffer) != -1)
     RealPath->append(Buffer, Buffer + strlen(Buffer));
 #else
   char Buffer[PATH_MAX];
@@ -1163,7 +1165,7 @@ std::error_code openFileForRead(const Twine &Name, int &ResultFD,
   if (hasProcSelfFD()) {
     char ProcPath[64];
     snprintf(ProcPath, sizeof(ProcPath), "/proc/self/fd/%d", ResultFD);
-    ssize_t CharCount = ::readlink(ProcPath, Buffer, sizeof(Buffer));
+    ssize_t CharCount = readlink(ProcPath, Buffer, sizeof(Buffer));
     if (CharCount > 0)
       RealPath->append(Buffer, Buffer + CharCount);
   } else {
@@ -1171,8 +1173,8 @@ std::error_code openFileForRead(const Twine &Name, int &ResultFD,
     SmallString<128> Storage;
     StringRef P = Name.toNullTerminatedStringRef(Storage);
 
-    // Use ::realpath to get the real path name
-    if (::realpath(P.begin(), Buffer) != nullptr)
+    // Use realpath to get the real path name
+    if (realpath(P.begin(), Buffer) != nullptr)
       RealPath->append(Buffer, Buffer + strlen(Buffer));
 #if defined(TRY_PROC_SELF_FD)
   }
@@ -1224,11 +1226,11 @@ Expected<size_t> readNativeFileSlice(file_t FD, MutableArrayRef<char> Buf,
 #endif
 #ifdef HAVE_PREAD
   ssize_t NumRead =
-      sys::RetryAfterSignal(-1, ::pread, FD, Buf.data(), Size, Offset);
+      sys::RetryAfterSignal(-1, pread, FD, Buf.data(), Size, Offset);
 #else
   if (lseek(FD, Offset, SEEK_SET) == -1)
     return errorCodeToError(errnoAsErrorCode());
-  ssize_t NumRead = sys::RetryAfterSignal(-1, ::read, FD, Buf.data(), Size);
+  ssize_t NumRead = sys::RetryAfterSignal(-1, read, FD, Buf.data(), Size);
 #endif
   if (NumRead == -1)
     return errorCodeToError(errnoAsErrorCode());
@@ -1253,7 +1255,7 @@ std::error_code tryLockFile(int FD, std::chrono::milliseconds Timeout,
     Lock.l_whence = SEEK_SET;
     Lock.l_start = 0;
     Lock.l_len = 0;
-    if (::fcntl(FD, F_SETLK, &Lock) != -1)
+    if (fcntl(FD, F_SETLK, &Lock) != -1)
       return std::error_code();
     int Error = errno;
     if (Error != EACCES && Error != EAGAIN)
@@ -1279,7 +1281,7 @@ std::error_code lockFile(int FD, LockKind Kind) {
   Lock.l_whence = SEEK_SET;
   Lock.l_start = 0;
   Lock.l_len = 0;
-  if (::fcntl(FD, F_SETLKW, &Lock) != -1)
+  if (fcntl(FD, F_SETLKW, &Lock) != -1)
     return std::error_code();
   return errnoAsErrorCode();
 }
@@ -1290,7 +1292,7 @@ std::error_code unlockFile(int FD) {
   Lock.l_whence = SEEK_SET;
   Lock.l_start = 0;
   Lock.l_len = 0;
-  if (::fcntl(FD, F_SETLK, &Lock) != -1)
+  if (fcntl(FD, F_SETLK, &Lock) != -1)
     return std::error_code();
   return errnoAsErrorCode();
 }
@@ -1357,14 +1359,14 @@ std::error_code real_path(const Twine &path, SmallVectorImpl<char> &dest,
   SmallString<128> Storage;
   StringRef P = path.toNullTerminatedStringRef(Storage);
   char Buffer[PATH_MAX];
-  if (::realpath(P.begin(), Buffer) == nullptr)
+  if (realpath(P.begin(), Buffer) == nullptr)
     return errnoAsErrorCode();
   dest.append(Buffer, Buffer + strlen(Buffer));
   return std::error_code();
 }
 
 std::error_code changeFileOwnership(int FD, uint32_t Owner, uint32_t Group) {
-  auto FChown = [&]() { return ::fchown(FD, Owner, Group); };
+  auto FChown = [&]() { return fchown(FD, Owner, Group); };
   // Retry if fchown call fails due to interruption.
   if ((sys::RetryAfterSignal(-1, FChown)) < 0)
     return errnoAsErrorCode();
diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp
index 1d27d02a27f47..ecd50a8004f70 100644
--- a/llvm/lib/Support/VirtualFileSystem.cpp
+++ b/llvm/lib/Support/VirtualFileSystem.cpp
@@ -222,6 +222,7 @@ RealFile::~RealFile() { close(); }
 ErrorOr<Status> RealFile::status() {
   assert(FD != kInvalidFile && "cannot stat closed file");
   if (!S.isStatusKnown()) {
+    auto BypassSandbox = sys::sandbox_scoped_disable();
     file_status RealStatus;
     if (std::error_code EC = sys::fs::status(FD, RealStatus))
       return EC;
@@ -238,6 +239,7 @@ ErrorOr<std::unique_ptr<MemoryBuffer>>
 RealFile::getBuffer(const Twine &Name, int64_t FileSize,
                     bool RequiresNullTerminator, bool IsVolatile) {
   assert(FD != kInvalidFile && "cannot get buffer for closed file");
+  auto BypassSandbox = sys::sandbox_scoped_disable();
   return MemoryBuffer::getOpenFile(FD, Name, FileSize, RequiresNullTerminator,
                                    IsVolatile);
 }
@@ -307,6 +309,7 @@ class RealFileSystem : public FileSystem {
 
   ErrorOr<std::unique_ptr<File>>
   openFileForReadWithFlags(const Twine &Name, sys::fs::OpenFlags Flags) {
+    auto BypassSandbox = sys::sandbox_scoped_disable();
     SmallString<256> RealName, Storage;
     Expected<file_t> FDOrErr = sys::fs::openNativeFileForRead(
         adjustPath(Name, Storage), Flags, &RealName);
@@ -328,6 +331,7 @@ class RealFileSystem : public FileSystem {
 } // namespace
 
 ErrorOr<Status> RealFileSystem::status(const Twine &Path) {
+  auto BypassSandbox = sys::sandbox_scoped_disable();
   SmallString<256> Storage;
   sys::fs::file_status RealStatus;
   if (std::error_code EC =
@@ -352,6 +356,7 @@ llvm::ErrorOr<std::string> RealFileSystem::getCurrentWorkingDirectory() const {
   if (WD)
     return WD->getError();
 
+  auto BypassSandbox = sys::sandbox_scoped_disable();
   SmallString<128> Dir;
   if (std::error_code EC = llvm::sys::fs::current_path(Dir))
     return EC;
@@ -382,6 +387,7 @@ std::error_code RealFileSystem::isLocal(const Twine &Path, bool &Result) {
 
 std::error_code RealFileSystem::getRealPath(const Twine &Path,
                                             SmallVectorImpl<char> &Output) {
+  auto BypassSandbox = sys::sandbox_scoped_disable();
   SmallString<256> Storage;
   return llvm::sys::fs::real_path(adjustPath(Path, Storage), Output);
 }
@@ -415,12 +421,15 @@ class RealFSDirIter : public llvm::vfs::detail::DirIterImpl {
   llvm::sys::fs::directory_iterator Iter;
 
 public:
-  RealFSDirIter(const Twine &Path, std::error_code &EC) : Iter(Path, EC) {
+  RealFSDirIter(const Twine &Path, std::error_code &EC) {
+    auto BypassSandbox = sys::sandbox_scoped_disable();
+    Iter = llvm::sys::fs::directory_iterator{Path, EC};
     if (Iter != llvm::sys::fs::directory_iterator())
       CurrentEntry = directory_entry(Iter->path(), Iter->type());
   }
 
   std::error_code increment() override {
+    auto BypassSandbox = sys::sandbox_scoped_disable();
     std::error_code EC;
     Iter.increment(EC);
     CurrentEntry = (Iter == llvm::sys::fs::directory_iterator())

>From 4a28775603ec70b80d1359509b968dad1bbcfb6f Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Thu, 10 Jul 2025 14:11:14 -0700
Subject: [PATCH 04/19] [llvm][clang] Sandbox IO operations (mmap)

---
 clang/lib/Driver/Driver.cpp          | 3 +++
 llvm/lib/Support/IOSandboxInternal.h | 4 ++++
 2 files changed, 7 insertions(+)

diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 40ea513e85427..7cf23fdb2b97b 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -87,6 +87,7 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FileUtilities.h"
 #include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/MD5.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/PrettyStackTrace.h"
@@ -1870,6 +1871,8 @@ bool Driver::getCrashDiagnosticFile(StringRef ReproCrashFilename,
   using namespace llvm::sys;
   assert(llvm::Triple(llvm::sys::getProcessTriple()).isOSDarwin() &&
          "Only knows about .crash files on Darwin");
+  // This is not a formal output of the compiler, let's bypass the sandbox.
+  auto BypassSandbox = sandbox_scoped_disable();
 
   // The .crash file can be found on at ~/Library/Logs/DiagnosticReports/
   // (or /Library/Logs/DiagnosticReports for root) and has the filename pattern
diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
index 72845966d5eb3..f784ef7131403 100644
--- a/llvm/lib/Support/IOSandboxInternal.h
+++ b/llvm/lib/Support/IOSandboxInternal.h
@@ -7,6 +7,9 @@
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
 
 extern thread_local bool IOSandboxEnabled;
 
@@ -41,6 +44,7 @@ template <class FnTy> constexpr auto interpose(FnTy Fn) {
 
 static constexpr auto read = detail::interpose(::read);
 static constexpr auto pread = detail::interpose(::pread);
+static constexpr auto mmap = detail::interpose(::mmap);
 } // namespace llvm
 
 #endif

>From 1b017822596f2206c4b343aaff79cc65c4ffa3d3 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Thu, 10 Jul 2025 14:31:20 -0700
Subject: [PATCH 05/19] [llvm][clang] Sandbox IO operations (readdir)

---
 clang/lib/Serialization/GlobalModuleIndex.cpp | 3 +++
 clang/lib/Serialization/ModuleCache.cpp       | 4 ++++
 llvm/lib/Support/IOSandboxInternal.h          | 2 ++
 3 files changed, 9 insertions(+)

diff --git a/clang/lib/Serialization/GlobalModuleIndex.cpp b/clang/lib/Serialization/GlobalModuleIndex.cpp
index 613121a9596fd..99d90054cb09f 100644
--- a/clang/lib/Serialization/GlobalModuleIndex.cpp
+++ b/clang/lib/Serialization/GlobalModuleIndex.cpp
@@ -847,6 +847,9 @@ llvm::Error
 GlobalModuleIndex::writeIndex(FileManager &FileMgr,
                               const PCHContainerReader &PCHContainerRdr,
                               StringRef Path) {
+  // This is a compiler-internal input/output, let's bypass the sandbox.
+  auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+
   llvm::SmallString<128> IndexPath;
   IndexPath += Path;
   llvm::sys::path::append(IndexPath, IndexFileName);
diff --git a/clang/lib/Serialization/ModuleCache.cpp b/clang/lib/Serialization/ModuleCache.cpp
index 9850956380423..28772da83b465 100644
--- a/clang/lib/Serialization/ModuleCache.cpp
+++ b/clang/lib/Serialization/ModuleCache.cpp
@@ -11,6 +11,7 @@
 #include "clang/Serialization/InMemoryModuleCache.h"
 #include "clang/Serialization/ModuleFile.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/LockFileManager.h"
 #include "llvm/Support/Path.h"
 
@@ -27,6 +28,9 @@ void clang::maybePruneImpl(StringRef Path, time_t PruneInterval,
   if (PruneInterval <= 0 || PruneAfter <= 0)
     return;
 
+  // This is a compiler-internal input/output, let's bypass the sandbox.
+  auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+
   llvm::SmallString<128> TimestampFile(Path);
   llvm::sys::path::append(TimestampFile, "modules.timestamp");
 
diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
index f784ef7131403..163c0cfc9dad8 100644
--- a/llvm/lib/Support/IOSandboxInternal.h
+++ b/llvm/lib/Support/IOSandboxInternal.h
@@ -10,6 +10,7 @@
 #ifdef HAVE_SYS_MMAN_H
 #include <sys/mman.h>
 #endif
+#include <dirent.h>
 
 extern thread_local bool IOSandboxEnabled;
 
@@ -45,6 +46,7 @@ template <class FnTy> constexpr auto interpose(FnTy Fn) {
 static constexpr auto read = detail::interpose(::read);
 static constexpr auto pread = detail::interpose(::pread);
 static constexpr auto mmap = detail::interpose(::mmap);
+static constexpr auto readdir = detail::interpose(::readdir);
 } // namespace llvm
 
 #endif

>From 85f9ca62a382acb393619c53e8032a9395efddb3 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Thu, 10 Jul 2025 15:46:01 -0700
Subject: [PATCH 06/19] [llvm][clang] Sandbox IO operations (stat, lstat,
 fstat)

---
 clang/lib/Serialization/ModuleCache.cpp           | 2 ++
 clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp | 4 ++++
 llvm/lib/Support/IOSandboxInternal.h              | 4 ++++
 llvm/lib/Support/LockFileManager.cpp              | 5 +++++
 llvm/lib/Support/VirtualOutputBackends.cpp        | 5 +++++
 llvm/lib/Support/raw_ostream.cpp                  | 4 ++++
 6 files changed, 24 insertions(+)

diff --git a/clang/lib/Serialization/ModuleCache.cpp b/clang/lib/Serialization/ModuleCache.cpp
index 28772da83b465..6a3675620977d 100644
--- a/clang/lib/Serialization/ModuleCache.cpp
+++ b/clang/lib/Serialization/ModuleCache.cpp
@@ -119,6 +119,8 @@ class CrossProcessModuleCache : public ModuleCache {
   }
 
   std::time_t getModuleTimestamp(StringRef ModuleFilename) override {
+    // This is a compiler-internal input/output, let's bypass the sandbox.
+    auto SandboxBypass = llvm::sys::sandbox_scoped_disable();
     std::string TimestampFilename =
         serialization::ModuleFile::getTimestampFilename(ModuleFilename);
     llvm::sys::fs::file_status Status;
diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
index 217b853305ed1..11e53a5132563 100644
--- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
+++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
@@ -37,6 +37,7 @@
 #include "llvm/Support/Errc.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
 #include <cassert>
@@ -257,6 +258,9 @@ void HTMLDiagnostics::FlushDiagnosticsImpl(
 
 void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
                                  FilesMade *filesMade) {
+  // FIXME(sandboxing): Remove this by adopting `llvm::vfs::OutputBackend`.
+  auto SandboxBypass = llvm::sys::sandbox_scoped_disable();
+
   // Create the HTML directory if it is missing.
   if (!createdDir) {
     createdDir = true;
diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
index 163c0cfc9dad8..044b7dd9bd781 100644
--- a/llvm/lib/Support/IOSandboxInternal.h
+++ b/llvm/lib/Support/IOSandboxInternal.h
@@ -4,6 +4,7 @@
 #include "llvm/Support/IOSandbox.h"
 
 #include <stdio.h>
+#include <sys/stat.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
@@ -47,6 +48,9 @@ static constexpr auto read = detail::interpose(::read);
 static constexpr auto pread = detail::interpose(::pread);
 static constexpr auto mmap = detail::interpose(::mmap);
 static constexpr auto readdir = detail::interpose(::readdir);
+static constexpr auto stat = detail::interpose(::stat);
+static constexpr auto lstat = detail::interpose(::lstat);
+static constexpr auto fstat = detail::interpose(::fstat);
 } // namespace llvm
 
 #endif
diff --git a/llvm/lib/Support/LockFileManager.cpp b/llvm/lib/Support/LockFileManager.cpp
index cdded51108b50..54c42e4dd53e6 100644
--- a/llvm/lib/Support/LockFileManager.cpp
+++ b/llvm/lib/Support/LockFileManager.cpp
@@ -14,6 +14,7 @@
 #include "llvm/Support/ErrorOr.h"
 #include "llvm/Support/ExponentialBackoff.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Process.h"
 #include "llvm/Support/Signals.h"
@@ -51,6 +52,8 @@ using namespace llvm;
 /// \returns The process ID of the process that owns this lock file
 std::optional<LockFileManager::OwnedByAnother>
 LockFileManager::readLockFile(StringRef LockFileName) {
+  auto SandboxBypass = sys::sandbox_scoped_disable();
+
   // Read the owning host and PID out of the lock file. If it appears that the
   // owning process is dead, the lock file is invalid.
   ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr =
@@ -246,6 +249,8 @@ Expected<bool> LockFileManager::tryLock() {
 }
 
 LockFileManager::~LockFileManager() {
+  auto SandboxBypass = sys::sandbox_scoped_disable();
+
   if (!std::holds_alternative<OwnedByUs>(Owner))
     return;
 
diff --git a/llvm/lib/Support/VirtualOutputBackends.cpp b/llvm/lib/Support/VirtualOutputBackends.cpp
index de59b8ab63a53..82ee6df891120 100644
--- a/llvm/lib/Support/VirtualOutputBackends.cpp
+++ b/llvm/lib/Support/VirtualOutputBackends.cpp
@@ -18,6 +18,7 @@
 #include "llvm/Support/VirtualOutputBackends.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/LockFileManager.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
@@ -553,6 +554,8 @@ Error OnDiskOutputFile::keep() {
 }
 
 Error OnDiskOutputFile::discard() {
+  auto BypassSandbox = sys::sandbox_scoped_disable();
+
   // Destroy the streams to flush them.
   if (auto E = reset())
     return E;
@@ -582,6 +585,8 @@ Error OnDiskOutputBackend::makeAbsolute(SmallVectorImpl<char> &Path) const {
 Expected<std::unique_ptr<OutputFileImpl>>
 OnDiskOutputBackend::createFileImpl(StringRef Path,
                                     std::optional<OutputConfig> Config) {
+  auto BypassSandbox = sys::sandbox_scoped_disable();
+
   SmallString<256> AbsPath;
   if (Path != "-") {
     AbsPath = Path;
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index 07b99896543bd..0ea5f024c82c5 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -20,6 +20,7 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/NativeFormatting.h"
 #include "llvm/Support/Process.h"
@@ -617,6 +618,9 @@ raw_fd_ostream::raw_fd_ostream(StringRef Filename, std::error_code &EC,
 raw_fd_ostream::raw_fd_ostream(int fd, bool shouldClose, bool unbuffered,
                                OStreamKind K)
     : raw_pwrite_stream(unbuffered, K), FD(fd), ShouldClose(shouldClose) {
+  // FIXME(sandboxing): Remove this by adopting `llvm::vfs::OutputBackend`.
+  auto BypassSandbox = sys::sandbox_scoped_disable();
+
   if (FD < 0 ) {
     ShouldClose = false;
     return;

>From 637ff5eeac4e4f4c2f5230e6ffffb3a0d14d816c Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 15 Jul 2025 15:50:29 -0700
Subject: [PATCH 07/19] [llvm][clang] Sandbox IO operations (getcwd)

---
 llvm/lib/Support/IOSandboxInternal.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
index 044b7dd9bd781..9e7bb7d6f4567 100644
--- a/llvm/lib/Support/IOSandboxInternal.h
+++ b/llvm/lib/Support/IOSandboxInternal.h
@@ -51,6 +51,7 @@ static constexpr auto readdir = detail::interpose(::readdir);
 static constexpr auto stat = detail::interpose(::stat);
 static constexpr auto lstat = detail::interpose(::lstat);
 static constexpr auto fstat = detail::interpose(::fstat);
+static constexpr auto getcwd = detail::interpose(::getcwd);
 } // namespace llvm
 
 #endif

>From 54de166adeafd87417663272f42ed6025be44bf9 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 15 Jul 2025 15:51:46 -0700
Subject: [PATCH 08/19] [llvm][clang] Sandbox IO operations (realpath)

---
 llvm/lib/Support/IOSandboxInternal.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
index 9e7bb7d6f4567..80fa79220cc54 100644
--- a/llvm/lib/Support/IOSandboxInternal.h
+++ b/llvm/lib/Support/IOSandboxInternal.h
@@ -52,6 +52,7 @@ static constexpr auto stat = detail::interpose(::stat);
 static constexpr auto lstat = detail::interpose(::lstat);
 static constexpr auto fstat = detail::interpose(::fstat);
 static constexpr auto getcwd = detail::interpose(::getcwd);
+static constexpr auto realpath = detail::interpose(::realpath);
 } // namespace llvm
 
 #endif

>From 84e59fa0ca27ed7671e248101d398c1687b18041 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 15 Jul 2025 15:52:09 -0700
Subject: [PATCH 09/19] [llvm][clang] Sandbox IO operations (readlink)

---
 llvm/lib/Support/IOSandboxInternal.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
index 80fa79220cc54..b7b8b10a87e78 100644
--- a/llvm/lib/Support/IOSandboxInternal.h
+++ b/llvm/lib/Support/IOSandboxInternal.h
@@ -53,6 +53,7 @@ static constexpr auto lstat = detail::interpose(::lstat);
 static constexpr auto fstat = detail::interpose(::fstat);
 static constexpr auto getcwd = detail::interpose(::getcwd);
 static constexpr auto realpath = detail::interpose(::realpath);
+static constexpr auto readlink = detail::interpose(::readlink);
 } // namespace llvm
 
 #endif

>From 175f70e761a73e1599083cf4b34e48ed8372ed0c Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 15 Jul 2025 15:52:51 -0700
Subject: [PATCH 10/19] [llvm][clang] Sandbox IO operations (access)

---
 llvm/lib/Support/IOSandboxInternal.h | 1 +
 llvm/lib/Support/Unix/Path.inc       | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
index b7b8b10a87e78..3e953c53c6e2a 100644
--- a/llvm/lib/Support/IOSandboxInternal.h
+++ b/llvm/lib/Support/IOSandboxInternal.h
@@ -48,6 +48,7 @@ static constexpr auto read = detail::interpose(::read);
 static constexpr auto pread = detail::interpose(::pread);
 static constexpr auto mmap = detail::interpose(::mmap);
 static constexpr auto readdir = detail::interpose(::readdir);
+static constexpr auto access = detail::interpose(::access);
 static constexpr auto stat = detail::interpose(::stat);
 static constexpr auto lstat = detail::interpose(::lstat);
 static constexpr auto fstat = detail::interpose(::fstat);
diff --git a/llvm/lib/Support/Unix/Path.inc b/llvm/lib/Support/Unix/Path.inc
index fa27f86241b7d..f51c847a5f457 100644
--- a/llvm/lib/Support/Unix/Path.inc
+++ b/llvm/lib/Support/Unix/Path.inc
@@ -623,7 +623,7 @@ std::error_code access(const Twine &Path, AccessMode Mode) {
   SmallString<128> PathStorage;
   StringRef P = Path.toNullTerminatedStringRef(PathStorage);
 
-  if (::access(P.begin(), convertAccessMode(Mode)) == -1)
+  if (llvm::access(P.begin(), convertAccessMode(Mode)) == -1)
     return errnoAsErrorCode();
 
   if (Mode == AccessMode::Execute) {
@@ -978,7 +978,7 @@ ErrorOr<basic_file_status> directory_entry::status() const {
 static bool hasProcSelfFD() {
   // If we have a /proc filesystem mounted, we can quickly establish the
   // real name of the file with readlink
-  static const bool Result = (access("/proc/self/fd", R_OK) == 0);
+  static const bool Result = (llvm::access("/proc/self/fd", R_OK) == 0);
   return Result;
 }
 #endif

>From 17ca1e05d57a085e05735840207f174f49877287 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Wed, 1 Oct 2025 15:44:03 -0700
Subject: [PATCH 11/19] Rename, re-layer, add build setting

---
 clang/lib/CodeGen/BackendUtil.cpp             |  2 +-
 clang/lib/Driver/Driver.cpp                   |  2 +-
 clang/lib/Driver/Job.cpp                      |  2 +-
 clang/lib/Serialization/GlobalModuleIndex.cpp |  4 +-
 clang/lib/Serialization/ModuleCache.cpp       |  4 +-
 .../StaticAnalyzer/Core/HTMLDiagnostics.cpp   |  2 +-
 clang/tools/driver/cc1_main.cpp               |  4 +-
 clang/tools/driver/cc1as_main.cpp             |  4 +-
 llvm/CMakeLists.txt                           |  1 +
 llvm/include/llvm/Support/IOSandbox.h         | 72 +++++++++++++++++--
 llvm/lib/Support/IOSandbox.cpp                | 19 -----
 llvm/lib/Support/IOSandboxInternal.h          | 60 ----------------
 llvm/lib/Support/LockFileManager.cpp          |  4 +-
 llvm/lib/Support/Signals.cpp                  |  2 +-
 llvm/lib/Support/Unix/Path.inc                | 26 ++++++-
 llvm/lib/Support/VirtualFileSystem.cpp        | 20 +++---
 llvm/lib/Support/VirtualOutputBackends.cpp    |  4 +-
 llvm/lib/Support/raw_ostream.cpp              |  2 +-
 18 files changed, 121 insertions(+), 113 deletions(-)
 delete mode 100644 llvm/lib/Support/IOSandbox.cpp
 delete mode 100644 llvm/lib/Support/IOSandboxInternal.h

diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 3066bd8313777..5a53e2a106b8b 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -1436,7 +1436,7 @@ void clang::emitBackendOutput(CompilerInstance &CI, CodeGenOptions &CGOpts,
   std::unique_ptr<llvm::Module> EmptyModule;
   if (!CGOpts.ThinLTOIndexFile.empty()) {
     // FIXME(sandboxing): Figure out how to support distributed indexing.
-    auto BypassSandbox = sys::sandbox_scoped_disable();
+    auto BypassSandbox = sys::sandbox::scopedDisable();
     // If we are performing a ThinLTO importing compile, load the function index
     // into memory and pass it into runThinLTOBackend, which will run the
     // function importer and invoke LTO passes.
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 7cf23fdb2b97b..95a09cd3afe3f 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -1872,7 +1872,7 @@ bool Driver::getCrashDiagnosticFile(StringRef ReproCrashFilename,
   assert(llvm::Triple(llvm::sys::getProcessTriple()).isOSDarwin() &&
          "Only knows about .crash files on Darwin");
   // This is not a formal output of the compiler, let's bypass the sandbox.
-  auto BypassSandbox = sandbox_scoped_disable();
+  auto BypassSandbox = sandbox::scopedDisable();
 
   // The .crash file can be found on at ~/Library/Logs/DiagnosticReports/
   // (or /Library/Logs/DiagnosticReports for root) and has the filename pattern
diff --git a/clang/lib/Driver/Job.cpp b/clang/lib/Driver/Job.cpp
index ab6cd2973dd6e..da7a1f2e07e90 100644
--- a/clang/lib/Driver/Job.cpp
+++ b/clang/lib/Driver/Job.cpp
@@ -429,7 +429,7 @@ int CC1Command::Execute(ArrayRef<std::optional<StringRef>> Redirects,
 
   // Enabling the sandbox here allows us to restore its previous state even when
   // this cc1 invocation crashes.
-  auto EnableSandbox = llvm::sys::sandbox_scoped_enable();
+  auto EnableSandbox = llvm::sys::sandbox::scopedEnable();
 
   llvm::CrashRecoveryContext CRC;
   CRC.DumpStackAndCleanupOnFailure = true;
diff --git a/clang/lib/Serialization/GlobalModuleIndex.cpp b/clang/lib/Serialization/GlobalModuleIndex.cpp
index 99d90054cb09f..2246a3ac0a57e 100644
--- a/clang/lib/Serialization/GlobalModuleIndex.cpp
+++ b/clang/lib/Serialization/GlobalModuleIndex.cpp
@@ -252,7 +252,7 @@ GlobalModuleIndex::~GlobalModuleIndex() {
 std::pair<GlobalModuleIndex *, llvm::Error>
 GlobalModuleIndex::readIndex(StringRef Path) {
   // This is a compiler-internal input/output, let's bypass the sandbox.
-  auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+  auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
 
   // Load the index file, if it's there.
   llvm::SmallString<128> IndexPath;
@@ -848,7 +848,7 @@ GlobalModuleIndex::writeIndex(FileManager &FileMgr,
                               const PCHContainerReader &PCHContainerRdr,
                               StringRef Path) {
   // This is a compiler-internal input/output, let's bypass the sandbox.
-  auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+  auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
 
   llvm::SmallString<128> IndexPath;
   IndexPath += Path;
diff --git a/clang/lib/Serialization/ModuleCache.cpp b/clang/lib/Serialization/ModuleCache.cpp
index 6a3675620977d..1a521920e9dd6 100644
--- a/clang/lib/Serialization/ModuleCache.cpp
+++ b/clang/lib/Serialization/ModuleCache.cpp
@@ -29,7 +29,7 @@ void clang::maybePruneImpl(StringRef Path, time_t PruneInterval,
     return;
 
   // This is a compiler-internal input/output, let's bypass the sandbox.
-  auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+  auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
 
   llvm::SmallString<128> TimestampFile(Path);
   llvm::sys::path::append(TimestampFile, "modules.timestamp");
@@ -120,7 +120,7 @@ class CrossProcessModuleCache : public ModuleCache {
 
   std::time_t getModuleTimestamp(StringRef ModuleFilename) override {
     // This is a compiler-internal input/output, let's bypass the sandbox.
-    auto SandboxBypass = llvm::sys::sandbox_scoped_disable();
+    auto SandboxBypass = llvm::sys::sandbox::scopedDisable();
     std::string TimestampFilename =
         serialization::ModuleFile::getTimestampFilename(ModuleFilename);
     llvm::sys::fs::file_status Status;
diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
index 11e53a5132563..aae4f4161fad4 100644
--- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
+++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
@@ -259,7 +259,7 @@ void HTMLDiagnostics::FlushDiagnosticsImpl(
 void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
                                  FilesMade *filesMade) {
   // FIXME(sandboxing): Remove this by adopting `llvm::vfs::OutputBackend`.
-  auto SandboxBypass = llvm::sys::sandbox_scoped_disable();
+  auto SandboxBypass = llvm::sys::sandbox::scopedDisable();
 
   // Create the HTML directory if it is missing.
   if (!createdDir) {
diff --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp
index af30ba68dfde4..579b441e5cb9c 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -274,7 +274,7 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
 
   /// Create the actual file system.
   auto VFS = [] {
-    auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+    auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
     return llvm::vfs::getRealFileSystem();
   }();
   Clang->createVirtualFileSystem(std::move(VFS), DiagsBuffer);
@@ -309,7 +309,7 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   // results now.  This happens in -disable-free mode.
   {
     // This isn't a formal input or output of the compiler.
-    auto BypassSandbox = llvm::sys::sandbox_scoped_disable();
+    auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
     std::unique_ptr<raw_ostream> IOFile = llvm::CreateInfoOutputFile();
     if (Clang->getCodeGenOpts().TimePassesJson) {
       *IOFile << "{\n";
diff --git a/clang/tools/driver/cc1as_main.cpp b/clang/tools/driver/cc1as_main.cpp
index 285a128add1e1..c2f45ee370fb3 100644
--- a/clang/tools/driver/cc1as_main.cpp
+++ b/clang/tools/driver/cc1as_main.cpp
@@ -428,7 +428,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
 
   ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = [=] {
     // FIXME(sandboxing): Make this a proper input file.
-    auto BypassSandbox = sys::sandbox_scoped_disable();
+    auto BypassSandbox = sys::sandbox::scopedDisable();
     return MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
   }();
 
@@ -677,7 +677,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   DiagnosticsEngine Diags(DiagnosticIDs::create(), DiagOpts, DiagClient);
 
   auto VFS = [] {
-    auto BypassSandbox = sys::sandbox_scoped_disable();
+    auto BypassSandbox = sys::sandbox::scopedDisable();
     return vfs::getRealFileSystem();
   }();
 
diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt
index c450ee5a3d72e..ac81506a02993 100644
--- a/llvm/CMakeLists.txt
+++ b/llvm/CMakeLists.txt
@@ -697,6 +697,7 @@ option(LLVM_ENABLE_WERROR "Fail and stop if a warning is triggered." OFF)
 
 option(LLVM_ENABLE_DUMP "Enable dump functions even when assertions are disabled" OFF)
 option(LLVM_UNREACHABLE_OPTIMIZE "Optimize llvm_unreachable() as undefined behavior (default), guaranteed trap when OFF" ON)
+option(LLVM_ENABLE_IO_SANDBOX "Enable IO sandboxing in supported tools" OFF)
 
 if( NOT uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG" )
   option(LLVM_ENABLE_ASSERTIONS "Enable assertions" OFF)
diff --git a/llvm/include/llvm/Support/IOSandbox.h b/llvm/include/llvm/Support/IOSandbox.h
index 1a8fb9788e3f6..cf6f4f2b857a0 100644
--- a/llvm/include/llvm/Support/IOSandbox.h
+++ b/llvm/include/llvm/Support/IOSandbox.h
@@ -9,12 +9,74 @@
 #ifndef LLVM_SUPPORT_IOSANDBOX_H
 #define LLVM_SUPPORT_IOSANDBOX_H
 
+// Always enable IO sandboxing in debug/assert builds for development,
+// but allow enablement even for release/no-assert builds for production.
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_IO_SANDBOX)
+
+#include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/SaveAndRestore.h"
 
-namespace llvm::sys {
-SaveAndRestore<bool> sandbox_scoped_enable();
-SaveAndRestore<bool> sandbox_scoped_disable();
-void sandbox_violation_if_enabled();
-} // namespace llvm::sys
+namespace llvm::sys::sandbox {
+inline thread_local bool Enabled = false;
+inline SaveAndRestore<bool> scopedEnable() { return {Enabled, true}; }
+inline SaveAndRestore<bool> scopedDisable() { return {Enabled, false}; }
+inline void violationIfEnabled() {
+  if (Enabled)
+    reportFatalInternalError("IO sandbox violation");
+}
+} // namespace llvm::sys::sandbox
+
+#else
+
+namespace llvm::sys::sandbox {
+inline int scopedEnable() {}
+inline int scopedDisable() {}
+inline void violationIfEnabled() {}
+} // namespace llvm::sys::sandbox
+
+#endif
+
+namespace llvm::sys::sandbox {
+/// Facility for seamlessly interposing function calls and sandbox enforcement.
+/// This is intended for creating static functors like so:
+///
+///   // before
+///   #include <unistd.h>
+///   namespace x {
+///     void perform_read() { read(); } // not sandboxed
+///   }
+///
+///   // after
+///   #include <unistd.h>
+///   namespace x {
+///     static constexpr auto read = llvm::sys::sandbox::interpose(::read);
+///     void perform_read() { read(); } // sandboxed
+///   }
+template <class FnTy> struct Interposed;
+
+template <class RetTy, class... ArgTy> struct Interposed<RetTy (*)(ArgTy...)> {
+  RetTy (*Fn)(ArgTy...);
+
+  RetTy operator()(ArgTy... Arg) const {
+    violationIfEnabled();
+    return Fn(std::forward<ArgTy>(Arg)...);
+  }
+};
+
+template <class RetTy, class... ArgTy>
+struct Interposed<RetTy (*)(ArgTy..., ...)> {
+  RetTy (*Fn)(ArgTy..., ...);
+
+  template <class... CVarArgTy>
+  RetTy operator()(ArgTy... Arg, CVarArgTy... CVarArg) const {
+    violationIfEnabled();
+    return Fn(std::forward<ArgTy>(Arg)..., std::forward<CVarArgTy>(CVarArg)...);
+  }
+};
+
+template <class FnTy> constexpr auto interpose(FnTy Fn) {
+  return Interposed<FnTy>{Fn};
+}
+} // namespace llvm::sys::sandbox
 
 #endif
diff --git a/llvm/lib/Support/IOSandbox.cpp b/llvm/lib/Support/IOSandbox.cpp
deleted file mode 100644
index a046d38b48fc3..0000000000000
--- a/llvm/lib/Support/IOSandbox.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-#include "llvm/Support/IOSandbox.h"
-
-#include <cassert>
-
-using namespace llvm;
-
-thread_local bool IOSandboxEnabled = false;
-
-SaveAndRestore<bool> sys::sandbox_scoped_enable() {
-  return {IOSandboxEnabled, true};
-}
-
-SaveAndRestore<bool> sys::sandbox_scoped_disable() {
-  return {IOSandboxEnabled, false};
-}
-
-void sys::sandbox_violation_if_enabled() {
-  assert(!IOSandboxEnabled && "sandbox violation");
-}
diff --git a/llvm/lib/Support/IOSandboxInternal.h b/llvm/lib/Support/IOSandboxInternal.h
deleted file mode 100644
index 3e953c53c6e2a..0000000000000
--- a/llvm/lib/Support/IOSandboxInternal.h
+++ /dev/null
@@ -1,60 +0,0 @@
-#ifndef LLVM_SUPPORT_IOSANDBOXINTERNAL_H
-#define LLVM_SUPPORT_IOSANDBOXINTERNAL_H
-
-#include "llvm/Support/IOSandbox.h"
-
-#include <stdio.h>
-#include <sys/stat.h>
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif
-#ifdef HAVE_SYS_MMAN_H
-#include <sys/mman.h>
-#endif
-#include <dirent.h>
-
-extern thread_local bool IOSandboxEnabled;
-
-namespace llvm {
-namespace detail {
-template <class FnTy> struct Interposed;
-
-template <class RetTy, class... ArgTy> struct Interposed<RetTy (*)(ArgTy...)> {
-  RetTy (*Fn)(ArgTy...);
-
-  RetTy operator()(ArgTy... Arg) const {
-    sys::sandbox_violation_if_enabled();
-    return Fn(std::forward<ArgTy>(Arg)...);
-  }
-};
-
-template <class RetTy, class... ArgTy>
-struct Interposed<RetTy (*)(ArgTy..., ...)> {
-  RetTy (*Fn)(ArgTy..., ...);
-
-  template <class... CVarArgTy>
-  RetTy operator()(ArgTy... Arg, CVarArgTy... CVarArg) const {
-    sys::sandbox_violation_if_enabled();
-    return Fn(std::forward<ArgTy>(Arg)..., std::forward<CVarArgTy>(CVarArg)...);
-  }
-};
-
-template <class FnTy> constexpr auto interpose(FnTy Fn) {
-  return Interposed<FnTy>{Fn};
-}
-} // namespace detail
-
-static constexpr auto read = detail::interpose(::read);
-static constexpr auto pread = detail::interpose(::pread);
-static constexpr auto mmap = detail::interpose(::mmap);
-static constexpr auto readdir = detail::interpose(::readdir);
-static constexpr auto access = detail::interpose(::access);
-static constexpr auto stat = detail::interpose(::stat);
-static constexpr auto lstat = detail::interpose(::lstat);
-static constexpr auto fstat = detail::interpose(::fstat);
-static constexpr auto getcwd = detail::interpose(::getcwd);
-static constexpr auto realpath = detail::interpose(::realpath);
-static constexpr auto readlink = detail::interpose(::readlink);
-} // namespace llvm
-
-#endif
diff --git a/llvm/lib/Support/LockFileManager.cpp b/llvm/lib/Support/LockFileManager.cpp
index 54c42e4dd53e6..795bffbbc648c 100644
--- a/llvm/lib/Support/LockFileManager.cpp
+++ b/llvm/lib/Support/LockFileManager.cpp
@@ -52,7 +52,7 @@ using namespace llvm;
 /// \returns The process ID of the process that owns this lock file
 std::optional<LockFileManager::OwnedByAnother>
 LockFileManager::readLockFile(StringRef LockFileName) {
-  auto SandboxBypass = sys::sandbox_scoped_disable();
+  auto SandboxBypass = sys::sandbox::scopedDisable();
 
   // Read the owning host and PID out of the lock file. If it appears that the
   // owning process is dead, the lock file is invalid.
@@ -249,7 +249,7 @@ Expected<bool> LockFileManager::tryLock() {
 }
 
 LockFileManager::~LockFileManager() {
-  auto SandboxBypass = sys::sandbox_scoped_disable();
+  auto SandboxBypass = sys::sandbox::scopedDisable();
 
   if (!std::holds_alternative<OwnedByUs>(Owner))
     return;
diff --git a/llvm/lib/Support/Signals.cpp b/llvm/lib/Support/Signals.cpp
index 076ec293750af..f160a135f623d 100644
--- a/llvm/lib/Support/Signals.cpp
+++ b/llvm/lib/Support/Signals.cpp
@@ -97,7 +97,7 @@ CallBacksToRun() {
 // Signal-safe.
 void sys::RunSignalHandlers() {
   // Let's not interfere with stack trace symbolication and friends.
-  auto BypassSandbox = sandbox_scoped_disable();
+  auto BypassSandbox = sandbox::scopedDisable();
 
   for (CallbackAndCookie &RunMe : CallBacksToRun()) {
     auto Expected = CallbackAndCookie::Status::Initialized;
diff --git a/llvm/lib/Support/Unix/Path.inc b/llvm/lib/Support/Unix/Path.inc
index f51c847a5f457..ba99c07f3b463 100644
--- a/llvm/lib/Support/Unix/Path.inc
+++ b/llvm/lib/Support/Unix/Path.inc
@@ -114,7 +114,31 @@ typedef uint_t uint;
 #define STATVFS_F_FLAG(vfs) (vfs).f_flags
 #endif
 
-#include "IOSandboxInternal.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+#include <dirent.h>
+
+#include "llvm/Support/IOSandbox.h"
+
+namespace llvm {
+static constexpr auto read = sys::sandbox::interpose(::read);
+static constexpr auto pread = sys::sandbox::interpose(::pread);
+static constexpr auto mmap = sys::sandbox::interpose(::mmap);
+static constexpr auto readdir = sys::sandbox::interpose(::readdir);
+static constexpr auto access = sys::sandbox::interpose(::access);
+static constexpr auto stat = sys::sandbox::interpose(::stat);
+static constexpr auto lstat = sys::sandbox::interpose(::lstat);
+static constexpr auto fstat = sys::sandbox::interpose(::fstat);
+static constexpr auto getcwd = sys::sandbox::interpose(::getcwd);
+static constexpr auto realpath = sys::sandbox::interpose(::realpath);
+static constexpr auto readlink = sys::sandbox::interpose(::readlink);
+} // namespace llvm
 
 using namespace llvm;
 
diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp
index ecd50a8004f70..62fe07d3e4af0 100644
--- a/llvm/lib/Support/VirtualFileSystem.cpp
+++ b/llvm/lib/Support/VirtualFileSystem.cpp
@@ -222,7 +222,7 @@ RealFile::~RealFile() { close(); }
 ErrorOr<Status> RealFile::status() {
   assert(FD != kInvalidFile && "cannot stat closed file");
   if (!S.isStatusKnown()) {
-    auto BypassSandbox = sys::sandbox_scoped_disable();
+    auto BypassSandbox = sys::sandbox::scopedDisable();
     file_status RealStatus;
     if (std::error_code EC = sys::fs::status(FD, RealStatus))
       return EC;
@@ -239,7 +239,7 @@ ErrorOr<std::unique_ptr<MemoryBuffer>>
 RealFile::getBuffer(const Twine &Name, int64_t FileSize,
                     bool RequiresNullTerminator, bool IsVolatile) {
   assert(FD != kInvalidFile && "cannot get buffer for closed file");
-  auto BypassSandbox = sys::sandbox_scoped_disable();
+  auto BypassSandbox = sys::sandbox::scopedDisable();
   return MemoryBuffer::getOpenFile(FD, Name, FileSize, RequiresNullTerminator,
                                    IsVolatile);
 }
@@ -309,7 +309,7 @@ class RealFileSystem : public FileSystem {
 
   ErrorOr<std::unique_ptr<File>>
   openFileForReadWithFlags(const Twine &Name, sys::fs::OpenFlags Flags) {
-    auto BypassSandbox = sys::sandbox_scoped_disable();
+    auto BypassSandbox = sys::sandbox::scopedDisable();
     SmallString<256> RealName, Storage;
     Expected<file_t> FDOrErr = sys::fs::openNativeFileForRead(
         adjustPath(Name, Storage), Flags, &RealName);
@@ -331,7 +331,7 @@ class RealFileSystem : public FileSystem {
 } // namespace
 
 ErrorOr<Status> RealFileSystem::status(const Twine &Path) {
-  auto BypassSandbox = sys::sandbox_scoped_disable();
+  auto BypassSandbox = sys::sandbox::scopedDisable();
   SmallString<256> Storage;
   sys::fs::file_status RealStatus;
   if (std::error_code EC =
@@ -356,7 +356,7 @@ llvm::ErrorOr<std::string> RealFileSystem::getCurrentWorkingDirectory() const {
   if (WD)
     return WD->getError();
 
-  auto BypassSandbox = sys::sandbox_scoped_disable();
+  auto BypassSandbox = sys::sandbox::scopedDisable();
   SmallString<128> Dir;
   if (std::error_code EC = llvm::sys::fs::current_path(Dir))
     return EC;
@@ -387,7 +387,7 @@ std::error_code RealFileSystem::isLocal(const Twine &Path, bool &Result) {
 
 std::error_code RealFileSystem::getRealPath(const Twine &Path,
                                             SmallVectorImpl<char> &Output) {
-  auto BypassSandbox = sys::sandbox_scoped_disable();
+  auto BypassSandbox = sys::sandbox::scopedDisable();
   SmallString<256> Storage;
   return llvm::sys::fs::real_path(adjustPath(Path, Storage), Output);
 }
@@ -406,12 +406,12 @@ void RealFileSystem::printImpl(raw_ostream &OS, PrintType Type,
 IntrusiveRefCntPtr<FileSystem> vfs::getRealFileSystem() {
   static IntrusiveRefCntPtr<FileSystem> FS =
       makeIntrusiveRefCnt<RealFileSystem>(true);
-  sys::sandbox_violation_if_enabled();
+  sys::sandbox::violationIfEnabled();
   return FS;
 }
 
 std::unique_ptr<FileSystem> vfs::createPhysicalFileSystem() {
-  sys::sandbox_violation_if_enabled();
+  sys::sandbox::violationIfEnabled();
   return std::make_unique<RealFileSystem>(false);
 }
 
@@ -422,14 +422,14 @@ class RealFSDirIter : public llvm::vfs::detail::DirIterImpl {
 
 public:
   RealFSDirIter(const Twine &Path, std::error_code &EC) {
-    auto BypassSandbox = sys::sandbox_scoped_disable();
+    auto BypassSandbox = sys::sandbox::scopedDisable();
     Iter = llvm::sys::fs::directory_iterator{Path, EC};
     if (Iter != llvm::sys::fs::directory_iterator())
       CurrentEntry = directory_entry(Iter->path(), Iter->type());
   }
 
   std::error_code increment() override {
-    auto BypassSandbox = sys::sandbox_scoped_disable();
+    auto BypassSandbox = sys::sandbox::scopedDisable();
     std::error_code EC;
     Iter.increment(EC);
     CurrentEntry = (Iter == llvm::sys::fs::directory_iterator())
diff --git a/llvm/lib/Support/VirtualOutputBackends.cpp b/llvm/lib/Support/VirtualOutputBackends.cpp
index 82ee6df891120..61b29b1955bca 100644
--- a/llvm/lib/Support/VirtualOutputBackends.cpp
+++ b/llvm/lib/Support/VirtualOutputBackends.cpp
@@ -554,7 +554,7 @@ Error OnDiskOutputFile::keep() {
 }
 
 Error OnDiskOutputFile::discard() {
-  auto BypassSandbox = sys::sandbox_scoped_disable();
+  auto BypassSandbox = sys::sandbox::scopedDisable();
 
   // Destroy the streams to flush them.
   if (auto E = reset())
@@ -585,7 +585,7 @@ Error OnDiskOutputBackend::makeAbsolute(SmallVectorImpl<char> &Path) const {
 Expected<std::unique_ptr<OutputFileImpl>>
 OnDiskOutputBackend::createFileImpl(StringRef Path,
                                     std::optional<OutputConfig> Config) {
-  auto BypassSandbox = sys::sandbox_scoped_disable();
+  auto BypassSandbox = sys::sandbox::scopedDisable();
 
   SmallString<256> AbsPath;
   if (Path != "-") {
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index 0ea5f024c82c5..3b608706f6c55 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -619,7 +619,7 @@ raw_fd_ostream::raw_fd_ostream(int fd, bool shouldClose, bool unbuffered,
                                OStreamKind K)
     : raw_pwrite_stream(unbuffered, K), FD(fd), ShouldClose(shouldClose) {
   // FIXME(sandboxing): Remove this by adopting `llvm::vfs::OutputBackend`.
-  auto BypassSandbox = sys::sandbox_scoped_disable();
+  auto BypassSandbox = sys::sandbox::scopedDisable();
 
   if (FD < 0 ) {
     ShouldClose = false;

>From 5ac2edd54f8b85635b52cede3404ed4021630846 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Mon, 6 Oct 2025 14:32:55 -0700
Subject: [PATCH 12/19] [[maybe_unused]], proper CMake flag setup

---
 clang/lib/CodeGen/BackendUtil.cpp                |  2 +-
 clang/lib/Driver/Driver.cpp                      |  2 +-
 clang/lib/Driver/Job.cpp                         |  2 +-
 clang/lib/Serialization/GlobalModuleIndex.cpp    |  4 ++--
 clang/lib/Serialization/ModuleCache.cpp          |  4 ++--
 .../lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp  |  2 +-
 clang/tools/driver/cc1_main.cpp                  |  4 ++--
 clang/tools/driver/cc1as_main.cpp                |  4 ++--
 llvm/include/llvm/Config/llvm-config.h.cmake     |  3 +++
 llvm/include/llvm/Support/IOSandbox.h            |  4 ++--
 llvm/lib/Support/LockFileManager.cpp             |  4 ++--
 llvm/lib/Support/Signals.cpp                     |  2 +-
 llvm/lib/Support/VirtualFileSystem.cpp           | 16 ++++++++--------
 llvm/lib/Support/VirtualOutputBackends.cpp       |  4 ++--
 llvm/lib/Support/raw_ostream.cpp                 |  2 +-
 15 files changed, 31 insertions(+), 28 deletions(-)

diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 5a53e2a106b8b..4a57dedb0e800 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -1436,7 +1436,7 @@ void clang::emitBackendOutput(CompilerInstance &CI, CodeGenOptions &CGOpts,
   std::unique_ptr<llvm::Module> EmptyModule;
   if (!CGOpts.ThinLTOIndexFile.empty()) {
     // FIXME(sandboxing): Figure out how to support distributed indexing.
-    auto BypassSandbox = sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
     // If we are performing a ThinLTO importing compile, load the function index
     // into memory and pass it into runThinLTOBackend, which will run the
     // function importer and invoke LTO passes.
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 95a09cd3afe3f..ede5fb0e94423 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -1872,7 +1872,7 @@ bool Driver::getCrashDiagnosticFile(StringRef ReproCrashFilename,
   assert(llvm::Triple(llvm::sys::getProcessTriple()).isOSDarwin() &&
          "Only knows about .crash files on Darwin");
   // This is not a formal output of the compiler, let's bypass the sandbox.
-  auto BypassSandbox = sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sandbox::scopedDisable();
 
   // The .crash file can be found on at ~/Library/Logs/DiagnosticReports/
   // (or /Library/Logs/DiagnosticReports for root) and has the filename pattern
diff --git a/clang/lib/Driver/Job.cpp b/clang/lib/Driver/Job.cpp
index da7a1f2e07e90..e52d1069b4209 100644
--- a/clang/lib/Driver/Job.cpp
+++ b/clang/lib/Driver/Job.cpp
@@ -429,7 +429,7 @@ int CC1Command::Execute(ArrayRef<std::optional<StringRef>> Redirects,
 
   // Enabling the sandbox here allows us to restore its previous state even when
   // this cc1 invocation crashes.
-  auto EnableSandbox = llvm::sys::sandbox::scopedEnable();
+  [[maybe_unused]] auto EnableSandbox = llvm::sys::sandbox::scopedEnable();
 
   llvm::CrashRecoveryContext CRC;
   CRC.DumpStackAndCleanupOnFailure = true;
diff --git a/clang/lib/Serialization/GlobalModuleIndex.cpp b/clang/lib/Serialization/GlobalModuleIndex.cpp
index 2246a3ac0a57e..297f5645b1645 100644
--- a/clang/lib/Serialization/GlobalModuleIndex.cpp
+++ b/clang/lib/Serialization/GlobalModuleIndex.cpp
@@ -252,7 +252,7 @@ GlobalModuleIndex::~GlobalModuleIndex() {
 std::pair<GlobalModuleIndex *, llvm::Error>
 GlobalModuleIndex::readIndex(StringRef Path) {
   // This is a compiler-internal input/output, let's bypass the sandbox.
-  auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
 
   // Load the index file, if it's there.
   llvm::SmallString<128> IndexPath;
@@ -848,7 +848,7 @@ GlobalModuleIndex::writeIndex(FileManager &FileMgr,
                               const PCHContainerReader &PCHContainerRdr,
                               StringRef Path) {
   // This is a compiler-internal input/output, let's bypass the sandbox.
-  auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
 
   llvm::SmallString<128> IndexPath;
   IndexPath += Path;
diff --git a/clang/lib/Serialization/ModuleCache.cpp b/clang/lib/Serialization/ModuleCache.cpp
index 1a521920e9dd6..1ae4d6fdc6247 100644
--- a/clang/lib/Serialization/ModuleCache.cpp
+++ b/clang/lib/Serialization/ModuleCache.cpp
@@ -29,7 +29,7 @@ void clang::maybePruneImpl(StringRef Path, time_t PruneInterval,
     return;
 
   // This is a compiler-internal input/output, let's bypass the sandbox.
-  auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
 
   llvm::SmallString<128> TimestampFile(Path);
   llvm::sys::path::append(TimestampFile, "modules.timestamp");
@@ -120,7 +120,7 @@ class CrossProcessModuleCache : public ModuleCache {
 
   std::time_t getModuleTimestamp(StringRef ModuleFilename) override {
     // This is a compiler-internal input/output, let's bypass the sandbox.
-    auto SandboxBypass = llvm::sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
     std::string TimestampFilename =
         serialization::ModuleFile::getTimestampFilename(ModuleFilename);
     llvm::sys::fs::file_status Status;
diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
index aae4f4161fad4..b201b644359e9 100644
--- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
+++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
@@ -259,7 +259,7 @@ void HTMLDiagnostics::FlushDiagnosticsImpl(
 void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
                                  FilesMade *filesMade) {
   // FIXME(sandboxing): Remove this by adopting `llvm::vfs::OutputBackend`.
-  auto SandboxBypass = llvm::sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
 
   // Create the HTML directory if it is missing.
   if (!createdDir) {
diff --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp
index 579b441e5cb9c..7da2f95eb5fdc 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -274,7 +274,7 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
 
   /// Create the actual file system.
   auto VFS = [] {
-    auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
     return llvm::vfs::getRealFileSystem();
   }();
   Clang->createVirtualFileSystem(std::move(VFS), DiagsBuffer);
@@ -309,7 +309,7 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   // results now.  This happens in -disable-free mode.
   {
     // This isn't a formal input or output of the compiler.
-    auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = llvm::sys::sandbox::scopedDisable();
     std::unique_ptr<raw_ostream> IOFile = llvm::CreateInfoOutputFile();
     if (Clang->getCodeGenOpts().TimePassesJson) {
       *IOFile << "{\n";
diff --git a/clang/tools/driver/cc1as_main.cpp b/clang/tools/driver/cc1as_main.cpp
index c2f45ee370fb3..eed2bb59f32bf 100644
--- a/clang/tools/driver/cc1as_main.cpp
+++ b/clang/tools/driver/cc1as_main.cpp
@@ -428,7 +428,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
 
   ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = [=] {
     // FIXME(sandboxing): Make this a proper input file.
-    auto BypassSandbox = sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
     return MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
   }();
 
@@ -677,7 +677,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   DiagnosticsEngine Diags(DiagnosticIDs::create(), DiagOpts, DiagClient);
 
   auto VFS = [] {
-    auto BypassSandbox = sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
     return vfs::getRealFileSystem();
   }();
 
diff --git a/llvm/include/llvm/Config/llvm-config.h.cmake b/llvm/include/llvm/Config/llvm-config.h.cmake
index 6488d6c01b5c6..9ac0115ee2184 100644
--- a/llvm/include/llvm/Config/llvm-config.h.cmake
+++ b/llvm/include/llvm/Config/llvm-config.h.cmake
@@ -126,6 +126,9 @@
  * in non assert builds */
 #cmakedefine01 LLVM_UNREACHABLE_OPTIMIZE
 
+/* Define if building LLVM with LLVM_ENABLE_IO_SANDBOX */
+#cmakedefine01 LLVM_ENABLE_IO_SANDBOX
+
 /* Define to 1 if you have the DIA SDK installed, and to 0 if you don't. */
 #cmakedefine01 LLVM_ENABLE_DIA_SDK
 
diff --git a/llvm/include/llvm/Support/IOSandbox.h b/llvm/include/llvm/Support/IOSandbox.h
index cf6f4f2b857a0..db0e134c48e5f 100644
--- a/llvm/include/llvm/Support/IOSandbox.h
+++ b/llvm/include/llvm/Support/IOSandbox.h
@@ -29,8 +29,8 @@ inline void violationIfEnabled() {
 #else
 
 namespace llvm::sys::sandbox {
-inline int scopedEnable() {}
-inline int scopedDisable() {}
+inline int scopedEnable() { return 0; }
+inline int scopedDisable() { return 0; }
 inline void violationIfEnabled() {}
 } // namespace llvm::sys::sandbox
 
diff --git a/llvm/lib/Support/LockFileManager.cpp b/llvm/lib/Support/LockFileManager.cpp
index 795bffbbc648c..586f4c98ae32c 100644
--- a/llvm/lib/Support/LockFileManager.cpp
+++ b/llvm/lib/Support/LockFileManager.cpp
@@ -52,7 +52,7 @@ using namespace llvm;
 /// \returns The process ID of the process that owns this lock file
 std::optional<LockFileManager::OwnedByAnother>
 LockFileManager::readLockFile(StringRef LockFileName) {
-  auto SandboxBypass = sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
 
   // Read the owning host and PID out of the lock file. If it appears that the
   // owning process is dead, the lock file is invalid.
@@ -249,7 +249,7 @@ Expected<bool> LockFileManager::tryLock() {
 }
 
 LockFileManager::~LockFileManager() {
-  auto SandboxBypass = sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
 
   if (!std::holds_alternative<OwnedByUs>(Owner))
     return;
diff --git a/llvm/lib/Support/Signals.cpp b/llvm/lib/Support/Signals.cpp
index f160a135f623d..786c4576f13e3 100644
--- a/llvm/lib/Support/Signals.cpp
+++ b/llvm/lib/Support/Signals.cpp
@@ -97,7 +97,7 @@ CallBacksToRun() {
 // Signal-safe.
 void sys::RunSignalHandlers() {
   // Let's not interfere with stack trace symbolication and friends.
-  auto BypassSandbox = sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sandbox::scopedDisable();
 
   for (CallbackAndCookie &RunMe : CallBacksToRun()) {
     auto Expected = CallbackAndCookie::Status::Initialized;
diff --git a/llvm/lib/Support/VirtualFileSystem.cpp b/llvm/lib/Support/VirtualFileSystem.cpp
index 62fe07d3e4af0..0683cf8b3bd1f 100644
--- a/llvm/lib/Support/VirtualFileSystem.cpp
+++ b/llvm/lib/Support/VirtualFileSystem.cpp
@@ -222,7 +222,7 @@ RealFile::~RealFile() { close(); }
 ErrorOr<Status> RealFile::status() {
   assert(FD != kInvalidFile && "cannot stat closed file");
   if (!S.isStatusKnown()) {
-    auto BypassSandbox = sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
     file_status RealStatus;
     if (std::error_code EC = sys::fs::status(FD, RealStatus))
       return EC;
@@ -239,7 +239,7 @@ ErrorOr<std::unique_ptr<MemoryBuffer>>
 RealFile::getBuffer(const Twine &Name, int64_t FileSize,
                     bool RequiresNullTerminator, bool IsVolatile) {
   assert(FD != kInvalidFile && "cannot get buffer for closed file");
-  auto BypassSandbox = sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
   return MemoryBuffer::getOpenFile(FD, Name, FileSize, RequiresNullTerminator,
                                    IsVolatile);
 }
@@ -309,7 +309,7 @@ class RealFileSystem : public FileSystem {
 
   ErrorOr<std::unique_ptr<File>>
   openFileForReadWithFlags(const Twine &Name, sys::fs::OpenFlags Flags) {
-    auto BypassSandbox = sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
     SmallString<256> RealName, Storage;
     Expected<file_t> FDOrErr = sys::fs::openNativeFileForRead(
         adjustPath(Name, Storage), Flags, &RealName);
@@ -331,7 +331,7 @@ class RealFileSystem : public FileSystem {
 } // namespace
 
 ErrorOr<Status> RealFileSystem::status(const Twine &Path) {
-  auto BypassSandbox = sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
   SmallString<256> Storage;
   sys::fs::file_status RealStatus;
   if (std::error_code EC =
@@ -356,7 +356,7 @@ llvm::ErrorOr<std::string> RealFileSystem::getCurrentWorkingDirectory() const {
   if (WD)
     return WD->getError();
 
-  auto BypassSandbox = sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
   SmallString<128> Dir;
   if (std::error_code EC = llvm::sys::fs::current_path(Dir))
     return EC;
@@ -387,7 +387,7 @@ std::error_code RealFileSystem::isLocal(const Twine &Path, bool &Result) {
 
 std::error_code RealFileSystem::getRealPath(const Twine &Path,
                                             SmallVectorImpl<char> &Output) {
-  auto BypassSandbox = sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
   SmallString<256> Storage;
   return llvm::sys::fs::real_path(adjustPath(Path, Storage), Output);
 }
@@ -422,14 +422,14 @@ class RealFSDirIter : public llvm::vfs::detail::DirIterImpl {
 
 public:
   RealFSDirIter(const Twine &Path, std::error_code &EC) {
-    auto BypassSandbox = sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
     Iter = llvm::sys::fs::directory_iterator{Path, EC};
     if (Iter != llvm::sys::fs::directory_iterator())
       CurrentEntry = directory_entry(Iter->path(), Iter->type());
   }
 
   std::error_code increment() override {
-    auto BypassSandbox = sys::sandbox::scopedDisable();
+    [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
     std::error_code EC;
     Iter.increment(EC);
     CurrentEntry = (Iter == llvm::sys::fs::directory_iterator())
diff --git a/llvm/lib/Support/VirtualOutputBackends.cpp b/llvm/lib/Support/VirtualOutputBackends.cpp
index 61b29b1955bca..90b4c3173b28d 100644
--- a/llvm/lib/Support/VirtualOutputBackends.cpp
+++ b/llvm/lib/Support/VirtualOutputBackends.cpp
@@ -554,7 +554,7 @@ Error OnDiskOutputFile::keep() {
 }
 
 Error OnDiskOutputFile::discard() {
-  auto BypassSandbox = sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
 
   // Destroy the streams to flush them.
   if (auto E = reset())
@@ -585,7 +585,7 @@ Error OnDiskOutputBackend::makeAbsolute(SmallVectorImpl<char> &Path) const {
 Expected<std::unique_ptr<OutputFileImpl>>
 OnDiskOutputBackend::createFileImpl(StringRef Path,
                                     std::optional<OutputConfig> Config) {
-  auto BypassSandbox = sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
 
   SmallString<256> AbsPath;
   if (Path != "-") {
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index 3b608706f6c55..99ac3e1d75b47 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -619,7 +619,7 @@ raw_fd_ostream::raw_fd_ostream(int fd, bool shouldClose, bool unbuffered,
                                OStreamKind K)
     : raw_pwrite_stream(unbuffered, K), FD(fd), ShouldClose(shouldClose) {
   // FIXME(sandboxing): Remove this by adopting `llvm::vfs::OutputBackend`.
-  auto BypassSandbox = sys::sandbox::scopedDisable();
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
 
   if (FD < 0 ) {
     ShouldClose = false;

>From 0792e518f2f53a44e1d0c5b22f439756c5031f9f Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Mon, 6 Oct 2025 14:48:46 -0700
Subject: [PATCH 13/19] noexcept

---
 llvm/include/llvm/Support/IOSandbox.h | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/llvm/include/llvm/Support/IOSandbox.h b/llvm/include/llvm/Support/IOSandbox.h
index db0e134c48e5f..400767f711d58 100644
--- a/llvm/include/llvm/Support/IOSandbox.h
+++ b/llvm/include/llvm/Support/IOSandbox.h
@@ -54,21 +54,22 @@ namespace llvm::sys::sandbox {
 ///   }
 template <class FnTy> struct Interposed;
 
-template <class RetTy, class... ArgTy> struct Interposed<RetTy (*)(ArgTy...)> {
+template <class RetTy, class... ArgTy, bool NE>
+struct Interposed<RetTy (*)(ArgTy...) noexcept(NE)> {
   RetTy (*Fn)(ArgTy...);
 
-  RetTy operator()(ArgTy... Arg) const {
+  RetTy operator()(ArgTy... Arg) const noexcept(NE) {
     violationIfEnabled();
     return Fn(std::forward<ArgTy>(Arg)...);
   }
 };
 
-template <class RetTy, class... ArgTy>
-struct Interposed<RetTy (*)(ArgTy..., ...)> {
+template <class RetTy, class... ArgTy, bool NE>
+struct Interposed<RetTy (*)(ArgTy..., ...) noexcept(NE)> {
   RetTy (*Fn)(ArgTy..., ...);
 
   template <class... CVarArgTy>
-  RetTy operator()(ArgTy... Arg, CVarArgTy... CVarArg) const {
+  RetTy operator()(ArgTy... Arg, CVarArgTy... CVarArg) const noexcept(NE) {
     violationIfEnabled();
     return Fn(std::forward<ArgTy>(Arg)..., std::forward<CVarArgTy>(CVarArg)...);
   }

>From f5fb3fddd31b8bbf9b72b9d4314cf6e68218e4b4 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Mon, 6 Oct 2025 15:27:55 -0700
Subject: [PATCH 14/19] MSVC noexcept(X) workaround

---
 llvm/include/llvm/Support/IOSandbox.h | 32 ++++++++++++++++++++++-----
 1 file changed, 26 insertions(+), 6 deletions(-)

diff --git a/llvm/include/llvm/Support/IOSandbox.h b/llvm/include/llvm/Support/IOSandbox.h
index 400767f711d58..e5267fe2b5c39 100644
--- a/llvm/include/llvm/Support/IOSandbox.h
+++ b/llvm/include/llvm/Support/IOSandbox.h
@@ -54,22 +54,42 @@ namespace llvm::sys::sandbox {
 ///   }
 template <class FnTy> struct Interposed;
 
-template <class RetTy, class... ArgTy, bool NE>
-struct Interposed<RetTy (*)(ArgTy...) noexcept(NE)> {
+template <class RetTy, class... ArgTy> struct Interposed<RetTy (*)(ArgTy...)> {
   RetTy (*Fn)(ArgTy...);
 
-  RetTy operator()(ArgTy... Arg) const noexcept(NE) {
+  RetTy operator()(ArgTy... Arg) const {
     violationIfEnabled();
     return Fn(std::forward<ArgTy>(Arg)...);
   }
 };
 
-template <class RetTy, class... ArgTy, bool NE>
-struct Interposed<RetTy (*)(ArgTy..., ...) noexcept(NE)> {
+template <class RetTy, class... ArgTy>
+struct Interposed<RetTy (*)(ArgTy...) noexcept> {
+  RetTy (*Fn)(ArgTy...) noexcept;
+
+  RetTy operator()(ArgTy... Arg) const noexcept {
+    violationIfEnabled();
+    return Fn(std::forward<ArgTy>(Arg)...);
+  }
+};
+
+template <class RetTy, class... ArgTy>
+struct Interposed<RetTy (*)(ArgTy..., ...)> {
   RetTy (*Fn)(ArgTy..., ...);
 
   template <class... CVarArgTy>
-  RetTy operator()(ArgTy... Arg, CVarArgTy... CVarArg) const noexcept(NE) {
+  RetTy operator()(ArgTy... Arg, CVarArgTy... CVarArg) const {
+    violationIfEnabled();
+    return Fn(std::forward<ArgTy>(Arg)..., std::forward<CVarArgTy>(CVarArg)...);
+  }
+};
+
+template <class RetTy, class... ArgTy>
+struct Interposed<RetTy (*)(ArgTy..., ...) noexcept> {
+  RetTy (*Fn)(ArgTy..., ...) noexcept;
+
+  template <class... CVarArgTy>
+  RetTy operator()(ArgTy... Arg, CVarArgTy... CVarArg) const noexcept {
     violationIfEnabled();
     return Fn(std::forward<ArgTy>(Arg)..., std::forward<CVarArgTy>(CVarArg)...);
   }

>From 5d6acd9e20e1205cc93dc2a5d6f015e00c6e0387 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Tue, 7 Oct 2025 12:16:47 -0700
Subject: [PATCH 15/19] Initial version of a clang-tidy check

---
 .../clang-tidy/llvm/CMakeLists.txt            |   1 +
 .../clang-tidy/llvm/IOSandboxCheck.cpp        | 319 ++++++++++++++++++
 .../clang-tidy/llvm/IOSandboxCheck.h          |  31 ++
 .../clang-tidy/llvm/LLVMTidyModule.cpp        |   2 +
 4 files changed, 353 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/llvm/IOSandboxCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/llvm/IOSandboxCheck.h

diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index 78ef0444305ff..73c8dc4f17696 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS
 add_clang_library(clangTidyLLVMModule STATIC
   HeaderGuardCheck.cpp
   IncludeOrderCheck.cpp
+  IOSandboxCheck.cpp
   LLVMTidyModule.cpp
   PreferIsaOrDynCastInConditionalsCheck.cpp
   PreferRegisterOverUnsignedCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/IOSandboxCheck.cpp b/clang-tools-extra/clang-tidy/llvm/IOSandboxCheck.cpp
new file mode 100644
index 0000000000000..92247524ee548
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/IOSandboxCheck.cpp
@@ -0,0 +1,319 @@
+//===--- FilesystemAccessCheck.cpp - clang-tidy --------------------------===//
+//
+// Enforces controlled filesystem access patterns
+//
+//===----------------------------------------------------------------------===//
+
+#include "IOSandboxCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+// Low-level filesystem functions that should only be called from llvm::sys::fs
+static const llvm::StringSet<> &getForbiddenFilesystemFunctions() {
+  static const llvm::StringSet<> Functions = {
+      // POSIX file operations
+      "open",
+      "openat",
+      "creat",
+      "close",
+      "read",
+      "write",
+      "pread",
+      "pwrite",
+      "lseek",
+      "ftruncate",
+      "truncate",
+      "stat",
+      "fstat",
+      "lstat",
+      "fstatat",
+      "access",
+      "faccessat",
+      "chmod",
+      "fchmod",
+      "fchmodat",
+      "chown",
+      "fchown",
+      "lchown",
+      "fchownat",
+      "link",
+      "linkat",
+      "symlink",
+      "symlinkat",
+      "readlink",
+      "readlinkat",
+      "unlink",
+      "unlinkat",
+      "remove",
+      "rename",
+      "renameat",
+      "mkdir",
+      "mkdirat",
+      "rmdir",
+      "opendir",
+      "readdir",
+      "closedir",
+      "fdopendir",
+      "chdir",
+      "fchdir",
+      "getcwd",
+      "dup",
+      "dup2",
+      "dup3",
+      "fcntl",
+      "pipe",
+      "pipe2",
+      "mkfifo",
+      "mkfifoat",
+      "mknod",
+      "mknodat",
+      "utimes",
+      "futimes",
+      "utimensat",
+      "futimens",
+
+      // C standard library file operations
+      "fopen",
+      "freopen",
+      "fclose",
+      "fflush",
+      "fread",
+      "fwrite",
+      "fgetc",
+      "fputc",
+      "fgets",
+      "fputs",
+      "fseek",
+      "ftell",
+      "rewind",
+      "fgetpos",
+      "fsetpos",
+      "tmpfile",
+      "tmpnam",
+      "tempnam",
+
+      // Windows file operations
+      "CreateFileA",
+      "CreateFileW",
+      "CreateFile",
+      "ReadFile",
+      "WriteFile",
+      "CloseHandle",
+      "DeleteFileA",
+      "DeleteFileW",
+      "DeleteFile",
+      "MoveFileA",
+      "MoveFileW",
+      "MoveFile",
+      "CopyFileA",
+      "CopyFileW",
+      "CopyFile",
+      "GetFileAttributesA",
+      "GetFileAttributesW",
+      "GetFileAttributes",
+      "SetFileAttributesA",
+      "SetFileAttributesW",
+      "SetFileAttributes",
+      "CreateDirectoryA",
+      "CreateDirectoryW",
+      "CreateDirectory",
+      "RemoveDirectoryA",
+      "RemoveDirectoryW",
+      "RemoveDirectory",
+      "FindFirstFileA",
+      "FindFirstFileW",
+      "FindFirstFile",
+      "FindNextFileA",
+      "FindNextFileW",
+      "FindNextFile",
+      "FindClose",
+      "GetCurrentDirectoryA",
+      "GetCurrentDirectoryW",
+      "SetCurrentDirectoryA",
+      "SetCurrentDirectoryW",
+
+      // Memory-mapped files
+      "mmap",
+      "munmap",
+      "mprotect",
+      "msync",
+      "MapViewOfFile",
+      "UnmapViewOfFile",
+  };
+  return Functions;
+}
+
+static bool isInLLVMSysFsNamespace(const FunctionDecl *FD) {
+  if (!FD)
+    return false;
+
+  auto IsAnonymousNamespace = [](const DeclContext *DC) {
+    if (!DC)
+      return false;
+    const auto *ND = dyn_cast<NamespaceDecl>(DC);
+    if (!ND)
+      return false;
+    return ND->isAnonymousNamespace();
+  };
+
+  auto GetNamedNamespace = [](const DeclContext *DC) -> const NamespaceDecl * {
+    if (!DC)
+      return nullptr;
+    const auto *ND = dyn_cast<NamespaceDecl>(DC);
+    if (!ND)
+      return nullptr;
+    if (ND->isAnonymousNamespace())
+      return nullptr;
+    return ND;
+  };
+
+  const DeclContext *DC = FD->getDeclContext();
+
+  // Walk up the context chain looking for llvm::sys::fs
+  SmallVector<StringRef> ReverseNamespaces;
+  while (IsAnonymousNamespace(DC))
+    DC = DC->getParent();
+  while (const auto *ND = GetNamedNamespace(DC)) {
+    ReverseNamespaces.push_back(ND->getName());
+    DC = DC->getParent();
+  }
+  auto Namespaces = llvm::reverse(ReverseNamespaces);
+
+  return llvm::equal(Namespaces, SmallVector<StringRef>{"llvm", "sys", "fs"});
+}
+
+static bool isLLVMSysFsCall(const CallExpr *CE) {
+  if (!CE)
+    return false;
+
+  const FunctionDecl *Callee = CE->getDirectCallee();
+  if (!Callee)
+    return false;
+
+  return isInLLVMSysFsNamespace(Callee) && !Callee->isOverloadedOperator();
+}
+
+static bool isForbiddenFilesystemCall(const CallExpr *CE) {
+  if (!CE)
+    return false;
+
+  const FunctionDecl *Callee = CE->getDirectCallee();
+  if (!Callee)
+    return false;
+
+  const auto &ForbiddenFuncs = getForbiddenFilesystemFunctions();
+
+  return ForbiddenFuncs.contains(Callee->getQualifiedNameAsString());
+}
+
+static bool hasSandboxBypass(const FunctionDecl *FD, SourceLocation CallLoc) {
+  if (!FD || !FD->hasBody())
+    return false;
+
+  const Stmt *Body = FD->getBody();
+  if (!Body)
+    return false;
+
+  // Look for variable declarations of the bypass type
+  // We need to check if the bypass variable is declared before the call site
+  class BypassFinder : public RecursiveASTVisitor<BypassFinder> {
+  public:
+    bool FoundBypass = false;
+    SourceLocation CallLocation;
+    const SourceManager *SM;
+
+    bool VisitVarDecl(VarDecl *VD) {
+      if (!VD)
+        return true;
+
+      // Check if this is a sandbox bypass variable
+      const Type *T = VD->getType().getTypePtrOrNull();
+      if (!T)
+        return true;
+
+      const CXXRecordDecl *RD = T->getAsCXXRecordDecl();
+      if (!RD)
+        return true;
+
+      // Check for ScopedSandboxDisable or similar RAII types
+      std::string TypeName = RD->getQualifiedNameAsString();
+      if (TypeName.find("ScopedSandboxDisable") != std::string::npos ||
+          TypeName.find("scopedDisable") != std::string::npos) {
+
+        // Check if this declaration comes before the call
+        if (SM &&
+            SM->isBeforeInTranslationUnit(VD->getLocation(), CallLocation)) {
+          FoundBypass = true;
+          return false; // Stop searching
+        }
+      }
+
+      return true;
+    }
+  };
+
+  BypassFinder Finder;
+  Finder.CallLocation = CallLoc;
+  Finder.SM = &FD->getASTContext().getSourceManager();
+  Finder.TraverseStmt(const_cast<Stmt *>(Body));
+
+  return Finder.FoundBypass;
+}
+
+void IOSandboxCheck::registerMatchers(MatchFinder *Finder) {
+  // Match any call expression within a function.
+  Finder->addMatcher(
+      callExpr(hasAncestor(functionDecl().bind("parent_func"))).bind("call"),
+      this);
+
+  // Also match variable declarations to find sandbox bypass objects
+  Finder->addMatcher(
+      varDecl(hasType(cxxRecordDecl(hasName("ScopedSandboxDisable"))),
+              hasAncestor(functionDecl().bind("func_with_bypass")))
+          .bind("bypass_var"),
+      this);
+}
+
+void IOSandboxCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
+  const auto *ParentFunc = Result.Nodes.getNodeAs<FunctionDecl>("parent_func");
+
+  if (!Call || !ParentFunc)
+    return;
+
+  // Skip system headers and template instantiations
+  if (Call->getBeginLoc().isInvalid() ||
+      Result.Context->getSourceManager().isInSystemHeader(
+          Call->getBeginLoc()) ||
+      ParentFunc->isTemplateInstantiation())
+    return;
+
+  // Rule 1: Check if calling llvm::sys::fs without sandbox bypass
+  if (isLLVMSysFsCall(Call)) {
+    if (!hasSandboxBypass(ParentFunc, Call->getBeginLoc())) {
+      diag(Call->getBeginLoc(), "call to llvm::sys::fs function")
+          << Call->getSourceRange();
+    }
+    return; // Don't check rule 2 for llvm::sys::fs calls
+  }
+
+  // Rule 2: Check if calling forbidden filesystem functions outside
+  // llvm::sys::fs
+  if (isForbiddenFilesystemCall(Call)) {
+    if (!isInLLVMSysFsNamespace(ParentFunc)) {
+      const auto *Callee = Call->getDirectCallee();
+      std::string CalleeName = Callee ? Callee->getNameAsString() : "unknown";
+
+      diag(Call->getBeginLoc(),
+           "low-level filesystem function '%0' may only be called from a "
+           "llvm::sys::fs function")
+          << CalleeName << Call->getSourceRange();
+    }
+  }
+}
+} // namespace clang::tidy::llvm_check
diff --git a/clang-tools-extra/clang-tidy/llvm/IOSandboxCheck.h b/clang-tools-extra/clang-tidy/llvm/IOSandboxCheck.h
new file mode 100644
index 0000000000000..99886989bdae7
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/IOSandboxCheck.h
@@ -0,0 +1,31 @@
+//===--- LLVMFilesystemAccessCheck.h - clang-tidy ---------------*- C++ -*-===//
+//
+// Enforces controlled filesystem access patterns
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_IOSANDBOXCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_IOSANDBOXCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::llvm_check {
+
+/// Enforces two rules for filesystem access:
+/// 1. Functions calling llvm::sys::fs must have a sandbox bypass RAII object
+/// 2. Only llvm::sys::fs functions may call low-level filesystem functions
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/llvm/io-sandbox.html
+class IOSandboxCheck : public ClangTidyCheck {
+public:
+  IOSandboxCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::llvm_check
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_IOSANDBOXCHECK_H
diff --git a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
index ed65cd1720457..be32495746035 100644
--- a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
@@ -13,6 +13,7 @@
 #include "../readability/NamespaceCommentCheck.h"
 #include "../readability/QualifiedAutoCheck.h"
 #include "HeaderGuardCheck.h"
+#include "IOSandboxCheck.h"
 #include "IncludeOrderCheck.h"
 #include "PreferIsaOrDynCastInConditionalsCheck.h"
 #include "PreferRegisterOverUnsignedCheck.h"
@@ -31,6 +32,7 @@ class LLVMModule : public ClangTidyModule {
         "llvm-else-after-return");
     CheckFactories.registerCheck<LLVMHeaderGuardCheck>("llvm-header-guard");
     CheckFactories.registerCheck<IncludeOrderCheck>("llvm-include-order");
+    CheckFactories.registerCheck<IOSandboxCheck>("llvm-io-sandbox");
     CheckFactories.registerCheck<readability::NamespaceCommentCheck>(
         "llvm-namespace-comment");
     CheckFactories.registerCheck<PreferIsaOrDynCastInConditionalsCheck>(

>From 26abceea738edd6abea4e061463711c0aa6b40dd Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Wed, 15 Oct 2025 16:22:44 -0700
Subject: [PATCH 16/19] AsmPrinter + BTFDebug

---
 llvm/include/llvm/CodeGen/AsmPrinter.h              |  3 ---
 llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp          |  1 -
 llvm/lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp |  9 +++++++--
 llvm/lib/Target/BPF/BTFDebug.cpp                    | 10 ++++++++--
 4 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/AsmPrinter.h b/llvm/include/llvm/CodeGen/AsmPrinter.h
index 9ace2555b4b62..0c73eddca3526 100644
--- a/llvm/include/llvm/CodeGen/AsmPrinter.h
+++ b/llvm/include/llvm/CodeGen/AsmPrinter.h
@@ -110,9 +110,6 @@ class LLVM_ABI AsmPrinter : public MachineFunctionPass {
   /// generating (such as the current section etc).
   std::unique_ptr<MCStreamer> OutStreamer;
 
-  /// The VFS to resolve asm include directives.
-  IntrusiveRefCntPtr<vfs::FileSystem> VFS;
-
   /// The current machine function.
   MachineFunction *MF = nullptr;
 
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index e2af0c5925248..06262f9ce3147 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -477,7 +477,6 @@ void AsmPrinter::getAnalysisUsage(AnalysisUsage &AU) const {
 }
 
 bool AsmPrinter::doInitialization(Module &M) {
-  VFS = vfs::getRealFileSystem();
   auto *MMIWP = getAnalysisIfAvailable<MachineModuleInfoWrapperPass>();
   MMI = MMIWP ? &MMIWP->getMMI() : nullptr;
   HasSplitStack = false;
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp
index 8dd8b9da9c50c..6b0fd456391e2 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp
@@ -98,8 +98,13 @@ void AsmPrinter::emitInlineAsm(StringRef Str, const MCSubtargetInfo &STI,
 
   unsigned BufNum = addInlineAsmDiagBuffer(Str, LocMDNode);
   SourceMgr &SrcMgr = *MMI->getContext().getInlineSourceManager();
-  SrcMgr.setIncludeDirs(MCOptions.IASSearchPaths);
-  SrcMgr.setVirtualFileSystem(VFS);
+  // FIXME(sandboxing): This is not executed in tests, but might be common.
+  //                    Propagating vfs::FileSystem here is lots of work,
+  //                    consider bypassing the sandbox.
+  if (!MCOptions.IASSearchPaths.empty()) {
+    SrcMgr.setIncludeDirs(MCOptions.IASSearchPaths);
+    SrcMgr.setVirtualFileSystem(vfs::getRealFileSystem());
+  }
 
   std::unique_ptr<MCAsmParser> Parser(
       createMCAsmParser(SrcMgr, OutContext, *OutStreamer, *MAI, BufNum));
diff --git a/llvm/lib/Target/BPF/BTFDebug.cpp b/llvm/lib/Target/BPF/BTFDebug.cpp
index ba4b48990c647..f0c82427ad057 100644
--- a/llvm/lib/Target/BPF/BTFDebug.cpp
+++ b/llvm/lib/Target/BPF/BTFDebug.cpp
@@ -23,6 +23,7 @@
 #include "llvm/MC/MCObjectFileInfo.h"
 #include "llvm/MC/MCSectionELF.h"
 #include "llvm/MC/MCStreamer.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/LineIterator.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Target/TargetLoweringObjectFile.h"
@@ -1017,12 +1018,17 @@ std::string BTFDebug::populateFileContent(const DIFile *File) {
   std::string Line;
   Content.push_back(Line); // Line 0 for empty string
 
+  auto LoadFile = [](StringRef FileName) {
+    // FIXME(sandboxing): Propagating vfs::FileSystem here is lots of work.
+    [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
+    return MemoryBuffer::getFile(FileName);
+  };
+
   std::unique_ptr<MemoryBuffer> Buf;
   auto Source = File->getSource();
   if (Source)
     Buf = MemoryBuffer::getMemBufferCopy(*Source);
-  else if (ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
-               MemoryBuffer::getFile(FileName))
+  else if (ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = LoadFile(FileName))
     Buf = std::move(*BufOrErr);
   if (Buf)
     for (line_iterator I(*Buf, false), E; I != E; ++I)

>From 64820bde80cce25ea26ba868d81a4a68c1890b63 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Wed, 15 Oct 2025 19:45:05 -0700
Subject: [PATCH 17/19] Proper sandbox

---
 llvm/include/llvm/CodeGen/AsmPrinter.h             |  5 -----
 .../lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp | 14 +++++++-------
 2 files changed, 7 insertions(+), 12 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/AsmPrinter.h b/llvm/include/llvm/CodeGen/AsmPrinter.h
index 0c73eddca3526..19ca44429af4d 100644
--- a/llvm/include/llvm/CodeGen/AsmPrinter.h
+++ b/llvm/include/llvm/CodeGen/AsmPrinter.h
@@ -16,7 +16,6 @@
 #define LLVM_CODEGEN_ASMPRINTER_H
 
 #include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/SmallVector.h"
@@ -88,10 +87,6 @@ namespace remarks {
 class RemarkStreamer;
 }
 
-namespace vfs {
-class FileSystem;
-}
-
 /// This class is intended to be used as a driving class for all asm writers.
 class LLVM_ABI AsmPrinter : public MachineFunctionPass {
 public:
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp
index 6b0fd456391e2..8c2467037b4cb 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinterInlineAsm.cpp
@@ -34,6 +34,7 @@
 #include "llvm/MC/MCSymbol.h"
 #include "llvm/MC/TargetRegistry.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/IOSandbox.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/VirtualFileSystem.h"
@@ -98,13 +99,12 @@ void AsmPrinter::emitInlineAsm(StringRef Str, const MCSubtargetInfo &STI,
 
   unsigned BufNum = addInlineAsmDiagBuffer(Str, LocMDNode);
   SourceMgr &SrcMgr = *MMI->getContext().getInlineSourceManager();
-  // FIXME(sandboxing): This is not executed in tests, but might be common.
-  //                    Propagating vfs::FileSystem here is lots of work,
-  //                    consider bypassing the sandbox.
-  if (!MCOptions.IASSearchPaths.empty()) {
-    SrcMgr.setIncludeDirs(MCOptions.IASSearchPaths);
-    SrcMgr.setVirtualFileSystem(vfs::getRealFileSystem());
-  }
+  SrcMgr.setIncludeDirs(MCOptions.IASSearchPaths);
+  SrcMgr.setVirtualFileSystem([] {
+    // FIXME(sandboxing): Propagating vfs::FileSystem here is lots of work.
+    [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
+    return vfs::getRealFileSystem();
+  }());
 
   std::unique_ptr<MCAsmParser> Parser(
       createMCAsmParser(SrcMgr, OutContext, *OutStreamer, *MAI, BufNum));

>From 36094dcae296318a11d69f4e8f5877c24f539449 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Thu, 16 Oct 2025 09:11:58 -0700
Subject: [PATCH 18/19] Sanitizers

---
 clang/lib/CodeGen/BackendUtil.cpp                          | 7 ++++---
 .../llvm/Transforms/Instrumentation/SanitizerCoverage.h    | 7 +++----
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 4a57dedb0e800..9985a0141f65f 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -716,9 +716,10 @@ static void addSanitizers(const Triple &TargetTriple,
                                 ThinOrFullLTOPhase) {
     if (CodeGenOpts.hasSanitizeCoverage()) {
       auto SancovOpts = getSancovOptsFromCGOpts(CodeGenOpts);
-      MPM.addPass(SanitizerCoveragePass(
-          SancovOpts, CodeGenOpts.SanitizeCoverageAllowlistFiles,
-          CodeGenOpts.SanitizeCoverageIgnorelistFiles));
+      MPM.addPass(
+          SanitizerCoveragePass(SancovOpts, PB.getVirtualFileSystemPtr(),
+                                CodeGenOpts.SanitizeCoverageAllowlistFiles,
+                                CodeGenOpts.SanitizeCoverageIgnorelistFiles));
     }
 
     if (CodeGenOpts.hasSanitizeBinaryMetadata()) {
diff --git a/llvm/include/llvm/Transforms/Instrumentation/SanitizerCoverage.h b/llvm/include/llvm/Transforms/Instrumentation/SanitizerCoverage.h
index f14f5b90a5cc9..f2676d1234222 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/SanitizerCoverage.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/SanitizerCoverage.h
@@ -32,17 +32,16 @@ class SanitizerCoveragePass : public PassInfoMixin<SanitizerCoveragePass> {
 public:
   explicit SanitizerCoveragePass(
       SanitizerCoverageOptions Options = SanitizerCoverageOptions(),
+      IntrusiveRefCntPtr<vfs::FileSystem> VFS = vfs::getRealFileSystem(),
       const std::vector<std::string> &AllowlistFiles =
           std::vector<std::string>(),
       const std::vector<std::string> &BlocklistFiles =
           std::vector<std::string>())
       : Options(Options) {
     if (AllowlistFiles.size() > 0)
-      Allowlist = SpecialCaseList::createOrDie(AllowlistFiles,
-                                               *vfs::getRealFileSystem());
+      Allowlist = SpecialCaseList::createOrDie(AllowlistFiles, *VFS);
     if (BlocklistFiles.size() > 0)
-      Blocklist = SpecialCaseList::createOrDie(BlocklistFiles,
-                                               *vfs::getRealFileSystem());
+      Blocklist = SpecialCaseList::createOrDie(BlocklistFiles, *VFS);
   }
   LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
   static bool isRequired() { return true; }

>From 4702b7e42108dbbfb1eee095607ea5fc27e973f5 Mon Sep 17 00:00:00 2001
From: Jan Svoboda <jan_svoboda at apple.com>
Date: Thu, 16 Oct 2025 10:55:29 -0700
Subject: [PATCH 19/19] LockFileManager

---
 llvm/lib/Support/LockFileManager.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/llvm/lib/Support/LockFileManager.cpp b/llvm/lib/Support/LockFileManager.cpp
index 586f4c98ae32c..215f2f1d2699d 100644
--- a/llvm/lib/Support/LockFileManager.cpp
+++ b/llvm/lib/Support/LockFileManager.cpp
@@ -167,6 +167,8 @@ Expected<bool> LockFileManager::tryLock() {
   assert(std::holds_alternative<OwnerUnknown>(Owner) &&
          "lock has already been attempted");
 
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
+
   SmallString<128> AbsoluteFileName(FileName);
   if (std::error_code EC = sys::fs::make_absolute(AbsoluteFileName))
     return createStringError(EC, "failed to obtain absolute path for " +
@@ -264,6 +266,8 @@ LockFileManager::~LockFileManager() {
 
 WaitForUnlockResult
 LockFileManager::waitForUnlockFor(std::chrono::seconds MaxSeconds) {
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
+
   auto *LockFileOwner = std::get_if<OwnedByAnother>(&Owner);
   assert(LockFileOwner &&
          "waiting for lock to be unlocked without knowing the owner");
@@ -293,5 +297,7 @@ LockFileManager::waitForUnlockFor(std::chrono::seconds MaxSeconds) {
 }
 
 std::error_code LockFileManager::unsafeMaybeUnlock() {
+  [[maybe_unused]] auto BypassSandbox = sys::sandbox::scopedDisable();
+
   return sys::fs::remove(LockFileName);
 }



More information about the cfe-commits mailing list