Files
VeraCrypt/Tests/fuset_startup_faults.c
T
Mounir IDRASSI aedb2ef863 macOS: harden FUSE-T dismount ownership and teardown
Keep FUSE serving until the auxiliary SMB filesystem is unmounted, then
join the shutdown worker before destroying the FUSE handle. Authenticate
socket peers and bind each request to the service and filesystem instance;
carry the force flag through to unmount and reply before teardown.

Resolve the current disk image before detach instead of trusting cached
BSD device numbers. Clear device and mount metadata when the image is gone,
and abort on inventory errors. Refresh ownership during enumeration and
before filesystem checks. Give each auxiliary mount a random path so an old
backend cannot target a subsequent VeraCrypt mount during cleanup.

Canonicalize the auxiliary path before service startup and hdiutil attach.
Resolve older clients' image paths through TMPDIR aliases without accessing
unrelated images. Treat candidate resolution failures as errors rather than
evidence that an attached image is gone.

Keep a new service provisional over a private inherited socketpair until
control-file readiness and public shutdown endpoint checks succeed. On
startup failure or caller exit, unmount while FUSE still serves and wait for
volume closure and service exit. Report incomplete cleanup explicitly and
keep retrying cleanup in the service if unmounting is temporarily blocked.

Restore dismounts of released services without /shutdown through a
validated legacy flow. Preserve incoming file-protocol notifications and
watch for external unmounts independently of those notifications. Legacy
unmount cannot guarantee termination of an already-running old service.

Use one fixed versioned socket frame and publish the random endpoint in
/shutdown-socket, preserving the three-field /shutdown identity. Remove
compatibility with unpublished socket protocols. Recover from transient
accept and mount-enumeration failures, bound partial-request lifetimes,
and report connection refusal as an availability error. Handle join failure
without unwinding the destructor or freeing a live worker's context.

Map auxiliary EBUSY to MountedVolumeInUse for the GUI force prompt. Return
failure for a single busy non-interactive dismount and log automatic-dismount
failures. Mark inherited descriptors close-on-exec before FUSE setup and
make File::SetCloseOnExec const.

Extend disposable-container tests for released clients and services,
reused device numbers, partial requests, identity validation, forced write
integrity, TMPDIR aliases, and injected startup and rollback failures.
Validated without sudo with a clean arm64 build, unchanged warnings,
algorithm self-tests, the compatibility matrix, descriptor audits, worker
fault-recovery checks, and 24 conditional compilation checks.
2026-09-27 09:28:37 +02:00

116 lines
3.0 KiB
C

/* Scoped macOS fault injection for test_fuset_dismount.py --startup-faults.
* Copyright (c) 2026 AM Crypto. Licensed under the Apache License 2.0.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
static int mode_is (const char *mode)
{
const char *fault = getenv ("VC_FUSET_TEST_FAULT");
return fault && strcmp (fault, mode) == 0;
}
static int fixture_path (const char *path, const char *suffix)
{
const char *root = getenv ("VC_FUSET_TEST_ROOT");
size_t size = strlen (path), suffix_size = strlen (suffix);
return root && strncmp (path, root, strlen (root)) == 0
&& path[strlen (root)] == '/' && size >= suffix_size
&& strcmp (path + size - suffix_size, suffix) == 0;
}
static void mark_fault (void)
{
const char *path = getenv ("VC_FUSET_TEST_FAULT_MARKER");
if (path)
{
int fd = open (path, O_WRONLY | O_CREAT, 0600);
if (fd != -1)
close (fd);
}
}
static int test_connect (int fd, const struct sockaddr *address, socklen_t length)
{
static const char prefix[] = "/private/tmp/.veracrypt-shutdown-";
if ((mode_is ("refused") || mode_is ("rollback-blocked")) && address->sa_family == AF_UNIX
&& strncmp (((const struct sockaddr_un *) address)->sun_path, prefix, sizeof (prefix) - 1) == 0)
{
mark_fault();
const char *log = getenv ("VC_FUSET_TEST_SOCKET_LOG");
if (log)
{
int output = open (log, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (output != -1)
{
const char *path = ((const struct sockaddr_un *) address)->sun_path;
write (output, path, strlen (path));
close (output);
}
}
errno = ECONNREFUSED;
return -1;
}
return connect (fd, address, length);
}
static int test_open (const char *path, int flags, ...)
{
if (mode_is ("metadata") && fixture_path (path, "/shutdown-socket"))
{
mark_fault();
errno = EIO;
return -1;
}
if (flags & O_CREAT)
{
va_list args;
va_start (args, flags);
int mode = va_arg (args, int);
va_end (args);
return open (path, flags, mode);
}
return open (path, flags);
}
static int test_stat (const char *path, struct stat *value)
{
if (mode_is ("control") && fixture_path (path, "/control"))
{
mark_fault();
errno = ENOENT;
return -1;
}
return stat (path, value);
}
static int test_unmount (const char *path, int flags)
{
const char *gate = getenv ("VC_FUSET_TEST_UNMOUNT_GATE");
if (mode_is ("rollback-blocked") && gate && fixture_path (path, "") && access (gate, F_OK) == 0)
{
mark_fault();
errno = EBUSY;
return -1;
}
return unmount (path, flags);
}
#define INTERPOSE(replacement, original) \
__attribute__((used)) static const struct { const void *replace; const void *replacee; } \
interpose_##original __attribute__((section("__DATA,__interpose"))) = { \
(const void *) replacement, (const void *) original }
INTERPOSE (test_connect, connect);
INTERPOSE (test_open, open);
INTERPOSE (test_stat, stat);
INTERPOSE (test_unmount, unmount);