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
This commit is contained in:
Mounir IDRASSI
2026-10-13 14:36:40 +09:00
parent a835875912
commit eb9d10f430
8 changed files with 666 additions and 2 deletions
+4 -1
View File
@@ -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);
}
+10
View File
@@ -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();
+6
View File
@@ -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
+26 -1
View File
@@ -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 &);
+3
View File
@@ -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
+34
View File
@@ -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
+578
View File
@@ -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 <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#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 <uid_t> (-1), uid)
|| !ParseRequesterId (sudoGroupId, static_cast <gid_t> (-1), gid))
return false;
userId = static_cast <uid_t> (uid);
groupId = static_cast <gid_t> (gid);
}
else if (!GetDoasUserIds (&userId, &groupId))
return false;
return userId != 0 && userId != static_cast <uid_t> (-1) && groupId != static_cast <gid_t> (-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 <unsigned int> (keep) - 1, 0) == 0)
&& syscall (SYS_close_range, static_cast <unsigned int> (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 <uint64> (INT_MAX) + 1, fd)
&& static_cast <int> (fd) != keep && static_cast <int> (fd) != dirfd (directory))
close (static_cast <int> (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 <void *> (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_t> (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 <int> 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_t> (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 <uint64> (INT_MAX) + 1, parsedFd))
return 1;
int socketFd = static_cast <int> (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 <uid_t> (-1)
|| request.GroupId == static_cast <gid_t> (-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 <char *> ("veracrypt"), const_cast <char *> (TC_REQUESTER_HELPER_CMDLINE_OPTION),
const_cast <char *> (socketArgument.c_str()), nullptr };
char *helperEnv[] = { libraryEnvironment.empty() ? nullptr : const_cast <char *> (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 <time_t> (reply.AccessTime);
ModTime = static_cast <time_t> (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);
}
}
+5
View File
@@ -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 <File::FileOpenFlags> (flags | File::AllowRequesterAccess);
#endif
try
{