mirror of
https://github.com/veracrypt/VeraCrypt.git
synced 2026-09-26 01:44:53 +00:00
Add an authenticated shutdown endpoint to the auxiliary FUSE filesystem and wait for the matching service process to terminate before removing the mount point.
1064 lines
27 KiB
C++
1064 lines
27 KiB
C++
/*
|
|
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.
|
|
*/
|
|
|
|
#ifdef TC_OPENBSD
|
|
# define FUSE_USE_VERSION 26
|
|
#else
|
|
# ifndef VC_FUSE_VERSION
|
|
# define VC_FUSE_VERSION 2
|
|
# endif
|
|
# if VC_FUSE_VERSION < 3
|
|
# define FUSE_USE_VERSION 25
|
|
# else
|
|
# define FUSE_USE_VERSION 301
|
|
# define VC_FUSE3 1
|
|
# endif
|
|
#endif
|
|
|
|
|
|
#ifdef VC_FUSE3
|
|
#define VC_FUSE_FILL_DIR(filler, buf, name, st, off) filler(buf, name, st, off, (enum fuse_fill_dir_flags)0)
|
|
#else
|
|
#define VC_FUSE_FILL_DIR(filler, buf, name, st, off) filler(buf, name, st, off)
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <fuse.h>
|
|
#include <atomic>
|
|
#include <iostream>
|
|
#include <signal.h>
|
|
#include <sstream>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/statvfs.h>
|
|
#include <sys/time.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include "FuseService.h"
|
|
#if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET)
|
|
#include <fuse_lowlevel.h>
|
|
#undef fuse_unmount
|
|
#ifdef ERR_SUCCESS
|
|
#undef ERR_SUCCESS
|
|
#endif
|
|
#endif
|
|
#include "Platform/FileStream.h"
|
|
#include "Platform/MemoryStream.h"
|
|
#include "Platform/Serializable.h"
|
|
#include "Platform/SystemLog.h"
|
|
#include "Platform/Unix/Pipe.h"
|
|
#include "Platform/Unix/Poller.h"
|
|
#include "Core/Unix/UnixUser.h"
|
|
#include "Volume/EncryptionThreadPool.h"
|
|
#include "Core/Core.h"
|
|
|
|
namespace VeraCrypt
|
|
{
|
|
static const ino_t VC_FUSE_INODE_ROOT = 1;
|
|
static const ino_t VC_FUSE_INODE_VOLUME = 2;
|
|
static const ino_t VC_FUSE_INODE_CONTROL = 3;
|
|
static const ino_t VC_FUSE_INODE_AUX_DEVICE_INFO = 4;
|
|
#if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET)
|
|
static const ino_t VC_FUSE_INODE_SHUTDOWN = 5;
|
|
static atomic <bool> FuseServiceShutdownRequested (false);
|
|
|
|
struct FuseServiceShutdownContext
|
|
{
|
|
explicit FuseServiceShutdownContext (struct fuse *fuseHandle) : FuseHandle (fuseHandle) { }
|
|
struct fuse *FuseHandle;
|
|
};
|
|
#endif
|
|
static const uint64 VC_FUSE_BLOCK_SIZE = 4096;
|
|
static const uint64 VC_FUSE_METADATA_SIZE = 64 * 1024;
|
|
static const uint64 VC_FUSE_STAT_BLOCK_SIZE = 512;
|
|
|
|
static uint64 fuse_service_ceil_div (uint64 value, uint64 divisor)
|
|
{
|
|
return (value / divisor) + ((value % divisor) ? 1 : 0);
|
|
}
|
|
|
|
static void fuse_service_set_stat_blocks (struct stat *statData)
|
|
{
|
|
statData->st_blksize = VC_FUSE_BLOCK_SIZE;
|
|
statData->st_blocks = fuse_service_ceil_div ((uint64) statData->st_size, VC_FUSE_STAT_BLOCK_SIZE);
|
|
}
|
|
|
|
static shared_ptr <Buffer> fuse_service_get_control_info (struct fuse_file_info *fi)
|
|
{
|
|
if (fi && fi->fh)
|
|
return *reinterpret_cast <shared_ptr <Buffer> *> (fi->fh);
|
|
|
|
return FuseService::GetVolumeInfo();
|
|
}
|
|
|
|
#if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET)
|
|
static string fuse_service_get_shutdown_identity ()
|
|
{
|
|
stringstream identity;
|
|
identity << getpid() << " " << FuseService::GetSerialInstanceNumber() << " " << FuseService::GetSlotNumber() << "\n";
|
|
return identity.str();
|
|
}
|
|
|
|
static bool fuse_service_parse_shutdown_identity (const string &identity, pid_t &processId, uint64 &serialInstanceNumber, VolumeSlotNumber &slotNumber)
|
|
{
|
|
long long parsedProcessId;
|
|
uint64 parsedSerialInstanceNumber;
|
|
VolumeSlotNumber parsedSlotNumber;
|
|
stringstream parser (identity);
|
|
|
|
if (!(parser >> parsedProcessId >> parsedSerialInstanceNumber >> parsedSlotNumber))
|
|
return false;
|
|
|
|
parser >> ws;
|
|
if (!parser.eof() || parsedProcessId <= 1 || static_cast <pid_t> (parsedProcessId) != parsedProcessId)
|
|
return false;
|
|
|
|
processId = static_cast <pid_t> (parsedProcessId);
|
|
serialInstanceNumber = parsedSerialInstanceNumber;
|
|
slotNumber = parsedSlotNumber;
|
|
return true;
|
|
}
|
|
|
|
static TC_THREAD_PROC fuse_service_shutdown (void *contextArg)
|
|
{
|
|
unique_ptr <FuseServiceShutdownContext> context (static_cast <FuseServiceShutdownContext *> (contextArg));
|
|
|
|
// Let the write reply reach the caller before closing FUSE-T's channel.
|
|
Thread::Sleep (100);
|
|
fuse_exit (context->FuseHandle);
|
|
|
|
struct fuse_session *session = fuse_get_session (context->FuseHandle);
|
|
struct fuse_chan *channel = session ? fuse_session_next_chan (session, NULL) : NULL;
|
|
if (channel)
|
|
fuse_unmount (NULL, channel);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int fuse_service_fill_dir_entry (void *buf, fuse_fill_dir_t filler, const char *name, mode_t mode, ino_t ino, off_t nextOff)
|
|
{
|
|
struct stat st;
|
|
Memory::Zero (&st, sizeof (st));
|
|
st.st_mode = mode;
|
|
st.st_nlink = S_ISDIR (mode) ? 2 : 1;
|
|
st.st_uid = FuseService::GetUserId();
|
|
st.st_gid = FuseService::GetGroupId();
|
|
st.st_ino = ino;
|
|
fuse_service_set_stat_blocks (&st);
|
|
|
|
return VC_FUSE_FILL_DIR (filler, buf, name, &st, nextOff);
|
|
}
|
|
|
|
static int fuse_service_access (const char *path, int mask)
|
|
{
|
|
try
|
|
{
|
|
if (!FuseService::CheckAccessRights())
|
|
return -EACCES;
|
|
}
|
|
catch (...)
|
|
{
|
|
return FuseService::ExceptionToErrorCode();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *fuse_service_init_common ()
|
|
{
|
|
try
|
|
{
|
|
// Termination signals are handled by a separate process to allow clean dismount on shutdown
|
|
struct sigaction action;
|
|
Memory::Zero (&action, sizeof (action));
|
|
action.sa_handler = SIG_IGN;
|
|
|
|
sigaction (SIGINT, &action, nullptr);
|
|
sigaction (SIGQUIT, &action, nullptr);
|
|
sigaction (SIGTERM, &action, nullptr);
|
|
|
|
if (!EncryptionThreadPool::IsRunning())
|
|
EncryptionThreadPool::Start();
|
|
}
|
|
catch (exception &e)
|
|
{
|
|
SystemLog::WriteException (e);
|
|
}
|
|
catch (...)
|
|
{
|
|
SystemLog::WriteException (UnknownException (SRC_POS));
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
#if defined(VC_FUSE3)
|
|
static void *fuse_service_init (struct fuse_conn_info *conn, struct fuse_config *cfg)
|
|
{
|
|
if (cfg)
|
|
{
|
|
cfg->set_uid = 1;
|
|
cfg->set_gid = 1;
|
|
cfg->uid = FuseService::GetUserId();
|
|
cfg->gid = FuseService::GetGroupId();
|
|
|
|
cfg->use_ino = 1;
|
|
}
|
|
|
|
return fuse_service_init_common ();
|
|
}
|
|
#elif defined(TC_OPENBSD) || (FUSE_USE_VERSION >= 26)
|
|
static void *fuse_service_init (struct fuse_conn_info *conn)
|
|
{
|
|
(void) conn;
|
|
return fuse_service_init_common ();
|
|
}
|
|
#else
|
|
static void *fuse_service_init ()
|
|
{
|
|
return fuse_service_init_common ();
|
|
}
|
|
#endif
|
|
|
|
static void fuse_service_destroy (void *userdata)
|
|
{
|
|
try
|
|
{
|
|
FuseService::Dismount();
|
|
}
|
|
catch (exception &e)
|
|
{
|
|
SystemLog::WriteException (e);
|
|
}
|
|
catch (...)
|
|
{
|
|
SystemLog::WriteException (UnknownException (SRC_POS));
|
|
}
|
|
}
|
|
|
|
static int fuse_service_getattr_impl (const char *path, struct stat *statData)
|
|
{
|
|
try
|
|
{
|
|
Memory::Zero (statData, sizeof(*statData));
|
|
|
|
statData->st_uid = FuseService::GetUserId();
|
|
statData->st_gid = FuseService::GetGroupId();
|
|
statData->st_atime = time (NULL);
|
|
statData->st_ctime = time (NULL);
|
|
statData->st_mtime = time (NULL);
|
|
statData->st_blksize = VC_FUSE_BLOCK_SIZE;
|
|
|
|
if (strcmp (path, "/") == 0)
|
|
{
|
|
statData->st_mode = S_IFDIR | 0500;
|
|
statData->st_nlink = 2;
|
|
statData->st_ino = VC_FUSE_INODE_ROOT;
|
|
}
|
|
else
|
|
{
|
|
if (!FuseService::CheckAccessRights())
|
|
return -EACCES;
|
|
|
|
if (strcmp (path, FuseService::GetAuxDeviceInfoPath()) == 0)
|
|
{
|
|
statData->st_mode = S_IFREG | 0600;
|
|
statData->st_nlink = 1;
|
|
statData->st_size = VC_FUSE_METADATA_SIZE;
|
|
statData->st_ino = VC_FUSE_INODE_AUX_DEVICE_INFO;
|
|
fuse_service_set_stat_blocks (statData);
|
|
}
|
|
else if (strcmp (path, FuseService::GetVolumeImagePath()) == 0)
|
|
{
|
|
statData->st_mode = S_IFREG | 0600;
|
|
statData->st_nlink = 1;
|
|
statData->st_size = FuseService::GetVolumeSize();
|
|
statData->st_ino = VC_FUSE_INODE_VOLUME;
|
|
fuse_service_set_stat_blocks (statData);
|
|
}
|
|
else if (strcmp (path, FuseService::GetControlPath()) == 0)
|
|
{
|
|
statData->st_mode = S_IFREG | 0600;
|
|
statData->st_nlink = 1;
|
|
statData->st_size = VC_FUSE_METADATA_SIZE;
|
|
statData->st_ino = VC_FUSE_INODE_CONTROL;
|
|
fuse_service_set_stat_blocks (statData);
|
|
}
|
|
#if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET)
|
|
else if (strcmp (path, FuseService::GetShutdownPath()) == 0)
|
|
{
|
|
statData->st_mode = S_IFREG | 0600;
|
|
statData->st_nlink = 1;
|
|
statData->st_size = fuse_service_get_shutdown_identity().size();
|
|
statData->st_ino = VC_FUSE_INODE_SHUTDOWN;
|
|
fuse_service_set_stat_blocks (statData);
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
return FuseService::ExceptionToErrorCode();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(VC_FUSE3)
|
|
static int fuse_service_getattr (const char *path, struct stat *statData, struct fuse_file_info *fi)
|
|
{
|
|
(void) fi;
|
|
return fuse_service_getattr_impl (path, statData);
|
|
}
|
|
#else
|
|
static int fuse_service_getattr (const char *path, struct stat *statData)
|
|
{
|
|
return fuse_service_getattr_impl (path, statData);
|
|
}
|
|
#endif
|
|
|
|
static int fuse_service_statfs (const char *path, struct statvfs *statData)
|
|
{
|
|
try
|
|
{
|
|
(void) path;
|
|
|
|
uint64 blockCount = fuse_service_ceil_div (FuseService::GetVolumeSize(), VC_FUSE_BLOCK_SIZE);
|
|
if (blockCount == 0)
|
|
blockCount = 1;
|
|
|
|
Memory::Zero (statData, sizeof (*statData));
|
|
statData->f_bsize = VC_FUSE_BLOCK_SIZE;
|
|
statData->f_frsize = VC_FUSE_BLOCK_SIZE;
|
|
statData->f_blocks = blockCount;
|
|
statData->f_bfree = blockCount;
|
|
statData->f_bavail = blockCount;
|
|
statData->f_files = 4;
|
|
statData->f_ffree = 0;
|
|
statData->f_favail = 0;
|
|
statData->f_namemax = 255;
|
|
}
|
|
catch (...)
|
|
{
|
|
return FuseService::ExceptionToErrorCode();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fuse_service_opendir (const char *path, struct fuse_file_info *fi)
|
|
{
|
|
try
|
|
{
|
|
if (!FuseService::CheckAccessRights())
|
|
return -EACCES;
|
|
|
|
if (strcmp (path, "/") != 0)
|
|
return -ENOENT;
|
|
}
|
|
catch (...)
|
|
{
|
|
return FuseService::ExceptionToErrorCode();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fuse_service_open (const char *path, struct fuse_file_info *fi)
|
|
{
|
|
try
|
|
{
|
|
if (!FuseService::CheckAccessRights())
|
|
return -EACCES;
|
|
|
|
if (strcmp (path, FuseService::GetVolumeImagePath()) == 0)
|
|
return 0;
|
|
|
|
if (strcmp (path, FuseService::GetAuxDeviceInfoPath()) == 0)
|
|
{
|
|
fi->direct_io = 1;
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp (path, FuseService::GetControlPath()) == 0)
|
|
{
|
|
fi->fh = reinterpret_cast <uint64> (new shared_ptr <Buffer> (FuseService::GetVolumeInfo()));
|
|
fi->direct_io = 1;
|
|
return 0;
|
|
}
|
|
|
|
#if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET)
|
|
if (strcmp (path, FuseService::GetShutdownPath()) == 0)
|
|
{
|
|
fi->direct_io = 1;
|
|
return 0;
|
|
}
|
|
#endif
|
|
}
|
|
catch (...)
|
|
{
|
|
return FuseService::ExceptionToErrorCode();
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int fuse_service_read (const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
|
|
{
|
|
try
|
|
{
|
|
if (!FuseService::CheckAccessRights())
|
|
return -EACCES;
|
|
|
|
if (strcmp (path, FuseService::GetVolumeImagePath()) == 0)
|
|
{
|
|
try
|
|
{
|
|
// Test for read beyond the end of the volume
|
|
if ((uint64) offset + size > FuseService::GetVolumeSize())
|
|
size = FuseService::GetVolumeSize() - offset;
|
|
|
|
size_t sectorSize = FuseService::GetVolumeSectorSize();
|
|
if (size % sectorSize != 0 || offset % sectorSize != 0)
|
|
{
|
|
// Support for non-sector-aligned read operations is required by some loop device tools
|
|
// which may analyze the volume image before attaching it as a device
|
|
|
|
uint64 alignedOffset = offset - (offset % sectorSize);
|
|
uint64 alignedSize = size + (offset % sectorSize);
|
|
|
|
if (alignedSize % sectorSize != 0)
|
|
alignedSize += sectorSize - (alignedSize % sectorSize);
|
|
|
|
SecureBuffer alignedBuffer (alignedSize);
|
|
|
|
FuseService::ReadVolumeSectors (alignedBuffer, alignedOffset);
|
|
BufferPtr ((uint8 *) buf, size).CopyFrom (alignedBuffer.GetRange (offset % sectorSize, size));
|
|
}
|
|
else
|
|
{
|
|
FuseService::ReadVolumeSectors (BufferPtr ((uint8 *) buf, size), offset);
|
|
}
|
|
}
|
|
catch (MissingVolumeData&)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
if (strcmp (path, FuseService::GetControlPath()) == 0)
|
|
{
|
|
shared_ptr <Buffer> infoBuf = fuse_service_get_control_info (fi);
|
|
BufferPtr outBuf ((uint8 *)buf, size);
|
|
|
|
if (offset >= (off_t) infoBuf->Size())
|
|
return 0;
|
|
|
|
if (offset + size > infoBuf->Size())
|
|
size = infoBuf->Size () - offset;
|
|
|
|
outBuf.CopyFrom (infoBuf->GetRange (offset, size));
|
|
return size;
|
|
}
|
|
|
|
if (strcmp (path, FuseService::GetAuxDeviceInfoPath()) == 0)
|
|
{
|
|
shared_ptr <Buffer> infoBuf = FuseService::GetAuxDeviceInfo();
|
|
BufferPtr outBuf ((uint8 *)buf, size);
|
|
|
|
if (offset >= (off_t) infoBuf->Size())
|
|
return 0;
|
|
|
|
if (offset + size > infoBuf->Size())
|
|
size = infoBuf->Size () - offset;
|
|
|
|
outBuf.CopyFrom (infoBuf->GetRange (offset, size));
|
|
return size;
|
|
}
|
|
|
|
#if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET)
|
|
if (strcmp (path, FuseService::GetShutdownPath()) == 0)
|
|
{
|
|
string identity = fuse_service_get_shutdown_identity();
|
|
if (offset < 0)
|
|
return -EINVAL;
|
|
|
|
if (offset >= (off_t) identity.size())
|
|
return 0;
|
|
|
|
if (offset + size > identity.size())
|
|
size = identity.size() - offset;
|
|
|
|
memcpy (buf, identity.data() + offset, size);
|
|
return size;
|
|
}
|
|
#endif
|
|
}
|
|
catch (...)
|
|
{
|
|
return FuseService::ExceptionToErrorCode();
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int fuse_service_release (const char *path, struct fuse_file_info *fi)
|
|
{
|
|
try
|
|
{
|
|
if (strcmp (path, FuseService::GetControlPath()) == 0 && fi && fi->fh)
|
|
{
|
|
delete reinterpret_cast <shared_ptr <Buffer> *> (fi->fh);
|
|
fi->fh = 0;
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
return FuseService::ExceptionToErrorCode();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fuse_service_readdir_impl (const char *path, void *buf, fuse_fill_dir_t filler, struct fuse_file_info *fi)
|
|
{
|
|
(void) fi;
|
|
|
|
try
|
|
{
|
|
if (!FuseService::CheckAccessRights())
|
|
return -EACCES;
|
|
|
|
if (strcmp (path, "/") != 0)
|
|
return -ENOENT;
|
|
|
|
if (fuse_service_fill_dir_entry (buf, filler, ".", S_IFDIR | 0500, VC_FUSE_INODE_ROOT, 0) != 0)
|
|
return 0;
|
|
if (fuse_service_fill_dir_entry (buf, filler, "..", S_IFDIR | 0500, VC_FUSE_INODE_ROOT, 0) != 0)
|
|
return 0;
|
|
if (fuse_service_fill_dir_entry (buf, filler, FuseService::GetVolumeImagePath() + 1, S_IFREG | 0600, VC_FUSE_INODE_VOLUME, 0) != 0)
|
|
return 0;
|
|
if (fuse_service_fill_dir_entry (buf, filler, FuseService::GetControlPath() + 1, S_IFREG | 0600, VC_FUSE_INODE_CONTROL, 0) != 0)
|
|
return 0;
|
|
if (fuse_service_fill_dir_entry (buf, filler, FuseService::GetAuxDeviceInfoPath() + 1, S_IFREG | 0600, VC_FUSE_INODE_AUX_DEVICE_INFO, 0) != 0)
|
|
return 0;
|
|
#if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET)
|
|
if (fuse_service_fill_dir_entry (buf, filler, FuseService::GetShutdownPath() + 1, S_IFREG | 0600, VC_FUSE_INODE_SHUTDOWN, 0) != 0)
|
|
return 0;
|
|
#endif
|
|
}
|
|
catch (...)
|
|
{
|
|
return FuseService::ExceptionToErrorCode();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(VC_FUSE3)
|
|
static int fuse_service_readdir (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags)
|
|
{
|
|
(void) offset;
|
|
(void) flags;
|
|
return fuse_service_readdir_impl (path, buf, filler, fi);
|
|
}
|
|
#else
|
|
static int fuse_service_readdir (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
|
|
{
|
|
(void) offset;
|
|
return fuse_service_readdir_impl (path, buf, filler, fi);
|
|
}
|
|
#endif
|
|
|
|
static int fuse_service_write (const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
|
|
{
|
|
try
|
|
{
|
|
if (!FuseService::CheckAccessRights())
|
|
return -EACCES;
|
|
|
|
if (strcmp (path, FuseService::GetVolumeImagePath()) == 0)
|
|
{
|
|
FuseService::WriteVolumeSectors (BufferPtr ((uint8 *) buf, size), offset);
|
|
return size;
|
|
}
|
|
|
|
if (strcmp (path, FuseService::GetAuxDeviceInfoPath()) == 0)
|
|
{
|
|
if (FuseService::AuxDeviceInfoReceived())
|
|
return -EACCES;
|
|
|
|
FuseService::ReceiveAuxDeviceInfo (ConstBufferPtr ((const uint8 *) buf, size));
|
|
return size;
|
|
}
|
|
|
|
#if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET)
|
|
if (strcmp (path, FuseService::GetShutdownPath()) == 0)
|
|
{
|
|
pid_t processId;
|
|
uint64 serialInstanceNumber;
|
|
VolumeSlotNumber slotNumber;
|
|
struct fuse_context *context = fuse_get_context();
|
|
|
|
if (offset != 0 || !context || !context->fuse
|
|
|| !fuse_service_parse_shutdown_identity (string (buf, size), processId, serialInstanceNumber, slotNumber)
|
|
|| processId != getpid()
|
|
|| serialInstanceNumber != FuseService::GetSerialInstanceNumber()
|
|
|| slotNumber != FuseService::GetSlotNumber())
|
|
return -EINVAL;
|
|
|
|
bool expected = false;
|
|
if (FuseServiceShutdownRequested.compare_exchange_strong (expected, true))
|
|
{
|
|
try
|
|
{
|
|
unique_ptr <FuseServiceShutdownContext> shutdownContext (new FuseServiceShutdownContext (context->fuse));
|
|
Thread shutdownThread;
|
|
shutdownThread.Start (fuse_service_shutdown, shutdownContext.get());
|
|
shutdownContext.release();
|
|
shutdownThread.Detach();
|
|
}
|
|
catch (...)
|
|
{
|
|
FuseServiceShutdownRequested = false;
|
|
throw;
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef TC_FREEBSD
|
|
// FreeBSD apparently retries failed write operations forever, which may lead to a system crash.
|
|
catch (VolumeReadOnly&)
|
|
{
|
|
return size;
|
|
}
|
|
catch (VolumeProtected&)
|
|
{
|
|
return size;
|
|
}
|
|
#endif
|
|
catch (...)
|
|
{
|
|
return FuseService::ExceptionToErrorCode();
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
bool FuseService::CheckAccessRights ()
|
|
{
|
|
return fuse_get_context()->uid == 0 || fuse_get_context()->uid == UserId;
|
|
}
|
|
|
|
void FuseService::CloseMountedVolume ()
|
|
{
|
|
if (MountedVolume)
|
|
{
|
|
// This process will exit before the use count of MountedVolume reaches zero
|
|
if (MountedVolume->GetFile().use_count() > 1)
|
|
MountedVolume->GetFile()->Close();
|
|
|
|
if (MountedVolume.use_count() > 1)
|
|
delete MountedVolume.get();
|
|
|
|
MountedVolume.reset();
|
|
}
|
|
}
|
|
|
|
void FuseService::Dismount ()
|
|
{
|
|
CloseMountedVolume();
|
|
|
|
if (EncryptionThreadPool::IsRunning())
|
|
EncryptionThreadPool::Stop();
|
|
}
|
|
|
|
int FuseService::ExceptionToErrorCode ()
|
|
{
|
|
try
|
|
{
|
|
throw;
|
|
}
|
|
catch (std::bad_alloc&)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
catch (ParameterIncorrect &e)
|
|
{
|
|
SystemLog::WriteException (e);
|
|
return -EINVAL;
|
|
}
|
|
catch (VolumeProtected&)
|
|
{
|
|
return -EPERM;
|
|
}
|
|
catch (VolumeReadOnly&)
|
|
{
|
|
return -EPERM;
|
|
}
|
|
catch (SystemException &e)
|
|
{
|
|
SystemLog::WriteException (e);
|
|
return -static_cast <int> (e.GetErrorCode());
|
|
}
|
|
catch (std::exception &e)
|
|
{
|
|
SystemLog::WriteException (e);
|
|
return -EIO;
|
|
}
|
|
catch (...)
|
|
{
|
|
SystemLog::WriteException (UnknownException (SRC_POS));
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
shared_ptr <Buffer> FuseService::GetAuxDeviceInfo ()
|
|
{
|
|
shared_ptr <Stream> stream (new MemoryStream);
|
|
Serializer sr (stream);
|
|
|
|
{
|
|
ScopeLock lock (OpenVolumeInfoMutex);
|
|
|
|
sr.Serialize ("VirtualDevice", string (OpenVolumeInfo.VirtualDevice));
|
|
sr.Serialize ("LoopDevice", string (OpenVolumeInfo.LoopDevice));
|
|
}
|
|
|
|
ConstBufferPtr infoBuf = dynamic_cast <MemoryStream&> (*stream);
|
|
shared_ptr <Buffer> outBuf (new Buffer (infoBuf.Size()));
|
|
outBuf->CopyFrom (infoBuf);
|
|
|
|
return outBuf;
|
|
}
|
|
|
|
shared_ptr <Buffer> FuseService::GetVolumeInfo ()
|
|
{
|
|
shared_ptr <Stream> stream (new MemoryStream);
|
|
|
|
{
|
|
ScopeLock lock (OpenVolumeInfoMutex);
|
|
|
|
OpenVolumeInfo.Set (*MountedVolume);
|
|
OpenVolumeInfo.SlotNumber = SlotNumber;
|
|
|
|
OpenVolumeInfo.Serialize (stream);
|
|
}
|
|
|
|
ConstBufferPtr infoBuf = dynamic_cast <MemoryStream&> (*stream);
|
|
shared_ptr <Buffer> outBuf (new Buffer (infoBuf.Size()));
|
|
outBuf->CopyFrom (infoBuf);
|
|
|
|
return outBuf;
|
|
}
|
|
|
|
const char *FuseService::GetVolumeImagePath ()
|
|
{
|
|
#ifdef TC_MACOSX
|
|
return "/volume.dmg";
|
|
#else
|
|
return "/volume";
|
|
#endif
|
|
}
|
|
|
|
uint64 FuseService::GetVolumeSize ()
|
|
{
|
|
if (!MountedVolume)
|
|
throw NotInitialized (SRC_POS);
|
|
|
|
return MountedVolume->GetSize();
|
|
}
|
|
|
|
void FuseService::Mount (shared_ptr <Volume> openVolume, VolumeSlotNumber slotNumber, const string &fuseMountPoint)
|
|
{
|
|
list <string> args;
|
|
args.push_back (FuseService::GetDeviceType());
|
|
args.push_back (fuseMountPoint);
|
|
|
|
#ifdef TC_MACOSX
|
|
args.push_back ("-o");
|
|
args.push_back ("noping_diskarb");
|
|
args.push_back ("-o");
|
|
args.push_back ("nobrowse");
|
|
|
|
#ifdef VC_MACOSX_FUSET
|
|
// Use FUSE-T's SMB backend for the auxiliary mount. The default NFS
|
|
// backend can be affected by macOS Network Volumes privacy state.
|
|
args.push_back ("-o");
|
|
args.push_back ("backend=smb");
|
|
args.push_back ("-o");
|
|
args.push_back ("nonamedattr");
|
|
args.push_back ("-o");
|
|
args.push_back ("rwsize=262144");
|
|
#endif
|
|
|
|
if (getuid() == 0 || geteuid() == 0)
|
|
#endif
|
|
{
|
|
args.push_back ("-o");
|
|
args.push_back ("allow_other");
|
|
}
|
|
|
|
#if defined(TC_LINUX) && !defined(VC_FUSE3)
|
|
// FUSE2 has no fuse_config init hook; pass the mount option instead.
|
|
args.push_back ("-o");
|
|
args.push_back ("use_ino");
|
|
#endif
|
|
|
|
ExecFunctor execFunctor (openVolume, slotNumber);
|
|
Process::Execute ("fuse", args, -1, &execFunctor);
|
|
|
|
for (int t = 0; true; t++)
|
|
{
|
|
try
|
|
{
|
|
if (FilesystemPath (fuseMountPoint + FuseService::GetControlPath()).GetType() == FilesystemPathType::File)
|
|
break;
|
|
}
|
|
catch (...)
|
|
{
|
|
// Ignore exceptions since we will retry
|
|
}
|
|
|
|
if (t > 50)
|
|
throw TimeOut (SRC_POS);
|
|
|
|
Thread::Sleep (100);
|
|
}
|
|
}
|
|
|
|
void FuseService::ReadVolumeSectors (const BufferPtr &buffer, uint64 byteOffset)
|
|
{
|
|
if (!MountedVolume)
|
|
throw NotInitialized (SRC_POS);
|
|
|
|
MountedVolume->ReadSectors (buffer, byteOffset);
|
|
}
|
|
|
|
void FuseService::ReceiveAuxDeviceInfo (const ConstBufferPtr &buffer)
|
|
{
|
|
shared_ptr <Stream> stream (new MemoryStream (buffer));
|
|
Serializer sr (stream);
|
|
DevicePath virtualDevice = sr.DeserializeString ("VirtualDevice");
|
|
DevicePath loopDevice = sr.DeserializeString ("LoopDevice");
|
|
|
|
ScopeLock lock (OpenVolumeInfoMutex);
|
|
OpenVolumeInfo.VirtualDevice = virtualDevice;
|
|
OpenVolumeInfo.LoopDevice = loopDevice;
|
|
}
|
|
|
|
void FuseService::SendAuxDeviceInfo (const DirectoryPath &fuseMountPoint, const DevicePath &virtualDevice, const DevicePath &loopDevice)
|
|
{
|
|
File fuseServiceControl;
|
|
fuseServiceControl.Open (string (fuseMountPoint) + GetAuxDeviceInfoPath(), File::OpenWrite);
|
|
|
|
shared_ptr <Stream> stream (new MemoryStream);
|
|
Serializer sr (stream);
|
|
|
|
sr.Serialize ("VirtualDevice", string (virtualDevice));
|
|
sr.Serialize ("LoopDevice", string (loopDevice));
|
|
fuseServiceControl.Write (dynamic_cast <MemoryStream&> (*stream));
|
|
fuseServiceControl.Close();
|
|
}
|
|
|
|
#if defined(TC_MACOSX) && defined(VC_MACOSX_FUSET)
|
|
pid_t FuseService::RequestDismount (const DirectoryPath &fuseMountPoint, uint64 serialInstanceNumber, VolumeSlotNumber slotNumber)
|
|
{
|
|
shared_ptr <File> shutdownFile (new File);
|
|
shutdownFile->Open (string (fuseMountPoint) + GetShutdownPath());
|
|
|
|
FileStream shutdownReader (shutdownFile);
|
|
string identity = shutdownReader.ReadToEnd();
|
|
shutdownFile->Close();
|
|
if (identity.empty() || identity.size() > 256)
|
|
throw ParameterIncorrect (SRC_POS);
|
|
|
|
pid_t processId;
|
|
uint64 serviceSerialInstanceNumber;
|
|
VolumeSlotNumber serviceSlotNumber;
|
|
if (!fuse_service_parse_shutdown_identity (identity, processId, serviceSerialInstanceNumber, serviceSlotNumber)
|
|
|| serviceSerialInstanceNumber != serialInstanceNumber || serviceSlotNumber != slotNumber)
|
|
{
|
|
stringstream logMessage;
|
|
logMessage << "Refusing to shut down mismatched VeraCrypt FUSE service: slot=" << slotNumber
|
|
<< ", auxiliary mount=" << string (fuseMountPoint);
|
|
SystemLog::WriteError (logMessage.str());
|
|
throw ParameterIncorrect (SRC_POS);
|
|
}
|
|
|
|
shutdownFile->Open (string (fuseMountPoint) + GetShutdownPath(), File::OpenWrite);
|
|
shutdownFile->Write (ConstBufferPtr (reinterpret_cast <const uint8 *> (identity.data()), identity.size()));
|
|
shutdownFile->Close();
|
|
return processId;
|
|
}
|
|
|
|
void FuseService::WaitForDismount (pid_t processId, const DirectoryPath &fuseMountPoint, VolumeSlotNumber slotNumber, int timeOut)
|
|
{
|
|
for (int timeTaken = 0; ; timeTaken += 100)
|
|
{
|
|
if (kill (processId, 0) == -1)
|
|
{
|
|
if (errno == ESRCH)
|
|
return;
|
|
|
|
if (errno != EPERM)
|
|
throw SystemException (SRC_POS);
|
|
}
|
|
|
|
if (timeTaken >= timeOut)
|
|
{
|
|
stringstream logMessage;
|
|
logMessage << "VeraCrypt FUSE service did not terminate after shutdown request: pid=" << processId
|
|
<< ", slot=" << slotNumber << ", auxiliary mount=" << string (fuseMountPoint);
|
|
SystemLog::WriteError (logMessage.str());
|
|
throw TimeOut (SRC_POS);
|
|
}
|
|
|
|
Thread::Sleep (100);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void FuseService::WriteVolumeSectors (const ConstBufferPtr &buffer, uint64 byteOffset)
|
|
{
|
|
if (!MountedVolume)
|
|
throw NotInitialized (SRC_POS);
|
|
|
|
MountedVolume->WriteSectors (buffer, byteOffset);
|
|
}
|
|
|
|
void FuseService::OnSignal (int signal)
|
|
{
|
|
try
|
|
{
|
|
shared_ptr <VolumeInfo> volume = Core->GetMountedVolume (SlotNumber);
|
|
|
|
if (volume)
|
|
Core->DismountVolume (volume, true);
|
|
}
|
|
catch (...) { }
|
|
|
|
_exit (0);
|
|
}
|
|
|
|
void FuseService::ExecFunctor::operator() (int argc, char *argv[])
|
|
{
|
|
struct timeval tv;
|
|
gettimeofday (&tv, NULL);
|
|
FuseService::OpenVolumeInfo.SerialInstanceNumber = (uint64)tv.tv_sec * 1000000ULL + tv.tv_usec;
|
|
|
|
FuseService::MountedVolume = MountedVolume;
|
|
FuseService::SlotNumber = SlotNumber;
|
|
|
|
FuseService::UserId = getuid();
|
|
FuseService::GroupId = getgid();
|
|
|
|
if (getenv ("SUDO_UID"))
|
|
{
|
|
try
|
|
{
|
|
string s (getenv ("SUDO_UID"));
|
|
FuseService::UserId = static_cast <uid_t> (StringConverter::ToUInt64 (s));
|
|
|
|
if (getenv ("SUDO_GID"))
|
|
{
|
|
s = getenv ("SUDO_GID");
|
|
FuseService::GroupId = static_cast <gid_t> (StringConverter::ToUInt64 (s));
|
|
}
|
|
}
|
|
catch (...) { }
|
|
}
|
|
else
|
|
{
|
|
uid_t doasUid;
|
|
gid_t doasGid;
|
|
if (GetDoasUserIds (&doasUid, &doasGid))
|
|
{
|
|
FuseService::UserId = doasUid;
|
|
FuseService::GroupId = doasGid;
|
|
}
|
|
}
|
|
|
|
static fuse_operations fuse_service_oper;
|
|
|
|
fuse_service_oper.access = fuse_service_access;
|
|
fuse_service_oper.destroy = fuse_service_destroy;
|
|
fuse_service_oper.getattr = fuse_service_getattr;
|
|
fuse_service_oper.init = fuse_service_init;
|
|
fuse_service_oper.open = fuse_service_open;
|
|
fuse_service_oper.opendir = fuse_service_opendir;
|
|
fuse_service_oper.read = fuse_service_read;
|
|
fuse_service_oper.readdir = fuse_service_readdir;
|
|
fuse_service_oper.release = fuse_service_release;
|
|
fuse_service_oper.statfs = fuse_service_statfs;
|
|
fuse_service_oper.write = fuse_service_write;
|
|
|
|
// Create a new session
|
|
setsid ();
|
|
|
|
// Fork handler of termination signals
|
|
SignalHandlerPipe.reset (new Pipe);
|
|
|
|
int forkedPid = fork();
|
|
throw_sys_if (forkedPid == -1);
|
|
|
|
if (forkedPid == 0)
|
|
{
|
|
CloseMountedVolume();
|
|
|
|
struct sigaction action;
|
|
Memory::Zero (&action, sizeof (action));
|
|
action.sa_handler = OnSignal;
|
|
|
|
sigaction (SIGINT, &action, nullptr);
|
|
sigaction (SIGQUIT, &action, nullptr);
|
|
sigaction (SIGTERM, &action, nullptr);
|
|
|
|
// Wait for the exit of the main service
|
|
uint8 buf[1];
|
|
if (read (SignalHandlerPipe->GetReadFD(), buf, sizeof (buf))) { } // Errors ignored
|
|
|
|
_exit (0);
|
|
}
|
|
|
|
SignalHandlerPipe->GetWriteFD();
|
|
|
|
#ifdef VC_FUSE3
|
|
_exit (fuse_main (argc, argv, &fuse_service_oper, nullptr));
|
|
#elif defined(TC_OPENBSD)
|
|
_exit (fuse_main (argc, argv, &fuse_service_oper, NULL));
|
|
#else
|
|
_exit (fuse_main (argc, argv, &fuse_service_oper));
|
|
#endif
|
|
}
|
|
|
|
VolumeInfo FuseService::OpenVolumeInfo;
|
|
Mutex FuseService::OpenVolumeInfoMutex;
|
|
shared_ptr <Volume> FuseService::MountedVolume;
|
|
VolumeSlotNumber FuseService::SlotNumber;
|
|
uid_t FuseService::UserId;
|
|
gid_t FuseService::GroupId;
|
|
unique_ptr <Pipe> FuseService::SignalHandlerPipe;
|
|
}
|