From eb9d10f43068d4928e01ee377d6d38b06631181c Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Tue, 13 Oct 2026 14:31:33 +0900 Subject: [PATCH] Linux: mount volume files that only the requesting user can access A FUSE file system mounted without allow_other, such as a GVfs share, denies root access to files that its owner can open. Mounting a volume stored there, or formatting a new one with a non-FAT file system, failed with "Permission denied". When root is denied opening an existing volume file, re-execute VeraCrypt with the internal option --requester-helper. The helper permanently takes the credentials of the user who requested elevation through sudo or doas, opens the file and passes the descriptor back. Root does all I/O through that descriptor and mounts the volume through the FUSE volume image. Another helper restores the timestamps when the FUSE service closes the file. This also lets volumes on NFS with root_squash mount read-write instead of falling back to read-only. Fixes #23 --- src/Core/Unix/Linux/CoreLinux.cpp | 5 +- src/Driver/Fuse/FuseService.cpp | 10 + src/Main/Unix/Main.cpp | 6 + src/Platform/File.h | 27 +- src/Platform/Platform.make | 3 + src/Platform/Unix/File.cpp | 34 ++ src/Platform/Unix/RequesterAccess.cpp | 578 ++++++++++++++++++++++++++ src/Volume/Volume.cpp | 5 + 8 files changed, 666 insertions(+), 2 deletions(-) create mode 100644 src/Platform/Unix/RequesterAccess.cpp diff --git a/src/Core/Unix/Linux/CoreLinux.cpp b/src/Core/Unix/Linux/CoreLinux.cpp index a58f5738..831bef8b 100644 --- a/src/Core/Unix/Linux/CoreLinux.cpp +++ b/src/Core/Unix/Linux/CoreLinux.cpp @@ -1052,7 +1052,10 @@ namespace VeraCrypt || !xts || algoNotSupported || volume->IsEncryptionNotCompleted () - || volume->GetProtectionType() == VolumeProtection::HiddenVolumeReadOnly) + || volume->GetProtectionType() == VolumeProtection::HiddenVolumeReadOnly + // Only the open descriptor gives root access to a requester-backed file: + // serve it to the loop device through the FUSE volume image. + || volume->GetFile()->IsRequesterBacked()) { throw NotApplicable (SRC_POS); } diff --git a/src/Driver/Fuse/FuseService.cpp b/src/Driver/Fuse/FuseService.cpp index a92c6893..055eb1de 100644 --- a/src/Driver/Fuse/FuseService.cpp +++ b/src/Driver/Fuse/FuseService.cpp @@ -1367,6 +1367,12 @@ namespace VeraCrypt #endif Process::Execute ("fuse", args, -1, &execFunctor); +#ifdef TC_LINUX + // The service has started with its copy of the volume file. Its final close + // restores the timestamps of a requester-backed file; this copy is only released. + openVolume->GetFile()->SetReleaseOnly(); +#endif + #if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET) close (startup[1]); startup[1] = -1; @@ -1810,6 +1816,10 @@ namespace VeraCrypt { #if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET) close (StartupFd); +#endif +#ifdef TC_LINUX + // The FUSE service process, not this one, closes the volume file last. + FuseService::MountedVolume->GetFile()->SetReleaseOnly(); #endif CloseMountedVolume(); diff --git a/src/Main/Unix/Main.cpp b/src/Main/Unix/Main.cpp index abed3916..247eca7f 100644 --- a/src/Main/Unix/Main.cpp +++ b/src/Main/Unix/Main.cpp @@ -30,6 +30,12 @@ using namespace VeraCrypt; int main (int argc, char **argv) { +#ifdef TC_LINUX + // The requester helper must never initialize a core service or handle secrets. + if (argc > 1 && strcmp (argv[1], TC_REQUESTER_HELPER_CMDLINE_OPTION) == 0) + return argc == 3 ? File::RunRequesterHelperService (argv[2]) : 1; +#endif + try { // Make sure all required commands can be executed via default search path diff --git a/src/Platform/File.h b/src/Platform/File.h index 345d7954..c3253a1a 100644 --- a/src/Platform/File.h +++ b/src/Platform/File.h @@ -18,6 +18,10 @@ #include "FilesystemPath.h" #include "SystemException.h" +#ifdef TC_LINUX +#define TC_REQUESTER_HELPER_CMDLINE_OPTION "--requester-helper" +#endif + namespace VeraCrypt { class File @@ -45,7 +49,10 @@ namespace VeraCrypt // Bitmap FlagsNone = 0, PreserveTimestamps = 1 << 0, - DisableWriteCaching = 1 << 1 + DisableWriteCaching = 1 << 1, + // Linux: when root is denied access to an existing file, open it with + // the credentials of the user who requested elevation. + AllowRequesterAccess = 1 << 2 }; #ifdef TC_WINDOWS @@ -79,6 +86,15 @@ namespace VeraCrypt static size_t GetOptimalWriteSize () { return OptimalWriteSize; } uint64 GetPartitionDeviceStartOffset () const; bool IsOpen () const { return FileIsOpen; } +#ifdef TC_LINUX + // Internal, freshly executed process; called before core/UI initialization. + static int RunRequesterHelperService (const char *socketDescriptor); + // The descriptor was opened with the requester's credentials (AllowRequesterAccess). + bool IsRequesterBacked () const { return RequesterBacked; } + // For copies held by processes that do not serve the volume: release the + // descriptor without restoring timestamps through a requester process. + void SetReleaseOnly () { ReleaseOnly = true; } +#endif FilePath GetPath () const; uint64 Length () const; void Open (const FilePath &path, FileOpenMode mode = OpenRead, FileShareMode shareMode = ShareReadWrite, FileOpenFlags flags = FlagsNone); @@ -112,6 +128,15 @@ namespace VeraCrypt time_t AccTime; time_t ModTime; #endif +#ifdef TC_LINUX + bool OpenAsRequester (const FilePath &path, int sysFlags); + void RestoreTimestampsAsRequester () const; + + bool RequesterBacked = false; + bool ReleaseOnly = false; + uid_t RequesterUserId = 0; + gid_t RequesterGroupId = 0; +#endif private: File (const File &); diff --git a/src/Platform/Platform.make b/src/Platform/Platform.make index 0fa1487d..b4c8e69c 100644 --- a/src/Platform/Platform.make +++ b/src/Platform/Platform.make @@ -35,5 +35,8 @@ OBJS += Unix/SystemInfo.o OBJS += Unix/SystemLog.o OBJS += Unix/Thread.o OBJS += Unix/Time.o +ifeq "$(PLATFORM)" "Linux" +OBJS += Unix/RequesterAccess.o +endif include $(BUILD_INC)/Makefile.inc diff --git a/src/Platform/Unix/File.cpp b/src/Platform/Unix/File.cpp index efd5a1cc..6706408e 100644 --- a/src/Platform/Unix/File.cpp +++ b/src/Platform/Unix/File.cpp @@ -71,6 +71,24 @@ namespace VeraCrypt close (FileHandle); FileIsOpen = false; +#ifdef TC_LINUX + if (RequesterBacked) + { + // Root may be denied changing this file's attributes: restore them with the + // requester's credentials, unless another process serves the file. + RequesterBacked = false; + if ((mFileOpenFlags & File::PreserveTimestamps) && !ReleaseOnly) + { + try + { + RestoreTimestampsAsRequester(); + } + catch (...) { } + } + return; + } +#endif + if ((mFileOpenFlags & File::PreserveTimestamps) && Path.IsFile()) { struct utimbuf u; @@ -311,6 +329,22 @@ namespace VeraCrypt } FileHandle = open (string (path).c_str(), sysFlags, S_IRUSR | S_IWUSR); +#ifdef TC_LINUX + if (FileHandle == -1 && (errno == EACCES || errno == EPERM) && (flags & File::AllowRequesterAccess) + && (mode == OpenRead || mode == OpenWrite || mode == OpenReadWrite)) + { + int rootError = errno; + if (OpenAsRequester (path, sysFlags)) + { + Path = path; + mFileOpenFlags = flags; + FileIsOpen = true; + return; + } + + errno = rootError; + } +#endif throw_sys_sub_if (FileHandle == -1, wstring (path)); #if 0 // File locking is disabled to avoid remote filesystem locking issues diff --git a/src/Platform/Unix/RequesterAccess.cpp b/src/Platform/Unix/RequesterAccess.cpp new file mode 100644 index 00000000..f5006bcd --- /dev/null +++ b/src/Platform/Unix/RequesterAccess.cpp @@ -0,0 +1,578 @@ +/* + Derived from source code of TrueCrypt 7.1a, which is + Copyright (c) 2008-2012 TrueCrypt Developers Association and which is governed + by the TrueCrypt License 3.0. + + Modifications and additions to the original source code (contained in this file) + and all other portions of this file are Copyright (c) 2013-2026 AM Crypto + and are governed by the Apache License 2.0 the full text of which is + contained in the file License.txt included in VeraCrypt binary and source + code distribution packages. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Platform/File.h" +#include "Platform/ForEach.h" +#include "Core/Unix/UnixUser.h" + +namespace VeraCrypt +{ + // Requester access: a FUSE file system mounted by a user without allow_other, + // such as a GVfs share, denies root even when its owner can open the file. Once + // root has been denied, a freshly executed copy of VeraCrypt permanently adopts + // the credentials of the user who requested elevation, opens the file and + // passes the descriptor back. Root then performs the I/O through the descriptor. + // Changing the file's attributes again requires the requester's credentials. + // Like any I/O on the file, this waits for the requester's file system. + + enum RequesterHelperStage + { + RequesterHelperStartup = 1, + RequesterHelperSucceeded, + RequesterHelperCredentials, + RequesterHelperOpen, + RequesterHelperStat, + RequesterHelperNotRegular, + RequesterHelperFlags, + RequesterHelperTimes + }; + + struct RequesterHelperReply + { + int32 Stage; + int32 Error; + int64 AccessTime; + int64 ModificationTime; + }; + + // Both ends run the same executable (/proc/self/exe), including after an upgrade. + // A request opens the file or, once it has been closed, restores its timestamps. + struct RequesterHelperRequest + { + uint32 OpenFile; + int32 Flags; + uid_t UserId; + gid_t GroupId; + struct timespec Times[2]; + char Path[PATH_MAX]; + }; + + static bool ParseRequesterId (const char *value, uint64 invalidId, uint64 &id) + { + if (!value || value[0] < '0' || value[0] > '9') + return false; + + char *end = nullptr; + errno = 0; + unsigned long long parsed = strtoull (value, &end, 10); + if (errno != 0 || !end || *end != '\0' || parsed >= invalidId) + return false; + + id = parsed; + return true; + } + + // The user who requested elevation, as attributed elsewhere in VeraCrypt (sudo, + // or the trusted doas service). No identity is guessed if it is missing or invalid. + static bool GetRequesterIdentity (uid_t &userId, gid_t &groupId) + { + if (geteuid () != 0) + return false; + + const char *sudoUserId = getenv ("SUDO_UID"); + const char *sudoGroupId = getenv ("SUDO_GID"); + if (sudoUserId || sudoGroupId) + { + uint64 uid, gid; + if (!ParseRequesterId (sudoUserId, static_cast (-1), uid) + || !ParseRequesterId (sudoGroupId, static_cast (-1), gid)) + return false; + + userId = static_cast (uid); + groupId = static_cast (gid); + } + else if (!GetDoasUserIds (&userId, &groupId)) + return false; + + return userId != 0 && userId != static_cast (-1) && groupId != static_cast (-1); + } + + // Only after exec: even closing an inherited descriptor may block on FUSE. + static bool CloseRequesterHelperDescriptors (int keep) + { + bool closed = false; +#ifdef SYS_close_range + closed = (keep == 0 || syscall (SYS_close_range, 0U, static_cast (keep) - 1, 0) == 0) + && syscall (SYS_close_range, static_cast (keep) + 1, ~0U, 0) == 0; +#endif + if (!closed) + { + // This is a fresh, single-threaded process, so directory enumeration is safe. + // Do not cap descriptor numbers: inherited fds can exceed today's rlimit. + DIR *directory = opendir ("/proc/self/fd"); + if (!directory) + return false; + int error; + while (true) + { + errno = 0; + struct dirent *entry = readdir (directory); + if (!entry) + { + error = errno; + break; + } + uint64 fd; + if (ParseRequesterId (entry->d_name, static_cast (INT_MAX) + 1, fd) + && static_cast (fd) != keep && static_cast (fd) != dirfd (directory)) + close (static_cast (fd)); + } + closedir (directory); + errno = error; + if (error != 0) + return false; + } + + // Occupy 0-2, so that the file opened later cannot become one of them. + for (int fd = 0; fd < 3; ++fd) + { + if (fd != keep && open ("/dev/null", O_RDWR) != fd) + return false; + } + + return true; + } + + // Helper process only. + static bool SetRequesterCredentials (uid_t userId, gid_t groupId) + { + // Supplementary groups of the account, not of the caller's session. FUSE only + // checks the user and group IDs; other file systems may use the groups. + struct passwd *entry = getpwuid (userId); + if ((!entry || initgroups (entry->pw_name, groupId) != 0) && setgroups (1, &groupId) != 0) + return false; + + if (setresgid (groupId, groupId, groupId) != 0 || setresuid (userId, userId, userId) != 0 + || prctl (PR_SET_DUMPABLE, 0, 0, 0, 0) != 0) + return false; + + uid_t realUserId, effectiveUserId, savedUserId; + gid_t realGroupId, effectiveGroupId, savedGroupId; + if (getresuid (&realUserId, &effectiveUserId, &savedUserId) != 0 + || getresgid (&realGroupId, &effectiveGroupId, &savedGroupId) != 0) + return false; + + // After exec, the change to nonzero IDs cleared all capabilities, unless + // SECBIT_NO_SETUID_FIXUP kept them: CAP_SETUID would then restore root. + if (realUserId != userId || effectiveUserId != userId || savedUserId != userId + || realGroupId != groupId || effectiveGroupId != groupId || savedGroupId != groupId + || setresuid (0, 0, 0) == 0) + { + errno = EPERM; + return false; + } + + return true; + } + + // One packet, optionally carrying one descriptor. + static int SendRequesterHelperData (int socketFd, const void *buffer, size_t length, int descriptor = -1) + { + union + { + char Buffer[CMSG_SPACE (sizeof (int))]; + struct cmsghdr Alignment; + } control; + memset (&control, 0, sizeof (control)); + struct iovec data = { const_cast (buffer), length }; + struct msghdr message; + memset (&message, 0, sizeof (message)); + message.msg_iov = &data; + message.msg_iovlen = 1; + if (descriptor != -1) + { + message.msg_control = control.Buffer; + message.msg_controllen = sizeof (control.Buffer); + struct cmsghdr *header = CMSG_FIRSTHDR (&message); + header->cmsg_level = SOL_SOCKET; + header->cmsg_type = SCM_RIGHTS; + header->cmsg_len = CMSG_LEN (sizeof (int)); + memcpy (CMSG_DATA (header), &descriptor, sizeof (descriptor)); + } + + ssize_t size; + do + { + size = sendmsg (socketFd, &message, MSG_NOSIGNAL); + } while (size == -1 && errno == EINTR); + + if (size == -1) + return errno; + + return static_cast (size) == length ? 0 : EIO; + } + + // One packet, optionally carrying one descriptor. Own all received fds. + static int ReceiveRequesterHelperData (int socketFd, void *buffer, size_t length, int *descriptor = nullptr) + { + if (descriptor) + *descriptor = -1; + + union + { + char Buffer[CMSG_SPACE (sizeof (int) * 4)]; + struct cmsghdr Alignment; + } control; + memset (&control, 0, sizeof (control)); + + struct iovec data = { buffer, length }; + struct msghdr message; + memset (&message, 0, sizeof (message)); + message.msg_iov = &data; + message.msg_iovlen = 1; + message.msg_control = control.Buffer; + message.msg_controllen = sizeof (control.Buffer); + + ssize_t size; + do + { + size = recvmsg (socketFd, &message, MSG_CMSG_CLOEXEC); + } while (size == -1 && errno == EINTR); + + if (size == -1) + return errno; + + // Own every received descriptor before validating the packet. + vector descriptors; + bool unexpectedControl = false; + + for (struct cmsghdr *header = CMSG_FIRSTHDR (&message); header; header = CMSG_NXTHDR (&message, header)) + { + if (header->cmsg_level == SOL_SOCKET && header->cmsg_type == SCM_RIGHTS && header->cmsg_len >= CMSG_LEN (0)) + { + size_t count = (header->cmsg_len - CMSG_LEN (0)) / sizeof (int); + for (size_t i = 0; i < count; ++i) + { + int fd; + memcpy (&fd, CMSG_DATA (header) + i * sizeof (int), sizeof (fd)); + descriptors.push_back (fd); + } + } + else + unexpectedControl = true; + } + + if (static_cast (size) != length || (message.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) || unexpectedControl + || descriptors.size() > (descriptor ? 1U : 0U)) + { + foreach (int fd, descriptors) + close (fd); + + return EPROTO; + } + + if (!descriptors.empty()) + *descriptor = descriptors.front(); + + return 0; + } + + // Returns 0 with a validated reply, or the error that prevented one. + static int ReceiveRequesterHelperReply (int socketFd, bool descriptorExpected, RequesterHelperReply &reply, int &descriptor) + { + memset (&reply, 0, sizeof (reply)); + int error = ReceiveRequesterHelperData (socketFd, &reply, sizeof (reply), &descriptor); + if (error != 0) + return error; + bool succeeded = reply.Stage == RequesterHelperSucceeded; + if (reply.Stage < RequesterHelperSucceeded || reply.Stage > RequesterHelperTimes + || (succeeded ? reply.Error != 0 : reply.Error <= 0) + || (descriptor != -1) != (succeeded && descriptorExpected)) + { + if (descriptor != -1) + close (descriptor); + descriptor = -1; + return EPROTO; + } + return 0; + } + + // Helper process only: never returns. + static void RunRequesterHelper (int socketFd, const RequesterHelperRequest &request) + { + RequesterHelperReply reply; + memset (&reply, 0, sizeof (reply)); + int fd = -1; + struct stat statData; + + if (!SetRequesterCredentials (request.UserId, request.GroupId)) + { + reply.Stage = RequesterHelperCredentials; + reply.Error = errno; + } + else if (!request.OpenFile) + { + if (utimensat (AT_FDCWD, request.Path, request.Times, 0) == 0) + reply.Stage = RequesterHelperSucceeded; + else + { + reply.Stage = RequesterHelperTimes; + reply.Error = errno; + } + } + else if ((fd = open (request.Path, request.Flags | O_CLOEXEC | O_NOCTTY | O_NONBLOCK)) == -1) + { + reply.Stage = RequesterHelperOpen; + reply.Error = errno; + } + else if (fstat (fd, &statData) == -1) + { + reply.Stage = RequesterHelperStat; + reply.Error = errno; + } + else if (!S_ISREG (statData.st_mode)) + { + reply.Stage = RequesterHelperNotRegular; + reply.Error = S_ISDIR (statData.st_mode) ? EISDIR : EINVAL; + } + else + { + int flags = fcntl (fd, F_GETFL); + if (flags == -1 || fcntl (fd, F_SETFL, flags & ~O_NONBLOCK) == -1) + { + reply.Stage = RequesterHelperFlags; + reply.Error = errno; + } + else + { + reply.Stage = RequesterHelperSucceeded; + reply.AccessTime = statData.st_atime; + reply.ModificationTime = statData.st_mtime; + } + } + + if (reply.Stage != RequesterHelperSucceeded && reply.Error <= 0) + reply.Error = EIO; + + // Send a descriptor only with a successful opening reply. + SendRequesterHelperData (socketFd, &reply, sizeof (reply), request.OpenFile && reply.Stage == RequesterHelperSucceeded ? fd : -1); + _exit (0); + } + + int File::RunRequesterHelperService (const char *socketDescriptor) + { + try + { + uint64 parsedFd; + if (geteuid() != 0 || !ParseRequesterId (socketDescriptor, static_cast (INT_MAX) + 1, parsedFd)) + return 1; + int socketFd = static_cast (parsedFd); + int socketType; + socklen_t size = sizeof (socketType); + struct ucred peer; + socklen_t peerSize = sizeof (peer); + // Only root may send requests. Otherwise a user allowed to run 'sudo veracrypt' + // could pass a socket of their own and obtain files opened as any other user. + if (getsockopt (socketFd, SOL_SOCKET, SO_TYPE, &socketType, &size) != 0 || socketType != SOCK_SEQPACKET + || getsockopt (socketFd, SOL_SOCKET, SO_PEERCRED, &peer, &peerSize) != 0 || peer.uid != 0) + return 1; + + // Otherwise named after /proc/self/exe, e.g. in ps. + prctl (PR_SET_NAME, "veracrypt", 0, 0, 0); + + int32 startupError = 0; + if (fcntl (socketFd, F_SETFD, FD_CLOEXEC) == -1 || !CloseRequesterHelperDescriptors (socketFd)) + startupError = errno; + if (SendRequesterHelperData (socketFd, &startupError, sizeof (startupError)) != 0 || startupError != 0) + return 1; + + RequesterHelperRequest request; + if (ReceiveRequesterHelperData (socketFd, &request, sizeof (request)) != 0 + || request.OpenFile > 1 || request.UserId == 0 || request.UserId == static_cast (-1) + || request.GroupId == static_cast (-1) || !memchr (request.Path, '\0', sizeof (request.Path)) + || request.Path[0] == '\0' + || (request.OpenFile ? ((request.Flags & ~(O_ACCMODE | O_LARGEFILE | O_CLOEXEC | O_NOCTTY | O_NONBLOCK)) != 0 + || (request.Flags & O_ACCMODE) == O_ACCMODE) + : request.Flags != 0)) + return 1; + + RunRequesterHelper (socketFd, request); + } + catch (...) { } + return 1; + } + + // Returns 0 with a validated reply, or the error that prevented one. Startup + // failures are reported with the RequesterHelperStartup stage. + static int RunRequesterHelperProcess (const RequesterHelperRequest &request, RequesterHelperReply &reply, int &descriptor) + { + descriptor = -1; + memset (&reply, 0, sizeof (reply)); + reply.Stage = RequesterHelperStartup; + + int sockets[2]; + if (socketpair (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) == -1) + return errno; + + // Prepare all allocations before fork; only async-signal-safe calls until exec. + const string socketArgument = to_string (sockets[1]); + // AppImage's AppRun supplies the library path used to load this executable. + // Preserve that setting only, not the caller's other environment/loader hooks. + const char *libraryPath = getenv ("LD_LIBRARY_PATH"); + const string libraryEnvironment = libraryPath ? string ("LD_LIBRARY_PATH=") + libraryPath : string(); + char *helperArgv[] = { const_cast ("veracrypt"), const_cast (TC_REQUESTER_HELPER_CMDLINE_OPTION), + const_cast (socketArgument.c_str()), nullptr }; + char *helperEnv[] = { libraryEnvironment.empty() ? nullptr : const_cast (libraryEnvironment.c_str()), nullptr }; + + pid_t pid = fork(); + if (pid == -1) + { + int error = errno; + close (sockets[0]); + close (sockets[1]); + return error; + } + + if (pid == 0) + { + // No inherited signal handler may run in this copy of the process; the + // helper keeps the mask. Do not close inherited descriptors or access the + // backing filesystem here. Existing CLOEXEC flags take effect at exec; + // the fresh helper closes the rest. + sigset_t signals; + sigfillset (&signals); + if (sigprocmask (SIG_SETMASK, &signals, nullptr) == 0 && fcntl (sockets[1], F_SETFD, 0) != -1) + execve ("/proc/self/exe", helperArgv, helperEnv); + // Preserve the startup errno without allocating or blocking before exec. + int32 startupError = errno; + while (send (sockets[1], &startupError, sizeof (startupError), MSG_NOSIGNAL | MSG_DONTWAIT) == -1 && errno == EINTR) { } + _exit (1); // Never fall back to running the helper with inherited secrets. + } + + close (sockets[1]); + // Wait for fresh-process readiness before sending the request. Otherwise a + // failed exec can race the send and obscure its errno with EPIPE or ECONNRESET. + int32 startupError; + int result = ReceiveRequesterHelperData (sockets[0], &startupError, sizeof (startupError)); + if (result == 0) + result = startupError >= 0 ? startupError : EPROTO; + if (result == 0) + { + reply.Stage = 0; + result = SendRequesterHelperData (sockets[0], &request, sizeof (request)); + } + if (result == 0) + result = ReceiveRequesterHelperReply (sockets[0], request.OpenFile != 0, reply, descriptor); + + // Closing the socket first ends any wait of the helper for this process. + close (sockets[0]); + while (waitpid (pid, nullptr, 0) == -1 && errno == EINTR) { } + return result; + } + + bool File::OpenAsRequester (const FilePath &path, int sysFlags) + { + const string pathString (path); + RequesterHelperRequest request; + memset (&request, 0, sizeof (request)); + + uid_t userId; + gid_t groupId; + if (!GetRequesterIdentity (userId, groupId) || pathString.size() >= sizeof (request.Path)) + return false; + + request.OpenFile = 1; + // Never create or truncate, block on a FIFO or acquire a controlling terminal. + request.Flags = (sysFlags & ~(O_CREAT | O_EXCL | O_TRUNC)) | O_CLOEXEC | O_NOCTTY | O_NONBLOCK; + request.UserId = userId; + request.GroupId = groupId; + memcpy (request.Path, pathString.c_str(), pathString.size()); + + RequesterHelperReply reply; + int descriptor; + int result = RunRequesterHelperProcess (request, reply, descriptor); + + if (reply.Stage == RequesterHelperStartup) + { + stringstream message; + message << "Cannot start VeraCrypt's requester-access process (/proc/self/exe): "; + if (result == EPROTO || result == ECONNRESET) + message << "the process did not report readiness"; + else + message << strerror (result); + message << "\n" << pathString; + throw ExternalException (SRC_POS, StringConverter::ToWide (message.str())); + } + + // Failures of the helper itself must not look like access-mode errors, + // which would make mounting fall back to read-only access. + if (result != 0 || reply.Stage == RequesterHelperCredentials) + { + stringstream message; + message << "Cannot access the file with the credentials of user " << userId << ": " + << strerror (result != 0 ? result : reply.Error) << "\n" << pathString; + throw ExternalException (SRC_POS, StringConverter::ToWide (message.str())); + } + + if (reply.Stage != RequesterHelperSucceeded) + { + errno = reply.Error; + + // The requester's file system cannot open the file for writing in place, + // e.g. a GVfs backend without edit support. Report it as read-only, so that + // mounting can fall back to read-only access. ENOTSUP from any other + // operation remains an error. + if (reply.Stage == RequesterHelperOpen && (sysFlags & O_ACCMODE) != O_RDONLY + && (reply.Error == ENOTSUP || reply.Error == EOPNOTSUPP)) + errno = EROFS; + + throw SystemException (SRC_POS, wstring (path)); + } + + FileHandle = descriptor; + AccTime = static_cast (reply.AccessTime); + ModTime = static_cast (reply.ModificationTime); + RequesterBacked = true; + ReleaseOnly = false; + RequesterUserId = userId; + RequesterGroupId = groupId; + return true; + } + + // Called once the file is closed, so through its path, as for other files. + void File::RestoreTimestampsAsRequester () const + { + const string pathString (Path); + RequesterHelperRequest request; + memset (&request, 0, sizeof (request)); + if (pathString.size() >= sizeof (request.Path)) + return; + + request.Times[0].tv_sec = AccTime; + request.Times[1].tv_sec = ModTime; + request.UserId = RequesterUserId; + request.GroupId = RequesterGroupId; + memcpy (request.Path, pathString.c_str(), pathString.size()); + + // Best effort, like restoring the timestamps of other files. + RequesterHelperReply reply; + int descriptor; + RunRequesterHelperProcess (request, reply, descriptor); + } +} diff --git a/src/Volume/Volume.cpp b/src/Volume/Volume.cpp index ceca072e..0cee6344 100644 --- a/src/Volume/Volume.cpp +++ b/src/Volume/Volume.cpp @@ -75,6 +75,11 @@ namespace VeraCrypt make_shared_auto (File, file); File::FileOpenFlags flags = (preserveTimestamps ? File::PreserveTimestamps : File::FlagsNone); +#ifdef TC_LINUX + // An elevated process may be denied access to a volume file that the + // requesting user can open, e.g. on a GVfs share. + flags = static_cast (flags | File::AllowRequesterAccess); +#endif try {