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 {