From: Joel Rosdahl Date: Fri, 31 Jul 2026 09:58:40 +0000 (+0200) Subject: fix: Avoid spawning storage helper when IPC access is denied X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;ds=inline;p=thirdparty%2Fccache.git fix: Avoid spawning storage helper when IPC access is denied 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. --- diff --git a/src/ccache/storage/remote/client.cpp b/src/ccache/storage/remote/client.cpp index 633e310f..4cb70c43 100644 --- a/src/ccache/storage/remote/client.cpp +++ b/src/ccache/storage/remote/client.cpp @@ -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, diff --git a/src/ccache/storage/remote/client.hpp b/src/ccache/storage/remote/client.hpp index 70b952ee..d6a78250 100644 --- a/src/ccache/storage/remote/client.hpp +++ b/src/ccache/storage/remote/client.hpp @@ -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 diff --git a/src/ccache/storage/remote/helper.cpp b/src/ccache/storage/remote/helper.cpp index 1c017ef9..0f76ed5c 100644 --- a/src/ccache/storage/remote/helper.cpp +++ b/src/ccache/storage/remote/helper.cpp @@ -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); } diff --git a/src/ccache/util/ipcchannelclient.hpp b/src/ccache/util/ipcchannelclient.hpp index cce2f9cd..7e81df17 100644 --- a/src/ccache/util/ipcchannelclient.hpp +++ b/src/ccache/util/ipcchannelclient.hpp @@ -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; diff --git a/src/ccache/util/unixsocketclient.cpp b/src/ccache/util/unixsocketclient.cpp index af2bc7da..149f3361 100644 --- a/src/ccache/util/unixsocketclient.cpp +++ b/src/ccache/util/unixsocketclient.cpp @@ -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)))); } } diff --git a/src/ccache/util/winnamedpipeclient.cpp b/src/ccache/util/winnamedpipeclient.cpp index e84d33cb..ae666525 100644 --- a/src/ccache/util/winnamedpipeclient.cpp +++ b/src/ccache/util/winnamedpipeclient.cpp @@ -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))));