]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
fix: Avoid spawning storage helper when IPC access is denied master
authorJoel Rosdahl <joel@rosdahl.net>
Fri, 31 Jul 2026 09:58:40 +0000 (11:58 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Fri, 31 Jul 2026 10:15:49 +0000 (12:15 +0200)
A sandbox (e.g. bubblewrap) may reject Unix socket connections with
EPERM. Spawning a helper can't fix that, so fail immediately instead of
launching the helper and retrying for 10 seconds.

src/ccache/storage/remote/client.cpp
src/ccache/storage/remote/client.hpp
src/ccache/storage/remote/helper.cpp
src/ccache/util/ipcchannelclient.hpp
src/ccache/util/unixsocketclient.cpp
src/ccache/util/winnamedpipeclient.cpp

index 633e310fa11737f633f7813700580e748fc429ff..4cb70c432a471e6afb8ca30a15c8e12f9d7d80f1 100644 (file)
@@ -40,10 +40,16 @@ constexpr uint8_t k_request_exists = 0x05;
 static Client::Error
 make_error(const util::IpcError& ipc_error)
 {
-  auto failure = (ipc_error.failure == util::IpcError::Failure::timeout)
-                   ? Client::Failure::timeout
-                   : Client::Failure::error;
-  return Client::Error(failure, ipc_error.message);
+  switch (ipc_error.failure) {
+  case util::IpcError::Failure::error:
+    return Client::Error(Client::Failure::error, ipc_error.message);
+  case util::IpcError::Failure::permission_denied:
+    return Client::Error(Client::Failure::permission_denied, ipc_error.message);
+  case util::IpcError::Failure::timeout:
+    return Client::Error(Client::Failure::timeout, ipc_error.message);
+  default:
+    return Client::Error(Client::Failure::error, "internal error");
+  }
 }
 
 Client::Client(std::chrono::milliseconds data_timeout,
index 70b952ee93699b3568acb12c2984470db5e50184..d6a78250b7e7d7dc8f8a55cdf8dbcd982f5f9826 100644 (file)
@@ -60,8 +60,9 @@ public:
   };
 
   enum class Failure {
-    error,   // Operation error (protocol error, connection failure, etc.)
-    timeout, // Timeout (data timeout or request timeout exceeded)
+    error,             // Operation error (protocol/connection error, etc.)
+    permission_denied, // IPC operation prohibited by the operating system
+    timeout,           // Timeout (data timeout or request timeout exceeded)
   };
 
   struct Error
index 1c017ef9aff3e0a7bc5a0404442f93d3665571e3..0f76ed5c2e3cc3e651f4850350375a740f61715a 100644 (file)
@@ -493,6 +493,11 @@ HelperBackend::ensure_connected(bool spawn)
     connect_result.error().message,
     timer.measure_ms());
 
+  if (connect_result.error().failure == Client::Failure::permission_denied) {
+    LOG("Not spawning remote storage helper since IPC access was denied");
+    return tl::unexpected(Failure::error);
+  }
+
   if (!spawn) {
     return {};
   }
@@ -512,13 +517,18 @@ HelperBackend::ensure_connected(bool spawn)
 
   // We have the lock. Check again if another process spawned while we waited.
   timer.reset();
-  if (m_client.connect(m_endpoint)) {
+  connect_result = m_client.connect(m_endpoint);
+  if (connect_result) {
     LOG(
       "Connected to remote storage helper spawned by another process ({:.2f}"
       " ms)",
       timer.measure_ms());
     return finalize_connection();
   }
+  if (connect_result.error().failure == Client::Failure::permission_denied) {
+    LOG("Not spawning remote storage helper since IPC access was denied");
+    return tl::unexpected(Failure::error);
+  }
 
   // No helper exists, spawn it now.
   timer.reset();
@@ -545,6 +555,13 @@ HelperBackend::ensure_connected(bool spawn)
       return finalize_connection();
     }
 
+    if (connect_result.error().failure == Client::Failure::permission_denied) {
+      LOG(
+        "Giving up connecting to spawned remote storage helper since IPC"
+        " access was denied");
+      return tl::unexpected(Failure::error);
+    }
+
     std::this_thread::sleep_for(sleep_duration);
   }
 
index cce2f9cd7daf5183f77b4c26a654c82d82f609b6..7e81df1763012979f43faab9620d76717637a246 100644 (file)
@@ -30,8 +30,9 @@ namespace util {
 struct IpcError
 {
   enum class Failure {
-    error,   // Permanent error (connection refused, invalid state, etc.)
-    timeout, // Transient timeout (may succeed on retry)
+    error,             // Permanent error (connection refused, etc.)
+    permission_denied, // IPC operation prohibited by the operating system
+    timeout,           // Transient timeout (may succeed on retry)
   };
 
   Failure failure;
index af2bc7dad2a26aed2939a8afe2fa5529814e7d7a..149f33612527742764e10706d5d6ca2e2038bfae 100644 (file)
@@ -34,6 +34,14 @@ namespace util {
 
 namespace {
 
+IpcError::Failure
+failure_from_errno(int error_number)
+{
+  return error_number == EACCES || error_number == EPERM
+           ? IpcError::Failure::permission_denied
+           : IpcError::Failure::error;
+}
+
 std::chrono::milliseconds
 remaining_timeout(const std::chrono::steady_clock::time_point start_time,
                   const std::chrono::milliseconds timeout)
@@ -66,7 +74,7 @@ poll_with_timeout(int fd,
         IpcError(IpcError::Failure::timeout, FMT("{} timeout", action)));
     }
     if (errno != EINTR) {
-      return tl::unexpected(IpcError(IpcError::Failure::error,
+      return tl::unexpected(IpcError(failure_from_errno(errno),
                                      FMT("Poll failed: {}", strerror(errno))));
     }
   }
@@ -100,7 +108,7 @@ UnixSocketClient::connect(const std::string& endpoint,
   m_fd = socket(AF_UNIX, SOCK_STREAM, 0);
   if (m_fd == -1) {
     return tl::unexpected(
-      IpcError(IpcError::Failure::error,
+      IpcError(failure_from_errno(errno),
                FMT("Failed to create socket: {}", strerror(errno))));
   }
 
@@ -110,7 +118,7 @@ UnixSocketClient::connect(const std::string& endpoint,
     int saved_errno = errno;
     close();
     return tl::unexpected(IpcError(
-      IpcError::Failure::error,
+      failure_from_errno(saved_errno),
       FMT("Failed to set socket non-blocking: {}", strerror(saved_errno))));
   }
 
@@ -129,7 +137,7 @@ UnixSocketClient::connect(const std::string& endpoint,
       int saved_errno = errno;
       close();
       return tl::unexpected(IpcError(
-        IpcError::Failure::error,
+        failure_from_errno(saved_errno),
         FMT("Connection to {} failed: {}", endpoint, strerror(saved_errno))));
     }
 
@@ -146,13 +154,13 @@ UnixSocketClient::connect(const std::string& endpoint,
       int saved_errno = errno;
       close();
       return tl::unexpected(
-        IpcError(IpcError::Failure::error,
+        IpcError(failure_from_errno(saved_errno),
                  FMT("Failed to get socket error: {}", strerror(saved_errno))));
     }
     if (error != 0) {
       close();
       return tl::unexpected(
-        IpcError(IpcError::Failure::error,
+        IpcError(failure_from_errno(error),
                  FMT("Connection failed: {}", strerror(error))));
     }
   }
index e84d33cb784dac3ec9034b33f73d0106ac4ea758..ae6665254d670c3260333b142a4778e3bd83b7f2 100644 (file)
@@ -64,7 +64,10 @@ WinNamedPipeClient::connect(const std::string& endpoint,
 
     DWORD error = GetLastError();
     if (error != ERROR_PIPE_BUSY) {
-      return tl::unexpected(IpcError(IpcError::Failure::error,
+      const auto failure = error == ERROR_ACCESS_DENIED
+                             ? IpcError::Failure::permission_denied
+                             : IpcError::Failure::error;
+      return tl::unexpected(IpcError(failure,
                                      FMT("Failed to connect to pipe {}: {}",
                                          endpoint,
                                          util::win32_error_message(error))));