Linux: forward FUSE volume fsync to the backing storage

Volumes encrypted in user space are mounted through a loop device on the
FUSE volume image, and their writes end in buffered pwrite() calls to the
backing file or device. The FUSE service had no fsync callback, so libfuse
answered the fsync requests that the loop device issues for block-layer
flushes with ENOSYS, which Linux FUSE reports as success. Synced data
could therefore remain dirty in the host cache, and writeback errors were
not reported.

Register an fsync callback that syncs the backing storage for the volume
image and returns its errors through the existing mapping. Never return
ENOSYS, which would make Linux FUSE stop forwarding fsync for the whole
mount: report a backing ENOSYS as EIO, as the loop driver does, and
return success for the other files. Other Unix platforms are outside this
change.

Add regression coverage on device- and file-backed volumes: fsync must
flush the backing device, a backing failure must be reported as EIO, and
synced data must survive a simulated power cut. Without this change, the
synced data is lost. Validated with FUSE2 and FUSE3 builds.
This commit is contained in:
Mounir IDRASSI committed 2026-10-03 20:33:03 +09:00
1 parent d24b82102a
commit 58344c204e
3 files changed
+328

No files matched your search

+37
View File
@@ -1144,6 +1144,32 @@ namespace VeraCrypt
return -ENOENT;
}
#ifdef TC_LINUX
static int fuse_service_fsync (const char *path, int datasync, struct fuse_file_info *fi)
{
try
{
if (!FuseService::CheckAccessRights())
return -EACCES;
// Loop devices turn block-layer flushes into fsync requests on the volume image.
// Data written by WriteVolumeSectors() reaches the backing storage only when it is synced.
if (strcmp (path, FuseService::GetVolumeImagePath()) == 0)
FuseService::FlushVolume();
}
catch (...)
{
// Never return -ENOSYS, even if the backing storage does: Linux FUSE would then
// report success for every later fsync on this mount without forwarding it.
int error = FuseService::ExceptionToErrorCode();
return error == -ENOSYS ? -EIO : error;
}
// Other files have nothing to sync, and must not get -ENOSYS either.
return 0;
}
#endif
bool FuseService::CheckAccessRights ()
{
return fuse_get_context()->uid == 0 || fuse_get_context()->uid == UserId;
@@ -1212,6 +1238,14 @@ namespace VeraCrypt
}
}
void FuseService::FlushVolume ()
{
if (!MountedVolume)
throw NotInitialized (SRC_POS);
MountedVolume->GetFile()->Flush();
}
shared_ptr <Buffer> FuseService::GetAuxDeviceInfo ()
{
shared_ptr <Stream> stream (new MemoryStream);
@@ -1750,6 +1784,9 @@ namespace VeraCrypt
fuse_service_oper.access = fuse_service_access;
fuse_service_oper.destroy = fuse_service_destroy;
#ifdef TC_LINUX
fuse_service_oper.fsync = fuse_service_fsync;
#endif
fuse_service_oper.getattr = fuse_service_getattr;
fuse_service_oper.init = fuse_service_init;
fuse_service_oper.open = fuse_service_open;
+1
View File
@@ -55,6 +55,7 @@ namespace VeraCrypt
static bool CheckAccessRights ();
static void Dismount ();
static int ExceptionToErrorCode ();
static void FlushVolume ();
static const char *GetAuxDeviceInfoPath () { return "/aux-device-info"; }
static const char *GetControlPath () { return "/control"; }
static const char *GetVolumeImagePath ();