Files
codebase-memory-mcp/src/daemon/ipc.c
T

7300 lines
301 KiB
C

/*
* ipc.c — Authenticated, owner-scoped local transport for the CBM daemon.
*/
#include "daemon/ipc.h"
#include "daemon/ipc_internal.h"
#include "foundation/compat.h"
#include "foundation/compat_fs.h"
#include "foundation/compat_thread.h"
#include "foundation/log.h"
#include "foundation/macos_acl.h"
#include "foundation/platform.h"
#include "foundation/private_file_lock_internal.h"
#include "foundation/sha256.h"
#include "foundation/secure_random.h"
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__linux__)
#include <sys/vfs.h>
#endif
enum {
CBM_DAEMON_IPC_SEND_TIMEOUT_MS = 5000,
CBM_DAEMON_IPC_PATH_CAP = 4096,
CBM_DAEMON_IPC_RETRY_INTERVAL_MS = 10,
CBM_DAEMON_IPC_COORDINATION_CLEANUP_MS = 500,
CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RETRY_MS = 250,
};
/* Last private-namespace validation refusal, set at the exact failing check.
* A bare "cache-private" status is undiagnosable in the field; the refusal
* names the object and rule instead. Last-writer-wins, diagnostic only. */
static char ipc_validation_detail_buffer[384];
static void ipc_validation_detail_set(const char *format, ...) {
va_list arguments;
va_start(arguments, format);
(void)vsnprintf(ipc_validation_detail_buffer, sizeof(ipc_validation_detail_buffer), format,
arguments);
va_end(arguments);
}
#ifdef CBM_ENABLE_TEST_SEAMS
void cbm_daemon_ipc_set_validation_detail_for_testing(const char *detail) {
(void)snprintf(ipc_validation_detail_buffer, sizeof(ipc_validation_detail_buffer), "%s",
detail ? detail : "");
}
#endif
const char *cbm_daemon_ipc_validation_detail(void) {
return ipc_validation_detail_buffer;
}
/* #1717: a cache root on a network filesystem (a shared HPC home, an NFS or
* SMB mount) is ACCEPTED -- this is advice, never a refusal. SQLite's WAL mode
* relies on shared-memory locking that network filesystems do not provide
* reliably, so indexes there can be slow or corrupt. Detection is statfs
* f_type, Linux only; other platforms never warn. */
static const char *ipc_network_fs_name(unsigned long magic) {
static const struct {
unsigned long magic;
const char *name;
} kinds[] = {
{0x6969UL, "NFS"}, {0xFF534D42UL, "CIFS"}, {0xFE534D42UL, "SMB2"},
{0x0BD00BD0UL, "Lustre"}, {0x47504653UL, "GPFS"},
};
for (size_t index = 0; index < sizeof(kinds) / sizeof(kinds[0]); index++) {
if (kinds[index].magic == magic) {
return kinds[index].name;
}
}
return NULL;
}
static atomic_bool g_ipc_network_cache_warned;
#ifdef CBM_ENABLE_TEST_SEAMS
static bool g_ipc_fs_magic_override_active;
static unsigned long g_ipc_fs_magic_override;
void cbm_daemon_ipc_set_fs_magic_for_test(bool active, unsigned long magic) {
g_ipc_fs_magic_override_active = active;
g_ipc_fs_magic_override = magic;
atomic_store(&g_ipc_network_cache_warned, false);
}
#endif
static bool ipc_path_fs_magic(const char *path, unsigned long *magic_out) {
#ifdef CBM_ENABLE_TEST_SEAMS
if (g_ipc_fs_magic_override_active) {
*magic_out = g_ipc_fs_magic_override;
return true;
}
#endif
#if defined(__linux__)
struct statfs status;
if (statfs(path, &status) != 0) {
return false;
}
/* f_type is a signed word on some ABIs; the magics are 32-bit. */
*magic_out = (unsigned long)status.f_type & 0xFFFFFFFFUL;
return true;
#else
(void)path;
(void)magic_out;
return false;
#endif
}
bool cbm_daemon_ipc_warn_if_network_cache_root(const char *cache_root) {
unsigned long magic = 0;
const char *kind = NULL;
if (!cache_root || !cache_root[0] || !ipc_path_fs_magic(cache_root, &magic) ||
!(kind = ipc_network_fs_name(magic))) {
return false;
}
if (atomic_exchange(&g_ipc_network_cache_warned, true)) {
return false; /* one warning per process */
}
cbm_log_warn("daemon.cache_root_network_fs", "path", cache_root, "fs", kind, "reason",
"sqlite_wal_unreliable_on_network_fs", "hint",
"set CBM_CACHE_DIR to local storage (docs/CONFIGURATION.md)");
return true;
}
static cbm_daemon_ipc_listen_failure_t ipc_listen_failure;
/* The failure recorder and its reset are only reached from the POSIX socket
* listener (all callers live in the `#ifndef _WIN32` block below); the Windows
* named-pipe listener does its own reporting. Guarding them keeps the always-
* compiled struct + accessor cross-platform while avoiding -Werror,
* -Wunused-function on the Windows build (#1828 CI). */
#ifndef _WIN32
static void ipc_listen_failure_reset(void) {
memset(&ipc_listen_failure, 0, sizeof(ipc_listen_failure));
}
/* Record and log one listener failure. `name` is an artifact inside the
* endpoint's runtime directory, "" for the directory itself, or NULL when
* the stage has no path. The log line names the errno symbolically and the
* full path, so `daemon.ipc.listen_failed stage=pending_publication` alone
* (the whole trace #1828's reporter had) can no longer happen. */
static void ipc_listen_failed(const char *runtime_dir, const char *stage, int errno_value,
const char *name) {
ipc_listen_failure_reset();
(void)snprintf(ipc_listen_failure.stage, sizeof(ipc_listen_failure.stage), "%s",
stage ? stage : "");
ipc_listen_failure.errno_value = errno_value;
if (runtime_dir && name) {
int written = name[0] ? snprintf(ipc_listen_failure.path, sizeof(ipc_listen_failure.path),
"%s/%s", runtime_dir, name)
: snprintf(ipc_listen_failure.path, sizeof(ipc_listen_failure.path),
"%s", runtime_dir);
if (written <= 0 || written >= (int)sizeof(ipc_listen_failure.path)) {
ipc_listen_failure.path[0] = '\0';
}
}
const char *errno_name = errno_value != 0 ? cbm_errno_name(errno_value) : NULL;
if (errno_name && ipc_listen_failure.path[0]) {
cbm_log_error("daemon.ipc.listen_failed", "stage", stage, "errno", errno_name, "path",
ipc_listen_failure.path);
} else if (errno_name) {
cbm_log_error("daemon.ipc.listen_failed", "stage", stage, "errno", errno_name);
} else if (ipc_listen_failure.path[0]) {
cbm_log_error("daemon.ipc.listen_failed", "stage", stage, "path", ipc_listen_failure.path);
} else {
cbm_log_error("daemon.ipc.listen_failed", "stage", stage);
}
}
#endif /* !_WIN32 */
bool cbm_daemon_ipc_listen_failure_detail(cbm_daemon_ipc_listen_failure_t *out) {
if (!out) {
return false;
}
*out = ipc_listen_failure;
return ipc_listen_failure.stage[0] != '\0';
}
static bool instance_key_valid(const char *key) {
if (!key) {
return false;
}
for (size_t i = 0; i < 16; i++) {
char ch = key[i];
bool decimal = ch >= '0' && ch <= '9';
bool lower = ch >= 'a' && ch <= 'f';
if (!decimal && !lower) {
return false;
}
}
return key[16] == '\0';
}
static char *string_copy(const char *value) {
if (!value) {
return NULL;
}
size_t length = strlen(value);
char *copy = malloc(length + 1);
if (copy) {
memcpy(copy, value, length + 1);
}
return copy;
}
static char *string_format(const char *format, ...) {
va_list args;
va_start(args, format);
va_list measure;
va_copy(measure, args);
int needed = vsnprintf(NULL, 0, format, measure);
va_end(measure);
if (needed < 0) {
va_end(args);
return NULL;
}
char *result = malloc((size_t)needed + 1);
if (!result || vsnprintf(result, (size_t)needed + 1, format, args) != needed) {
free(result);
result = NULL;
}
va_end(args);
return result;
}
#ifndef _WIN32
static cbm_daemon_ipc_posix_publication_hook_fn g_posix_publication_hook_for_test;
static void *g_posix_publication_hook_context_for_test;
#endif
static atomic_uint g_windows_legacy_guard_release_failures_for_test;
void cbm_daemon_ipc_posix_publication_hook_set_for_test(
cbm_daemon_ipc_posix_publication_hook_fn hook, void *context) {
#ifndef _WIN32
g_posix_publication_hook_context_for_test = context;
g_posix_publication_hook_for_test = hook;
#else
(void)hook;
(void)context;
#endif
}
void cbm_daemon_ipc_windows_legacy_guard_release_failures_set_for_test(unsigned int count) {
atomic_store_explicit(&g_windows_legacy_guard_release_failures_for_test, count,
memory_order_release);
}
#ifndef _WIN32
static atomic_int g_posix_record_write_failure_errno_for_test;
#endif
void cbm_daemon_ipc_posix_record_write_failure_set_for_test(int errno_value) {
#ifndef _WIN32
atomic_store_explicit(&g_posix_record_write_failure_errno_for_test, errno_value,
memory_order_release);
#else
(void)errno_value;
#endif
}
#ifdef _WIN32
static cbm_daemon_ipc_startup_gate_fn g_startup_gate_for_test;
static void *g_startup_gate_context_for_test;
#endif
void cbm_daemon_ipc_startup_gate_set_for_test(cbm_daemon_ipc_startup_gate_fn gate, void *context) {
#ifdef _WIN32
g_startup_gate_context_for_test = context;
g_startup_gate_for_test = gate;
#else
(void)gate;
(void)context;
#endif
}
#ifdef _WIN32
static void ipc_startup_gate_run(void) {
cbm_daemon_ipc_startup_gate_fn gate = g_startup_gate_for_test;
if (gate) {
gate(g_startup_gate_context_for_test);
}
}
#endif
#ifdef _WIN32
static cbm_daemon_ipc_win_directory_create_hook_fn g_win_directory_create_hook_for_test;
static void *g_win_directory_create_hook_context_for_test;
#endif
void cbm_daemon_ipc_win_directory_create_hook_set_for_test(
cbm_daemon_ipc_win_directory_create_hook_fn hook, void *context) {
#ifdef _WIN32
g_win_directory_create_hook_context_for_test = context;
g_win_directory_create_hook_for_test = hook;
#else
(void)hook;
(void)context;
#endif
}
#ifdef _WIN32
static void ipc_win_directory_create_hook_run(const wchar_t *path) {
cbm_daemon_ipc_win_directory_create_hook_fn hook = g_win_directory_create_hook_for_test;
if (hook) {
hook(path, g_win_directory_create_hook_context_for_test);
}
}
#endif
bool cbm_daemon_ipc_windows_legacy_names(const char *canonical_runtime_parent,
const char *instance_key,
char pipe_out[CBM_DAEMON_IPC_WINDOWS_NAME_CAP],
char startup_mutex_out[CBM_DAEMON_IPC_WINDOWS_NAME_CAP]) {
if (!canonical_runtime_parent || !canonical_runtime_parent[0] ||
!instance_key_valid(instance_key) || !pipe_out || !startup_mutex_out) {
return false;
}
uint64_t hash = UINT64_C(14695981039346656037);
const unsigned char *cursor = (const unsigned char *)canonical_runtime_parent;
while (*cursor) {
hash ^= *cursor++;
hash *= UINT64_C(1099511628211);
}
int pipe_length =
snprintf(pipe_out, CBM_DAEMON_IPC_WINDOWS_NAME_CAP, "\\\\.\\pipe\\cbm-daemon-%016llx-%s",
(unsigned long long)hash, instance_key);
int mutex_length =
snprintf(startup_mutex_out, CBM_DAEMON_IPC_WINDOWS_NAME_CAP,
"Local\\cbm-daemon-%016llx-%s-startup", (unsigned long long)hash, instance_key);
return pipe_length > 0 && (size_t)pipe_length < CBM_DAEMON_IPC_WINDOWS_NAME_CAP &&
mutex_length > 0 && (size_t)mutex_length < CBM_DAEMON_IPC_WINDOWS_NAME_CAP;
}
static bool windows_sid_valid(const uint8_t *sid, size_t sid_length) {
enum {
WINDOWS_SID_HEADER_SIZE = 8,
WINDOWS_SID_SUBAUTHORITY_SIZE = 4,
WINDOWS_SID_SUBAUTHORITY_MAX = 15,
};
return sid && sid_length >= WINDOWS_SID_HEADER_SIZE && sid[0] == 1 &&
sid[1] <= WINDOWS_SID_SUBAUTHORITY_MAX &&
sid_length == WINDOWS_SID_HEADER_SIZE + (size_t)sid[1] * WINDOWS_SID_SUBAUTHORITY_SIZE;
}
static bool windows_pipe_address_valid(const char *address) {
static const char prefix[] = "\\\\.\\pipe\\cbm-daemon-";
if (!address || strncmp(address, prefix, sizeof(prefix) - 1U) != 0) {
return false;
}
const char *digest = address + sizeof(prefix) - 1U;
for (size_t index = 0; index < CBM_SHA256_HEX_LEN; index++) {
char character = digest[index];
if (!((character >= '0' && character <= '9') || (character >= 'a' && character <= 'f'))) {
return false;
}
}
return digest[CBM_SHA256_HEX_LEN] == '\0';
}
bool cbm_daemon_ipc_windows_generation_address(
const uint8_t *sid, size_t sid_length, const char *instance_key,
const uint8_t nonce[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE],
char address_out[CBM_DAEMON_IPC_WINDOWS_NAME_CAP]) {
static const uint8_t domain[] = "cbm-daemon-win-pipe-v1";
if (!windows_sid_valid(sid, sid_length) || !instance_key_valid(instance_key) || !nonce ||
!address_out) {
return false;
}
cbm_sha256_ctx context;
cbm_sha256_init(&context);
cbm_sha256_update(&context, domain, sizeof(domain) - 1U);
cbm_sha256_update(&context, sid, sid_length);
cbm_sha256_update(&context, instance_key, 16U);
cbm_sha256_update(&context, nonce, CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE);
uint8_t digest[CBM_SHA256_DIGEST_LEN];
cbm_sha256_final(&context, digest);
char digest_hex[CBM_SHA256_HEX_LEN + 1U];
static const char hex[] = "0123456789abcdef";
for (size_t index = 0; index < sizeof(digest); index++) {
digest_hex[index * 2U] = hex[digest[index] >> 4U];
digest_hex[index * 2U + 1U] = hex[digest[index] & 0x0fU];
}
digest_hex[CBM_SHA256_HEX_LEN] = '\0';
int written = snprintf(address_out, CBM_DAEMON_IPC_WINDOWS_NAME_CAP,
"\\\\.\\pipe\\cbm-daemon-%s", digest_hex);
return written > 0 && (size_t)written < CBM_DAEMON_IPC_WINDOWS_NAME_CAP;
}
bool cbm_daemon_ipc_windows_rendezvous_record_encode(
const uint8_t nonce[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE], const char *address,
uint8_t record_out[CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RECORD_SIZE]) {
static const uint8_t magic[8] = {'C', 'B', 'M', 'R', 'D', 'V', '1', 0};
if (!nonce || !windows_pipe_address_valid(address) || !record_out) {
return false;
}
memset(record_out, 0, CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RECORD_SIZE);
memcpy(record_out, magic, sizeof(magic));
memcpy(record_out + sizeof(magic), nonce, CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE);
size_t address_length = strlen(address);
memcpy(record_out + sizeof(magic) + CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE, address,
address_length + 1U);
return true;
}
bool cbm_daemon_ipc_windows_rendezvous_record_decode(
const uint8_t *record, size_t record_length,
uint8_t nonce_out[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE],
char address_out[CBM_DAEMON_IPC_WINDOWS_NAME_CAP]) {
static const uint8_t magic[8] = {'C', 'B', 'M', 'R', 'D', 'V', '1', 0};
if (!record || record_length != CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RECORD_SIZE || !nonce_out ||
!address_out || memcmp(record, magic, sizeof(magic)) != 0) {
return false;
}
const uint8_t *encoded_address = record + sizeof(magic) + CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE;
const uint8_t *terminator = memchr(encoded_address, 0, CBM_DAEMON_IPC_WINDOWS_NAME_CAP);
if (!terminator) {
return false;
}
size_t address_length = (size_t)(terminator - encoded_address);
if (address_length + 1U > CBM_DAEMON_IPC_WINDOWS_NAME_CAP) {
return false;
}
for (size_t index = address_length + 1U; index < CBM_DAEMON_IPC_WINDOWS_NAME_CAP; index++) {
if (encoded_address[index] != 0) {
return false;
}
}
memcpy(address_out, encoded_address, address_length + 1U);
if (!windows_pipe_address_valid(address_out)) {
memset(address_out, 0, CBM_DAEMON_IPC_WINDOWS_NAME_CAP);
return false;
}
memcpy(nonce_out, record + sizeof(magic), CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE);
return true;
}
int cbm_daemon_ipc_wait_pending(const cbm_ipc_pending_ops_t *ops, uint32_t timeout_ms,
uint32_t *transferred_out) {
if (!ops || !ops->wait || !ops->cancel || !ops->finish || !transferred_out) {
return -1;
}
cbm_ipc_pending_wait_status_t wait_status = ops->wait(ops->context, timeout_ms);
if (wait_status == CBM_IPC_PENDING_WAIT_SIGNALED) {
return ops->finish(ops->context, false, transferred_out) == CBM_IPC_PENDING_FINISH_COMPLETED
? 1
: -1;
}
/* Cancellation is asynchronous. Keep the platform operation alive and
* drain it to a terminal state before returning, even when waiting itself
* failed. */
ops->cancel(ops->context);
uint32_t transferred = 0;
cbm_ipc_pending_finish_status_t finish_status = ops->finish(ops->context, true, &transferred);
if (wait_status != CBM_IPC_PENDING_WAIT_TIMEOUT) {
return -1;
}
if (finish_status == CBM_IPC_PENDING_FINISH_COMPLETED) {
*transferred_out = transferred;
return 1;
}
return finish_status == CBM_IPC_PENDING_FINISH_CANCELLED ? 0 : -1;
}
#ifndef _WIN32
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stddef.h>
#if !defined(__linux__)
#include <pwd.h>
#endif
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
#if defined(__linux__)
#include <sys/vfs.h>
#endif
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
#ifndef O_DIRECTORY
#define O_DIRECTORY 0
#endif
#ifndef O_NOFOLLOW
#define O_NOFOLLOW 0
#endif
#ifndef AT_SYMLINK_NOFOLLOW
#define AT_SYMLINK_NOFOLLOW 0
#endif
enum {
POSIX_SOCKET_RECORD_MAGIC_SIZE = 8,
POSIX_SOCKET_IDENTITY_DEVICE_OFFSET = POSIX_SOCKET_RECORD_MAGIC_SIZE,
POSIX_SOCKET_IDENTITY_INODE_OFFSET = POSIX_SOCKET_IDENTITY_DEVICE_OFFSET + 8,
POSIX_SOCKET_IDENTITY_CTIME_SECONDS_OFFSET = POSIX_SOCKET_IDENTITY_INODE_OFFSET + 8,
POSIX_SOCKET_IDENTITY_CTIME_NANOSECONDS_OFFSET = POSIX_SOCKET_IDENTITY_CTIME_SECONDS_OFFSET + 8,
POSIX_SOCKET_IDENTITY_RESERVED_OFFSET = POSIX_SOCKET_IDENTITY_CTIME_NANOSECONDS_OFFSET + 4,
POSIX_SOCKET_ANCHOR_NAME_OFFSET = POSIX_SOCKET_IDENTITY_RESERVED_OFFSET + 4,
POSIX_SOCKET_ANCHOR_NAME_CAP = 64,
POSIX_SOCKET_RECORD_SIZE = POSIX_SOCKET_ANCHOR_NAME_OFFSET + POSIX_SOCKET_ANCHOR_NAME_CAP,
};
static const uint8_t POSIX_SOCKET_MARKER_MAGIC[POSIX_SOCKET_RECORD_MAGIC_SIZE] = {
'C', 'B', 'M', 'S', 'O', 'C', 2, 0,
};
static const uint8_t POSIX_SOCKET_PENDING_MAGIC[POSIX_SOCKET_RECORD_MAGIC_SIZE] = {
'C', 'B', 'M', 'P', 'E', 'N', 1, 0,
};
typedef struct {
dev_t device;
ino_t inode;
int64_t ctime_seconds;
uint32_t ctime_nanoseconds;
} posix_socket_identity_t;
typedef struct {
posix_socket_identity_t identity;
char anchor_name[POSIX_SOCKET_ANCHOR_NAME_CAP];
} posix_socket_record_t;
static void posix_publication_stage_reached(cbm_daemon_ipc_posix_publication_stage_t stage) {
cbm_daemon_ipc_posix_publication_hook_fn hook = g_posix_publication_hook_for_test;
if (hook) {
hook(stage, g_posix_publication_hook_context_for_test);
}
}
static void posix_record_publication_stage_reached(
const uint8_t magic[POSIX_SOCKET_RECORD_MAGIC_SIZE], bool linked) {
cbm_daemon_ipc_posix_publication_stage_t stage;
if (memcmp(magic, POSIX_SOCKET_PENDING_MAGIC, POSIX_SOCKET_RECORD_MAGIC_SIZE) == 0) {
stage = linked ? CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_RECORD_LINKED
: CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_TEMP_SYNCED;
} else if (memcmp(magic, POSIX_SOCKET_MARKER_MAGIC, POSIX_SOCKET_RECORD_MAGIC_SIZE) == 0) {
stage = linked ? CBM_DAEMON_IPC_POSIX_PUBLICATION_MARKER_RECORD_LINKED
: CBM_DAEMON_IPC_POSIX_PUBLICATION_MARKER_TEMP_SYNCED;
} else {
return;
}
posix_publication_stage_reached(stage);
}
struct cbm_daemon_ipc_endpoint {
char *runtime_dir;
char *address;
char *socket_name;
char *socket_anchor_address;
char *socket_anchor_name;
char *socket_identity_name;
char *socket_pending_name;
char *lock_name;
char *startup_v2_lock_name;
char *lifetime_lock_name;
int dir_fd;
dev_t dir_device;
ino_t dir_inode;
};
struct cbm_daemon_ipc_lifetime_reservation {
int fd;
struct process_lock_entry *process_entry;
pid_t owner_pid;
};
struct cbm_daemon_ipc_listener {
int fd;
int dir_fd;
char *runtime_dir;
dev_t dir_device;
ino_t dir_inode;
char *address;
char *socket_name;
char *socket_anchor_name;
char *socket_identity_name;
char *socket_pending_name;
posix_socket_identity_t socket_identity;
dev_t identity_device;
ino_t identity_inode;
pid_t owner_pid;
cbm_daemon_ipc_lifetime_reservation_t *lifetime_reservation;
cbm_daemon_ipc_participant_guard_t *participant_guard;
};
struct cbm_daemon_ipc_connection {
int fd;
atomic_bool poisoned;
};
typedef struct process_lock_entry {
dev_t directory_device;
ino_t directory_inode;
char *lock_name;
bool shared;
struct process_lock_entry *next;
} process_lock_entry_t;
struct cbm_daemon_ipc_startup_lock {
int startup_v2_fd;
process_lock_entry_t *startup_v2_process_entry;
int legacy_fd;
process_lock_entry_t *legacy_process_entry;
cbm_daemon_ipc_endpoint_t *endpoint_snapshot;
pid_t owner_pid;
bool prepared;
};
struct cbm_daemon_ipc_local_transition {
int startup_v2_fd;
process_lock_entry_t *startup_v2_process_entry;
int legacy_fd;
process_lock_entry_t *legacy_process_entry;
const cbm_daemon_ipc_endpoint_t *endpoint;
pid_t owner_pid;
bool sealed;
bool work_begun;
};
struct cbm_daemon_ipc_participant_guard {
int legacy_fd;
process_lock_entry_t *legacy_process_entry;
pid_t owner_pid;
};
static pthread_mutex_t process_locks_mutex = PTHREAD_MUTEX_INITIALIZER;
static process_lock_entry_t *process_locks;
static bool posix_socket_status_secure_transport(const struct stat *status);
static int process_lock_claim_mode(const cbm_daemon_ipc_endpoint_t *endpoint, const char *lock_name,
bool shared, process_lock_entry_t **entry_out) {
*entry_out = NULL;
if (!endpoint || !lock_name || pthread_mutex_lock(&process_locks_mutex) != 0) {
return -1;
}
for (process_lock_entry_t *entry = process_locks; entry; entry = entry->next) {
if (entry->directory_device == endpoint->dir_device &&
entry->directory_inode == endpoint->dir_inode &&
strcmp(entry->lock_name, lock_name) == 0 && (!shared || !entry->shared)) {
(void)pthread_mutex_unlock(&process_locks_mutex);
return 0;
}
}
process_lock_entry_t *entry = calloc(1, sizeof(*entry));
if (entry) {
entry->lock_name = string_copy(lock_name);
}
if (!entry || !entry->lock_name) {
free(entry);
(void)pthread_mutex_unlock(&process_locks_mutex);
return -1;
}
entry->directory_device = endpoint->dir_device;
entry->directory_inode = endpoint->dir_inode;
entry->shared = shared;
entry->next = process_locks;
process_locks = entry;
*entry_out = entry;
(void)pthread_mutex_unlock(&process_locks_mutex);
return 1;
}
static int process_lock_claim(const cbm_daemon_ipc_endpoint_t *endpoint, const char *lock_name,
process_lock_entry_t **entry_out) {
return process_lock_claim_mode(endpoint, lock_name, false, entry_out);
}
static void process_lock_unclaim(process_lock_entry_t *claimed) {
if (!claimed || pthread_mutex_lock(&process_locks_mutex) != 0) {
return;
}
process_lock_entry_t **cursor = &process_locks;
while (*cursor && *cursor != claimed) {
cursor = &(*cursor)->next;
}
if (*cursor) {
*cursor = claimed->next;
free(claimed->lock_name);
free(claimed);
}
(void)pthread_mutex_unlock(&process_locks_mutex);
}
static int process_lock_is_claimed(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *lock_name) {
if (!endpoint || !lock_name || pthread_mutex_lock(&process_locks_mutex) != 0) {
return -1;
}
int claimed = 0;
for (process_lock_entry_t *entry = process_locks; entry; entry = entry->next) {
if (entry->directory_device == endpoint->dir_device &&
entry->directory_inode == endpoint->dir_inode &&
strcmp(entry->lock_name, lock_name) == 0) {
claimed = 1;
break;
}
}
(void)pthread_mutex_unlock(&process_locks_mutex);
return claimed;
}
static uint64_t ipc_now_ms(void) {
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
return 0;
}
return (uint64_t)now.tv_sec * 1000U + (uint64_t)now.tv_nsec / 1000000U;
}
static uint64_t ipc_deadline_after(uint32_t timeout_ms) {
if (timeout_ms == CBM_DAEMON_IPC_WAIT_FOREVER) {
return UINT64_MAX;
}
return ipc_now_ms() + (uint64_t)timeout_ms;
}
static _Noreturn void ipc_coordination_cleanup_fail_stop(const char *component) {
cbm_log_error("daemon.forced_shutdown", "component", component, "action",
"coordination_cleanup");
(void)fflush(stdout);
(void)fflush(stderr);
_exit(EXIT_FAILURE);
}
static void ipc_startup_lock_release_complete(cbm_daemon_ipc_startup_lock_t **lock_io) {
uint64_t deadline = ipc_deadline_after(CBM_DAEMON_IPC_COORDINATION_CLEANUP_MS);
while (lock_io && *lock_io) {
(void)cbm_daemon_ipc_startup_lock_release(lock_io);
if (!*lock_io) {
return;
}
if (ipc_now_ms() >= deadline) {
ipc_coordination_cleanup_fail_stop("startup_lock_cleanup");
}
cbm_usleep(1000);
}
}
static void ipc_participant_guard_release_complete(cbm_daemon_ipc_participant_guard_t **guard_io) {
uint64_t deadline = ipc_deadline_after(CBM_DAEMON_IPC_COORDINATION_CLEANUP_MS);
while (guard_io && *guard_io) {
(void)cbm_daemon_ipc_participant_guard_release(guard_io);
if (!*guard_io) {
return;
}
if (ipc_now_ms() >= deadline) {
ipc_coordination_cleanup_fail_stop("participant_guard_cleanup");
}
cbm_usleep(1000);
}
}
static int deadline_remaining_ms(uint64_t deadline_ms) {
uint64_t now_ms = ipc_now_ms();
if (now_ms >= deadline_ms) {
return 0;
}
uint64_t remaining = deadline_ms - now_ms;
return remaining > (uint64_t)INT_MAX ? INT_MAX : (int)remaining;
}
static bool ipc_retry_pause(uint64_t deadline_ms) {
int remaining_ms = deadline_remaining_ms(deadline_ms);
if (remaining_ms <= 0) {
return false;
}
int pause_ms = remaining_ms < CBM_DAEMON_IPC_RETRY_INTERVAL_MS
? remaining_ms
: CBM_DAEMON_IPC_RETRY_INTERVAL_MS;
struct timespec pause = {
.tv_sec = 0,
.tv_nsec = (long)pause_ms * 1000000L,
};
while (nanosleep(&pause, &pause) != 0 && errno == EINTR) {}
return true;
}
static bool fd_set_cloexec(int fd) {
int flags = fcntl(fd, F_GETFD);
return flags >= 0 && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0;
}
static bool fd_set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL);
return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
}
static bool socket_disable_sigpipe(int fd) {
#ifdef SO_NOSIGPIPE
int enabled = 1;
return setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof(enabled)) == 0;
#else
(void)fd;
return true;
#endif
}
static int local_socket_new(void) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
return -1;
}
if (!fd_set_cloexec(fd) || !fd_set_nonblocking(fd) || !socket_disable_sigpipe(fd)) {
(void)close(fd);
return -1;
}
return fd;
}
/* Keep the only raw connect call at a sockaddr_un-typed boundary so the
* security audit can count and review the complete local IPC surface. */
static int local_socket_connect(int fd, const struct sockaddr_un *address,
socklen_t address_length) {
return connect(fd, (const struct sockaddr *)address, address_length);
}
static bool unix_address_set(struct sockaddr_un *address, const char *path, socklen_t *length_out) {
if (!address || !path || !length_out) {
return false;
}
size_t path_length = strlen(path);
if (path_length == 0 || path_length >= sizeof(address->sun_path)) {
return false;
}
memset(address, 0, sizeof(*address));
address->sun_family = AF_UNIX;
memcpy(address->sun_path, path, path_length + 1);
socklen_t address_length =
(socklen_t)(offsetof(struct sockaddr_un, sun_path) + path_length + 1);
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
address->sun_len = (uint8_t)address_length;
#endif
*length_out = address_length;
return true;
}
static bool runtime_parent_valid(const char *path) {
struct stat status;
return path && path[0] != '\0' && lstat(path, &status) == 0 && S_ISDIR(status.st_mode);
}
static bool private_runtime_snapshot(int fd, const char *path, bool require_empty_acl,
struct stat *status_out) {
struct stat by_handle;
struct stat by_path;
bool valid = fd >= 0 && path && path[0] != '\0' && fstat(fd, &by_handle) == 0 &&
lstat(path, &by_path) == 0 && S_ISDIR(by_handle.st_mode) &&
S_ISDIR(by_path.st_mode) && by_handle.st_uid == geteuid() &&
by_path.st_uid == geteuid() && (by_handle.st_mode & 07777) == 0700 &&
(by_path.st_mode & 07777) == 0700 && by_handle.st_dev == by_path.st_dev &&
by_handle.st_ino == by_path.st_ino &&
(!require_empty_acl || cbm_macos_extended_acl_fd_is_empty(fd));
if (valid && status_out) {
*status_out = by_handle;
}
return valid;
}
static int private_directory_tree_open(const char *directory_path);
static bool private_runtime_open(const char *path, int *fd_out, dev_t *device_out,
ino_t *inode_out) {
if (!path || !fd_out || !device_out || !inode_out) {
return false;
}
int fd = private_directory_tree_open(path);
if (fd < 0) {
return false;
}
struct stat before;
bool secured = private_runtime_snapshot(fd, path, true, &before);
if (!secured) {
(void)close(fd);
return false;
}
*fd_out = fd;
*device_out = before.st_dev;
*inode_out = before.st_ino;
return true;
}
static bool endpoint_runtime_still_valid(const cbm_daemon_ipc_endpoint_t *endpoint) {
if (!endpoint || endpoint->dir_fd < 0) {
return false;
}
struct stat current;
return private_runtime_snapshot(endpoint->dir_fd, endpoint->runtime_dir, true, &current) &&
current.st_dev == endpoint->dir_device && current.st_ino == endpoint->dir_inode;
}
static bool private_regular_file_snapshot(int directory_fd, const char *base_name, int fd,
nlink_t expected_links, struct stat *status_out) {
struct stat by_handle;
struct stat by_path;
bool valid = directory_fd >= 0 && base_name && base_name[0] != '\0' && fd >= 0 &&
fstat(fd, &by_handle) == 0 &&
fstatat(directory_fd, base_name, &by_path, AT_SYMLINK_NOFOLLOW) == 0 &&
S_ISREG(by_handle.st_mode) && S_ISREG(by_path.st_mode) &&
by_handle.st_uid == geteuid() && by_path.st_uid == geteuid() &&
by_handle.st_nlink == expected_links && by_path.st_nlink == expected_links &&
(by_handle.st_mode & 07777) == 0600 && (by_path.st_mode & 07777) == 0600 &&
by_handle.st_dev == by_path.st_dev && by_handle.st_ino == by_path.st_ino &&
cbm_macos_extended_acl_fd_is_empty(directory_fd) &&
cbm_macos_extended_acl_fd_is_empty(fd);
if (valid && status_out) {
*status_out = by_handle;
}
return valid;
}
static bool private_regular_file_at_is_safe(int directory_fd, const char *base_name,
nlink_t expected_links) {
int fd = openat(directory_fd, base_name, O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK);
bool safe = fd >= 0 && fd_set_cloexec(fd) &&
private_regular_file_snapshot(directory_fd, base_name, fd, expected_links, NULL);
if (fd >= 0 && close(fd) != 0) {
safe = false;
}
return safe;
}
static int poll_until(int fd, short events, uint64_t deadline_ms) {
for (;;) {
struct pollfd descriptor = {.fd = fd, .events = events, .revents = 0};
int timeout_ms = deadline_ms == UINT64_MAX ? -1 : deadline_remaining_ms(deadline_ms);
int result = poll(&descriptor, 1, timeout_ms);
if (result > 0) {
if ((descriptor.revents & events) != 0) {
return 1;
}
return -1;
}
if (result == 0) {
return 0;
}
if (errno != EINTR) {
return -1;
}
}
}
static bool socket_peer_is_current_user(int fd) {
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
uid_t peer_uid = (uid_t)-1;
gid_t peer_gid = (gid_t)-1;
return getpeereid(fd, &peer_uid, &peer_gid) == 0 && peer_uid == geteuid();
#elif defined(__linux__)
struct ucred credentials;
socklen_t length = sizeof(credentials);
return getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &length) == 0 &&
length == sizeof(credentials) && credentials.uid == geteuid();
#else
(void)fd;
return false;
#endif
}
static int connection_read_full(cbm_daemon_ipc_connection_t *connection, void *buffer,
size_t length, uint64_t deadline_ms) {
if (!connection || atomic_load_explicit(&connection->poisoned, memory_order_acquire)) {
return -1;
}
size_t offset = 0;
while (offset < length) {
int ready = poll_until(connection->fd, POLLIN, deadline_ms);
if (ready != 1) {
/* Framing is fail-stop across platforms. Even at offset zero a
* deadline may race transport progress (notably OVERLAPPED I/O on
* Windows), so callers must reconnect instead of reusing a stream
* whose next byte boundary is uncertain. */
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return ready;
}
ssize_t received = recv(connection->fd, (uint8_t *)buffer + offset, length - offset, 0);
if (received > 0) {
offset += (size_t)received;
continue;
}
if (received == 0) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return -1;
}
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return -1;
}
}
return 1;
}
static int connection_write_full(cbm_daemon_ipc_connection_t *connection, const void *buffer,
size_t length, uint64_t deadline_ms) {
if (!connection || atomic_load_explicit(&connection->poisoned, memory_order_acquire)) {
return -1;
}
size_t offset = 0;
while (offset < length) {
int ready = poll_until(connection->fd, POLLOUT, deadline_ms);
if (ready != 1) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return ready;
}
#ifdef MSG_NOSIGNAL
ssize_t sent =
send(connection->fd, (const uint8_t *)buffer + offset, length - offset, MSG_NOSIGNAL);
#else
ssize_t sent = send(connection->fd, (const uint8_t *)buffer + offset, length - offset, 0);
#endif
if (sent > 0) {
offset += (size_t)sent;
continue;
}
if (sent == 0) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return -1;
}
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return -1;
}
}
return 1;
}
cbm_daemon_ipc_endpoint_t *cbm_daemon_ipc_endpoint_new(const char *instance_key,
const char *runtime_parent) {
if (!instance_key_valid(instance_key)) {
return NULL;
}
#ifdef __APPLE__
const char *default_parent = "/private/tmp"; /* /tmp itself is a symlink on macOS. */
#else
const char *default_parent = "/tmp";
#endif
const char *requested_parent = runtime_parent ? runtime_parent : default_parent;
char canonical_parent[CBM_DAEMON_IPC_PATH_CAP];
if (!cbm_canonical_path(requested_parent, canonical_parent, sizeof(canonical_parent))) {
return NULL;
}
const char *parent = canonical_parent;
if (!runtime_parent_valid(parent)) {
return NULL;
}
cbm_daemon_ipc_endpoint_t *endpoint = calloc(1, sizeof(*endpoint));
if (!endpoint) {
return NULL;
}
endpoint->dir_fd = -1;
endpoint->runtime_dir =
string_format("%s%s%s%lu", parent, parent[strlen(parent) - 1] == '/' ? "" : "/",
"cbm-daemon-", (unsigned long)geteuid());
endpoint->socket_name = string_format("cbm-%s.sock", instance_key);
endpoint->socket_anchor_name = string_format("cbm-%s.anc", instance_key);
endpoint->socket_identity_name = string_format("cbm-%s.sock.identity", instance_key);
endpoint->socket_pending_name = string_format("cbm-%s.sock.pending", instance_key);
endpoint->lock_name = string_format("cbm-%s.lock", instance_key);
endpoint->startup_v2_lock_name = string_format("cbm-%s.startup-v2.lock", instance_key);
endpoint->lifetime_lock_name = string_format("cbm-%s.lifetime.lock", instance_key);
if (!endpoint->runtime_dir || !endpoint->socket_name || !endpoint->socket_anchor_name ||
!endpoint->socket_identity_name || !endpoint->socket_pending_name || !endpoint->lock_name ||
!endpoint->startup_v2_lock_name || !endpoint->lifetime_lock_name ||
!private_runtime_open(endpoint->runtime_dir, &endpoint->dir_fd, &endpoint->dir_device,
&endpoint->dir_inode)) {
cbm_daemon_ipc_endpoint_free(endpoint);
return NULL;
}
endpoint->address = string_format("%s/%s", endpoint->runtime_dir, endpoint->socket_name);
endpoint->socket_anchor_address =
string_format("%s/%s", endpoint->runtime_dir, endpoint->socket_anchor_name);
struct sockaddr_un address;
socklen_t address_length;
if (!endpoint->address || !endpoint->socket_anchor_address ||
strlen(endpoint->socket_anchor_name) >= POSIX_SOCKET_ANCHOR_NAME_CAP ||
!unix_address_set(&address, endpoint->address, &address_length) ||
!unix_address_set(&address, endpoint->socket_anchor_address, &address_length)) {
cbm_daemon_ipc_endpoint_free(endpoint);
return NULL;
}
return endpoint;
}
void cbm_daemon_ipc_endpoint_free(cbm_daemon_ipc_endpoint_t *endpoint) {
if (!endpoint) {
return;
}
if (endpoint->dir_fd >= 0) {
(void)close(endpoint->dir_fd);
}
free(endpoint->runtime_dir);
free(endpoint->address);
free(endpoint->socket_name);
free(endpoint->socket_anchor_address);
free(endpoint->socket_anchor_name);
free(endpoint->socket_identity_name);
free(endpoint->socket_pending_name);
free(endpoint->lock_name);
free(endpoint->startup_v2_lock_name);
free(endpoint->lifetime_lock_name);
free(endpoint);
}
static cbm_daemon_ipc_endpoint_t *endpoint_snapshot_new(const cbm_daemon_ipc_endpoint_t *endpoint) {
if (!endpoint_runtime_still_valid(endpoint)) {
return NULL;
}
cbm_daemon_ipc_endpoint_t *snapshot = calloc(1, sizeof(*snapshot));
if (!snapshot) {
return NULL;
}
snapshot->dir_fd = -1;
snapshot->runtime_dir = string_copy(endpoint->runtime_dir);
snapshot->address = string_copy(endpoint->address);
snapshot->socket_name = string_copy(endpoint->socket_name);
snapshot->socket_anchor_address = string_copy(endpoint->socket_anchor_address);
snapshot->socket_anchor_name = string_copy(endpoint->socket_anchor_name);
snapshot->socket_identity_name = string_copy(endpoint->socket_identity_name);
snapshot->socket_pending_name = string_copy(endpoint->socket_pending_name);
snapshot->lock_name = string_copy(endpoint->lock_name);
snapshot->startup_v2_lock_name = string_copy(endpoint->startup_v2_lock_name);
snapshot->lifetime_lock_name = string_copy(endpoint->lifetime_lock_name);
snapshot->dir_fd = dup(endpoint->dir_fd);
snapshot->dir_device = endpoint->dir_device;
snapshot->dir_inode = endpoint->dir_inode;
if (!snapshot->runtime_dir || !snapshot->address || !snapshot->socket_name ||
!snapshot->socket_anchor_address || !snapshot->socket_anchor_name ||
!snapshot->socket_identity_name || !snapshot->socket_pending_name || !snapshot->lock_name ||
!snapshot->startup_v2_lock_name || !snapshot->lifetime_lock_name || snapshot->dir_fd < 0 ||
!fd_set_cloexec(snapshot->dir_fd) || !endpoint_runtime_still_valid(snapshot)) {
cbm_daemon_ipc_endpoint_free(snapshot);
return NULL;
}
return snapshot;
}
const char *cbm_daemon_ipc_endpoint_address(const cbm_daemon_ipc_endpoint_t *endpoint) {
return endpoint ? endpoint->address : NULL;
}
const char *cbm_daemon_ipc_endpoint_runtime_dir(const cbm_daemon_ipc_endpoint_t *endpoint) {
return endpoint ? endpoint->runtime_dir : NULL;
}
cbm_private_file_lock_status_t cbm_daemon_ipc_private_lock_directory_new(
const cbm_daemon_ipc_endpoint_t *endpoint, cbm_private_lock_directory_t **directory_out) {
if (directory_out) {
*directory_out = NULL;
}
if (!directory_out || !endpoint_runtime_still_valid(endpoint)) {
return CBM_PRIVATE_FILE_LOCK_UNSAFE;
}
#ifdef F_DUPFD_CLOEXEC
int duplicate = fcntl(endpoint->dir_fd, F_DUPFD_CLOEXEC, 0);
#else
int duplicate = fcntl(endpoint->dir_fd, F_DUPFD, 0);
if (duplicate >= 0 && !fd_set_cloexec(duplicate)) {
(void)close(duplicate);
duplicate = -1;
}
#endif
if (duplicate < 0) {
return CBM_PRIVATE_FILE_LOCK_IO;
}
cbm_private_file_lock_status_t status =
cbm_private_lock_directory_adopt_posix(duplicate, endpoint->runtime_dir, directory_out);
if (status != CBM_PRIVATE_FILE_LOCK_OK) {
(void)close(duplicate);
}
return status;
}
static void posix_named_lock_release(int fd, process_lock_entry_t *process_entry) {
if (fd >= 0) {
(void)flock(fd, LOCK_UN);
(void)close(fd);
}
process_lock_unclaim(process_entry);
}
static int posix_named_lock_try_acquire_mode(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *lock_name, bool shared, int *fd_out,
process_lock_entry_t **process_entry_out) {
if (fd_out) {
*fd_out = -1;
}
if (process_entry_out) {
*process_entry_out = NULL;
}
if (!fd_out || !process_entry_out || !lock_name || !endpoint_runtime_still_valid(endpoint)) {
return -1;
}
process_lock_entry_t *process_entry = NULL;
int process_result = process_lock_claim_mode(endpoint, lock_name, shared, &process_entry);
if (process_result != 1) {
return process_result == 0 &&
private_regular_file_at_is_safe(endpoint->dir_fd, lock_name, 1) &&
endpoint_runtime_still_valid(endpoint)
? 0
: -1;
}
int fd = openat(endpoint->dir_fd, lock_name, O_RDWR | O_CREAT | O_CLOEXEC | O_NOFOLLOW, 0600);
if (fd < 0 || !fd_set_cloexec(fd)) {
if (fd >= 0) {
(void)close(fd);
}
process_lock_unclaim(process_entry);
return -1;
}
if (fchmod(fd, 0600) != 0 ||
!private_regular_file_snapshot(endpoint->dir_fd, lock_name, fd, 1, NULL) ||
!endpoint_runtime_still_valid(endpoint)) {
(void)close(fd);
process_lock_unclaim(process_entry);
return -1;
}
if (flock(fd, (shared ? LOCK_SH : LOCK_EX) | LOCK_NB) != 0) {
int lock_error = errno;
bool still_private =
private_regular_file_snapshot(endpoint->dir_fd, lock_name, fd, 1, NULL) &&
endpoint_runtime_still_valid(endpoint);
(void)close(fd);
process_lock_unclaim(process_entry);
return still_private && (lock_error == EWOULDBLOCK || lock_error == EAGAIN) ? 0 : -1;
}
if (!private_regular_file_snapshot(endpoint->dir_fd, lock_name, fd, 1, NULL) ||
!endpoint_runtime_still_valid(endpoint)) {
posix_named_lock_release(fd, process_entry);
return -1;
}
*fd_out = fd;
*process_entry_out = process_entry;
return 1;
}
static int posix_named_lock_try_acquire(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *lock_name, int *fd_out,
process_lock_entry_t **process_entry_out) {
return posix_named_lock_try_acquire_mode(endpoint, lock_name, false, fd_out, process_entry_out);
}
static int posix_named_shared_lock_try_acquire(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *lock_name, int *fd_out,
process_lock_entry_t **process_entry_out) {
return posix_named_lock_try_acquire_mode(endpoint, lock_name, true, fd_out, process_entry_out);
}
static int posix_record_lock_set(int fd, short lock_type) {
struct flock record_lock = {
.l_type = lock_type,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
};
int result;
do {
result = fcntl(fd, F_SETLK, &record_lock);
} while (result != 0 && errno == EINTR);
return result;
}
static int posix_lifetime_lock_probe(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *lock_name) {
if (!lock_name || !endpoint_runtime_still_valid(endpoint)) {
return -1;
}
int process_claimed = process_lock_is_claimed(endpoint, lock_name);
if (process_claimed < 0) {
return -1;
}
int fd = openat(endpoint->dir_fd, lock_name, O_RDWR | O_CLOEXEC | O_NOFOLLOW);
if (fd < 0) {
return errno == ENOENT && process_claimed == 0 ? 0 : -1;
}
if (!fd_set_cloexec(fd) ||
!private_regular_file_snapshot(endpoint->dir_fd, lock_name, fd, 1, NULL) ||
!endpoint_runtime_still_valid(endpoint)) {
(void)close(fd);
return -1;
}
if (process_claimed == 1) {
bool still_private = endpoint_runtime_still_valid(endpoint);
return close(fd) == 0 && still_private ? 1 : -1;
}
struct flock record_lock = {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
};
int query_result;
do {
query_result = fcntl(fd, F_GETLK, &record_lock);
} while (query_result != 0 && errno == EINTR);
bool still_private = private_regular_file_snapshot(endpoint->dir_fd, lock_name, fd, 1, NULL) &&
endpoint_runtime_still_valid(endpoint);
(void)close(fd);
if (query_result != 0 || !still_private) {
return -1;
}
if (record_lock.l_type == F_UNLCK) {
return 0;
}
return record_lock.l_type == F_RDLCK || record_lock.l_type == F_WRLCK ? 1 : -1;
}
static int posix_lifetime_lock_try_acquire(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *lock_name, int *fd_out,
process_lock_entry_t **process_entry_out) {
if (fd_out) {
*fd_out = -1;
}
if (process_entry_out) {
*process_entry_out = NULL;
}
if (!fd_out || !process_entry_out || !lock_name || !endpoint_runtime_still_valid(endpoint)) {
return -1;
}
process_lock_entry_t *process_entry = NULL;
int process_result = process_lock_claim(endpoint, lock_name, &process_entry);
if (process_result != 1) {
return process_result == 0 &&
private_regular_file_at_is_safe(endpoint->dir_fd, lock_name, 1) &&
endpoint_runtime_still_valid(endpoint)
? 0
: -1;
}
int fd = openat(endpoint->dir_fd, lock_name, O_RDWR | O_CREAT | O_CLOEXEC | O_NOFOLLOW, 0600);
if (fd < 0 || !fd_set_cloexec(fd)) {
if (fd >= 0) {
(void)close(fd);
}
process_lock_unclaim(process_entry);
return -1;
}
if (fchmod(fd, 0600) != 0 ||
!private_regular_file_snapshot(endpoint->dir_fd, lock_name, fd, 1, NULL) ||
!endpoint_runtime_still_valid(endpoint)) {
(void)close(fd);
process_lock_unclaim(process_entry);
return -1;
}
if (posix_record_lock_set(fd, F_WRLCK) != 0) {
int lock_error = errno;
bool still_private =
private_regular_file_snapshot(endpoint->dir_fd, lock_name, fd, 1, NULL) &&
endpoint_runtime_still_valid(endpoint);
(void)close(fd);
process_lock_unclaim(process_entry);
return still_private && (lock_error == EACCES || lock_error == EAGAIN) ? 0 : -1;
}
if (!private_regular_file_snapshot(endpoint->dir_fd, lock_name, fd, 1, NULL) ||
!endpoint_runtime_still_valid(endpoint)) {
(void)posix_record_lock_set(fd, F_UNLCK);
(void)close(fd);
process_lock_unclaim(process_entry);
return -1;
}
*fd_out = fd;
*process_entry_out = process_entry;
return 1;
}
static void posix_lifetime_lock_release(int fd, process_lock_entry_t *process_entry) {
if (fd >= 0) {
(void)posix_record_lock_set(fd, F_UNLCK);
(void)close(fd);
}
process_lock_unclaim(process_entry);
}
int cbm_daemon_ipc_lifetime_reservation_try_acquire(
const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_daemon_ipc_lifetime_reservation_t **reservation_out) {
if (reservation_out) {
*reservation_out = NULL;
}
if (!endpoint || !reservation_out) {
return -1;
}
int fd = -1;
process_lock_entry_t *process_entry = NULL;
int result = posix_lifetime_lock_try_acquire(endpoint, endpoint->lifetime_lock_name, &fd,
&process_entry);
if (result != 1) {
return result;
}
cbm_daemon_ipc_lifetime_reservation_t *reservation = malloc(sizeof(*reservation));
if (!reservation) {
posix_lifetime_lock_release(fd, process_entry);
return -1;
}
reservation->fd = fd;
reservation->process_entry = process_entry;
reservation->owner_pid = getpid();
*reservation_out = reservation;
return 1;
}
void cbm_daemon_ipc_lifetime_reservation_release(
cbm_daemon_ipc_lifetime_reservation_t *reservation) {
if (!reservation) {
return;
}
posix_lifetime_lock_release(reservation->fd, reservation->process_entry);
free(reservation);
}
static bool lifetime_reservation_matches_endpoint(
const cbm_daemon_ipc_endpoint_t *endpoint,
const cbm_daemon_ipc_lifetime_reservation_t *reservation) {
const process_lock_entry_t *entry = reservation ? reservation->process_entry : NULL;
if (!endpoint_runtime_still_valid(endpoint) || !reservation || reservation->fd < 0 ||
reservation->owner_pid != getpid() || !entry || !endpoint->lifetime_lock_name ||
entry->directory_device != endpoint->dir_device ||
entry->directory_inode != endpoint->dir_inode || !entry->lock_name ||
strcmp(entry->lock_name, endpoint->lifetime_lock_name) != 0) {
return false;
}
if (!private_regular_file_snapshot(endpoint->dir_fd, endpoint->lifetime_lock_name,
reservation->fd, 1, NULL) ||
!endpoint_runtime_still_valid(endpoint)) {
return false;
}
/* Reasserting the same process-owned record lock is a no-op for a valid
* reservation and fails if another process has replaced ownership. */
return posix_record_lock_set(reservation->fd, F_WRLCK) == 0;
}
int cbm_daemon_ipc_lifetime_reservation_probe(const cbm_daemon_ipc_endpoint_t *endpoint) {
return endpoint ? posix_lifetime_lock_probe(endpoint, endpoint->lifetime_lock_name) : -1;
}
static int posix_startup_lock_probe(const cbm_daemon_ipc_endpoint_t *endpoint) {
if (!endpoint_runtime_still_valid(endpoint)) {
return -1;
}
int process_claimed = process_lock_is_claimed(endpoint, endpoint->lock_name);
if (process_claimed < 0) {
return -1;
}
int fd = openat(endpoint->dir_fd, endpoint->lock_name, O_RDWR | O_CLOEXEC | O_NOFOLLOW);
if (fd < 0) {
return errno == ENOENT && process_claimed == 0 ? 0 : -1;
}
if (!fd_set_cloexec(fd) ||
!private_regular_file_snapshot(endpoint->dir_fd, endpoint->lock_name, fd, 1, NULL)) {
(void)close(fd);
return -1;
}
if (process_claimed == 1) {
bool still_private = endpoint_runtime_still_valid(endpoint);
return close(fd) == 0 && still_private ? 1 : -1;
}
int lock_result;
do {
lock_result = flock(fd, LOCK_SH | LOCK_NB);
} while (lock_result != 0 && errno == EINTR);
if (lock_result != 0) {
int lock_error = errno;
bool still_private =
private_regular_file_snapshot(endpoint->dir_fd, endpoint->lock_name, fd, 1, NULL) &&
endpoint_runtime_still_valid(endpoint);
(void)close(fd);
return still_private && (lock_error == EWOULDBLOCK || lock_error == EAGAIN) ? 1 : -1;
}
int unlock_result;
do {
unlock_result = flock(fd, LOCK_UN);
} while (unlock_result != 0 && errno == EINTR);
bool still_private =
private_regular_file_snapshot(endpoint->dir_fd, endpoint->lock_name, fd, 1, NULL) &&
endpoint_runtime_still_valid(endpoint);
int close_result = close(fd);
return unlock_result == 0 && still_private && close_result == 0 ? 0 : -1;
}
int cbm_daemon_ipc_legacy_generation_probe(const cbm_daemon_ipc_endpoint_t *endpoint) {
if (!endpoint_runtime_still_valid(endpoint)) {
return -1;
}
struct stat status;
if (fstatat(endpoint->dir_fd, endpoint->socket_name, &status, AT_SYMLINK_NOFOLLOW) == 0) {
return posix_socket_status_secure_transport(&status) ? 1 : -1;
}
if (errno != ENOENT) {
return -1;
}
return posix_startup_lock_probe(endpoint);
}
static bool private_log_base_name_valid(const char *base_name) {
if (!base_name || !base_name[0] || strcmp(base_name, ".") == 0 ||
strcmp(base_name, "..") == 0 || strchr(base_name, '/') || strchr(base_name, '\\')) {
return false;
}
size_t length = strlen(base_name);
return length <= NAME_MAX - 2;
}
static char *private_log_directory_path_copy(const char *directory_path) {
#if defined(__APPLE__) || defined(__FreeBSD__)
/* Resolve only those root-owned aliases before the
* component-wise O_NOFOLLOW walk. Canonicalizing the complete caller path
* would follow an attacker-controlled cache/log symlink and is forbidden. */
#if defined(__APPLE__)
/* Darwin exposes the trusted top-level aliases /tmp -> /private/tmp and
* /var -> /private/var. */
static const char *const aliases[] = {"/tmp", "/var"};
#else
/* FreeBSD additionally exposes /home -> /usr/home as a root-owned alias. */
static const char *const aliases[] = {"/tmp", "/var", "/home"};
#endif
for (size_t index = 0; index < sizeof(aliases) / sizeof(aliases[0]); index++) {
const char *alias = aliases[index];
size_t alias_length = strlen(alias);
if (strncmp(directory_path, alias, alias_length) != 0 ||
(directory_path[alias_length] != '\0' && directory_path[alias_length] != '/')) {
continue;
}
struct stat alias_status;
if (lstat(alias, &alias_status) != 0 || !S_ISLNK(alias_status.st_mode)) {
break;
}
struct stat root_status;
char resolved[CBM_DAEMON_IPC_PATH_CAP];
if (alias_status.st_uid != 0 || lstat("/", &root_status) != 0 ||
!S_ISDIR(root_status.st_mode) || root_status.st_uid != 0 ||
(root_status.st_mode & 0022) != 0 || !realpath(alias, resolved)) {
return NULL;
}
struct stat resolved_status;
if (lstat(resolved, &resolved_status) != 0 || !S_ISDIR(resolved_status.st_mode) ||
resolved_status.st_uid != 0) {
return NULL;
}
return string_format("%s%s", resolved, directory_path + alias_length);
}
#endif
return string_copy(directory_path);
}
/* #1830 — single-uid user-namespace ancestors.
*
* Inside a 1:1 user namespace (for example `podman --userns=keep-id` or
* `unshare -U --map-current-user`), ancestors whose real owner is unmapped —
* the root-owned /, /tmp and the like — appear owned by the kernel overflow
* uid (`/proc/sys/kernel/overflowuid`, conventionally 65534). Refusing every
* overflow-owned ancestor made the daemon unusable in that legitimate setup.
*
* The overflow owner is tolerated for ANCESTORS ONLY, and ONLY when
* /proc/self/uid_map is a single-uid map whose sole inside id is our euid. In
* that shape no IN-NAMESPACE principal can be the overflow owner.
*
* Be precise about what that does and does not buy, because an earlier version
* of this comment overstated it. The overflow uid is what EVERY unmapped host
* uid maps to, not only host root (user_namespaces(7)), so "overflow-owned"
* does NOT prove "created by root". On a shared host, a directory owned by
* another local user is indistinguishable from root-owned /tmp once you are
* inside the namespace -- and there is no in-namespace discriminator that could
* tell them apart, which is precisely why the tolerance is scoped the way it
* is rather than made smarter. The real guarantee is narrower and still
* sufficient: no principal REACHABLE FROM INSIDE the namespace can create or
* mutate such an ancestor, and the private directory itself is never tolerated
* as overflow (see below), so a hostile host-side owner of an ancestor is
* bounded to denial of service and socket-path control. It cannot reach the
* leaf, which stays 0700 and euid-rechecked. A multi-uid map
* (rootless podman with subuids) still shows host root as overflow and is
* refused — conservative scope, not the safety argument. There is deliberately
* no environment escape hatch.
*
* The private directory itself is never tolerated as overflow: it is created by
* us (euid-owned) and re-checked `== geteuid()` by the created/final/snapshot
* gates, which stay strict. A host-squatted /tmp/cbm-daemon-<uid> appears
* overflow-owned and is refused there. */
#define POSIX_NO_OVERFLOW_UID ((uid_t) - 1)
#ifdef CBM_ENABLE_TEST_SEAMS
static bool g_posix_overflow_override_active;
static uid_t g_posix_overflow_override_uid = POSIX_NO_OVERFLOW_UID;
void cbm_daemon_ipc_posix_set_ancestor_overflow_uid_for_test(bool active,
unsigned long overflow_uid) {
g_posix_overflow_override_active = active;
g_posix_overflow_override_uid = active ? (uid_t)overflow_uid : POSIX_NO_OVERFLOW_UID;
}
#endif
#if defined(__linux__) || defined(CBM_ENABLE_TEST_SEAMS)
/* Parse a /proc/self/uid_map image. True iff it is exactly one mapping line
* "<inside> <outside> <count>" with count == 1 and inside == euid. Extra lines,
* a count other than 1, a different inside id, or malformed input all yield
* false (no tolerance). */
static bool posix_uid_map_is_single_uid(const char *uid_map, uid_t euid) {
if (!uid_map) {
return false;
}
unsigned long inside = 0;
unsigned long outside = 0;
unsigned long count = 0;
int consumed = 0;
if (sscanf(uid_map, " %lu %lu %lu %n", &inside, &outside, &count, &consumed) != 3) {
return false;
}
if (count != 1UL || (uid_t)inside != euid) {
return false;
}
(void)outside;
/* Reject a second mapping line: a single-uid map has exactly one. */
const char *rest = uid_map + consumed;
while (*rest == ' ' || *rest == '\t' || *rest == '\n' || *rest == '\r') {
rest++;
}
return *rest == '\0';
}
#endif
#if defined(__linux__)
static bool posix_read_small_proc_file(const char *path, char *buffer, size_t capacity) {
int fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return false;
}
size_t total = 0;
bool ok = true;
while (total + 1 < capacity) {
ssize_t got = read(fd, buffer + total, capacity - 1 - total);
if (got < 0) {
if (errno == EINTR) {
continue;
}
ok = false;
break;
}
if (got == 0) {
break;
}
total += (size_t)got;
}
(void)close(fd);
if (!ok) {
return false;
}
buffer[total] = '\0';
return true;
}
#ifdef CBM_ENABLE_TEST_SEAMS
/* Counts real derivations. A cache here was a security hazard once (see the
* note above posix_ancestor_overflow_uid); the contract test asserts this
* climbs on EVERY call so re-introducing one fails loudly. */
static unsigned g_posix_overflow_compute_count;
unsigned cbm_daemon_ipc_posix_overflow_compute_count_for_test(void) {
return g_posix_overflow_compute_count;
}
#endif
static uid_t posix_compute_ancestor_overflow_uid(void) {
#ifdef CBM_ENABLE_TEST_SEAMS
g_posix_overflow_compute_count++;
#endif
char map[256];
if (!posix_read_small_proc_file("/proc/self/uid_map", map, sizeof(map)) ||
!posix_uid_map_is_single_uid(map, geteuid())) {
return POSIX_NO_OVERFLOW_UID;
}
char overflow_text[32];
/* Unreadable → no tolerance; never hardcode 65534. */
if (!posix_read_small_proc_file("/proc/sys/kernel/overflowuid", overflow_text,
sizeof(overflow_text))) {
return POSIX_NO_OVERFLOW_UID;
}
unsigned long overflow = 0;
if (sscanf(overflow_text, " %lu", &overflow) != 1) {
return POSIX_NO_OVERFLOW_UID;
}
return (uid_t)overflow;
}
#endif /* __linux__ */
/* The overflow uid tolerated for ancestors, or POSIX_NO_OVERFLOW_UID when none.
*
* DERIVED FRESH ON EVERY CALL, deliberately. This used to memoise via
* pthread_once behind a comment claiming "immutable /proc state". That claim
* was false in both halves: unshare(CLONE_NEWUSER) rewrites
* /proc/self/uid_map, and pthread_once state survives a forked child already
* marked done -- so a process that forked and then changed namespace kept the
* parent answer and refused a directory it should have accepted.
* (Spelled without the call syntax on purpose: scripts/security-audit.sh
* blocks that literal in src/, and an allow-list entry to let a COMMENT pass
* would weaken a real check on a real file.) It happened to be
* harmless because every caller today runs in a freshly exec'd process, but
* that made a security decision depend on an invariant nothing enforced, and
* the next fork-without-exec caller would have silently inherited a stale
* verdict.
*
* The cost of not caching is two small /proc reads per ancestor check, against
* an openat + fstat + fchmod + ACL check per path component in the same walk.
* Do not re-introduce a cache here; the contract test counts derivations. */
static uid_t posix_ancestor_overflow_uid(void) {
#ifdef CBM_ENABLE_TEST_SEAMS
if (g_posix_overflow_override_active) {
return g_posix_overflow_override_uid;
}
#endif
#if defined(__linux__)
return posix_compute_ancestor_overflow_uid();
#else
return POSIX_NO_OVERFLOW_UID;
#endif
}
/* Ancestor owner acceptance. euid and root are always trusted; the namespace
* overflow uid is trusted only when overflow_uid != POSIX_NO_OVERFLOW_UID. */
static bool posix_ancestor_owner_ok(uid_t owner, uid_t euid, uid_t overflow_uid) {
return owner == (uid_t)0 || owner == euid ||
(overflow_uid != POSIX_NO_OVERFLOW_UID && owner == overflow_uid);
}
/* Pure ancestor accept/refuse over (owner, mode). Mirrors the owner + write-bit
* policy of posix_directory_parent_secure so the decision is unit-testable
* without a chown-able overflow-owned directory (unprivileged tests cannot
* create one). The ACL and fstat checks stay in the caller. */
static bool posix_ancestor_stat_ok(uid_t owner, mode_t mode, uid_t euid, uid_t overflow_uid) {
if (!posix_ancestor_owner_ok(owner, euid, overflow_uid)) {
return false;
}
bool owner_is_overflow = overflow_uid != POSIX_NO_OVERFLOW_UID && owner == overflow_uid &&
owner != euid && owner != (uid_t)0;
if ((mode & 0002) != 0) {
/* World-writable ancestor: only the root/overflow-owned sticky pattern
* (for example /tmp). A plain writable ancestor lets any local user swap
* a path component. */
return (owner == (uid_t)0 || owner_is_overflow) && (mode & S_ISVTX) != 0;
}
if ((mode & 0020) != 0 && owner_is_overflow) {
/* Group-writable is admitted with a warning for euid/root owners where
* the group is knowable (#1537); for an unmapped overflow owner the
* host-side group membership is unknown, so refuse. */
return false;
}
return true;
}
/* #1687: WSL2 mounts Windows drives (/mnt/c, /mnt/e, ...) as DrvFs over 9p.
* Without the `metadata` mount option such a mount reports 0777 for every
* directory and ignores chmod, so both the ancestor rule (not world-writable)
* and the leaf 0700 rule refuse it -- correctly: the 0777 is real. The gate
* stays exactly as it is; this only lets the refusal name the remedy instead
* of leaving a WSL user to guess. Detection is a statfs magic (9p) confirmed by
* a WSL kernel release string, so plain 9p mounts elsewhere (QEMU virtfs) keep
* the generic message. Diagnostic only: it never changes accept/refuse. */
#define POSIX_WSL_DRVFS_REMEDY \
"WSL DrvFs mount: set /etc/wsl.conf [automount] options = " \
"\"metadata,umask=22,fmask=11\", then run `wsl --shutdown`; or keep the cache on the Linux " \
"filesystem (e.g. ~/.cache)"
#ifdef CBM_ENABLE_TEST_SEAMS
static bool g_posix_force_wsl_drvfs;
void cbm_daemon_ipc_posix_force_wsl_drvfs_for_test(bool force) {
g_posix_force_wsl_drvfs = force;
}
#endif
#if defined(__linux__)
#define POSIX_V9FS_MAGIC 0x01021997UL
static bool posix_kernel_is_wsl(void) {
char release[128];
if (!posix_read_small_proc_file("/proc/sys/kernel/osrelease", release, sizeof(release))) {
return false;
}
return strstr(release, "microsoft") != NULL || strstr(release, "Microsoft") != NULL ||
strstr(release, "WSL") != NULL;
}
#endif
static bool posix_fd_on_wsl_drvfs(int directory_fd) {
#ifdef CBM_ENABLE_TEST_SEAMS
if (g_posix_force_wsl_drvfs) {
return true;
}
#endif
#if defined(__linux__)
struct statfs filesystem;
return directory_fd >= 0 && fstatfs(directory_fd, &filesystem) == 0 &&
(unsigned long)filesystem.f_type == POSIX_V9FS_MAGIC && posix_kernel_is_wsl();
#else
(void)directory_fd;
return false;
#endif
}
static bool posix_directory_ancestor_owner_trusted(uid_t owner) {
return posix_ancestor_owner_ok(owner, geteuid(), posix_ancestor_overflow_uid());
}
#ifdef CBM_ENABLE_TEST_SEAMS
bool cbm_daemon_ipc_posix_uid_map_is_single_uid_for_test(const char *uid_map, unsigned long euid) {
return posix_uid_map_is_single_uid(uid_map, (uid_t)euid);
}
bool cbm_daemon_ipc_posix_ancestor_stat_ok_for_test(unsigned long owner, unsigned int mode,
unsigned long euid, bool overflow_active,
unsigned long overflow_uid) {
return posix_ancestor_stat_ok((uid_t)owner, (mode_t)mode, (uid_t)euid,
overflow_active ? (uid_t)overflow_uid : POSIX_NO_OVERFLOW_UID);
}
#endif
static bool posix_directory_parent_secure(int directory_fd) {
struct stat status;
if (directory_fd < 0 || fstat(directory_fd, &status) != 0 || !S_ISDIR(status.st_mode) ||
!cbm_macos_extended_acl_fd_is_deny_only(directory_fd)) {
return false;
}
/* #1537: this ANCESTOR check refused any group-write bit, which is the same
* rule #1535 removed on the activation side — and the sibling that decision
* covers but that never got changed. A group-writable ~ or ~/.cache is
* ordinary (WSL2 ships 0775, so do several distro skeletons and any site
* with a shared primary group), and refusing it here made the daemon
* unusable with no way for the reader to see why.
*
* WORLD-writable is still refused (apart from the root/overflow-owned sticky
* pattern such as /tmp): any local user could swap a path component.
* Group-writable is admitted for ancestors only — the private directory
* itself is chmod'd to 0700 and verified after this walk, so the thing that
* actually holds data stays owner-private either way. #1830 extends the
* accepted owner set to the single-uid user-namespace overflow uid; see
* posix_ancestor_stat_ok. */
if (!posix_ancestor_stat_ok(status.st_uid, status.st_mode, geteuid(),
posix_ancestor_overflow_uid())) {
return false;
}
if ((status.st_mode & 0020) != 0 && (status.st_mode & 0002) == 0) {
char mode_text[16];
(void)snprintf(mode_text, sizeof(mode_text), "%04o", (unsigned)(status.st_mode & 07777));
cbm_log_warn("daemon.private_dir_group_writable_ancestor", "mode", mode_text);
}
return true;
}
/* Validate a path transition only through the two already-open directory
* handles. A group/other-writable parent is unsafe unless it is the standard
* root/overflow-owned sticky-directory pattern (for example /tmp) and both the
* parent and the selected child are trusted-owned (euid/root, or the single-uid
* user-namespace overflow uid for #1830). Existing ancestors are observed,
* never chmod'd or ACL-rewritten. The euid-only enforcement that stops a
* squatted private directory lives in the created/final/snapshot checks, not
* here — this only walks ancestors. */
#ifdef CBM_ENABLE_TEST_SEAMS
/* #1717 seam: report one directory (by dev/ino) as owned by a foreign uid, so
* the foreign-owned-ancestor refusal and its diagnostic are testable without
* root (an unprivileged test cannot chown a directory to another account). */
static bool g_posix_foreign_owner_active;
static dev_t g_posix_foreign_owner_dev;
static ino_t g_posix_foreign_owner_ino;
static uid_t g_posix_foreign_owner_uid;
bool cbm_daemon_ipc_posix_set_foreign_owned_dir_for_test(const char *directory_path,
unsigned long foreign_uid) {
struct stat status;
g_posix_foreign_owner_active = false;
if (!directory_path || lstat(directory_path, &status) != 0 || !S_ISDIR(status.st_mode)) {
return false;
}
g_posix_foreign_owner_dev = status.st_dev;
g_posix_foreign_owner_ino = status.st_ino;
g_posix_foreign_owner_uid = (uid_t)foreign_uid;
g_posix_foreign_owner_active = true;
return true;
}
void cbm_daemon_ipc_posix_clear_foreign_owned_dir_for_test(void) {
g_posix_foreign_owner_active = false;
}
#endif
static uid_t posix_directory_observed_owner(const struct stat *status) {
#ifdef CBM_ENABLE_TEST_SEAMS
if (g_posix_foreign_owner_active && status->st_dev == g_posix_foreign_owner_dev &&
status->st_ino == g_posix_foreign_owner_ino) {
return g_posix_foreign_owner_uid;
}
#endif
return status->st_uid;
}
enum { POSIX_OWNER_NAME_CAP = 33, POSIX_PASSWD_LINE_CAP = 1024 };
/* The account name for uid, or "" when it cannot be named. Diagnostic only.
*
* Linux reads /etc/passwd directly instead of calling getpwuid(): the Linux
* release binary is statically linked against glibc, where getpwuid() loads the
* HOST's NSS modules at runtime -- a version-mismatch crash hazard exactly on
* the sssd/LDAP hosts (#1717's shared HPC filesystems) this message serves.
* A directory-service account is then shown by uid only. */
static void posix_owner_name(uid_t uid, char out[POSIX_OWNER_NAME_CAP]) {
out[0] = '\0';
#if defined(__linux__)
FILE *passwd = cbm_fopen("/etc/passwd", "r");
if (!passwd) {
return;
}
char line[POSIX_PASSWD_LINE_CAP];
while (fgets(line, sizeof(line), passwd)) {
char *name_end = strchr(line, ':');
char *password_end = name_end ? strchr(name_end + 1, ':') : NULL;
if (!password_end || name_end == line) {
continue;
}
char *uid_end = NULL;
errno = 0;
unsigned long line_uid = strtoul(password_end + 1, &uid_end, 10);
if (errno == 0 && uid_end != password_end + 1 && *uid_end == ':' &&
line_uid == (unsigned long)uid) {
(void)snprintf(out, POSIX_OWNER_NAME_CAP, "%.*s", (int)(name_end - line), line);
break;
}
}
(void)fclose(passwd);
#else
struct passwd entry;
struct passwd *found = NULL;
char storage[POSIX_PASSWD_LINE_CAP];
if (getpwuid_r(uid, &entry, storage, sizeof(storage), &found) == 0 && found && found->pw_name) {
(void)snprintf(out, POSIX_OWNER_NAME_CAP, "%s", found->pw_name);
}
#endif
}
/* The foreign-owned-ancestor refusal. The remedy is the part the reader acts
* on, so it must survive the fixed detail buffer: the path is shortened from
* the LEFT ("...") to fit, which keeps the refusing component at its end. */
static void posix_foreign_owner_detail_set(const char *child_path, uid_t owner) {
char name[POSIX_OWNER_NAME_CAP];
posix_owner_name(owner, name);
char owner_text[64];
if (name[0]) {
(void)snprintf(owner_text, sizeof(owner_text), "uid %ld (%s)", (long)owner, name);
} else {
(void)snprintf(owner_text, sizeof(owner_text), "uid %ld", (long)owner);
}
char tail[sizeof(ipc_validation_detail_buffer)];
int tail_length = snprintf(tail, sizeof(tail),
"' is owned by %s, not by you (uid %ld) or root; that account "
"controls this path. Use a location whose parent directories are "
"yours or root's (e.g. set CBM_CACHE_DIR)",
owner_text, (long)geteuid());
if (tail_length < 0) {
return;
}
size_t used = (size_t)tail_length + 1; /* the opening quote */
size_t room = used < sizeof(ipc_validation_detail_buffer)
? sizeof(ipc_validation_detail_buffer) - 1 - used
: 0;
size_t path_length = strlen(child_path);
const char *ellipsis = "";
const char *shown = child_path;
if (path_length > room) {
ellipsis = "...";
shown = child_path + path_length - (room > 3 ? room - 3 : 0);
}
ipc_validation_detail_set("'%s%s%s", ellipsis, shown, tail);
}
static bool posix_directory_transition_secure(int parent_fd, int child_fd, const char *child_path) {
struct stat parent;
struct stat child;
if (parent_fd < 0 || child_fd < 0 || !posix_directory_parent_secure(parent_fd) ||
fstat(parent_fd, &parent) != 0 || fstat(child_fd, &child) != 0 ||
!S_ISDIR(parent.st_mode) || !S_ISDIR(child.st_mode) ||
!posix_directory_ancestor_owner_trusted(posix_directory_observed_owner(&parent))) {
return false;
}
uid_t child_owner = posix_directory_observed_owner(&child);
if (!posix_directory_ancestor_owner_trusted(child_owner)) {
/* #1717: this refusal used to set no detail, so the caller printed
* "ancestry component validation failed (errno 17)" -- a stale EEXIST
* from the mkdirat() on the existing component, naming neither the
* directory nor the reason. The policy is unchanged: an account that
* owns an ancestor can rename or replace everything below it after
* this walk, so it could redirect the private directory. Name it. */
posix_foreign_owner_detail_set(child_path, child_owner);
return false;
}
return true;
}
static int private_directory_tree_open(const char *directory_path) {
if (!directory_path || !directory_path[0] || O_DIRECTORY == 0 || O_NOFOLLOW == 0) {
return -1;
}
char *path = private_log_directory_path_copy(directory_path);
if (!path) {
return -1;
}
bool absolute = path[0] == '/';
int current_fd = open(absolute ? "/" : ".", O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
bool ok = current_fd >= 0 && fd_set_cloexec(current_fd);
char *cursor = path;
while (ok && *cursor == '/') {
cursor++;
}
bool visited = false;
while (ok && *cursor) {
char *component = cursor;
while (*cursor && *cursor != '/') {
cursor++;
}
char saved = *cursor;
*cursor = '\0';
if (strcmp(component, ".") == 0) {
/* Relative paths may contain a harmless explicit current-dir
* component. Parent traversal is never valid for private logs. */
} else if (strcmp(component, "..") == 0 || !component[0]) {
ok = false;
} else {
ok = posix_directory_parent_secure(current_fd);
if (!ok) {
/* #1537: this branch used to leave the detail empty, so the
* caller fell back to printing errno — which NOTHING here sets.
* A reporter was handed "errno 2" (ENOENT) for a permission
* refusal and went looking for a missing file that existed.
* An unset errno is not a diagnosis.
*
* The first version of that fix then named the WRONG directory.
* posix_directory_parent_secure() validates current_fd — the
* directory we are already in — but the message printed
* `component`, the child about to be entered. So #1537 read
* "ancestor '.cache'" when /Users/<user> was refusing, and
* #1621 read "cbm-daemon-501" when /private/tmp was. Both
* reporters inspected a directory that was not the one
* refusing, found it clean, and said so — correctly. Naming the
* containing directory is the difference between a report we
* can act on and weeks of talking past each other. */
if (posix_fd_on_wsl_drvfs(current_fd)) {
/* #1687: shorter rule text so the remedy fits the buffer. */
ipc_validation_detail_set("%s: the directory CONTAINING '%s' is not private "
"(DrvFs reports 0777). " POSIX_WSL_DRVFS_REMEDY,
directory_path, component);
} else {
ipc_validation_detail_set(
"%s: the directory CONTAINING '%s' is not a usable private-directory "
"parent (it must be owned by you, not world-writable, and carry no "
"allow-ACL). Check that containing directory, not '%s' itself",
directory_path, component, component);
}
}
bool created = ok && mkdirat(current_fd, component, 0700) == 0;
if (!created && errno != EEXIST) {
ok = false;
}
int next_fd =
ok ? openat(current_fd, component, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW)
: -1;
struct stat status;
ok = next_fd >= 0 && fd_set_cloexec(next_fd) && fstat(next_fd, &status) == 0 &&
S_ISDIR(status.st_mode) &&
posix_directory_transition_secure(current_fd, next_fd, path);
if (ok && created) {
ok = status.st_uid == geteuid() && fchmod(next_fd, 0700) == 0 &&
cbm_macos_extended_acl_fd_clear(next_fd) &&
cbm_macos_extended_acl_fd_is_empty(next_fd);
}
if (ok) {
(void)close(current_fd);
current_fd = next_fd;
visited = true;
} else if (next_fd >= 0) {
(void)close(next_fd);
}
}
*cursor = saved;
while (*cursor == '/') {
cursor++;
}
}
struct stat final_status;
if (ok && !visited) {
ipc_validation_detail_set("%s: no path components resolved", directory_path);
ok = false;
}
if (ok && (fstat(current_fd, &final_status) != 0 || !S_ISDIR(final_status.st_mode))) {
ipc_validation_detail_set("%s: final stat failed or not a directory (errno %d)",
directory_path, errno);
ok = false;
}
if (ok && final_status.st_uid != geteuid()) {
ipc_validation_detail_set("%s: owner uid %ld, expected euid %ld", directory_path,
(long)final_status.st_uid, (long)geteuid());
ok = false;
}
if (ok && fchmod(current_fd, 0700) != 0) {
ipc_validation_detail_set("%s: chmod 0700 failed (errno %d)", directory_path, errno);
ok = false;
}
if (ok && !cbm_macos_extended_acl_fd_clear(current_fd)) {
ipc_validation_detail_set("%s: extended ACL not clearable", directory_path);
ok = false;
}
if (ok && (fstat(current_fd, &final_status) != 0 || (final_status.st_mode & 07777) != 0700)) {
/* #1687: DrvFs without `metadata` ignores chmod; name the remedy. */
bool drvfs = posix_fd_on_wsl_drvfs(current_fd);
ipc_validation_detail_set("%s: mode 0%o survived chmod, expected 0700%s%s", directory_path,
(unsigned)(final_status.st_mode & 07777), drvfs ? ". " : "",
drvfs ? POSIX_WSL_DRVFS_REMEDY : "");
ok = false;
}
if (ok && !cbm_macos_extended_acl_fd_is_empty(current_fd)) {
ipc_validation_detail_set("%s: extended ACL still present after clear", directory_path);
ok = false;
}
free(path);
if (!ok) {
if (current_fd >= 0) {
(void)close(current_fd);
}
return -1;
}
return current_fd;
}
bool cbm_daemon_ipc_private_directory_secure(const char *directory_path) {
ipc_validation_detail_set("%s", "");
int directory_fd = private_directory_tree_open(directory_path);
if (directory_fd < 0) {
if (!ipc_validation_detail_buffer[0]) {
ipc_validation_detail_set("%s: ancestry component validation failed (errno %d)",
directory_path, errno);
}
return false;
}
return close(directory_fd) == 0;
}
static int private_log_file_open(int directory_fd, const char *base_name, struct stat *status_out) {
int flags = O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK;
int fd = openat(directory_fd, base_name, flags | O_CREAT | O_EXCL, 0600);
if (fd < 0 && errno == EEXIST) {
fd = openat(directory_fd, base_name, flags);
}
struct stat status;
if (fd < 0 || !fd_set_cloexec(fd) || fchmod(fd, 0600) != 0 ||
!private_regular_file_snapshot(directory_fd, base_name, fd, 1, &status) ||
status.st_size < 0) {
if (fd >= 0) {
(void)close(fd);
}
return -1;
}
*status_out = status;
return fd;
}
FILE *cbm_daemon_ipc_private_log_open(const char *directory_path, const char *base_name,
size_t rotate_cap_bytes) {
if (!private_log_base_name_valid(base_name) || rotate_cap_bytes == 0) {
return NULL;
}
int directory_fd = private_directory_tree_open(directory_path);
if (directory_fd < 0) {
return NULL;
}
struct stat status;
int fd = private_log_file_open(directory_fd, base_name, &status);
bool ok = fd >= 0;
if (ok && (uintmax_t)status.st_size > (uintmax_t)rotate_cap_bytes) {
char rotated[NAME_MAX + 3];
int written = snprintf(rotated, sizeof(rotated), "%s.1", base_name);
struct stat destination;
bool destination_ok = false;
if (written > 0 && written < (int)sizeof(rotated)) {
if (fstatat(directory_fd, rotated, &destination, AT_SYMLINK_NOFOLLOW) == 0) {
destination_ok = S_ISREG(destination.st_mode) && destination.st_uid == geteuid() &&
destination.st_nlink == 1;
} else {
destination_ok = errno == ENOENT;
}
}
struct stat current;
bool current_ok = fstatat(directory_fd, base_name, &current, AT_SYMLINK_NOFOLLOW) == 0 &&
S_ISREG(current.st_mode) && current.st_uid == geteuid() &&
current.st_nlink == 1 && current.st_dev == status.st_dev &&
current.st_ino == status.st_ino;
ok = destination_ok && current_ok && close(fd) == 0;
fd = -1;
if (ok) {
ok = renameat(directory_fd, base_name, directory_fd, rotated) == 0;
}
if (ok) {
fd = private_log_file_open(directory_fd, base_name, &status);
ok = fd >= 0;
}
}
FILE *stream = NULL;
if (ok) {
stream = fdopen(fd, "ab");
if (stream) {
fd = -1;
}
}
if (fd >= 0) {
(void)close(fd);
}
(void)fsync(directory_fd);
(void)close(directory_fd);
return stream;
}
static void posix_u32_encode(uint8_t out[4], uint32_t value) {
out[0] = (uint8_t)(value & 0xffU);
out[1] = (uint8_t)((value >> 8U) & 0xffU);
out[2] = (uint8_t)((value >> 16U) & 0xffU);
out[3] = (uint8_t)((value >> 24U) & 0xffU);
}
static uint32_t posix_u32_decode(const uint8_t in[4]) {
return (uint32_t)in[0] | ((uint32_t)in[1] << 8U) | ((uint32_t)in[2] << 16U) |
((uint32_t)in[3] << 24U);
}
static void posix_u64_encode(uint8_t out[8], uint64_t value) {
for (size_t index = 0; index < 8; index++) {
out[index] = (uint8_t)(value >> (index * 8U));
}
}
static uint64_t posix_u64_decode(const uint8_t in[8]) {
uint64_t value = 0;
for (size_t index = 0; index < 8; index++) {
value |= (uint64_t)in[index] << (index * 8U);
}
return value;
}
static bool posix_stat_ctime(const struct stat *status, int64_t *seconds_out,
uint32_t *nanoseconds_out) {
if (!status || !seconds_out || !nanoseconds_out) {
return false;
}
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
time_t raw_seconds = status->st_ctimespec.tv_sec;
long raw_nanoseconds = status->st_ctimespec.tv_nsec;
#else
time_t raw_seconds = status->st_ctim.tv_sec;
long raw_nanoseconds = status->st_ctim.tv_nsec;
#endif
int64_t seconds = (int64_t)raw_seconds;
if ((time_t)seconds != raw_seconds || seconds < 0 || raw_nanoseconds < 0 ||
raw_nanoseconds >= 1000000000L) {
return false;
}
*seconds_out = seconds;
*nanoseconds_out = (uint32_t)raw_nanoseconds;
return true;
}
static bool posix_socket_identity_from_stat(const struct stat *status,
posix_socket_identity_t *identity_out) {
if (!status || !identity_out || !S_ISSOCK(status->st_mode)) {
return false;
}
uint64_t device = (uint64_t)status->st_dev;
uint64_t inode = (uint64_t)status->st_ino;
int64_t ctime_seconds = 0;
uint32_t ctime_nanoseconds = 0;
if ((dev_t)device != status->st_dev || (ino_t)inode != status->st_ino ||
!posix_stat_ctime(status, &ctime_seconds, &ctime_nanoseconds)) {
return false;
}
identity_out->device = status->st_dev;
identity_out->inode = status->st_ino;
identity_out->ctime_seconds = ctime_seconds;
identity_out->ctime_nanoseconds = ctime_nanoseconds;
return true;
}
static bool posix_socket_identity_equal(const posix_socket_identity_t *left,
const posix_socket_identity_t *right) {
return left && right && left->device == right->device && left->inode == right->inode &&
left->ctime_seconds == right->ctime_seconds &&
left->ctime_nanoseconds == right->ctime_nanoseconds;
}
static bool posix_socket_inode_equal(const posix_socket_identity_t *left,
const posix_socket_identity_t *right) {
return left && right && left->device == right->device && left->inode == right->inode;
}
static bool posix_socket_status_secure_links(const struct stat *status, nlink_t links) {
return status && S_ISSOCK(status->st_mode) && status->st_uid == geteuid() &&
status->st_nlink == links && (status->st_mode & 0777) == 0600;
}
static bool posix_socket_status_secure_transport(const struct stat *status) {
return status && S_ISSOCK(status->st_mode) && status->st_uid == geteuid() &&
(status->st_nlink == 1 || status->st_nlink == 2) && (status->st_mode & 0777) == 0600;
}
static bool posix_socket_record_encode(const uint8_t magic[POSIX_SOCKET_RECORD_MAGIC_SIZE],
const posix_socket_record_t *source,
uint8_t record[POSIX_SOCKET_RECORD_SIZE]) {
if (!magic || !source || !record || source->identity.ctime_seconds < 0 ||
source->identity.ctime_nanoseconds >= 1000000000U) {
return false;
}
size_t anchor_length = strnlen(source->anchor_name, POSIX_SOCKET_ANCHOR_NAME_CAP);
uint64_t device = (uint64_t)source->identity.device;
uint64_t inode = (uint64_t)source->identity.inode;
if ((dev_t)device != source->identity.device || (ino_t)inode != source->identity.inode ||
anchor_length == 0 || anchor_length >= POSIX_SOCKET_ANCHOR_NAME_CAP ||
strchr(source->anchor_name, '/')) {
return false;
}
memset(record, 0, POSIX_SOCKET_RECORD_SIZE);
memcpy(record, magic, POSIX_SOCKET_RECORD_MAGIC_SIZE);
posix_u64_encode(record + POSIX_SOCKET_IDENTITY_DEVICE_OFFSET, device);
posix_u64_encode(record + POSIX_SOCKET_IDENTITY_INODE_OFFSET, inode);
posix_u64_encode(record + POSIX_SOCKET_IDENTITY_CTIME_SECONDS_OFFSET,
(uint64_t)source->identity.ctime_seconds);
posix_u32_encode(record + POSIX_SOCKET_IDENTITY_CTIME_NANOSECONDS_OFFSET,
source->identity.ctime_nanoseconds);
memcpy(record + POSIX_SOCKET_ANCHOR_NAME_OFFSET, source->anchor_name, anchor_length + 1U);
return true;
}
static bool posix_socket_record_decode(const uint8_t record[POSIX_SOCKET_RECORD_SIZE],
const uint8_t magic[POSIX_SOCKET_RECORD_MAGIC_SIZE],
posix_socket_record_t *record_out) {
if (!record || !magic || !record_out ||
memcmp(record, magic, POSIX_SOCKET_RECORD_MAGIC_SIZE) != 0 ||
posix_u32_decode(record + POSIX_SOCKET_IDENTITY_RESERVED_OFFSET) != 0) {
return false;
}
uint64_t device = posix_u64_decode(record + POSIX_SOCKET_IDENTITY_DEVICE_OFFSET);
uint64_t inode = posix_u64_decode(record + POSIX_SOCKET_IDENTITY_INODE_OFFSET);
uint64_t seconds = posix_u64_decode(record + POSIX_SOCKET_IDENTITY_CTIME_SECONDS_OFFSET);
uint32_t nanoseconds =
posix_u32_decode(record + POSIX_SOCKET_IDENTITY_CTIME_NANOSECONDS_OFFSET);
if ((dev_t)device != device || (ino_t)inode != inode || seconds > (uint64_t)INT64_MAX ||
nanoseconds >= 1000000000U) {
return false;
}
const uint8_t *anchor = record + POSIX_SOCKET_ANCHOR_NAME_OFFSET;
const uint8_t *terminator = memchr(anchor, 0, POSIX_SOCKET_ANCHOR_NAME_CAP);
if (!terminator || terminator == anchor || memchr(anchor, '/', (size_t)(terminator - anchor))) {
return false;
}
size_t anchor_length = (size_t)(terminator - anchor);
for (size_t index = anchor_length + 1U; index < POSIX_SOCKET_ANCHOR_NAME_CAP; index++) {
if (anchor[index] != 0) {
return false;
}
}
memset(record_out, 0, sizeof(*record_out));
record_out->identity.device = (dev_t)device;
record_out->identity.inode = (ino_t)inode;
record_out->identity.ctime_seconds = (int64_t)seconds;
record_out->identity.ctime_nanoseconds = nanoseconds;
memcpy(record_out->anchor_name, anchor, anchor_length + 1U);
return true;
}
static bool posix_fd_write_all(int fd, const uint8_t *buffer, size_t length) {
size_t offset = 0;
while (offset < length) {
ssize_t written = write(fd, buffer + offset, length - offset);
if (written > 0) {
offset += (size_t)written;
} else if (written < 0 && errno == EINTR) {
continue;
} else {
return false;
}
}
return true;
}
/* The failing step of the most recent record publication in this process:
* its errno (0 when a validation predicate, not a syscall, refused) and the
* artifact name it was operating on. Read by the listener right after a
* publish returns false. */
static int posix_publish_failure_errno;
static char posix_publish_failure_name[NAME_MAX + 1];
static void posix_publish_failure_note(int errno_value, const char *name) {
posix_publish_failure_errno = errno_value;
(void)snprintf(posix_publish_failure_name, sizeof(posix_publish_failure_name), "%s",
name ? name : "");
}
static bool posix_record_fd_write(int fd, const uint8_t *buffer, size_t length) {
int injected =
atomic_load_explicit(&g_posix_record_write_failure_errno_for_test, memory_order_acquire);
if (injected != 0) {
errno = injected;
return false;
}
return posix_fd_write_all(fd, buffer, length);
}
static bool posix_fd_pread_all(int fd, uint8_t *buffer, size_t length) {
size_t offset = 0;
while (offset < length) {
ssize_t count = pread(fd, buffer + offset, length - offset, (off_t)offset);
if (count > 0) {
offset += (size_t)count;
} else if (count < 0 && errno == EINTR) {
continue;
} else {
return false;
}
}
return true;
}
typedef enum {
POSIX_RECORD_UNSAFE = -1,
POSIX_RECORD_ABSENT = 0,
POSIX_RECORD_VALID = 1,
POSIX_RECORD_UNKNOWN = 2,
} posix_record_state_t;
static posix_record_state_t posix_socket_record_read_links(
const cbm_daemon_ipc_endpoint_t *endpoint, const char *record_name,
const uint8_t magic[POSIX_SOCKET_RECORD_MAGIC_SIZE], nlink_t expected_links,
posix_socket_record_t *record_out, struct stat *record_status_out) {
if (!endpoint || !record_name || !magic || !record_out || !record_status_out ||
!endpoint->socket_anchor_name || (expected_links != 1 && expected_links != 2) ||
!endpoint_runtime_still_valid(endpoint)) {
return POSIX_RECORD_UNSAFE;
}
int fd = openat(endpoint->dir_fd, record_name, O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK);
if (fd < 0) {
return errno == ENOENT ? POSIX_RECORD_ABSENT : POSIX_RECORD_UNSAFE;
}
struct stat before;
struct stat after;
struct stat by_path;
int64_t before_ctime_seconds = 0;
uint32_t before_ctime_nanoseconds = 0;
int64_t after_ctime_seconds = 0;
uint32_t after_ctime_nanoseconds = 0;
bool metadata_safe =
fd_set_cloexec(fd) && fstat(fd, &before) == 0 && S_ISREG(before.st_mode) &&
before.st_uid == geteuid() && before.st_nlink == expected_links &&
(before.st_mode & 07777) == 0600 && cbm_macos_extended_acl_fd_is_empty(fd) &&
posix_stat_ctime(&before, &before_ctime_seconds, &before_ctime_nanoseconds);
if (!metadata_safe) {
(void)close(fd);
return POSIX_RECORD_UNSAFE;
}
if (before.st_size != (off_t)POSIX_SOCKET_RECORD_SIZE) {
bool still_private = private_regular_file_snapshot(endpoint->dir_fd, record_name, fd,
expected_links, NULL) &&
endpoint_runtime_still_valid(endpoint);
int close_result = close(fd);
return still_private && close_result == 0 ? POSIX_RECORD_UNKNOWN : POSIX_RECORD_UNSAFE;
}
uint8_t record[POSIX_SOCKET_RECORD_SIZE];
bool read_ok = posix_fd_pread_all(fd, record, sizeof(record));
bool stable = read_ok && fstat(fd, &after) == 0 &&
posix_stat_ctime(&after, &after_ctime_seconds, &after_ctime_nanoseconds) &&
fstatat(endpoint->dir_fd, record_name, &by_path, AT_SYMLINK_NOFOLLOW) == 0 &&
S_ISREG(after.st_mode) && after.st_uid == geteuid() &&
after.st_nlink == expected_links && (after.st_mode & 07777) == 0600 &&
cbm_macos_extended_acl_fd_is_empty(fd) && after.st_size == before.st_size &&
after.st_dev == before.st_dev && after.st_ino == before.st_ino &&
after_ctime_seconds == before_ctime_seconds &&
after_ctime_nanoseconds == before_ctime_nanoseconds && S_ISREG(by_path.st_mode) &&
by_path.st_uid == geteuid() && by_path.st_nlink == expected_links &&
(by_path.st_mode & 07777) == 0600 && by_path.st_dev == before.st_dev &&
by_path.st_ino == before.st_ino && endpoint_runtime_still_valid(endpoint);
int close_result = close(fd);
if (!stable || close_result != 0) {
return POSIX_RECORD_UNSAFE;
}
if (!posix_socket_record_decode(record, magic, record_out) ||
strcmp(record_out->anchor_name, endpoint->socket_anchor_name) != 0) {
return POSIX_RECORD_UNKNOWN;
}
*record_status_out = before;
return POSIX_RECORD_VALID;
}
static posix_record_state_t posix_socket_record_read(
const cbm_daemon_ipc_endpoint_t *endpoint, const char *record_name,
const uint8_t magic[POSIX_SOCKET_RECORD_MAGIC_SIZE], posix_socket_record_t *record_out,
struct stat *record_status_out) {
return posix_socket_record_read_links(endpoint, record_name, magic, 1, record_out,
record_status_out);
}
static int posix_socket_path_identity_read(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *socket_name,
posix_socket_identity_t *identity_out,
struct stat *status_out) {
struct stat status;
if (!endpoint || !socket_name || !identity_out) {
return -1;
}
if (fstatat(endpoint->dir_fd, socket_name, &status, AT_SYMLINK_NOFOLLOW) != 0) {
return errno == ENOENT ? 0 : -1;
}
if (!posix_socket_status_secure_transport(&status) ||
!posix_socket_identity_from_stat(&status, identity_out)) {
return -1;
}
if (status_out) {
*status_out = status;
}
return 1;
}
static bool posix_path_unlink_regular_if_matches(int dir_fd, const char *name, dev_t device,
ino_t inode, nlink_t links) {
if (dir_fd < 0 || !name) {
return false;
}
int fd = openat(dir_fd, name, O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK);
struct stat status;
bool matches = fd >= 0 && fd_set_cloexec(fd) &&
private_regular_file_snapshot(dir_fd, name, fd, links, &status) &&
status.st_dev == device && status.st_ino == inode;
if (fd >= 0 && close(fd) != 0) {
matches = false;
}
return matches && unlinkat(dir_fd, name, 0) == 0;
}
static bool posix_socket_path_unlink_inode_if_matches(int dir_fd, const char *socket_name,
const posix_socket_identity_t *identity,
nlink_t links) {
struct stat status;
posix_socket_identity_t observed;
return dir_fd >= 0 && socket_name && identity &&
fstatat(dir_fd, socket_name, &status, AT_SYMLINK_NOFOLLOW) == 0 &&
posix_socket_status_secure_links(&status, links) &&
posix_socket_identity_from_stat(&status, &observed) &&
posix_socket_inode_equal(&observed, identity) && unlinkat(dir_fd, socket_name, 0) == 0;
}
static bool posix_directory_sync(int dir_fd) {
if (dir_fd < 0) {
return false;
}
int result;
do {
result = fsync(dir_fd);
} while (result != 0 && errno == EINTR);
if (result == 0) {
return true;
}
int sync_error = errno;
bool unsupported = sync_error == EINVAL;
#ifdef ENOTSUP
unsupported = unsupported || sync_error == ENOTSUP;
#endif
#ifdef EOPNOTSUPP
unsupported = unsupported || sync_error == EOPNOTSUPP;
#endif
/* Some POSIX filesystems, including Darwin filesystems, do not expose a
* directory-sync operation. Record contents are still fsynced before
* publication, and the recovery state machine accepts either atomic
* namespace outcome after a crash. */
return unsupported;
}
static bool posix_socket_link_pair_unlink_if_matches(const cbm_daemon_ipc_endpoint_t *endpoint,
const posix_socket_identity_t *identity) {
posix_socket_identity_t stable = {0};
posix_socket_identity_t anchor = {0};
struct stat stable_status = {0};
struct stat anchor_status = {0};
return endpoint && identity &&
posix_socket_path_identity_read(endpoint, endpoint->socket_name, &stable,
&stable_status) == 1 &&
posix_socket_path_identity_read(endpoint, endpoint->socket_anchor_name, &anchor,
&anchor_status) == 1 &&
stable_status.st_nlink == 2 && anchor_status.st_nlink == 2 &&
posix_socket_identity_equal(&stable, identity) &&
posix_socket_identity_equal(&anchor, identity) &&
posix_socket_path_unlink_inode_if_matches(endpoint->dir_fd, endpoint->socket_name,
identity, 2) &&
posix_socket_path_unlink_inode_if_matches(endpoint->dir_fd, endpoint->socket_anchor_name,
identity, 1);
}
static bool posix_socket_record_temp_name(const char *record_name, char temp_name[NAME_MAX + 1]) {
if (!record_name || !temp_name) {
return false;
}
int written = snprintf(temp_name, NAME_MAX + 1, "%s.tmp", record_name);
return written > 0 && written <= NAME_MAX;
}
static int posix_record_artifact_status(const cbm_daemon_ipc_endpoint_t *endpoint, const char *name,
struct stat *status_out) {
if (!endpoint || !name || !status_out || !endpoint_runtime_still_valid(endpoint)) {
return -1;
}
struct stat status;
if (fstatat(endpoint->dir_fd, name, &status, AT_SYMLINK_NOFOLLOW) != 0) {
return errno == ENOENT ? 0 : -1;
}
if (!S_ISREG(status.st_mode) || status.st_uid != geteuid() ||
(status.st_mode & 07777) != 0600 || (status.st_nlink != 1 && status.st_nlink != 2)) {
return -1;
}
int fd = openat(endpoint->dir_fd, name, O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK);
struct stat by_handle;
bool safe =
fd >= 0 && fd_set_cloexec(fd) &&
private_regular_file_snapshot(endpoint->dir_fd, name, fd, status.st_nlink, &by_handle) &&
by_handle.st_dev == status.st_dev && by_handle.st_ino == status.st_ino;
if (fd >= 0 && close(fd) != 0) {
safe = false;
}
if (!safe) {
return -1;
}
*status_out = status;
return 1;
}
static int posix_socket_record_identity_corroborated(
const cbm_daemon_ipc_endpoint_t *endpoint, const uint8_t magic[POSIX_SOCKET_RECORD_MAGIC_SIZE],
const posix_socket_record_t *record) {
if (!endpoint || !magic || !record) {
return -1;
}
posix_socket_identity_t stable = {0};
posix_socket_identity_t anchor = {0};
struct stat stable_status = {0};
struct stat anchor_status = {0};
int stable_state =
posix_socket_path_identity_read(endpoint, endpoint->socket_name, &stable, &stable_status);
int anchor_state = posix_socket_path_identity_read(endpoint, endpoint->socket_anchor_name,
&anchor, &anchor_status);
if (stable_state < 0 || anchor_state < 0) {
return -1;
}
if (memcmp(magic, POSIX_SOCKET_PENDING_MAGIC, POSIX_SOCKET_RECORD_MAGIC_SIZE) == 0) {
return stable_state == 0 && anchor_state == 1 && anchor_status.st_nlink == 1 &&
posix_socket_identity_equal(&record->identity, &anchor)
? 1
: 0;
}
if (memcmp(magic, POSIX_SOCKET_MARKER_MAGIC, POSIX_SOCKET_RECORD_MAGIC_SIZE) == 0) {
return stable_state == 1 && anchor_state == 1 && stable_status.st_nlink == 2 &&
anchor_status.st_nlink == 2 &&
posix_socket_identity_equal(&record->identity, &stable) &&
posix_socket_identity_equal(&record->identity, &anchor)
? 1
: 0;
}
return -1;
}
/* Normalize only publication artifacts whose deterministic temp name,
* owner/mode/link shape, encoded record, and socket phase all agree. A
* mismatched artifact is preserved and blocks startup rather than becoming
* deletion authority. */
static int posix_socket_record_publication_recover(
const cbm_daemon_ipc_endpoint_t *endpoint, const char *record_name,
const uint8_t magic[POSIX_SOCKET_RECORD_MAGIC_SIZE]) {
char temp_name[NAME_MAX + 1];
if (!posix_socket_record_temp_name(record_name, temp_name)) {
return -1;
}
struct stat record_status = {0};
struct stat temp_status = {0};
int record_state = posix_record_artifact_status(endpoint, record_name, &record_status);
int temp_state = posix_record_artifact_status(endpoint, temp_name, &temp_status);
if (record_state < 0 || temp_state < 0) {
return -1;
}
if (temp_state == 0) {
return record_state == 0 || record_status.st_nlink == 1 ? 1 : 0;
}
posix_socket_record_t recovered = {0};
struct stat recovered_status = {0};
if (record_state == 0) {
if (temp_status.st_nlink != 1) {
return 0;
}
posix_record_state_t read_state = posix_socket_record_read_links(
endpoint, temp_name, magic, 1, &recovered, &recovered_status);
if (read_state != POSIX_RECORD_VALID) {
return read_state == POSIX_RECORD_UNSAFE ? -1 : 0;
}
} else {
if (record_status.st_nlink != 2 || temp_status.st_nlink != 2 ||
record_status.st_dev != temp_status.st_dev ||
record_status.st_ino != temp_status.st_ino) {
return 0;
}
posix_record_state_t read_state = posix_socket_record_read_links(
endpoint, record_name, magic, 2, &recovered, &recovered_status);
if (read_state != POSIX_RECORD_VALID) {
return read_state == POSIX_RECORD_UNSAFE ? -1 : 0;
}
if (recovered_status.st_dev != temp_status.st_dev ||
recovered_status.st_ino != temp_status.st_ino) {
return 0;
}
}
int corroborated = posix_socket_record_identity_corroborated(endpoint, magic, &recovered);
if (corroborated != 1) {
return corroborated;
}
if (!posix_path_unlink_regular_if_matches(endpoint->dir_fd, temp_name, recovered_status.st_dev,
recovered_status.st_ino, record_state == 1 ? 2 : 1) ||
!posix_directory_sync(endpoint->dir_fd)) {
return -1;
}
return 1;
}
static int posix_linkat_no_follow(int source_dir_fd, const char *source_name,
int destination_dir_fd, const char *destination_name) {
/* linkat without AT_SYMLINK_FOLLOW links the source entry itself, which is
* the required no-follow behavior. Do not pass Darwin's broader
* AT_SYMLINK_NOFOLLOW_ANY constant: macOS 14 headers define it, but that
* kernel rejects it for linkat with EINVAL. The retained and validated
* source state plus post-link inode/link-count checks retain the
* fail-closed publication contract. */
return linkat(source_dir_fd, source_name, destination_dir_fd, destination_name, 0);
}
static void posix_bound_socket_unlink_if_matches(int dir_fd, const char *socket_name, dev_t device,
ino_t inode) {
struct stat status;
if (dir_fd >= 0 && socket_name &&
fstatat(dir_fd, socket_name, &status, AT_SYMLINK_NOFOLLOW) == 0 &&
S_ISSOCK(status.st_mode) && status.st_uid == geteuid() && status.st_dev == device &&
status.st_ino == inode) {
(void)unlinkat(dir_fd, socket_name, 0);
}
}
static bool posix_socket_record_publish(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *record_name,
const uint8_t magic[POSIX_SOCKET_RECORD_MAGIC_SIZE],
const posix_socket_record_t *source, dev_t *device_out,
ino_t *inode_out) {
if (!endpoint || !record_name || !magic || !source || !device_out || !inode_out ||
!endpoint_runtime_still_valid(endpoint)) {
return false;
}
struct stat existing;
errno = 0;
if (fstatat(endpoint->dir_fd, record_name, &existing, AT_SYMLINK_NOFOLLOW) == 0 ||
errno != ENOENT) {
posix_publish_failure_note(errno == 0 ? EEXIST : errno, record_name);
return false;
}
uint8_t record[POSIX_SOCKET_RECORD_SIZE];
if (!posix_socket_record_encode(magic, source, record)) {
posix_publish_failure_note(0, record_name);
return false;
}
char temp_name[NAME_MAX + 1];
if (!posix_socket_record_temp_name(record_name, temp_name)) {
posix_publish_failure_note(ENAMETOOLONG, record_name);
return false;
}
int fd = openat(endpoint->dir_fd, temp_name,
O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK, 0600);
if (fd < 0) {
posix_publish_failure_note(errno, temp_name);
return false;
}
struct stat created = {0};
bool temp_exists = true;
bool stable_linked = false;
errno = 0;
bool ok = fd_set_cloexec(fd) && fchmod(fd, 0600) == 0 &&
private_regular_file_snapshot(endpoint->dir_fd, temp_name, fd, 1, &created) &&
posix_record_fd_write(fd, record, sizeof(record)) && fsync(fd) == 0 &&
private_regular_file_snapshot(endpoint->dir_fd, temp_name, fd, 1, &created) &&
created.st_size == (off_t)POSIX_SOCKET_RECORD_SIZE &&
endpoint_runtime_still_valid(endpoint);
if (!ok) {
posix_publish_failure_note(errno, temp_name);
}
if (ok) {
posix_record_publication_stage_reached(magic, false);
errno = 0;
ok =
posix_linkat_no_follow(endpoint->dir_fd, temp_name, endpoint->dir_fd, record_name) == 0;
stable_linked = ok;
if (!ok) {
posix_publish_failure_note(errno, record_name);
}
}
if (ok) {
posix_record_publication_stage_reached(magic, true);
errno = 0;
ok = posix_path_unlink_regular_if_matches(endpoint->dir_fd, temp_name, created.st_dev,
created.st_ino, 2);
temp_exists = !ok;
if (!ok) {
posix_publish_failure_note(errno, temp_name);
}
}
if (ok) {
errno = 0;
ok = posix_directory_sync(endpoint->dir_fd);
if (!ok) {
posix_publish_failure_note(errno, "");
}
}
errno = 0;
if (close(fd) != 0) {
if (ok) {
posix_publish_failure_note(errno, temp_name);
}
ok = false;
}
posix_socket_record_t published_record;
struct stat published_status;
if (ok) {
ok = posix_socket_record_read(endpoint, record_name, magic, &published_record,
&published_status) == POSIX_RECORD_VALID &&
posix_socket_identity_equal(&published_record.identity, &source->identity) &&
strcmp(published_record.anchor_name, source->anchor_name) == 0 &&
published_status.st_dev == created.st_dev && published_status.st_ino == created.st_ino;
if (!ok) {
posix_publish_failure_note(0, record_name);
}
}
if (!ok) {
if (stable_linked) {
(void)posix_path_unlink_regular_if_matches(
endpoint->dir_fd, record_name, created.st_dev, created.st_ino, temp_exists ? 2 : 1);
}
if (temp_exists) {
(void)posix_path_unlink_regular_if_matches(endpoint->dir_fd, temp_name, created.st_dev,
created.st_ino, 1);
}
(void)posix_directory_sync(endpoint->dir_fd);
return false;
}
*device_out = published_status.st_dev;
*inode_out = published_status.st_ino;
return true;
}
static bool posix_endpoint_namespace_equal(const cbm_daemon_ipc_endpoint_t *left,
const cbm_daemon_ipc_endpoint_t *right) {
return left && right && left->dir_device == right->dir_device &&
left->dir_inode == right->dir_inode && left->socket_name && right->socket_name &&
strcmp(left->socket_name, right->socket_name) == 0 && left->socket_anchor_name &&
right->socket_anchor_name &&
strcmp(left->socket_anchor_name, right->socket_anchor_name) == 0 &&
left->socket_anchor_address && right->socket_anchor_address &&
strcmp(left->socket_anchor_address, right->socket_anchor_address) == 0 &&
left->socket_identity_name && right->socket_identity_name &&
strcmp(left->socket_identity_name, right->socket_identity_name) == 0 &&
left->socket_pending_name && right->socket_pending_name &&
strcmp(left->socket_pending_name, right->socket_pending_name) == 0 && left->lock_name &&
right->lock_name && strcmp(left->lock_name, right->lock_name) == 0 &&
left->startup_v2_lock_name && right->startup_v2_lock_name &&
strcmp(left->startup_v2_lock_name, right->startup_v2_lock_name) == 0 &&
left->lifetime_lock_name && right->lifetime_lock_name &&
strcmp(left->lifetime_lock_name, right->lifetime_lock_name) == 0;
}
static bool posix_named_lock_is_retained(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *lock_name, bool shared, int fd,
const process_lock_entry_t *entry, pid_t owner_pid) {
if (!endpoint_runtime_still_valid(endpoint) || !lock_name || fd < 0 || !entry ||
entry->shared != shared || owner_pid != getpid()) {
return false;
}
if (entry->directory_device != endpoint->dir_device ||
entry->directory_inode != endpoint->dir_inode || !entry->lock_name ||
strcmp(entry->lock_name, lock_name) != 0 ||
process_lock_is_claimed(endpoint, lock_name) != 1) {
return false;
}
if (!private_regular_file_snapshot(endpoint->dir_fd, lock_name, fd, 1, NULL)) {
return false;
}
int lock_result;
do {
lock_result = flock(fd, (shared ? LOCK_SH : LOCK_EX) | LOCK_NB);
} while (lock_result != 0 && errno == EINTR);
return lock_result == 0;
}
static bool posix_startup_lock_matches_endpoint(const cbm_daemon_ipc_endpoint_t *endpoint,
const cbm_daemon_ipc_startup_lock_t *startup_lock) {
return startup_lock && endpoint_runtime_still_valid(startup_lock->endpoint_snapshot) &&
posix_endpoint_namespace_equal(endpoint, startup_lock->endpoint_snapshot) &&
posix_named_lock_is_retained(
endpoint, endpoint->startup_v2_lock_name, false, startup_lock->startup_v2_fd,
startup_lock->startup_v2_process_entry, startup_lock->owner_pid) &&
posix_named_lock_is_retained(endpoint, endpoint->lock_name, true,
startup_lock->legacy_fd, startup_lock->legacy_process_entry,
startup_lock->owner_pid);
}
static int posix_stale_generation_cleanup_locked(const cbm_daemon_ipc_endpoint_t *endpoint) {
/* Holding the startup lock excludes compliant starters. Reserving the
* endpoint lifetime as well closes the check/unlink race against a caller
* that already owns a transferred reservation. */
cbm_daemon_ipc_lifetime_reservation_t *cleanup_reservation = NULL;
int reservation_result =
cbm_daemon_ipc_lifetime_reservation_try_acquire(endpoint, &cleanup_reservation);
if (reservation_result != 1) {
return reservation_result == 0 ? 0 : -1;
}
int result = -1;
int pending_publication = posix_socket_record_publication_recover(
endpoint, endpoint->socket_pending_name, POSIX_SOCKET_PENDING_MAGIC);
int marker_publication = posix_socket_record_publication_recover(
endpoint, endpoint->socket_identity_name, POSIX_SOCKET_MARKER_MAGIC);
if (pending_publication != 1 || marker_publication != 1) {
result = pending_publication < 0 || marker_publication < 0 ? -1 : 0;
cbm_daemon_ipc_lifetime_reservation_release(cleanup_reservation);
return result;
}
posix_socket_record_t marker = {0};
posix_socket_record_t pending = {0};
struct stat marker_status = {0};
struct stat pending_status = {0};
posix_record_state_t marker_state =
posix_socket_record_read(endpoint, endpoint->socket_identity_name,
POSIX_SOCKET_MARKER_MAGIC, &marker, &marker_status);
posix_record_state_t pending_state =
posix_socket_record_read(endpoint, endpoint->socket_pending_name,
POSIX_SOCKET_PENDING_MAGIC, &pending, &pending_status);
posix_socket_identity_t stable_identity = {0};
posix_socket_identity_t anchor_identity = {0};
struct stat stable_status = {0};
struct stat anchor_status = {0};
int stable_state = posix_socket_path_identity_read(endpoint, endpoint->socket_name,
&stable_identity, &stable_status);
int anchor_state = posix_socket_path_identity_read(endpoint, endpoint->socket_anchor_name,
&anchor_identity, &anchor_status);
if (marker_state == POSIX_RECORD_UNSAFE || pending_state == POSIX_RECORD_UNSAFE ||
stable_state < 0 || anchor_state < 0) {
result = -1;
goto cleanup_done;
}
if (marker_state == POSIX_RECORD_UNKNOWN || pending_state == POSIX_RECORD_UNKNOWN ||
(marker_state == POSIX_RECORD_VALID && pending_state == POSIX_RECORD_VALID &&
!posix_socket_inode_equal(&marker.identity, &pending.identity))) {
result = 0;
goto cleanup_done;
}
/* A durable pending record may complete the commit only when both names
* still corroborate its exact anchor inode. It never authorizes deleting
* the stable name by itself. linkat changes ctime, so pending's pre-link
* identity is compared by dev+ino and the final identity is captured now. */
if (marker_state == POSIX_RECORD_ABSENT && pending_state == POSIX_RECORD_VALID &&
stable_state == 1 && anchor_state == 1 && stable_status.st_nlink == 2 &&
anchor_status.st_nlink == 2 &&
posix_socket_inode_equal(&stable_identity, &anchor_identity) &&
posix_socket_inode_equal(&pending.identity, &anchor_identity)) {
posix_socket_record_t completed = {
.identity = anchor_identity,
};
(void)snprintf(completed.anchor_name, sizeof(completed.anchor_name), "%s",
endpoint->socket_anchor_name);
if (!posix_socket_record_publish(endpoint, endpoint->socket_identity_name,
POSIX_SOCKET_MARKER_MAGIC, &completed,
&marker_status.st_dev, &marker_status.st_ino)) {
result = -1;
goto cleanup_done;
}
marker = completed;
marker_state = POSIX_RECORD_VALID;
}
if (marker_state == POSIX_RECORD_VALID) {
bool anchor_owned =
anchor_state == 1 && posix_socket_inode_equal(&marker.identity, &anchor_identity);
if (stable_state == 1 && anchor_owned &&
posix_socket_inode_equal(&stable_identity, &anchor_identity)) {
if (stable_status.st_nlink != 2 || anchor_status.st_nlink != 2 ||
!posix_socket_identity_equal(&marker.identity, &stable_identity) ||
!posix_socket_identity_equal(&marker.identity, &anchor_identity)) {
result = -1;
goto cleanup_done;
}
if (!posix_socket_link_pair_unlink_if_matches(endpoint, &marker.identity)) {
result = -1;
goto cleanup_done;
}
stable_state = 0;
anchor_state = 0;
} else {
/* A legacy/unknown process may have replaced stable. The retained
* anchor proves which inode is ours; preserve every differing
* stable socket and collect only our private publication state. */
if (anchor_state == 1) {
if (!anchor_owned || anchor_status.st_nlink != 1 ||
!posix_socket_path_unlink_inode_if_matches(
endpoint->dir_fd, endpoint->socket_anchor_name, &marker.identity, 1)) {
result = -1;
goto cleanup_done;
}
anchor_state = 0;
}
}
if (!posix_path_unlink_regular_if_matches(endpoint->dir_fd, endpoint->socket_identity_name,
marker_status.st_dev, marker_status.st_ino, 1) ||
(pending_state == POSIX_RECORD_VALID &&
!posix_path_unlink_regular_if_matches(endpoint->dir_fd, endpoint->socket_pending_name,
pending_status.st_dev, pending_status.st_ino,
1)) ||
!posix_directory_sync(endpoint->dir_fd)) {
result = -1;
goto cleanup_done;
}
result = stable_state == 0 ? 1 : 0;
goto cleanup_done;
}
if (pending_state == POSIX_RECORD_VALID) {
bool anchor_owned =
anchor_state == 1 && posix_socket_inode_equal(&pending.identity, &anchor_identity);
if (anchor_state == 1 && (!anchor_owned || anchor_status.st_nlink != 1)) {
result = -1;
goto cleanup_done;
}
if (anchor_owned && !posix_socket_path_unlink_inode_if_matches(endpoint->dir_fd,
endpoint->socket_anchor_name,
&pending.identity, 1)) {
result = -1;
goto cleanup_done;
}
if (!posix_path_unlink_regular_if_matches(endpoint->dir_fd, endpoint->socket_pending_name,
pending_status.st_dev, pending_status.st_ino,
1) ||
!posix_directory_sync(endpoint->dir_fd)) {
result = -1;
goto cleanup_done;
}
result = stable_state == 0 ? 1 : 0;
goto cleanup_done;
}
/* A hard kill can land after the stable link is durable but before either
* publication record exists. With startup serialized and lifetime
* reserved above, the exact owner-private two-name/two-link shape is
* sufficient authority: no unrelated socket can acquire the deterministic
* anchor name without being the same inode. Re-read both names immediately
* before inode-matched unlinking so replacement or link-count races fail
* closed. */
if (stable_state == 1 && anchor_state == 1 && stable_status.st_nlink == 2 &&
anchor_status.st_nlink == 2 &&
posix_socket_identity_equal(&stable_identity, &anchor_identity)) {
if (!posix_socket_link_pair_unlink_if_matches(endpoint, &stable_identity) ||
!posix_directory_sync(endpoint->dir_fd)) {
result = -1;
goto cleanup_done;
}
result = 1;
goto cleanup_done;
}
/* The deterministic generation-local anchor lets us collect the sole
* otherwise-untrackable crash boundary: bind/listen completed but the
* pending record was not yet durable. It never grants authority over a
* differing stable socket, so preserve both names when one is present. */
if (anchor_state == 1 && stable_state == 1) {
result = 0;
goto cleanup_done;
}
if (anchor_state == 1) {
if (anchor_status.st_nlink != 1 ||
!posix_socket_path_unlink_inode_if_matches(
endpoint->dir_fd, endpoint->socket_anchor_name, &anchor_identity, 1) ||
!posix_directory_sync(endpoint->dir_fd)) {
result = -1;
goto cleanup_done;
}
}
result = stable_state == 0 ? 1 : 0;
cleanup_done:
cbm_daemon_ipc_lifetime_reservation_release(cleanup_reservation);
return result;
}
int cbm_daemon_ipc_stale_generation_cleanup(const cbm_daemon_ipc_endpoint_t *endpoint,
const cbm_daemon_ipc_startup_lock_t *startup_lock) {
return posix_startup_lock_matches_endpoint(endpoint, startup_lock)
? posix_stale_generation_cleanup_locked(endpoint)
: -1;
}
static void posix_publication_abort(const cbm_daemon_ipc_endpoint_t *endpoint,
const posix_socket_identity_t *anchor_identity,
bool pending_published, const struct stat *pending_status,
bool marker_published, const struct stat *marker_status) {
if (!endpoint || !anchor_identity) {
return;
}
posix_socket_identity_t stable = {0};
posix_socket_identity_t anchor = {0};
struct stat stable_status = {0};
struct stat anchor_status = {0};
int stable_state =
posix_socket_path_identity_read(endpoint, endpoint->socket_name, &stable, &stable_status);
int anchor_state = posix_socket_path_identity_read(endpoint, endpoint->socket_anchor_name,
&anchor, &anchor_status);
if (stable_state == 1 && anchor_state == 1 && stable_status.st_nlink == 2 &&
anchor_status.st_nlink == 2 && posix_socket_inode_equal(&stable, anchor_identity) &&
posix_socket_inode_equal(&anchor, anchor_identity) &&
posix_socket_path_unlink_inode_if_matches(endpoint->dir_fd, endpoint->socket_name,
anchor_identity, 2)) {
anchor_status.st_nlink = 1;
}
if (anchor_state == 1 && anchor_status.st_nlink == 1 &&
posix_socket_inode_equal(&anchor, anchor_identity)) {
(void)posix_socket_path_unlink_inode_if_matches(
endpoint->dir_fd, endpoint->socket_anchor_name, anchor_identity, 1);
}
if (marker_published && marker_status) {
(void)posix_path_unlink_regular_if_matches(endpoint->dir_fd, endpoint->socket_identity_name,
marker_status->st_dev, marker_status->st_ino, 1);
}
if (pending_published && pending_status) {
(void)posix_path_unlink_regular_if_matches(endpoint->dir_fd, endpoint->socket_pending_name,
pending_status->st_dev, pending_status->st_ino,
1);
}
(void)posix_directory_sync(endpoint->dir_fd);
}
cbm_daemon_ipc_listener_t *cbm_daemon_ipc_listen_reserved(
const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_daemon_ipc_lifetime_reservation_t **reservation_io) {
cbm_daemon_ipc_lifetime_reservation_t *lifetime_reservation =
reservation_io ? *reservation_io : NULL;
ipc_listen_failure_reset();
posix_publish_failure_note(0, NULL);
const char *runtime_dir = endpoint ? endpoint->runtime_dir : NULL;
if (!lifetime_reservation_matches_endpoint(endpoint, lifetime_reservation)) {
ipc_listen_failed(runtime_dir, "reservation_validation", 0, NULL);
return NULL;
}
/* Stale removal happens only under the startup lock, before a daemon host
* transfers this lifetime reservation. The listener path itself never
* connects to, classifies, or removes pre-existing artifacts. */
char identity_temp_name[NAME_MAX + 1];
char pending_temp_name[NAME_MAX + 1];
if (!posix_socket_record_temp_name(endpoint->socket_identity_name, identity_temp_name) ||
!posix_socket_record_temp_name(endpoint->socket_pending_name, pending_temp_name)) {
ipc_listen_failed(runtime_dir, "temp_names", ENAMETOOLONG, endpoint->socket_pending_name);
return NULL;
}
struct stat existing;
const char *required_absent[] = {
endpoint->socket_name, endpoint->socket_anchor_name, endpoint->socket_identity_name,
endpoint->socket_pending_name, identity_temp_name, pending_temp_name,
};
bool namespace_absent = true;
const char *occupied_name = "";
int occupied_errno = 0;
for (size_t index = 0; index < sizeof(required_absent) / sizeof(required_absent[0]); index++) {
errno = 0;
if (fstatat(endpoint->dir_fd, required_absent[index], &existing, AT_SYMLINK_NOFOLLOW) ==
0 ||
errno != ENOENT) {
namespace_absent = false;
occupied_name = required_absent[index];
occupied_errno = errno == 0 ? EEXIST : errno;
break;
}
}
if (!endpoint_runtime_still_valid(endpoint) || !namespace_absent) {
ipc_listen_failed(runtime_dir, "namespace_validation", occupied_errno, occupied_name);
return NULL;
}
int fd = local_socket_new();
if (fd < 0) {
ipc_listen_failed(runtime_dir, "socket_creation", errno, NULL);
return NULL;
}
cbm_daemon_ipc_listener_t *listener = calloc(1, sizeof(*listener));
if (!listener) {
ipc_listen_failed(runtime_dir, "listener_allocation", ENOMEM, NULL);
(void)close(fd);
return NULL;
}
listener->fd = fd;
listener->dir_fd = dup(endpoint->dir_fd);
listener->runtime_dir = string_copy(endpoint->runtime_dir);
listener->dir_device = endpoint->dir_device;
listener->dir_inode = endpoint->dir_inode;
listener->address = string_copy(endpoint->address);
listener->socket_name = string_copy(endpoint->socket_name);
listener->socket_anchor_name = string_copy(endpoint->socket_anchor_name);
listener->socket_identity_name = string_copy(endpoint->socket_identity_name);
listener->socket_pending_name = string_copy(endpoint->socket_pending_name);
listener->owner_pid = getpid();
if (listener->dir_fd < 0 || !fd_set_cloexec(listener->dir_fd) || !listener->runtime_dir ||
!listener->address || !listener->socket_name || !listener->socket_anchor_name ||
!listener->socket_identity_name || !listener->socket_pending_name) {
ipc_listen_failed(runtime_dir, "listener_initialization",
listener->dir_fd < 0 ? errno : ENOMEM, NULL);
if (listener->dir_fd >= 0) {
(void)close(listener->dir_fd);
}
(void)close(fd);
free(listener->runtime_dir);
free(listener->address);
free(listener->socket_name);
free(listener->socket_anchor_name);
free(listener->socket_identity_name);
free(listener->socket_pending_name);
free(listener);
return NULL;
}
struct sockaddr_un address;
socklen_t address_length;
if (!unix_address_set(&address, endpoint->socket_anchor_address, &address_length)) {
ipc_listen_failed(runtime_dir, "socket_address", ENAMETOOLONG,
endpoint->socket_anchor_name);
cbm_daemon_ipc_listener_close(listener);
return NULL;
}
if (bind(fd, (const struct sockaddr *)&address, address_length) != 0) {
ipc_listen_failed(runtime_dir, "socket_bind", errno, endpoint->socket_anchor_name);
cbm_daemon_ipc_listener_close(listener);
return NULL;
}
struct stat bound_status;
errno = 0;
bool bound_path_ok = fstatat(endpoint->dir_fd, endpoint->socket_anchor_name, &bound_status,
AT_SYMLINK_NOFOLLOW) == 0 &&
S_ISSOCK(bound_status.st_mode) && bound_status.st_uid == geteuid();
struct stat anchor_status = {0};
bool secured =
bound_path_ok && fchmodat(endpoint->dir_fd, endpoint->socket_anchor_name, 0600, 0) == 0 &&
fstatat(endpoint->dir_fd, endpoint->socket_anchor_name, &anchor_status,
AT_SYMLINK_NOFOLLOW) == 0 &&
posix_socket_status_secure_links(&anchor_status, 1) &&
anchor_status.st_dev == bound_status.st_dev && anchor_status.st_ino == bound_status.st_ino;
posix_socket_identity_t anchor_identity = {0};
if (!secured || listen(fd, 32) != 0 ||
fstatat(endpoint->dir_fd, endpoint->socket_anchor_name, &anchor_status,
AT_SYMLINK_NOFOLLOW) != 0 ||
!posix_socket_status_secure_links(&anchor_status, 1) ||
anchor_status.st_dev != bound_status.st_dev ||
anchor_status.st_ino != bound_status.st_ino ||
!posix_socket_identity_from_stat(&anchor_status, &anchor_identity) ||
!posix_directory_sync(endpoint->dir_fd)) {
int security_errno = errno;
if (bound_path_ok) {
posix_bound_socket_unlink_if_matches(endpoint->dir_fd, endpoint->socket_anchor_name,
bound_status.st_dev, bound_status.st_ino);
}
ipc_listen_failed(runtime_dir, "socket_security", security_errno,
endpoint->socket_anchor_name);
cbm_daemon_ipc_listener_close(listener);
return NULL;
}
listener->socket_identity = anchor_identity;
posix_publication_stage_reached(CBM_DAEMON_IPC_POSIX_PUBLICATION_ANCHOR_DURABLE);
posix_socket_record_t pending = {
.identity = anchor_identity,
};
(void)snprintf(pending.anchor_name, sizeof(pending.anchor_name), "%s",
endpoint->socket_anchor_name);
struct stat pending_status = {0};
bool pending_published = posix_socket_record_publish(
endpoint, endpoint->socket_pending_name, POSIX_SOCKET_PENDING_MAGIC, &pending,
&pending_status.st_dev, &pending_status.st_ino);
if (!pending_published) {
ipc_listen_failed(runtime_dir, "pending_publication", posix_publish_failure_errno,
posix_publish_failure_name);
posix_publication_abort(endpoint, &anchor_identity, false, NULL, false, NULL);
cbm_daemon_ipc_listener_close(listener);
return NULL;
}
posix_publication_stage_reached(CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_DURABLE);
errno = 0;
bool stable_linked = posix_linkat_no_follow(endpoint->dir_fd, endpoint->socket_anchor_name,
endpoint->dir_fd, endpoint->socket_name) == 0 &&
posix_directory_sync(endpoint->dir_fd);
int stable_errno = stable_linked ? 0 : errno;
posix_socket_identity_t stable_identity = {0};
posix_socket_identity_t committed_identity = {0};
struct stat stable_status = {0};
struct stat committed_anchor_status = {0};
bool stable_valid =
stable_linked &&
posix_socket_path_identity_read(endpoint, endpoint->socket_name, &stable_identity,
&stable_status) == 1 &&
posix_socket_path_identity_read(endpoint, endpoint->socket_anchor_name, &committed_identity,
&committed_anchor_status) == 1 &&
stable_status.st_nlink == 2 && committed_anchor_status.st_nlink == 2 &&
posix_socket_identity_equal(&stable_identity, &committed_identity) &&
posix_socket_inode_equal(&anchor_identity, &committed_identity);
if (!stable_valid) {
ipc_listen_failed(runtime_dir, "stable_publication", stable_errno, endpoint->socket_name);
posix_publication_abort(endpoint, &anchor_identity, pending_published, &pending_status,
false, NULL);
cbm_daemon_ipc_listener_close(listener);
return NULL;
}
listener->socket_identity = committed_identity;
posix_publication_stage_reached(CBM_DAEMON_IPC_POSIX_PUBLICATION_STABLE_DURABLE);
posix_socket_record_t marker = {
.identity = committed_identity,
};
(void)snprintf(marker.anchor_name, sizeof(marker.anchor_name), "%s",
endpoint->socket_anchor_name);
struct stat marker_status = {0};
bool marker_published = posix_socket_record_publish(
endpoint, endpoint->socket_identity_name, POSIX_SOCKET_MARKER_MAGIC, &marker,
&marker_status.st_dev, &marker_status.st_ino);
if (!marker_published) {
ipc_listen_failed(runtime_dir, "marker_publication", posix_publish_failure_errno,
posix_publish_failure_name);
posix_publication_abort(endpoint, &committed_identity, pending_published, &pending_status,
false, NULL);
cbm_daemon_ipc_listener_close(listener);
return NULL;
}
listener->identity_device = marker_status.st_dev;
listener->identity_inode = marker_status.st_ino;
posix_publication_stage_reached(CBM_DAEMON_IPC_POSIX_PUBLICATION_MARKER_DURABLE);
errno = 0;
if (!posix_path_unlink_regular_if_matches(endpoint->dir_fd, endpoint->socket_pending_name,
pending_status.st_dev, pending_status.st_ino, 1) ||
!posix_directory_sync(endpoint->dir_fd)) {
ipc_listen_failed(runtime_dir, "pending_removal", errno, endpoint->socket_pending_name);
posix_publication_abort(endpoint, &committed_identity, true, &pending_status,
marker_published, &marker_status);
cbm_daemon_ipc_listener_close(listener);
return NULL;
}
posix_publication_stage_reached(CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_REMOVED);
listener->lifetime_reservation = lifetime_reservation;
*reservation_io = NULL;
return listener;
}
cbm_daemon_ipc_listener_t *cbm_daemon_ipc_listen(const cbm_daemon_ipc_endpoint_t *endpoint) {
cbm_daemon_ipc_startup_lock_t *startup_lock = NULL;
if (cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup_lock) != 1) {
return NULL;
}
cbm_daemon_ipc_lifetime_reservation_t *reservation = NULL;
cbm_daemon_ipc_participant_guard_t *participant_guard = NULL;
if (cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup_lock) != 1 ||
cbm_daemon_ipc_participant_guard_try_join(endpoint, &participant_guard) != 1 ||
cbm_daemon_ipc_lifetime_reservation_try_acquire(endpoint, &reservation) != 1) {
ipc_participant_guard_release_complete(&participant_guard);
ipc_startup_lock_release_complete(&startup_lock);
return NULL;
}
cbm_daemon_ipc_listener_t *listener = cbm_daemon_ipc_listen_reserved(endpoint, &reservation);
if (listener) {
listener->participant_guard = participant_guard;
participant_guard = NULL;
}
cbm_daemon_ipc_lifetime_reservation_release(reservation);
ipc_participant_guard_release_complete(&participant_guard);
ipc_startup_lock_release_complete(&startup_lock);
return listener;
}
static void posix_listener_artifacts_remove_if_matches(cbm_daemon_ipc_listener_t *listener) {
if (!listener || listener->dir_fd < 0 || !listener->socket_name ||
!listener->socket_anchor_name || !listener->socket_identity_name ||
!listener->socket_pending_name || listener->owner_pid != getpid()) {
return;
}
cbm_daemon_ipc_endpoint_t view = {
.runtime_dir = listener->runtime_dir,
.dir_device = listener->dir_device,
.dir_inode = listener->dir_inode,
.socket_name = listener->socket_name,
.socket_anchor_name = listener->socket_anchor_name,
.socket_identity_name = listener->socket_identity_name,
.socket_pending_name = listener->socket_pending_name,
.dir_fd = listener->dir_fd,
};
posix_socket_record_t marker = {0};
posix_socket_record_t pending = {0};
struct stat marker_status = {0};
struct stat pending_status = {0};
posix_record_state_t marker_state = posix_socket_record_read(
&view, listener->socket_identity_name, POSIX_SOCKET_MARKER_MAGIC, &marker, &marker_status);
posix_record_state_t pending_state =
posix_socket_record_read(&view, listener->socket_pending_name, POSIX_SOCKET_PENDING_MAGIC,
&pending, &pending_status);
bool marker_matches = marker_state == POSIX_RECORD_VALID &&
marker_status.st_dev == listener->identity_device &&
marker_status.st_ino == listener->identity_inode &&
posix_socket_identity_equal(&marker.identity, &listener->socket_identity);
bool pending_matches =
pending_state == POSIX_RECORD_ABSENT ||
(pending_state == POSIX_RECORD_VALID &&
posix_socket_inode_equal(&pending.identity, &listener->socket_identity));
if (!marker_matches || !pending_matches) {
return;
}
posix_socket_identity_t stable = {0};
posix_socket_identity_t anchor = {0};
struct stat stable_status = {0};
struct stat anchor_status = {0};
int stable_state =
posix_socket_path_identity_read(&view, listener->socket_name, &stable, &stable_status);
int anchor_state = posix_socket_path_identity_read(&view, listener->socket_anchor_name, &anchor,
&anchor_status);
if (stable_state < 0 || anchor_state < 0) {
return;
}
bool anchor_owned =
anchor_state == 1 && posix_socket_inode_equal(&anchor, &listener->socket_identity);
if (stable_state == 1 && anchor_owned && posix_socket_inode_equal(&stable, &anchor)) {
if (stable_status.st_nlink != 2 || anchor_status.st_nlink != 2 ||
!posix_socket_identity_equal(&stable, &listener->socket_identity) ||
!posix_socket_identity_equal(&anchor, &listener->socket_identity) ||
!posix_socket_path_unlink_inode_if_matches(listener->dir_fd, listener->socket_name,
&listener->socket_identity, 2) ||
!posix_socket_path_unlink_inode_if_matches(
listener->dir_fd, listener->socket_anchor_name, &listener->socket_identity, 1)) {
return;
}
} else if (anchor_state == 1) {
if (!anchor_owned || anchor_status.st_nlink != 1 ||
!posix_socket_path_unlink_inode_if_matches(
listener->dir_fd, listener->socket_anchor_name, &listener->socket_identity, 1)) {
return;
}
}
if (!posix_path_unlink_regular_if_matches(listener->dir_fd, listener->socket_identity_name,
listener->identity_device, listener->identity_inode,
1)) {
return;
}
if (pending_state == POSIX_RECORD_VALID &&
!posix_path_unlink_regular_if_matches(listener->dir_fd, listener->socket_pending_name,
pending_status.st_dev, pending_status.st_ino, 1)) {
return;
}
(void)posix_directory_sync(listener->dir_fd);
}
void cbm_daemon_ipc_listener_close(cbm_daemon_ipc_listener_t *listener) {
if (!listener) {
return;
}
if (listener->fd >= 0) {
(void)close(listener->fd);
listener->fd = -1;
}
if (listener->dir_fd >= 0) {
posix_listener_artifacts_remove_if_matches(listener);
(void)close(listener->dir_fd);
}
cbm_daemon_ipc_lifetime_reservation_release(listener->lifetime_reservation);
ipc_participant_guard_release_complete(&listener->participant_guard);
free(listener->runtime_dir);
free(listener->address);
free(listener->socket_name);
free(listener->socket_anchor_name);
free(listener->socket_identity_name);
free(listener->socket_pending_name);
free(listener);
}
int cbm_daemon_ipc_accept(cbm_daemon_ipc_listener_t *listener, uint32_t timeout_ms,
cbm_daemon_ipc_connection_t **connection_out) {
if (connection_out) {
*connection_out = NULL;
}
if (!listener || listener->fd < 0 || listener->owner_pid != getpid() || !connection_out) {
return -1;
}
uint64_t deadline_ms = ipc_deadline_after(timeout_ms);
for (;;) {
int ready = poll_until(listener->fd, POLLIN, deadline_ms);
if (ready != 1) {
return ready;
}
int fd = accept(listener->fd, NULL, NULL);
if (fd < 0) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
}
return -1;
}
if (!fd_set_cloexec(fd) || !fd_set_nonblocking(fd) || !socket_disable_sigpipe(fd) ||
!socket_peer_is_current_user(fd)) {
(void)close(fd);
return -1;
}
cbm_daemon_ipc_connection_t *connection = malloc(sizeof(*connection));
if (!connection) {
(void)close(fd);
return -1;
}
connection->fd = fd;
atomic_init(&connection->poisoned, false);
*connection_out = connection;
return 1;
}
}
int cbm_daemon_ipc_endpoint_probe(const cbm_daemon_ipc_endpoint_t *endpoint, uint32_t timeout_ms) {
if (!endpoint_runtime_still_valid(endpoint)) {
return -1;
}
struct stat status;
if (fstatat(endpoint->dir_fd, endpoint->socket_name, &status, AT_SYMLINK_NOFOLLOW) != 0) {
return errno == ENOENT ? 0 : -1;
}
if (!posix_socket_status_secure_transport(&status)) {
return -1;
}
int fd = local_socket_new();
if (fd < 0) {
return -1;
}
struct sockaddr_un address;
socklen_t address_length;
if (!unix_address_set(&address, endpoint->address, &address_length)) {
(void)close(fd);
return -1;
}
int connected = local_socket_connect(fd, &address, address_length);
int connect_error = connected == 0 ? 0 : errno;
if (connected == 0) {
bool authenticated = socket_peer_is_current_user(fd);
(void)close(fd);
return authenticated ? 1 : -1;
}
if (connect_error == ENOENT) {
(void)close(fd);
return 0;
}
if (connect_error == ECONNREFUSED) {
/* BSD-derived kernels may report ECONNREFUSED when the listen queue
* is full. A validated owner-only socket path therefore fails closed
* as active; treating it as stale could permit an in-place update of
* a running daemon. */
(void)close(fd);
return 1;
}
if (connect_error == EAGAIN || connect_error == EWOULDBLOCK) {
/* A secure listening socket whose accept queue is full is active. */
(void)close(fd);
return 1;
}
if (connect_error != EINPROGRESS) {
(void)close(fd);
return -1;
}
int ready = poll_until(fd, POLLOUT, ipc_deadline_after(timeout_ms));
int socket_error = 0;
socklen_t error_length = sizeof(socket_error);
if (ready == 0) {
/* Still pending against a validated local socket: conservatively
* active, most commonly a saturated accept queue. */
(void)close(fd);
return 1;
}
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &socket_error, &error_length) != 0) {
(void)close(fd);
return -1;
}
if (socket_error == 0) {
bool authenticated = socket_peer_is_current_user(fd);
(void)close(fd);
return authenticated ? 1 : -1;
}
(void)close(fd);
if (socket_error == ENOENT) {
return 0;
}
if (socket_error == ECONNREFUSED) {
return 1;
}
if (socket_error == EAGAIN || socket_error == EWOULDBLOCK || socket_error == EINPROGRESS) {
return 1;
}
return -1;
}
cbm_daemon_ipc_connection_t *cbm_daemon_ipc_connect(const cbm_daemon_ipc_endpoint_t *endpoint,
uint32_t timeout_ms) {
if (!endpoint_runtime_still_valid(endpoint)) {
return NULL;
}
uint64_t deadline_ms = ipc_deadline_after(timeout_ms);
for (;;) {
struct stat status;
if (fstatat(endpoint->dir_fd, endpoint->socket_name, &status, AT_SYMLINK_NOFOLLOW) != 0) {
int path_error = errno;
if (path_error == ENOENT && ipc_retry_pause(deadline_ms)) {
continue;
}
return NULL;
}
if (!posix_socket_status_secure_transport(&status)) {
return NULL;
}
int fd = local_socket_new();
if (fd < 0) {
return NULL;
}
struct sockaddr_un address;
socklen_t address_length;
if (!unix_address_set(&address, endpoint->address, &address_length)) {
(void)close(fd);
return NULL;
}
int result = local_socket_connect(fd, &address, address_length);
int connect_error = result == 0 ? 0 : errno;
if (result != 0 && connect_error != EINPROGRESS && connect_error != EAGAIN &&
connect_error != EWOULDBLOCK) {
(void)close(fd);
if ((connect_error == ENOENT || connect_error == ECONNREFUSED) &&
ipc_retry_pause(deadline_ms)) {
continue;
}
return NULL;
}
if (result != 0) {
int ready = poll_until(fd, POLLOUT, deadline_ms);
int socket_error = 0;
socklen_t error_length = sizeof(socket_error);
if (ready != 1 ||
getsockopt(fd, SOL_SOCKET, SO_ERROR, &socket_error, &error_length) != 0) {
(void)close(fd);
return NULL;
}
if (socket_error != 0) {
(void)close(fd);
if ((socket_error == ENOENT || socket_error == ECONNREFUSED) &&
ipc_retry_pause(deadline_ms)) {
continue;
}
return NULL;
}
}
if (!socket_peer_is_current_user(fd)) {
(void)close(fd);
return NULL;
}
cbm_daemon_ipc_connection_t *connection = malloc(sizeof(*connection));
if (!connection) {
(void)close(fd);
return NULL;
}
connection->fd = fd;
atomic_init(&connection->poisoned, false);
return connection;
}
}
void cbm_daemon_ipc_connection_close(cbm_daemon_ipc_connection_t *connection) {
if (!connection) {
return;
}
if (connection->fd >= 0) {
(void)close(connection->fd);
}
free(connection);
}
void cbm_daemon_ipc_connection_drain(cbm_daemon_ipc_connection_t *connection, uint32_t timeout_ms) {
/* POSIX stream sockets deliver buffered data after close; the final
* response cannot be lost to an immediate close, so no wait is needed. */
(void)connection;
(void)timeout_ms;
}
void cbm_daemon_ipc_connection_interrupt(cbm_daemon_ipc_connection_t *connection) {
if (connection && connection->fd >= 0) {
(void)shutdown(connection->fd, SHUT_RDWR);
}
}
uint64_t cbm_daemon_ipc_connection_peer_pid(const cbm_daemon_ipc_connection_t *connection) {
if (!connection || connection->fd < 0 || !socket_peer_is_current_user(connection->fd)) {
return 0;
}
#if defined(__linux__)
struct ucred credentials;
socklen_t length = sizeof(credentials);
if (getsockopt(connection->fd, SOL_SOCKET, SO_PEERCRED, &credentials, &length) != 0 ||
length != sizeof(credentials) || credentials.uid != geteuid() || credentials.pid <= 0) {
return 0;
}
return (uint64_t)credentials.pid;
#elif defined(SOL_LOCAL) && defined(LOCAL_PEERPID)
pid_t peer_pid = 0;
socklen_t length = sizeof(peer_pid);
if (getsockopt(connection->fd, SOL_LOCAL, LOCAL_PEERPID, &peer_pid, &length) != 0 ||
length != sizeof(peer_pid) || peer_pid <= 0) {
return 0;
}
return (uint64_t)peer_pid;
#elif defined(SOL_LOCAL) && defined(LOCAL_PEEREID)
struct unpcbid peer_id;
socklen_t length = sizeof(peer_id);
if (getsockopt(connection->fd, SOL_LOCAL, LOCAL_PEEREID, &peer_id, &length) != 0 ||
length != sizeof(peer_id) || peer_id.unp_pid <= 0) {
return 0;
}
return (uint64_t)peer_id.unp_pid;
#else
return 0;
#endif
}
int cbm_daemon_ipc_startup_lock_try_acquire(const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_daemon_ipc_startup_lock_t **lock_out) {
if (lock_out) {
*lock_out = NULL;
}
if (!lock_out || !endpoint) {
return -1;
}
int startup_v2_fd = -1;
process_lock_entry_t *startup_v2_process_entry = NULL;
int startup_v2_result = posix_named_lock_try_acquire(endpoint, endpoint->startup_v2_lock_name,
&startup_v2_fd, &startup_v2_process_entry);
if (startup_v2_result != 1) {
return startup_v2_result;
}
int legacy_fd = -1;
process_lock_entry_t *legacy_process_entry = NULL;
int legacy_result = posix_named_shared_lock_try_acquire(endpoint, endpoint->lock_name,
&legacy_fd, &legacy_process_entry);
if (legacy_result != 1) {
posix_named_lock_release(startup_v2_fd, startup_v2_process_entry);
return legacy_result;
}
cbm_daemon_ipc_startup_lock_t *lock = calloc(1, sizeof(*lock));
if (lock) {
lock->endpoint_snapshot = endpoint_snapshot_new(endpoint);
}
if (!lock || !lock->endpoint_snapshot) {
if (lock) {
cbm_daemon_ipc_endpoint_free(lock->endpoint_snapshot);
}
free(lock);
posix_named_lock_release(legacy_fd, legacy_process_entry);
posix_named_lock_release(startup_v2_fd, startup_v2_process_entry);
return -1;
}
lock->startup_v2_fd = startup_v2_fd;
lock->startup_v2_process_entry = startup_v2_process_entry;
lock->legacy_fd = legacy_fd;
lock->legacy_process_entry = legacy_process_entry;
lock->owner_pid = getpid();
*lock_out = lock;
return 1;
}
int cbm_daemon_ipc_generation_probe_under_startup_lock(
const cbm_daemon_ipc_endpoint_t *endpoint, const cbm_daemon_ipc_startup_lock_t *startup_lock) {
if (!posix_startup_lock_matches_endpoint(endpoint, startup_lock) || startup_lock->prepared) {
return -1;
}
int cleanup = cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup_lock);
if (cleanup == 1) {
return 0;
}
return cleanup == 0 ? 1 : -1;
}
bool cbm_daemon_ipc_startup_lock_prepare_handoff(cbm_daemon_ipc_startup_lock_t *lock) {
if (!lock || !lock->endpoint_snapshot) {
return false;
}
if (lock->prepared) {
return true;
}
lock->prepared = cbm_daemon_ipc_stale_generation_cleanup(lock->endpoint_snapshot, lock) == 1;
return lock->prepared;
}
bool cbm_daemon_ipc_startup_lock_release(cbm_daemon_ipc_startup_lock_t **lock_io) {
if (!lock_io) {
return false;
}
cbm_daemon_ipc_startup_lock_t *lock = *lock_io;
if (!lock) {
return true;
}
if (lock->owner_pid != getpid()) {
return false;
}
posix_named_lock_release(lock->legacy_fd, lock->legacy_process_entry);
posix_named_lock_release(lock->startup_v2_fd, lock->startup_v2_process_entry);
cbm_daemon_ipc_endpoint_free(lock->endpoint_snapshot);
free(lock);
*lock_io = NULL;
return true;
}
int cbm_daemon_ipc_participant_guard_try_join(const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_daemon_ipc_participant_guard_t **guard_out) {
if (guard_out) {
*guard_out = NULL;
}
if (!endpoint || !guard_out) {
return -1;
}
int legacy_fd = -1;
process_lock_entry_t *legacy_process_entry = NULL;
int result = posix_named_shared_lock_try_acquire(endpoint, endpoint->lock_name, &legacy_fd,
&legacy_process_entry);
if (result != 1) {
return result;
}
cbm_daemon_ipc_participant_guard_t *guard = calloc(1, sizeof(*guard));
if (!guard) {
posix_named_lock_release(legacy_fd, legacy_process_entry);
return -1;
}
guard->legacy_fd = legacy_fd;
guard->legacy_process_entry = legacy_process_entry;
guard->owner_pid = getpid();
*guard_out = guard;
return 1;
}
bool cbm_daemon_ipc_participant_guard_release(cbm_daemon_ipc_participant_guard_t **guard_io) {
if (!guard_io) {
return false;
}
cbm_daemon_ipc_participant_guard_t *guard = *guard_io;
if (!guard) {
return true;
}
if (guard->legacy_fd < 0 || !guard->legacy_process_entry || guard->owner_pid != getpid()) {
return false;
}
posix_named_lock_release(guard->legacy_fd, guard->legacy_process_entry);
free(guard);
*guard_io = NULL;
return true;
}
int cbm_daemon_ipc_local_transition_try_acquire(
const cbm_daemon_ipc_endpoint_t *endpoint, cbm_daemon_ipc_local_transition_t **transition_out) {
if (transition_out) {
*transition_out = NULL;
}
if (!endpoint || !transition_out) {
return -1;
}
int startup_v2_fd = -1;
process_lock_entry_t *startup_v2_process_entry = NULL;
int startup_v2_result = posix_named_lock_try_acquire(endpoint, endpoint->startup_v2_lock_name,
&startup_v2_fd, &startup_v2_process_entry);
if (startup_v2_result != 1) {
return startup_v2_result;
}
int legacy_fd = -1;
process_lock_entry_t *legacy_process_entry = NULL;
int legacy_result = posix_named_shared_lock_try_acquire(endpoint, endpoint->lock_name,
&legacy_fd, &legacy_process_entry);
if (legacy_result != 1) {
posix_named_lock_release(startup_v2_fd, startup_v2_process_entry);
return legacy_result;
}
cbm_daemon_ipc_local_transition_t *transition = calloc(1, sizeof(*transition));
if (!transition) {
posix_named_lock_release(legacy_fd, legacy_process_entry);
posix_named_lock_release(startup_v2_fd, startup_v2_process_entry);
return -1;
}
transition->startup_v2_fd = startup_v2_fd;
transition->startup_v2_process_entry = startup_v2_process_entry;
transition->legacy_fd = legacy_fd;
transition->legacy_process_entry = legacy_process_entry;
transition->endpoint = endpoint;
transition->owner_pid = getpid();
*transition_out = transition;
return 1;
}
int cbm_daemon_ipc_local_transition_seal_legacy(cbm_daemon_ipc_local_transition_t *transition) {
if (!transition || !transition->endpoint ||
!posix_named_lock_is_retained(
transition->endpoint, transition->endpoint->startup_v2_lock_name, false,
transition->startup_v2_fd, transition->startup_v2_process_entry,
transition->owner_pid) ||
!posix_named_lock_is_retained(transition->endpoint, transition->endpoint->lock_name, true,
transition->legacy_fd, transition->legacy_process_entry,
transition->owner_pid) ||
transition->work_begun) {
return -1;
}
if (transition->sealed) {
return 1;
}
int lifetime = cbm_daemon_ipc_lifetime_reservation_probe(transition->endpoint);
int result = lifetime == 1 ? 1
: lifetime == 0 ? posix_stale_generation_cleanup_locked(transition->endpoint)
: -1;
if (result == 1) {
transition->sealed = true;
}
return result;
}
int cbm_daemon_ipc_local_transition_lifetime_probe(
const cbm_daemon_ipc_endpoint_t *endpoint,
const cbm_daemon_ipc_local_transition_t *transition) {
if (!transition || !transition->sealed || !transition->endpoint || transition->work_begun ||
!posix_endpoint_namespace_equal(endpoint, transition->endpoint) ||
!posix_named_lock_is_retained(
endpoint, endpoint->startup_v2_lock_name, false, transition->startup_v2_fd,
transition->startup_v2_process_entry, transition->owner_pid) ||
!posix_named_lock_is_retained(endpoint, endpoint->lock_name, true, transition->legacy_fd,
transition->legacy_process_entry, transition->owner_pid)) {
return -1;
}
return cbm_daemon_ipc_lifetime_reservation_probe(endpoint);
}
bool cbm_daemon_ipc_local_transition_begin_work(cbm_daemon_ipc_local_transition_t *transition) {
if (!transition || !transition->sealed || transition->work_begun || !transition->endpoint ||
!posix_named_lock_is_retained(
transition->endpoint, transition->endpoint->startup_v2_lock_name, false,
transition->startup_v2_fd, transition->startup_v2_process_entry,
transition->owner_pid) ||
!posix_named_lock_is_retained(transition->endpoint, transition->endpoint->lock_name, true,
transition->legacy_fd, transition->legacy_process_entry,
transition->owner_pid)) {
return false;
}
posix_named_lock_release(transition->startup_v2_fd, transition->startup_v2_process_entry);
transition->startup_v2_fd = -1;
transition->startup_v2_process_entry = NULL;
transition->work_begun = true;
return true;
}
bool cbm_daemon_ipc_local_transition_release(cbm_daemon_ipc_local_transition_t **transition_io) {
if (!transition_io) {
return false;
}
cbm_daemon_ipc_local_transition_t *transition = *transition_io;
if (!transition) {
return true;
}
if (transition->legacy_fd < 0 || !transition->legacy_process_entry ||
transition->owner_pid != getpid() ||
(!transition->work_begun &&
(transition->startup_v2_fd < 0 || !transition->startup_v2_process_entry))) {
return false;
}
posix_named_lock_release(transition->legacy_fd, transition->legacy_process_entry);
if (!transition->work_begun) {
posix_named_lock_release(transition->startup_v2_fd, transition->startup_v2_process_entry);
}
free(transition);
*transition_io = NULL;
return true;
}
#else /* _WIN32 */
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <aclapi.h>
#include <sddl.h>
#include <ntsecapi.h>
#include <fcntl.h>
#include <io.h>
#include <shlobj.h>
#include <wchar.h>
#ifndef PIPE_REJECT_REMOTE_CLIENTS
#define PIPE_REJECT_REMOTE_CLIENTS 0x00000008
#endif
#ifndef PROCESS_QUERY_LIMITED_INFORMATION
#define PROCESS_QUERY_LIMITED_INFORMATION 0x1000
#endif
typedef BOOL(WINAPI *open_process_token_fn)(HANDLE, DWORD, PHANDLE);
typedef BOOL(WINAPI *open_thread_token_fn)(HANDLE, DWORD, BOOL, PHANDLE);
typedef BOOL(WINAPI *get_token_information_fn)(HANDLE, TOKEN_INFORMATION_CLASS, LPVOID, DWORD,
PDWORD);
typedef DWORD(WINAPI *get_length_sid_fn)(PSID);
typedef BOOL(WINAPI *copy_sid_fn)(DWORD, PSID, PSID);
typedef BOOL(WINAPI *equal_sid_fn)(PSID, PSID);
typedef BOOL(WINAPI *is_valid_sid_fn)(PSID);
typedef BOOL(WINAPI *is_well_known_sid_fn)(PSID, WELL_KNOWN_SID_TYPE);
typedef BOOL(WINAPI *is_valid_acl_fn)(PACL);
typedef BOOL(WINAPI *initialize_acl_fn)(PACL, DWORD, DWORD);
typedef BOOL(WINAPI *add_access_allowed_ace_fn)(PACL, DWORD, DWORD, PSID);
typedef BOOL(WINAPI *add_access_allowed_ace_ex_fn)(PACL, DWORD, DWORD, DWORD, PSID);
typedef BOOL(WINAPI *initialize_security_descriptor_fn)(PSECURITY_DESCRIPTOR, DWORD);
typedef BOOL(WINAPI *set_security_descriptor_dacl_fn)(PSECURITY_DESCRIPTOR, BOOL, PACL, BOOL);
typedef BOOL(WINAPI *set_security_descriptor_owner_fn)(PSECURITY_DESCRIPTOR, PSID, BOOL);
typedef BOOL(WINAPI *get_acl_information_fn)(PACL, LPVOID, DWORD, ACL_INFORMATION_CLASS);
typedef BOOL(WINAPI *get_security_descriptor_control_fn)(PSECURITY_DESCRIPTOR,
PSECURITY_DESCRIPTOR_CONTROL, LPDWORD);
typedef BOOL(WINAPI *get_ace_fn)(PACL, DWORD, LPVOID *);
typedef DWORD(WINAPI *get_security_info_fn)(HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, PSID *,
PSID *, PACL *, PACL *, PSECURITY_DESCRIPTOR *);
typedef DWORD(WINAPI *set_security_info_fn)(HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, PSID,
PSID, PACL, PACL);
typedef BOOL(WINAPI *impersonate_named_pipe_client_fn)(HANDLE);
typedef BOOL(WINAPI *revert_to_self_fn)(void);
typedef BOOL(WINAPI *get_named_pipe_client_process_id_fn)(HANDLE, PULONG);
typedef BOOL(WINAPI *get_named_pipe_server_process_id_fn)(HANDLE, PULONG);
typedef struct {
HMODULE advapi;
open_process_token_fn open_process_token;
open_thread_token_fn open_thread_token;
get_token_information_fn get_token_information;
get_length_sid_fn get_length_sid;
copy_sid_fn copy_sid;
equal_sid_fn equal_sid;
is_valid_sid_fn is_valid_sid;
is_well_known_sid_fn is_well_known_sid;
is_valid_acl_fn is_valid_acl;
initialize_acl_fn initialize_acl;
add_access_allowed_ace_fn add_access_allowed_ace;
add_access_allowed_ace_ex_fn add_access_allowed_ace_ex;
initialize_security_descriptor_fn initialize_security_descriptor;
set_security_descriptor_dacl_fn set_security_descriptor_dacl;
set_security_descriptor_owner_fn set_security_descriptor_owner;
get_acl_information_fn get_acl_information;
get_security_descriptor_control_fn get_security_descriptor_control;
get_ace_fn get_ace;
get_security_info_fn get_security_info;
set_security_info_fn set_security_info;
impersonate_named_pipe_client_fn impersonate_named_pipe_client;
revert_to_self_fn revert_to_self;
PSID user_sid;
PACL acl;
PSECURITY_DESCRIPTOR descriptor;
SECURITY_ATTRIBUTES attributes;
PACL directory_acl;
PSECURITY_DESCRIPTOR directory_descriptor;
SECURITY_ATTRIBUTES directory_attributes;
} win_security_t;
typedef struct win_generation_address {
char address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP];
wchar_t *pipe_name;
struct win_generation_address *next;
} win_generation_address_t;
typedef struct win_legacy_mutex_guard win_legacy_mutex_guard_t;
struct cbm_daemon_ipc_lifetime_reservation {
const struct cbm_daemon_ipc_endpoint *endpoint;
cbm_private_lock_directory_t *directory;
cbm_private_file_lock_t *lock;
};
struct cbm_daemon_ipc_endpoint {
char *runtime_dir;
char instance_key[17];
uint8_t *user_sid;
size_t user_sid_length;
wchar_t *legacy_pipe_name;
wchar_t *legacy_startup_mutex_name;
cbm_mutex_t generations_lock;
_Atomic(win_generation_address_t *) current_generation;
win_generation_address_t *generations;
};
struct cbm_daemon_ipc_listener {
wchar_t *pipe_name;
HANDLE pipe;
bool first_instance;
/* The pending overlapped ConnectNamedPipe persists across accept-poll
* timeouts. Destroying a listening instance on every poll timeout races
* clients attaching in the teardown window: they end up severed or,
* worse, attached to an orphaned pipe object no server will ever read,
* silently absorbing their HELLO until their own timeout expires. */
HANDLE connect_event;
OVERLAPPED connect_overlapped;
bool connect_pending;
cbm_daemon_ipc_lifetime_reservation_t *lifetime_reservation;
cbm_daemon_ipc_participant_guard_t *participant_guard;
};
typedef enum {
CBM_DAEMON_IPC_PIPE_ROLE_ACCEPTED_SERVER = 1,
CBM_DAEMON_IPC_PIPE_ROLE_CONNECTED_CLIENT = 2,
} cbm_daemon_ipc_pipe_role_t;
struct cbm_daemon_ipc_connection {
HANDLE handle;
atomic_bool poisoned;
cbm_daemon_ipc_pipe_role_t role;
};
struct cbm_daemon_ipc_startup_lock {
const cbm_daemon_ipc_endpoint_t *endpoint;
cbm_private_lock_directory_t *directory;
cbm_private_file_lock_t *startup_v2_lock;
win_legacy_mutex_guard_t *legacy_guard;
cbm_private_file_lock_t *group_lock;
HANDLE legacy_sentinel;
bool prepared;
};
struct cbm_daemon_ipc_local_transition {
const cbm_daemon_ipc_endpoint_t *endpoint;
cbm_private_lock_directory_t *directory;
cbm_private_file_lock_t *startup_v2_lock;
cbm_private_file_lock_t *group_lock;
HANDLE legacy_sentinel;
win_legacy_mutex_guard_t *teardown_legacy_guard;
bool sealed;
bool work_begun;
};
struct cbm_daemon_ipc_participant_guard {
const cbm_daemon_ipc_endpoint_t *endpoint;
cbm_private_lock_directory_t *directory;
cbm_private_file_lock_t *group_lock;
HANDLE legacy_sentinel;
cbm_private_file_lock_t *teardown_startup_v2_lock;
win_legacy_mutex_guard_t *teardown_legacy_guard;
};
static uint64_t ipc_now_ms(void) {
return (uint64_t)GetTickCount64();
}
static uint64_t ipc_deadline_after(uint32_t timeout_ms) {
if (timeout_ms == CBM_DAEMON_IPC_WAIT_FOREVER) {
return UINT64_MAX;
}
return ipc_now_ms() + (uint64_t)timeout_ms;
}
static _Noreturn void ipc_coordination_cleanup_fail_stop(const char *component) {
cbm_log_error("daemon.forced_shutdown", "component", component, "action",
"coordination_cleanup");
(void)fflush(stdout);
(void)fflush(stderr);
(void)TerminateProcess(GetCurrentProcess(), EXIT_FAILURE);
abort();
}
static void ipc_startup_lock_release_complete(cbm_daemon_ipc_startup_lock_t **lock_io) {
uint64_t deadline = ipc_deadline_after(CBM_DAEMON_IPC_COORDINATION_CLEANUP_MS);
while (lock_io && *lock_io) {
(void)cbm_daemon_ipc_startup_lock_release(lock_io);
if (!*lock_io) {
return;
}
if (ipc_now_ms() >= deadline) {
ipc_coordination_cleanup_fail_stop("startup_lock_cleanup");
}
Sleep(1);
}
}
static void ipc_participant_guard_release_complete(cbm_daemon_ipc_participant_guard_t **guard_io) {
uint64_t deadline = ipc_deadline_after(CBM_DAEMON_IPC_COORDINATION_CLEANUP_MS);
while (guard_io && *guard_io) {
(void)cbm_daemon_ipc_participant_guard_release(guard_io);
if (!*guard_io) {
return;
}
if (ipc_now_ms() >= deadline) {
ipc_coordination_cleanup_fail_stop("participant_guard_cleanup");
}
Sleep(1);
}
}
static DWORD win_deadline_remaining(uint64_t deadline_ms) {
if (deadline_ms == UINT64_MAX) {
return INFINITE;
}
uint64_t now_ms = ipc_now_ms();
if (now_ms >= deadline_ms) {
return 0;
}
uint64_t remaining = deadline_ms - now_ms;
return remaining > (uint64_t)(INFINITE - 1) ? INFINITE - 1 : (DWORD)remaining;
}
static bool win_retry_pause(uint64_t deadline_ms) {
DWORD remaining_ms = win_deadline_remaining(deadline_ms);
if (remaining_ms == 0) {
return false;
}
DWORD pause_ms = remaining_ms < CBM_DAEMON_IPC_RETRY_INTERVAL_MS
? remaining_ms
: CBM_DAEMON_IPC_RETRY_INTERVAL_MS;
Sleep(pause_ms);
return true;
}
static wchar_t *utf8_to_wide(const char *value) {
if (!value) {
return NULL;
}
int needed = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value, -1, NULL, 0);
if (needed <= 0) {
return NULL;
}
wchar_t *wide = malloc((size_t)needed * sizeof(*wide));
if (!wide || MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, value, -1, wide, needed) <= 0) {
free(wide);
return NULL;
}
return wide;
}
static char *wide_to_utf8(const wchar_t *value) {
if (!value) {
return NULL;
}
int needed = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value, -1, NULL, 0, NULL, NULL);
if (needed <= 0) {
return NULL;
}
char *utf8 = malloc((size_t)needed);
if (!utf8 || WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, value, -1, utf8, needed, NULL,
NULL) <= 0) {
free(utf8);
return NULL;
}
return utf8;
}
/* Record "<path>: <message>" as the validation detail, with the wide path
* rendered as UTF-8. One place owns the conversion buffer, so every refusal on
* the directory walk can name its component without each call site carrying
* its own allocation. */
static void ipc_validation_detail_set_for_path(const wchar_t *path, const char *message) {
char *path_utf8 = wide_to_utf8(path);
ipc_validation_detail_set("%s: %s", path_utf8 ? path_utf8 : "<path>", message);
free(path_utf8);
}
static wchar_t *wide_copy(const wchar_t *value) {
if (!value) {
return NULL;
}
size_t length = wcslen(value);
wchar_t *copy = malloc((length + 1) * sizeof(*copy));
if (copy) {
memcpy(copy, value, (length + 1) * sizeof(*copy));
}
return copy;
}
static void win_security_destroy(win_security_t *security) {
if (!security) {
return;
}
free(security->descriptor);
free(security->acl);
free(security->directory_descriptor);
free(security->directory_acl);
free(security->user_sid);
if (security->advapi) {
(void)FreeLibrary(security->advapi);
}
memset(security, 0, sizeof(*security));
}
static void *win_token_user_query(win_security_t *security, HANDLE token, PSID *sid_out) {
DWORD needed = 0;
(void)security->get_token_information(token, TokenUser, NULL, 0, &needed);
if (needed == 0 || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
return NULL;
}
/* The dynamically-resolved GetTokenInformation call initializes this
* buffer on success. Zero-initialize it as defense in depth and so
* static analysis does not have to infer writes through a function
* pointer before TOKEN_USER is inspected. */
void *buffer = calloc(1, needed);
if (!buffer || !security->get_token_information(token, TokenUser, buffer, needed, &needed)) {
free(buffer);
return NULL;
}
*sid_out = ((TOKEN_USER *)buffer)->User.Sid;
return buffer;
}
#define RESOLVE_ADVAPI_MEMBER(context, member, type, symbol) \
do { \
(context)->member = (type)(void (*)(void))GetProcAddress((context)->advapi, (symbol)); \
if (!(context)->member) { \
win_security_destroy((context)); \
return false; \
} \
} while (0)
static bool win_security_init(win_security_t *security) {
memset(security, 0, sizeof(*security));
security->advapi = LoadLibraryW(L"advapi32.dll");
if (!security->advapi) {
return false;
}
RESOLVE_ADVAPI_MEMBER(security, open_process_token, open_process_token_fn, "OpenProcessToken");
RESOLVE_ADVAPI_MEMBER(security, open_thread_token, open_thread_token_fn, "OpenThreadToken");
RESOLVE_ADVAPI_MEMBER(security, get_token_information, get_token_information_fn,
"GetTokenInformation");
RESOLVE_ADVAPI_MEMBER(security, get_length_sid, get_length_sid_fn, "GetLengthSid");
RESOLVE_ADVAPI_MEMBER(security, copy_sid, copy_sid_fn, "CopySid");
RESOLVE_ADVAPI_MEMBER(security, equal_sid, equal_sid_fn, "EqualSid");
RESOLVE_ADVAPI_MEMBER(security, is_valid_sid, is_valid_sid_fn, "IsValidSid");
RESOLVE_ADVAPI_MEMBER(security, is_well_known_sid, is_well_known_sid_fn, "IsWellKnownSid");
RESOLVE_ADVAPI_MEMBER(security, is_valid_acl, is_valid_acl_fn, "IsValidAcl");
RESOLVE_ADVAPI_MEMBER(security, initialize_acl, initialize_acl_fn, "InitializeAcl");
RESOLVE_ADVAPI_MEMBER(security, add_access_allowed_ace, add_access_allowed_ace_fn,
"AddAccessAllowedAce");
RESOLVE_ADVAPI_MEMBER(security, add_access_allowed_ace_ex, add_access_allowed_ace_ex_fn,
"AddAccessAllowedAceEx");
RESOLVE_ADVAPI_MEMBER(security, initialize_security_descriptor,
initialize_security_descriptor_fn, "InitializeSecurityDescriptor");
RESOLVE_ADVAPI_MEMBER(security, set_security_descriptor_dacl, set_security_descriptor_dacl_fn,
"SetSecurityDescriptorDacl");
RESOLVE_ADVAPI_MEMBER(security, set_security_descriptor_owner, set_security_descriptor_owner_fn,
"SetSecurityDescriptorOwner");
RESOLVE_ADVAPI_MEMBER(security, get_acl_information, get_acl_information_fn,
"GetAclInformation");
RESOLVE_ADVAPI_MEMBER(security, get_security_descriptor_control,
get_security_descriptor_control_fn, "GetSecurityDescriptorControl");
RESOLVE_ADVAPI_MEMBER(security, get_ace, get_ace_fn, "GetAce");
RESOLVE_ADVAPI_MEMBER(security, get_security_info, get_security_info_fn, "GetSecurityInfo");
RESOLVE_ADVAPI_MEMBER(security, set_security_info, set_security_info_fn, "SetSecurityInfo");
RESOLVE_ADVAPI_MEMBER(security, impersonate_named_pipe_client, impersonate_named_pipe_client_fn,
"ImpersonateNamedPipeClient");
RESOLVE_ADVAPI_MEMBER(security, revert_to_self, revert_to_self_fn, "RevertToSelf");
HANDLE token = NULL;
if (!security->open_process_token(GetCurrentProcess(), TOKEN_QUERY, &token)) {
win_security_destroy(security);
return false;
}
PSID token_sid = NULL;
void *token_user = win_token_user_query(security, token, &token_sid);
(void)CloseHandle(token);
if (!token_user || !token_sid) {
free(token_user);
win_security_destroy(security);
return false;
}
DWORD sid_length = security->get_length_sid(token_sid);
security->user_sid = malloc(sid_length);
if (sid_length == 0 || !security->user_sid ||
!security->copy_sid(sid_length, security->user_sid, token_sid)) {
free(token_user);
win_security_destroy(security);
return false;
}
free(token_user);
size_t acl_size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + sid_length;
if (acl_size > MAXDWORD) {
win_security_destroy(security);
return false;
}
security->acl = malloc(acl_size);
security->descriptor = malloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
if (!security->acl || !security->descriptor ||
!security->initialize_acl(security->acl, (DWORD)acl_size, ACL_REVISION) ||
!security->add_access_allowed_ace(security->acl, ACL_REVISION, GENERIC_ALL,
security->user_sid) ||
!security->initialize_security_descriptor(security->descriptor,
SECURITY_DESCRIPTOR_REVISION) ||
!security->set_security_descriptor_dacl(security->descriptor, TRUE, security->acl, FALSE) ||
/* The owner must be stamped explicitly at creation: members of the
* Administrators group can carry a default-owner policy of BUILTIN\
* Administrators (standard on Windows Server, including CI runners),
* and every private-namespace validation demands the exact token-user
* SID as owner. Relying on the token default makes the daemon reject
* objects it created itself. */
!security->set_security_descriptor_owner(security->descriptor, security->user_sid, FALSE)) {
win_security_destroy(security);
return false;
}
security->attributes.nLength = sizeof(security->attributes);
security->attributes.lpSecurityDescriptor = security->descriptor;
security->attributes.bInheritHandle = FALSE;
/* Containers need a second, inheritable ACL. AddAccessAllowedAce above
* cannot express inheritance flags, and a flagless ACE applied to a
* directory together with PROTECTED_DACL_SECURITY_INFORMATION yields
* D:PAI(A;;FA;;;<user>): the protection severs the inherited ACEs while
* the new one propagates nothing, so every child is created with an empty
* DACL. Files and kernel objects are leaves and keep the flagless ACL.
*
* The rights here must be specific (FILE_ALL_ACCESS) rather than
* GENERIC_ALL. Windows splits an inheritable generic-rights ACE into an
* effective mapped ACE plus an INHERIT_ONLY one carrying the generic bits,
* and the owner-only DACL validators require exactly one ACE. */
security->directory_acl = malloc(acl_size);
security->directory_descriptor = malloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
if (!security->directory_acl || !security->directory_descriptor ||
!security->initialize_acl(security->directory_acl, (DWORD)acl_size, ACL_REVISION) ||
!security->add_access_allowed_ace_ex(security->directory_acl, ACL_REVISION,
CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE,
FILE_ALL_ACCESS, security->user_sid) ||
!security->initialize_security_descriptor(security->directory_descriptor,
SECURITY_DESCRIPTOR_REVISION) ||
!security->set_security_descriptor_dacl(security->directory_descriptor, TRUE,
security->directory_acl, FALSE) ||
!security->set_security_descriptor_owner(security->directory_descriptor, security->user_sid,
FALSE)) {
win_security_destroy(security);
return false;
}
security->directory_attributes.nLength = sizeof(security->directory_attributes);
security->directory_attributes.lpSecurityDescriptor = security->directory_descriptor;
security->directory_attributes.bInheritHandle = FALSE;
return true;
}
#undef RESOLVE_ADVAPI_MEMBER
static bool win_kernel_mutex_current_user_only(win_security_t *security, HANDLE mutex) {
if (!security || !mutex || mutex == INVALID_HANDLE_VALUE) {
return false;
}
PSID owner = NULL;
PACL dacl = NULL;
PSECURITY_DESCRIPTOR descriptor = NULL;
DWORD status = security->get_security_info(
mutex, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, &owner,
NULL, &dacl, NULL, &descriptor);
ACL_SIZE_INFORMATION information;
memset(&information, 0, sizeof(information));
void *raw_ace = NULL;
bool valid = status == ERROR_SUCCESS && owner &&
security->equal_sid(owner, security->user_sid) && dacl &&
security->get_acl_information(dacl, &information, sizeof(information),
AclSizeInformation) &&
information.AceCount == 1 && security->get_ace(dacl, 0, &raw_ace) && raw_ace;
if (valid) {
const ACCESS_ALLOWED_ACE *ace = raw_ace;
const uint8_t *ace_sid = (const uint8_t *)&ace->SidStart;
size_t sid_offset = offsetof(ACCESS_ALLOWED_ACE, SidStart);
size_t sid_capacity =
ace->Header.AceSize >= sid_offset ? ace->Header.AceSize - sid_offset : 0;
size_t sid_length = sid_capacity >= 8U ? 8U + (size_t)ace_sid[1] * 4U : 0;
DWORD required = SYNCHRONIZE | MUTEX_MODIFY_STATE | READ_CONTROL;
bool access_ok = (ace->Mask & GENERIC_ALL) != 0 || (ace->Mask & required) == required;
valid = ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE && ace->Header.AceFlags == 0 &&
access_ok && sid_length <= sid_capacity && windows_sid_valid(ace_sid, sid_length) &&
security->equal_sid((PSID)ace_sid, security->user_sid);
}
if (descriptor) {
(void)LocalFree(descriptor);
}
return valid;
}
struct win_legacy_mutex_guard {
HANDLE mutex;
HANDLE ready_event;
HANDLE stop_event;
cbm_thread_t owner_thread;
bool owner_started;
atomic_bool release_ok;
atomic_int acquire_result;
};
static void *win_legacy_mutex_owner(void *opaque) {
win_legacy_mutex_guard_t *guard = opaque;
DWORD wait_result = WaitForSingleObject(guard->mutex, 0);
int acquire_result;
if (wait_result == WAIT_OBJECT_0 || wait_result == WAIT_ABANDONED) {
acquire_result = 1;
} else {
acquire_result = wait_result == WAIT_TIMEOUT ? 0 : -1;
}
atomic_store_explicit(&guard->acquire_result, acquire_result, memory_order_release);
bool ready = SetEvent(guard->ready_event) != 0;
bool release_ok = acquire_result != 1;
if (acquire_result == 1) {
DWORD stopped = WaitForSingleObject(guard->stop_event, INFINITE);
release_ok = stopped == WAIT_OBJECT_0 && ReleaseMutex(guard->mutex) != 0;
}
if (!ready) {
release_ok = false;
}
atomic_store_explicit(&guard->release_ok, release_ok, memory_order_release);
return NULL;
}
static bool win_legacy_mutex_guard_release(win_legacy_mutex_guard_t **guard_io) {
if (!guard_io || !*guard_io) {
return true;
}
win_legacy_mutex_guard_t *guard = *guard_io;
unsigned int failures = atomic_load_explicit(&g_windows_legacy_guard_release_failures_for_test,
memory_order_acquire);
while (failures > 0) {
if (atomic_compare_exchange_weak_explicit(&g_windows_legacy_guard_release_failures_for_test,
&failures, failures - 1U, memory_order_acq_rel,
memory_order_acquire)) {
return false;
}
}
bool stopped = !guard->owner_started || SetEvent(guard->stop_event) != 0;
bool joined = !guard->owner_started || cbm_thread_join(&guard->owner_thread) == 0;
bool released =
stopped && joined && atomic_load_explicit(&guard->release_ok, memory_order_acquire);
if (!joined) {
return false;
}
if (guard->ready_event) {
(void)CloseHandle(guard->ready_event);
}
if (guard->stop_event) {
(void)CloseHandle(guard->stop_event);
}
if (guard->mutex) {
(void)CloseHandle(guard->mutex);
}
free(guard);
*guard_io = NULL;
return released;
}
static void win_legacy_mutex_guard_release_complete(win_legacy_mutex_guard_t **guard_io) {
uint64_t deadline = ipc_deadline_after(CBM_DAEMON_IPC_COORDINATION_CLEANUP_MS);
while (guard_io && *guard_io) {
(void)win_legacy_mutex_guard_release(guard_io);
if (!*guard_io) {
return;
}
if (ipc_now_ms() >= deadline) {
ipc_coordination_cleanup_fail_stop("legacy_mutex_cleanup");
}
Sleep(1);
}
}
static int win_legacy_mutex_guard_try_acquire(const cbm_daemon_ipc_endpoint_t *endpoint,
win_legacy_mutex_guard_t **guard_out) {
if (guard_out) {
*guard_out = NULL;
}
if (!endpoint || !endpoint->legacy_startup_mutex_name || !guard_out) {
return -1;
}
win_security_t security;
if (!win_security_init(&security)) {
return -1;
}
SetLastError(ERROR_SUCCESS);
HANDLE mutex = CreateMutexW(&security.attributes, FALSE, endpoint->legacy_startup_mutex_name);
DWORD create_error = mutex ? GetLastError() : ERROR_GEN_FAILURE;
bool secured = mutex && mutex != INVALID_HANDLE_VALUE;
if (secured && create_error != ERROR_ALREADY_EXISTS) {
secured = security.set_security_info(mutex, SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
security.user_sid, NULL, security.acl,
NULL) == ERROR_SUCCESS;
}
secured = secured && SetHandleInformation(mutex, HANDLE_FLAG_INHERIT, 0) != 0 &&
win_kernel_mutex_current_user_only(&security, mutex);
win_security_destroy(&security);
if (!secured) {
if (mutex && mutex != INVALID_HANDLE_VALUE) {
(void)CloseHandle(mutex);
}
return -1;
}
win_legacy_mutex_guard_t *guard = calloc(1, sizeof(*guard));
if (guard) {
guard->mutex = mutex;
guard->ready_event = CreateEventW(NULL, TRUE, FALSE, NULL);
guard->stop_event = CreateEventW(NULL, TRUE, FALSE, NULL);
atomic_init(&guard->release_ok, false);
atomic_init(&guard->acquire_result, -1);
}
if (!guard || !guard->ready_event || !guard->stop_event ||
cbm_thread_create(&guard->owner_thread, 0, win_legacy_mutex_owner, guard) != 0) {
if (guard) {
win_legacy_mutex_guard_release_complete(&guard);
} else {
(void)CloseHandle(mutex);
}
return -1;
}
guard->owner_started = true;
DWORD ready = WaitForSingleObject(guard->ready_event, 5000);
int result = ready == WAIT_OBJECT_0
? atomic_load_explicit(&guard->acquire_result, memory_order_acquire)
: -1;
if (result != 1) {
win_legacy_mutex_guard_release_complete(&guard);
return result;
}
*guard_out = guard;
return 1;
}
typedef enum {
WIN_RENDEZVOUS_ERROR = -1,
WIN_RENDEZVOUS_ABSENT = 0,
WIN_RENDEZVOUS_VALID = 1,
WIN_RENDEZVOUS_BUSY = 2,
WIN_RENDEZVOUS_CORRUPT = 3,
} win_rendezvous_status_t;
static win_rendezvous_status_t win_endpoint_refresh_rendezvous(
const cbm_daemon_ipc_endpoint_t *endpoint);
static win_generation_address_t *win_endpoint_generation_snapshot(
const cbm_daemon_ipc_endpoint_t *endpoint);
static int win_legacy_pipe_probe(const cbm_daemon_ipc_endpoint_t *endpoint);
static uint32_t win_sid_read_u32_le(const uint8_t *bytes) {
return (uint32_t)bytes[0] | ((uint32_t)bytes[1] << 8U) | ((uint32_t)bytes[2] << 16U) |
((uint32_t)bytes[3] << 24U);
}
static bool win_sid_is_trusted_installer(const uint8_t *sid, size_t sid_length) {
static const uint32_t subauthorities[] = {
80U, 956008885U, 3418522649U, 1831038044U, 1853292631U, 2271478464U,
};
if (!windows_sid_valid(sid, sid_length) || sid[1] != 6U || sid[2] != 0U || sid[3] != 0U ||
sid[4] != 0U || sid[5] != 0U || sid[6] != 0U || sid[7] != 5U) {
return false;
}
for (size_t index = 0; index < sizeof(subauthorities) / sizeof(subauthorities[0]); index++) {
if (win_sid_read_u32_le(sid + 8U + index * 4U) != subauthorities[index]) {
return false;
}
}
return true;
}
/* #1705: the built-in Administrator ACCOUNT of THIS machine — RID 500 under the
* local machine's own account-domain SID (S-1-5-21-<machine>-500) — is a
* legitimate owner/grantee of directories an elevated install created, even when
* that account has been renamed or is disabled. It is resolved by asking LSA for
* the local machine account-domain SID and synthesizing its RID-500 SID with
* CreateWellKnownSid, then compared with EqualSid.
*
* It is deliberately NOT tested with IsWellKnownSid(sid, WinAccountAdministratorSid)
* and NOT by matching a trailing RID of 500: BOTH of those accept ANY domain's
* -500 — a domain administrator, or another machine's built-in Administrator —
* which is exactly the cross-machine trust escalation this must never open. Only
* THIS machine's -500 is trusted.
*
* Resolved once per process and cached; any LSA or synthesis failure leaves the
* cache NULL and therefore grants NO tolerance at all (fail closed). advapi32 is
* reached through the already-loaded module handle in win_security_t and the
* function pointers are resolved dynamically, matching this file's SID-API style
* and adding no static import. */
typedef NTSTATUS(NTAPI *lsa_open_policy_fn)(PLSA_UNICODE_STRING, PLSA_OBJECT_ATTRIBUTES,
ACCESS_MASK, PLSA_HANDLE);
typedef NTSTATUS(NTAPI *lsa_query_information_policy_fn)(LSA_HANDLE, POLICY_INFORMATION_CLASS,
PVOID *);
typedef NTSTATUS(NTAPI *lsa_free_memory_fn)(PVOID);
typedef NTSTATUS(NTAPI *lsa_close_fn)(LSA_HANDLE);
typedef BOOL(WINAPI *create_well_known_sid_fn)(WELL_KNOWN_SID_TYPE, PSID, PSID, DWORD *);
static INIT_ONCE g_local_admin_sid_once = INIT_ONCE_STATIC_INIT;
static PSID g_local_admin_sid = NULL; /* process-lifetime cache; NULL => no tolerance */
static BOOL CALLBACK win_resolve_local_admin_sid(PINIT_ONCE once, PVOID parameter, PVOID *context) {
(void)once;
(void)context;
win_security_t *security = (win_security_t *)parameter;
if (!security || !security->advapi) {
return TRUE; /* ran once; cache stays NULL (fail closed) */
}
HMODULE advapi = security->advapi;
lsa_open_policy_fn lsa_open =
(lsa_open_policy_fn)(void (*)(void))GetProcAddress(advapi, "LsaOpenPolicy");
lsa_query_information_policy_fn lsa_query = (lsa_query_information_policy_fn)(void (*)(
void))GetProcAddress(advapi, "LsaQueryInformationPolicy");
lsa_free_memory_fn lsa_free =
(lsa_free_memory_fn)(void (*)(void))GetProcAddress(advapi, "LsaFreeMemory");
lsa_close_fn lsa_close = (lsa_close_fn)(void (*)(void))GetProcAddress(advapi, "LsaClose");
create_well_known_sid_fn create_sid =
(create_well_known_sid_fn)(void (*)(void))GetProcAddress(advapi, "CreateWellKnownSid");
if (!lsa_open || !lsa_query || !lsa_free || !lsa_close || !create_sid) {
return TRUE;
}
LSA_OBJECT_ATTRIBUTES attributes;
memset(&attributes, 0, sizeof(attributes));
LSA_HANDLE policy = NULL;
/* STATUS_SUCCESS is 0; any other status (including informational positives) is
* treated as failure, keeping the outcome fail-closed. */
if (lsa_open(NULL, &attributes, POLICY_VIEW_LOCAL_INFORMATION, &policy) != 0 || !policy) {
return TRUE;
}
POLICY_ACCOUNT_DOMAIN_INFO *domain = NULL;
if (lsa_query(policy, PolicyAccountDomainInformation, (PVOID *)&domain) == 0 && domain &&
domain->DomainSid && security->is_valid_sid(domain->DomainSid)) {
DWORD needed = 0;
(void)create_sid(WinAccountAdministratorSid, domain->DomainSid, NULL, &needed);
if (needed > 0U) {
PSID resolved = malloc(needed);
if (resolved &&
create_sid(WinAccountAdministratorSid, domain->DomainSid, resolved, &needed) &&
security->is_valid_sid(resolved)) {
g_local_admin_sid = resolved;
} else {
free(resolved);
}
}
}
if (domain) {
(void)lsa_free(domain);
}
(void)lsa_close(policy);
return TRUE;
}
static PSID win_local_admin_sid(win_security_t *security) {
if (!security || !security->advapi) {
return NULL;
}
(void)InitOnceExecuteOnce(&g_local_admin_sid_once, win_resolve_local_admin_sid, (PVOID)security,
NULL);
return g_local_admin_sid;
}
static bool win_sid_trusted(win_security_t *security, PSID sid) {
if (!security || !sid || !security->is_valid_sid(sid)) {
return false;
}
DWORD sid_length = security->get_length_sid(sid);
PSID local_admin = win_local_admin_sid(security);
return (sid_length > 0U && security->equal_sid(sid, security->user_sid)) ||
security->is_well_known_sid(sid, WinLocalSystemSid) ||
security->is_well_known_sid(sid, WinBuiltinAdministratorsSid) ||
(local_admin && security->equal_sid(sid, local_admin)) ||
win_sid_is_trusted_installer((const uint8_t *)sid, (size_t)sid_length);
}
#ifdef CBM_ENABLE_TEST_SEAMS
bool cbm_daemon_ipc_win_sid_trusted_for_testing(void *sid) {
win_security_t security;
if (!win_security_init(&security)) {
return false;
}
bool trusted = win_sid_trusted(&security, (PSID)sid);
win_security_destroy(&security);
return trusted;
}
#endif
/* AppContainer identities: package SIDs (S-1-15-2-*) and capability SIDs
* (S-1-15-3-*), under the APP_PACKAGE identifier authority (15).
*
* These are tolerated on ANCESTOR components only — never on the private
* runtime directory itself, which keeps demanding the exact current user.
*
* Why they are admissible there: a sandboxed package's ACE on %LOCALAPPDATA%
* grants that package, and a process cannot select which AppContainer it runs
* in — the identity is stamped by the OS at process creation from the package
* it was launched from. So such an ACE cannot be exercised by arbitrary local
* code the way a live local group can. What it does permit is the packaged
* application itself; that is the residual risk this exemption accepts, and it
* is the same trust already extended to whatever installed that package.
*
* Why BOTH forms: capability SIDs alone are not enough. The most common real
* ACE of this shape is `S-1-15-2-*` — a package SID. On reported machines it
* resolves through HKCR\...\AppContainer\Mappings to Anthropic Claude
* Desktop, an application many of our users run and cannot be asked to
* uninstall. Covering only S-1-15-3-* leaves exactly that case failing.
*
* Grounded in #1533 (four independent reproductions across four SID classes)
* and #1574. Approach and the ancestor-only boundary follow @mlandolfi90's
* PR #1447, extended to package SIDs. */
static bool win_sid_is_app_container(const uint8_t *sid, size_t sid_length) {
if (!windows_sid_valid(sid, sid_length) || sid[1] < 1U) {
return false;
}
/* identifier authority must be exactly 15 (APP_PACKAGE_AUTHORITY) */
if (sid[2] != 0U || sid[3] != 0U || sid[4] != 0U || sid[5] != 0U || sid[6] != 0U ||
sid[7] != 15U) {
return false;
}
uint32_t first = win_sid_read_u32_le(sid + 8U);
return first == 2U || first == 3U;
}
static bool win_bounded_sid_trusted(win_security_t *security, const uint8_t *sid,
size_t sid_capacity, bool creator_owner_inherit_only,
bool ancestor) {
if (!security || !sid || sid_capacity < 8U || sid[1] > 15U) {
return false;
}
size_t sid_length = 8U + (size_t)sid[1] * 4U;
return sid_length <= sid_capacity && windows_sid_valid(sid, sid_length) &&
security->is_valid_sid((PSID)sid) &&
security->get_length_sid((PSID)sid) == (DWORD)sid_length &&
(win_sid_trusted(security, (PSID)sid) ||
(creator_owner_inherit_only &&
security->is_well_known_sid((PSID)sid, WinCreatorOwnerSid)) ||
/* OWNER RIGHTS (S-1-3-4) modulates the rights of whoever OWNS the
* object; the owner is separately validated as the exact current
* user, so such an ACE only ever grants to us. Default Windows
* profile/temp ACLs (and GitHub runner profiles) carry it, and
* rejecting it locked real current-user directories out. */
security->is_well_known_sid((PSID)sid, WinCreatorOwnerRightsSid) ||
(ancestor && win_sid_is_app_container(sid, sid_length)));
}
static bool win_file_owner_secure(win_security_t *security, HANDLE file,
bool require_current_user) {
PSID owner = NULL;
PSECURITY_DESCRIPTOR descriptor = NULL;
DWORD status = security->get_security_info(file, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION,
&owner, NULL, NULL, NULL, &descriptor);
bool queried = status == ERROR_SUCCESS && owner && security->is_valid_sid(owner);
bool secure =
queried && (require_current_user ? security->equal_sid(owner, security->user_sid) != 0
: win_sid_trusted(security, owner));
if (!queried) {
ipc_validation_detail_set("owner query failed (status %lu)", (unsigned long)status);
} else if (!secure) {
const char *owner_class = security->is_well_known_sid(owner, WinLocalSystemSid) ? "SYSTEM"
: security->is_well_known_sid(owner, WinBuiltinAdministratorsSid)
? "Administrators"
: "another account";
ipc_validation_detail_set("owner is %s, %s required", owner_class,
require_current_user ? "the exact user" : "a trusted identity");
}
if (descriptor) {
(void)LocalFree(descriptor);
}
return secure;
}
static DWORD win_private_mutation_rights(void) {
return GENERIC_ALL | GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_ADD_FILE |
FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES |
DELETE | WRITE_DAC | WRITE_OWNER | ACCESS_SYSTEM_SECURITY;
}
static bool win_file_acl_secure(win_security_t *security, HANDLE file, DWORD mutation,
bool ancestor) {
PACL dacl = NULL;
PSECURITY_DESCRIPTOR descriptor = NULL;
DWORD status = security->get_security_info(file, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION,
NULL, NULL, &dacl, NULL, &descriptor);
ACL_SIZE_INFORMATION information;
memset(&information, 0, sizeof(information));
bool secure =
status == ERROR_SUCCESS && descriptor && dacl && security->is_valid_acl(dacl) &&
security->get_acl_information(dacl, &information, sizeof(information), AclSizeInformation);
enum {
WIN_FILE_ACE_ALLOW = 0x00,
WIN_FILE_ACE_DENY = 0x01,
WIN_FILE_ACE_DENY_OBJECT = 0x06,
WIN_FILE_ACE_DENY_CALLBACK = 0x0a,
WIN_FILE_ACE_DENY_CALLBACK_OBJECT = 0x0c,
};
for (DWORD index = 0U; secure && index < information.AceCount; index++) {
void *opaque = NULL;
if (!security->get_ace(dacl, index, &opaque) || !opaque) {
secure = false;
break;
}
ACE_HEADER *header = opaque;
if (header->AceType == WIN_FILE_ACE_DENY || header->AceType == WIN_FILE_ACE_DENY_OBJECT ||
header->AceType == WIN_FILE_ACE_DENY_CALLBACK ||
header->AceType == WIN_FILE_ACE_DENY_CALLBACK_OBJECT) {
continue;
}
size_t sid_offset = offsetof(ACCESS_ALLOWED_ACE, SidStart);
if (header->AceType != WIN_FILE_ACE_ALLOW || (size_t)header->AceSize < sid_offset + 8U) {
secure = false;
break;
}
const ACCESS_ALLOWED_ACE *ace = opaque;
if ((ace->Mask & mutation) == 0U) {
continue;
}
const uint8_t *sid = (const uint8_t *)&ace->SidStart;
size_t sid_capacity = (size_t)header->AceSize - sid_offset;
bool creator_owner_inherit_only = (header->AceFlags & INHERIT_ONLY_ACE) != 0U;
if (!win_bounded_sid_trusted(security, sid, sid_capacity, creator_owner_inherit_only,
ancestor)) {
/* Name the untrusted identity class so a harness/profile ACL leak
* (an inherited Users / Authenticated Users / Everyone ACE) is
* distinguishable from a genuinely hostile grant. */
const char *sid_class =
security->is_well_known_sid((PSID)sid, WinBuiltinUsersSid) ? "BUILTIN\\Users"
: security->is_well_known_sid((PSID)sid, WinAuthenticatedUserSid)
? "Authenticated Users"
: security->is_well_known_sid((PSID)sid, WinWorldSid) ? "Everyone"
: security->is_well_known_sid((PSID)sid, WinInteractiveSid) ? "INTERACTIVE"
: "other";
/* Print the raw SID too: an "other" class is a specific account,
* and only its string form identifies the harness/profile leak. */
wchar_t *sid_text = NULL;
char sid_utf8[96] = "<unprintable>";
if (ConvertSidToStringSidW((PSID)sid, &sid_text) && sid_text) {
(void)WideCharToMultiByte(CP_UTF8, 0, sid_text, -1, sid_utf8, (int)sizeof(sid_utf8),
NULL, NULL);
LocalFree(sid_text);
}
ipc_validation_detail_set(
"DACL entry %lu grants mutation rights 0x%08lx to untrusted identity (%s %s)",
(unsigned long)index, (unsigned long)(ace->Mask & mutation), sid_class, sid_utf8);
secure = false;
}
}
if (descriptor) {
(void)LocalFree(descriptor);
}
return secure;
}
static bool win_file_security_secure(win_security_t *security, HANDLE file,
bool require_current_user, DWORD mutation, bool ancestor) {
return win_file_owner_secure(security, file, require_current_user) &&
win_file_acl_secure(security, file, mutation, ancestor);
}
/* Is this object's DACL present but EMPTY (zero ACEs)? That denies everyone,
* including the owner, for anything the owner-rights path does not cover.
*
* It needs its own test because win_file_acl_secure() cannot detect it: that
* function scans ACEs for untrusted mutation grants, and a DACL with zero ACEs
* trivially has none, so damage reads as compliance. */
/* The lock-directory ADOPTION predicate (private_win_owner_only_dacl in the
* foundation layer) demands a PROTECTED DACL whose single non-inherited ACE
* grants the CURRENT USER full access — strictly narrower than "no untrusted
* mutation rights", which also admits SYSTEM/Administrators ACEs. The
* already-correct fast path below must apply the CONSUMER'S predicate: a
* directory that merely passes the general secure() check but is not
* owner-only would skip the re-stamp and then strand every subsequent lock
* adoption (observed as 59 daemon-suite failures on a fresh runtime dir whose
* inherited DACL carried SYSTEM+Administrators). */
static bool win_file_dacl_is_owner_only(win_security_t *security, HANDLE file) {
if (!security->get_security_descriptor_control) {
return false;
}
PSID owner = NULL;
PACL dacl = NULL;
PSECURITY_DESCRIPTOR descriptor = NULL;
if (security->get_security_info(file, SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, &owner,
NULL, &dacl, NULL, &descriptor) != ERROR_SUCCESS) {
return false;
}
SECURITY_DESCRIPTOR_CONTROL control = 0;
DWORD revision = 0;
ACL_SIZE_INFORMATION information;
memset(&information, 0, sizeof(information));
LPVOID opaque_ace = NULL;
bool valid = descriptor && owner && dacl && security->is_valid_sid(owner) &&
security->equal_sid(owner, security->user_sid) &&
security->get_security_descriptor_control(descriptor, &control, &revision) &&
(control & SE_DACL_PRESENT) != 0 && (control & SE_DACL_PROTECTED) != 0 &&
security->is_valid_acl(dacl) &&
security->get_acl_information(dacl, &information, sizeof(information),
AclSizeInformation) &&
information.AceCount == 1U && security->get_ace(dacl, 0, &opaque_ace) &&
opaque_ace;
if (valid) {
ACCESS_ALLOWED_ACE *ace = (ACCESS_ALLOWED_ACE *)opaque_ace;
PSID ace_sid = (PSID)&ace->SidStart;
valid = ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE &&
ace->Header.AceSize >= sizeof(ACCESS_ALLOWED_ACE) &&
(ace->Header.AceFlags & (INHERITED_ACE | INHERIT_ONLY_ACE)) == 0 &&
security->is_valid_sid(ace_sid) &&
security->equal_sid(ace_sid, security->user_sid) &&
(ace->Mask == FILE_ALL_ACCESS || ace->Mask == GENERIC_ALL);
}
if (descriptor) {
(void)LocalFree(descriptor);
}
return valid;
}
static bool win_file_dacl_is_empty(win_security_t *security, HANDLE file) {
PACL dacl = NULL;
PSECURITY_DESCRIPTOR descriptor = NULL;
if (security->get_security_info(file, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL,
&dacl, NULL, &descriptor) != ERROR_SUCCESS) {
return false;
}
ACL_SIZE_INFORMATION information;
memset(&information, 0, sizeof(information));
bool empty = dacl && security->is_valid_acl(dacl) &&
security->get_acl_information(dacl, &information, sizeof(information),
AclSizeInformation) &&
information.AceCount == 0U;
if (descriptor) {
(void)LocalFree(descriptor);
}
return empty;
}
/* Repair cache/runtime children left unusable by the pre-v0.10.3 DACL regime.
*
* Between v0.9.1-rc and v0.10.2 the runtime directory carried a PROTECTED DACL
* whose ACE was not inheritable. Windows therefore gave every file created
* inside it either an empty DACL or the token default (SYSTEM + TokenOwner +
* logon SID). Under an elevated token TokenOwner is BUILTIN\Administrators, so
* the interactive user ends up with no durable grant at all and the file is
* unreadable after the next logon — #1601, where takeown and icacls both fail
* non-elevated and the daemon can no longer open _config.db.
*
* #1531 fixed the cause forward-only in v0.10.3: the directory ACE is
* inheritable now, so newly created children are fine. Nothing repaired the
* children already damaged, which is why upgrading did not rescue anyone whose
* cache was written under the old regime. This is that repair.
*
* Deliberately bounded and conservative:
* - immediate children only, no recursion, capped;
* - regular files only; directories, reparse points and symlinks are skipped
* entirely rather than followed;
* - a child is touched ONLY when it is demonstrably damaged - an empty DACL,
* or an owner that is not the current user. A child that is merely unusual
* is left alone;
* - failures are counted and reported, never fatal. This runs inside daemon
* startup and must not be able to prevent it.
*
* Scope note: this only ever runs on cbm's own runtime/cache directory, which
* we created and own. It does not reach into user directories. */
static void win_repair_runtime_children(win_security_t *security, const wchar_t *runtime_dir) {
enum { WIN_CHILD_REPAIR_MAX = 4096 };
if (!security || !runtime_dir || !security->user_sid) {
return;
}
size_t dir_length = wcslen(runtime_dir);
if (dir_length == 0U || dir_length > 32000U) {
return;
}
wchar_t *pattern = calloc(dir_length + 3U, sizeof(wchar_t));
if (!pattern) {
return;
}
(void)swprintf(pattern, dir_length + 3U, L"%ls\\*", runtime_dir);
WIN32_FIND_DATAW entry;
HANDLE search = FindFirstFileW(pattern, &entry);
free(pattern);
if (search == INVALID_HANDLE_VALUE) {
return;
}
unsigned examined = 0U;
unsigned repaired = 0U;
unsigned failed = 0U;
do {
if (wcscmp(entry.cFileName, L".") == 0 || wcscmp(entry.cFileName, L"..") == 0) {
continue;
}
if ((entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0U ||
(entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0U) {
continue;
}
if (++examined > (unsigned)WIN_CHILD_REPAIR_MAX) {
break;
}
size_t name_length = wcslen(entry.cFileName);
size_t child_capacity = dir_length + name_length + 2U;
wchar_t *child_path = calloc(child_capacity, sizeof(wchar_t));
if (!child_path) {
continue;
}
(void)swprintf(child_path, child_capacity, L"%ls\\%ls", runtime_dir, entry.cFileName);
HANDLE child = CreateFileW(child_path, READ_CONTROL | WRITE_DAC | WRITE_OWNER,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, NULL);
free(child_path);
if (child == INVALID_HANDLE_VALUE) {
continue;
}
BY_HANDLE_FILE_INFORMATION child_info;
bool regular = GetFileInformationByHandle(child, &child_info) != 0 &&
(child_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0U &&
(child_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0U;
bool damaged = regular && (win_file_dacl_is_empty(security, child) ||
!win_file_owner_secure(security, child, true));
if (damaged) {
if (security->set_security_info(
child, SE_FILE_OBJECT,
(DWORD)OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION |
PROTECTED_DACL_SECURITY_INFORMATION,
security->user_sid, NULL, security->acl, NULL) == ERROR_SUCCESS) {
repaired++;
} else {
failed++;
}
}
(void)CloseHandle(child);
} while (FindNextFileW(search, &entry) != 0);
(void)FindClose(search);
if (repaired > 0U || failed > 0U) {
char repaired_text[16];
char failed_text[16];
(void)snprintf(repaired_text, sizeof(repaired_text), "%u", repaired);
(void)snprintf(failed_text, sizeof(failed_text), "%u", failed);
cbm_log_warn("daemon.runtime_child_acl_repaired", "repaired", repaired_text, "failed",
failed_text);
}
}
static bool win_runtime_directory_secure(const wchar_t *runtime_dir) {
win_security_t security;
if (!win_security_init(&security)) {
return false;
}
bool created = CreateDirectoryW(runtime_dir, &security.directory_attributes) != 0;
DWORD create_error = created ? ERROR_SUCCESS : GetLastError();
if (!created && create_error != ERROR_ALREADY_EXISTS) {
char message[96];
(void)snprintf(message, sizeof(message),
"could not create the directory (Windows error %lu)",
(unsigned long)create_error);
ipc_validation_detail_set_for_path(runtime_dir, message);
win_security_destroy(&security);
return false;
}
DWORD attributes = GetFileAttributesW(runtime_dir);
if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0 ||
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
/* What already exists at this path is adopted only if it is a plain
* directory. Say which rule refused it: the walk in front of this
* function tolerates losing a creation race, so this check is what
* stands between that tolerance and a planted file or junction. */
const char *rule = attributes == INVALID_FILE_ATTRIBUTES ? "cannot be inspected"
: (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0
? "exists but is not a directory"
: "is a reparse point (junction or symlink)";
ipc_validation_detail_set_for_path(runtime_dir, rule);
win_security_destroy(&security);
return false;
}
HANDLE directory =
CreateFileW(runtime_dir, READ_CONTROL | WRITE_DAC | WRITE_OWNER,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
bool can_write_owner = directory != INVALID_HANDLE_VALUE;
if (!can_write_owner) {
directory =
CreateFileW(runtime_dir, READ_CONTROL | WRITE_DAC,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
}
if (directory == INVALID_HANDLE_VALUE) {
win_security_destroy(&security);
return false;
}
BY_HANDLE_FILE_INFORMATION file_info;
bool valid_handle = GetFileInformationByHandle(directory, &file_info) != 0 &&
(file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
(file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0;
bool owner_exact = valid_handle && win_file_owner_secure(&security, directory, true);
/* One-time normalization of the admin-group default-owner artifact: a
* directory created by plain mkdir under an Administrators-default-owner
* token (standard policy on Windows Server) is born owned by BUILTIN\
* Administrators even though it is this account's own private dir. A
* TRUSTED owner (the launcher's directory policy: SYSTEM, Administrators,
* TrustedInstaller) is re-stamped to the exact token user inside the same
* repair that already re-protects the DACL; any other owner remains
* refused, and the final validation below still demands the exact user. */
bool owner_ok = owner_exact || (valid_handle && can_write_owner &&
win_file_owner_secure(&security, directory, false));
/* Re-stamp only when the directory is not ALREADY correct.
*
* This used to fire on every process start, whether or not anything was
* wrong. Two costs, both observed in the field:
*
* - It rewrites the security descriptor of a directory that already has
* the right one, and Windows propagates that to children. #1601 counted
* ELEVEN "Security change" USN records against a single _config.db in
* one day, none of which changed anything.
* - Every rewrite is a window. #1620 loses an atomic publish to exactly
* this: MoveFileEx needs DELETE on the destination, and a concurrent
* re-protect of the parent is a chance to be refused for a state that is
* about to be correct again anyway.
*
* The repair is what matters, not the ritual. If the owner is already the
* exact current user and the DACL already passes the private-directory
* check, there is nothing to fix and the correct action is to leave it
* alone. When it IS wrong we still repair exactly as before. */
DWORD secure_result = ERROR_ACCESS_DENIED;
bool already_correct =
valid_handle && owner_exact && win_file_dacl_is_owner_only(&security, directory);
if (already_correct) {
secure_result = ERROR_SUCCESS;
} else if (valid_handle && owner_ok) {
secure_result = security.set_security_info(
directory, SE_FILE_OBJECT,
(owner_exact ? 0U : (DWORD)OWNER_SECURITY_INFORMATION) | DACL_SECURITY_INFORMATION |
PROTECTED_DACL_SECURITY_INFORMATION,
owner_exact ? NULL : security.user_sid, NULL, security.directory_acl, NULL);
}
if (valid_handle && owner_ok && secure_result != ERROR_SUCCESS) {
ipc_validation_detail_set("owner/DACL repair failed (status %lu%s)",
(unsigned long)secure_result,
can_write_owner ? "" : ", WRITE_OWNER unavailable");
}
bool final_private =
secure_result == ERROR_SUCCESS &&
win_file_security_secure(&security, directory, true, win_private_mutation_rights(), false);
/* Repair damaged children only once the directory itself is known good.
* Repairing into a parent we have not secured would re-derive the same
* broken state on the next file created there. */
if (final_private) {
win_repair_runtime_children(&security, runtime_dir);
}
(void)CloseHandle(directory);
win_security_destroy(&security);
return valid_handle && owner_ok && final_private;
}
static bool win_directory_component_secure(win_security_t *security, const wchar_t *path) {
HANDLE directory =
CreateFileW(path, FILE_READ_ATTRIBUTES | READ_CONTROL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (directory == INVALID_HANDLE_VALUE) {
return false;
}
BY_HANDLE_FILE_INFORMATION info;
/* Default Windows profile ancestors grant cross-account add-subdirectory.
* That permits siblings but cannot replace the existing next path
* component. Keep every other mutation right forbidden; the final runtime
* directory is separately owner-validated and given a protected DACL. */
DWORD mutation = win_private_mutation_rights() & ~((DWORD)FILE_ADD_SUBDIRECTORY);
bool valid = GetFileInformationByHandle(directory, &info) != 0 &&
(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0 &&
win_file_security_secure(security, directory, false, mutation, true);
(void)CloseHandle(directory);
return valid;
}
static bool win_private_directory_tree_secure(const wchar_t *directory_path) {
if (!directory_path) {
return false;
}
size_t length = wcslen(directory_path);
bool drive_absolute = length >= 4 &&
((directory_path[0] >= L'A' && directory_path[0] <= L'Z') ||
(directory_path[0] >= L'a' && directory_path[0] <= L'z')) &&
directory_path[1] == L':' &&
(directory_path[2] == L'\\' || directory_path[2] == L'/');
if (!drive_absolute) {
/* Local current-user ACLs do not provide the intended guarantee for
* UNC/device namespaces. Daemon cache logs must stay on a local
* absolute drive path. */
return false;
}
wchar_t *path = wide_copy(directory_path);
if (!path) {
return false;
}
for (size_t i = 0; i < length; i++) {
if (path[i] == L'/') {
path[i] = L'\\';
}
}
win_security_t security;
if (!win_security_init(&security)) {
free(path);
return false;
}
bool ok = true;
size_t component_start = 3;
for (size_t i = component_start; ok && i <= length; i++) {
if (i < length && path[i] != L'\\') {
continue;
}
if (i == component_start) {
component_start = i + 1;
continue;
}
wchar_t saved = path[i];
path[i] = L'\0';
const wchar_t *component = path + component_start;
if (wcscmp(component, L".") == 0 || wcscmp(component, L"..") == 0) {
ok = false;
} else {
DWORD attributes = GetFileAttributesW(path);
if (attributes == INVALID_FILE_ATTRIBUTES) {
DWORD error = GetLastError();
bool absent = error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND;
if (absent) {
ipc_win_directory_create_hook_run(path);
/* Several processes first-starting together all observe
* this component absent; one CreateDirectoryW wins and
* the rest get ERROR_ALREADY_EXISTS. Losing that race is
* not a failure -- the directory this process wanted now
* exists, exactly as if it had been there all along. It
* is NOT trusted for that: an ancestor still goes through
* win_directory_component_secure() below, and the final
* component through win_runtime_directory_secure(), which
* refuses a non-directory or reparse point and enforces
* owner and DACL. The POSIX walk tolerates EEXIST at the
* same point for the same reason. */
if (CreateDirectoryW(path, &security.directory_attributes) == 0) {
error = GetLastError();
absent = error == ERROR_ALREADY_EXISTS;
}
}
ok = absent;
if (!ok) {
/* Never a bare "(endpoint)": name the component and the
* Windows error that refused it. */
char message[96];
(void)snprintf(message, sizeof(message),
"could not create or inspect the directory (Windows error %lu)",
(unsigned long)error);
ipc_validation_detail_set_for_path(path, message);
}
}
/* Ancestors are observe-only and must already be secure. The
* final current-user directory is intentionally handled below by
* win_runtime_directory_secure(), which may replace its DACL. */
if (ok && i < length) {
ok = win_directory_component_secure(&security, path);
if (!ok) {
/* Name the ancestry component while it is NUL-terminated
* at this walk position; the helper set the inner rule. */
char inner[384];
(void)snprintf(inner, sizeof(inner), "%s", ipc_validation_detail_buffer);
ipc_validation_detail_set_for_path(path, inner);
}
}
}
path[i] = saved;
component_start = i + 1;
}
win_security_destroy(&security);
if (ok) {
ok = win_runtime_directory_secure(path);
}
free(path);
return ok;
}
bool cbm_daemon_ipc_private_directory_secure(const char *directory_path) {
ipc_validation_detail_set("%s", "");
if (!directory_path || !directory_path[0]) {
return false;
}
wchar_t *wide_directory = utf8_to_wide(directory_path);
bool secure = wide_directory && win_private_directory_tree_secure(wide_directory);
free(wide_directory);
return secure;
}
static bool private_log_base_name_valid(const char *base_name) {
if (!base_name || !base_name[0] || strcmp(base_name, ".") == 0 ||
strcmp(base_name, "..") == 0 || strchr(base_name, '/') || strchr(base_name, '\\')) {
return false;
}
return strlen(base_name) <= 253;
}
static HANDLE win_private_log_file_open(const wchar_t *path, DWORD creation_disposition,
win_security_t *security, LARGE_INTEGER *size_out) {
HANDLE file = CreateFileW(path, GENERIC_READ | GENERIC_WRITE | READ_CONTROL | WRITE_DAC,
FILE_SHARE_READ, &security->attributes, creation_disposition,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (file == INVALID_HANDLE_VALUE) {
return INVALID_HANDLE_VALUE;
}
(void)SetHandleInformation(file, HANDLE_FLAG_INHERIT, 0);
BY_HANDLE_FILE_INFORMATION info;
PSID owner = NULL;
PSECURITY_DESCRIPTOR descriptor = NULL;
bool valid =
GetFileType(file) == FILE_TYPE_DISK && GetFileInformationByHandle(file, &info) != 0 &&
(info.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) == 0 &&
info.nNumberOfLinks == 1 && GetFileSizeEx(file, size_out) != 0 && size_out->QuadPart >= 0;
DWORD owner_result = security->get_security_info(
file, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &owner, NULL, NULL, NULL, &descriptor);
bool owner_ok =
owner_result == ERROR_SUCCESS && owner && security->equal_sid(owner, security->user_sid);
if (descriptor) {
(void)LocalFree(descriptor);
}
DWORD secure_result = ERROR_ACCESS_DENIED;
if (valid && owner_ok) {
secure_result = security->set_security_info(
file, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
NULL, NULL, security->acl, NULL);
}
if (!valid || !owner_ok || secure_result != ERROR_SUCCESS) {
(void)CloseHandle(file);
return INVALID_HANDLE_VALUE;
}
return file;
}
FILE *cbm_daemon_ipc_private_log_open(const char *directory_path, const char *base_name,
size_t rotate_cap_bytes) {
if (!directory_path || !directory_path[0] || !private_log_base_name_valid(base_name) ||
rotate_cap_bytes == 0) {
return NULL;
}
wchar_t *wide_directory = utf8_to_wide(directory_path);
char *path = string_format("%s/%s", directory_path, base_name);
char *rotated_path = path ? string_format("%s.1", path) : NULL;
wchar_t *wide_path = path ? utf8_to_wide(path) : NULL;
wchar_t *wide_rotated = rotated_path ? utf8_to_wide(rotated_path) : NULL;
if (!wide_directory || !path || !rotated_path || !wide_path || !wide_rotated ||
!win_private_directory_tree_secure(wide_directory)) {
free(wide_directory);
free(path);
free(rotated_path);
free(wide_path);
free(wide_rotated);
return NULL;
}
free(wide_directory);
win_security_t security;
if (!win_security_init(&security)) {
free(path);
free(rotated_path);
free(wide_path);
free(wide_rotated);
return NULL;
}
LARGE_INTEGER size;
HANDLE file = win_private_log_file_open(wide_path, OPEN_ALWAYS, &security, &size);
bool ok = file != INVALID_HANDLE_VALUE;
if (ok && (uint64_t)size.QuadPart > (uint64_t)rotate_cap_bytes) {
(void)CloseHandle(file);
file = INVALID_HANDLE_VALUE;
DWORD attributes = GetFileAttributesW(wide_rotated);
DWORD attributes_error =
attributes == INVALID_FILE_ATTRIBUTES ? GetLastError() : ERROR_SUCCESS;
bool destination_ok =
attributes == INVALID_FILE_ATTRIBUTES &&
(attributes_error == ERROR_FILE_NOT_FOUND || attributes_error == ERROR_PATH_NOT_FOUND);
if (attributes != INVALID_FILE_ATTRIBUTES) {
LARGE_INTEGER destination_size;
HANDLE destination = win_private_log_file_open(wide_rotated, OPEN_EXISTING, &security,
&destination_size);
destination_ok = destination != INVALID_HANDLE_VALUE;
if (destination != INVALID_HANDLE_VALUE) {
(void)CloseHandle(destination);
}
}
ok = destination_ok && MoveFileExW(wide_path, wide_rotated,
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0;
if (ok) {
file = win_private_log_file_open(wide_path, OPEN_ALWAYS, &security, &size);
ok = file != INVALID_HANDLE_VALUE;
}
}
FILE *stream = NULL;
if (ok) {
LARGE_INTEGER end = {.QuadPart = 0};
ok = SetFilePointerEx(file, end, NULL, FILE_END) != 0;
}
if (ok) {
int fd = _open_osfhandle((intptr_t)file, _O_WRONLY | _O_APPEND | _O_BINARY);
if (fd >= 0) {
file = INVALID_HANDLE_VALUE;
stream = _fdopen(fd, "ab");
if (!stream) {
(void)_close(fd);
}
}
}
if (file != INVALID_HANDLE_VALUE) {
(void)CloseHandle(file);
}
win_security_destroy(&security);
free(path);
free(rotated_path);
free(wide_path);
free(wide_rotated);
return stream;
}
static bool win_parent_valid(const wchar_t *parent) {
DWORD attributes = GetFileAttributesW(parent);
return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0;
}
static wchar_t *win_default_runtime_parent(void) {
wchar_t path[MAX_PATH];
HRESULT result = SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, NULL,
SHGFP_TYPE_CURRENT, path);
return SUCCEEDED(result) ? wide_copy(path) : NULL;
}
cbm_daemon_ipc_endpoint_t *cbm_daemon_ipc_endpoint_new(const char *instance_key,
const char *runtime_parent) {
if (!instance_key_valid(instance_key)) {
return NULL;
}
char *parent_utf8 = NULL;
wchar_t *parent_wide = NULL;
if (runtime_parent) {
parent_utf8 = string_copy(runtime_parent);
parent_wide = utf8_to_wide(runtime_parent);
} else {
parent_wide = win_default_runtime_parent();
parent_utf8 = wide_to_utf8(parent_wide);
}
if (!parent_utf8 || !parent_wide) {
free(parent_utf8);
free(parent_wide);
return NULL;
}
free(parent_wide);
char canonical_parent[CBM_DAEMON_IPC_PATH_CAP];
if (!cbm_canonical_path(parent_utf8, canonical_parent, sizeof(canonical_parent))) {
free(parent_utf8);
return NULL;
}
free(parent_utf8);
parent_utf8 = string_copy(canonical_parent);
parent_wide = utf8_to_wide(parent_utf8);
if (!parent_utf8 || !parent_wide || !win_parent_valid(parent_wide)) {
free(parent_utf8);
free(parent_wide);
return NULL;
}
free(parent_wide);
size_t parent_length = strlen(parent_utf8);
bool has_separator = parent_length > 0 && (parent_utf8[parent_length - 1] == '/' ||
parent_utf8[parent_length - 1] == '\\');
cbm_daemon_ipc_endpoint_t *endpoint = calloc(1, sizeof(*endpoint));
if (!endpoint) {
free(parent_utf8);
return NULL;
}
win_security_t identity_security;
if (!win_security_init(&identity_security)) {
free(parent_utf8);
free(endpoint);
return NULL;
}
DWORD sid_length = identity_security.get_length_sid(identity_security.user_sid);
bool identity_ok = sid_length > 0 &&
windows_sid_valid((const uint8_t *)identity_security.user_sid, sid_length);
if (identity_ok) {
endpoint->user_sid = identity_security.user_sid;
endpoint->user_sid_length = sid_length;
identity_security.user_sid = NULL;
}
win_security_destroy(&identity_security);
if (!identity_ok) {
free(parent_utf8);
free(endpoint);
return NULL;
}
char legacy_pipe[CBM_DAEMON_IPC_WINDOWS_NAME_CAP];
char legacy_startup[CBM_DAEMON_IPC_WINDOWS_NAME_CAP];
bool legacy_names_ok =
cbm_daemon_ipc_windows_legacy_names(parent_utf8, instance_key, legacy_pipe, legacy_startup);
endpoint->runtime_dir =
string_format("%s%scbm-daemon-%s", parent_utf8, has_separator ? "" : "/", instance_key);
endpoint->legacy_pipe_name = legacy_names_ok ? utf8_to_wide(legacy_pipe) : NULL;
endpoint->legacy_startup_mutex_name = legacy_names_ok ? utf8_to_wide(legacy_startup) : NULL;
(void)memcpy(endpoint->instance_key, instance_key, sizeof(endpoint->instance_key));
cbm_mutex_init(&endpoint->generations_lock);
atomic_init(&endpoint->current_generation, NULL);
free(parent_utf8);
wchar_t *runtime_wide = utf8_to_wide(endpoint->runtime_dir);
if (!endpoint->runtime_dir || !runtime_wide || !legacy_names_ok ||
!endpoint->legacy_pipe_name || !endpoint->legacy_startup_mutex_name ||
!win_private_directory_tree_secure(runtime_wide)) {
free(runtime_wide);
cbm_daemon_ipc_endpoint_free(endpoint);
return NULL;
}
free(runtime_wide);
/* Construction never publishes or repairs rendezvous payload. Absence or
* a partial/corrupt record leaves the endpoint safely unaddressed; only a
* later startup-lock winner is permitted to publish a replacement. */
(void)win_endpoint_refresh_rendezvous(endpoint);
return endpoint;
}
void cbm_daemon_ipc_endpoint_free(cbm_daemon_ipc_endpoint_t *endpoint) {
if (!endpoint) {
return;
}
win_generation_address_t *generation = endpoint->generations;
while (generation) {
win_generation_address_t *next = generation->next;
free(generation->pipe_name);
free(generation);
generation = next;
}
cbm_mutex_destroy(&endpoint->generations_lock);
free(endpoint->runtime_dir);
free(endpoint->legacy_pipe_name);
free(endpoint->legacy_startup_mutex_name);
free(endpoint->user_sid);
free(endpoint);
}
const char *cbm_daemon_ipc_endpoint_address(const cbm_daemon_ipc_endpoint_t *endpoint) {
if (!endpoint || win_endpoint_refresh_rendezvous(endpoint) != WIN_RENDEZVOUS_VALID) {
return NULL;
}
win_generation_address_t *generation = win_endpoint_generation_snapshot(endpoint);
return generation ? generation->address : NULL;
}
const char *cbm_daemon_ipc_endpoint_runtime_dir(const cbm_daemon_ipc_endpoint_t *endpoint) {
return endpoint ? endpoint->runtime_dir : NULL;
}
cbm_private_file_lock_status_t cbm_daemon_ipc_private_lock_directory_new(
const cbm_daemon_ipc_endpoint_t *endpoint, cbm_private_lock_directory_t **directory_out) {
if (directory_out) {
*directory_out = NULL;
}
if (!directory_out || !endpoint || !endpoint->runtime_dir) {
return CBM_PRIVATE_FILE_LOCK_UNSAFE;
}
wchar_t *runtime_wide = utf8_to_wide(endpoint->runtime_dir);
if (!runtime_wide) {
return CBM_PRIVATE_FILE_LOCK_IO;
}
HANDLE directory =
CreateFileW(runtime_wide, FILE_READ_ATTRIBUTES | READ_CONTROL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
free(runtime_wide);
if (directory == INVALID_HANDLE_VALUE) {
return CBM_PRIVATE_FILE_LOCK_IO;
}
(void)SetHandleInformation(directory, HANDLE_FLAG_INHERIT, 0);
cbm_private_file_lock_status_t status =
cbm_private_lock_directory_adopt_windows(directory, endpoint->runtime_dir, directory_out);
if (status != CBM_PRIVATE_FILE_LOCK_OK) {
(void)CloseHandle(directory);
}
return status;
}
static const char WIN_STARTUP_V2_LOCK_NAME[] = "cbm-startup-v2.lock";
static const char WIN_PARTICIPANT_GROUP_LOCK_NAME[] = "cbm-participant-group-v1.lock";
static const char WIN_LIFETIME_LOCK_NAME[] = "cbm-lifetime.lock";
static void win_private_lock_release_complete(cbm_private_file_lock_t **lock_io) {
uint64_t deadline = ipc_deadline_after(CBM_DAEMON_IPC_COORDINATION_CLEANUP_MS);
while (lock_io && *lock_io) {
(void)cbm_private_file_lock_release(lock_io);
if (!*lock_io) {
return;
}
if (ipc_now_ms() >= deadline) {
ipc_coordination_cleanup_fail_stop("private_lock_cleanup");
}
Sleep(1);
}
}
static win_generation_address_t *win_endpoint_generation_snapshot(
const cbm_daemon_ipc_endpoint_t *endpoint) {
return endpoint ? atomic_load_explicit(&endpoint->current_generation, memory_order_acquire)
: NULL;
}
static void win_endpoint_generation_invalidate(const cbm_daemon_ipc_endpoint_t *endpoint) {
if (endpoint) {
atomic_store_explicit(&((cbm_daemon_ipc_endpoint_t *)endpoint)->current_generation, NULL,
memory_order_release);
}
}
static bool win_endpoint_generation_install(const cbm_daemon_ipc_endpoint_t *endpoint,
const char *address) {
if (!endpoint || !windows_pipe_address_valid(address)) {
return false;
}
win_generation_address_t *snapshot = win_endpoint_generation_snapshot(endpoint);
if (snapshot && strcmp(snapshot->address, address) == 0) {
return true;
}
win_generation_address_t *candidate = calloc(1, sizeof(*candidate));
if (candidate) {
(void)memcpy(candidate->address, address, strlen(address) + 1U);
candidate->pipe_name = utf8_to_wide(address);
}
if (!candidate || !candidate->pipe_name) {
free(candidate ? candidate->pipe_name : NULL);
free(candidate);
return false;
}
cbm_daemon_ipc_endpoint_t *mutable_endpoint = (cbm_daemon_ipc_endpoint_t *)endpoint;
cbm_mutex_lock(&mutable_endpoint->generations_lock);
win_generation_address_t *current =
atomic_load_explicit(&mutable_endpoint->current_generation, memory_order_acquire);
if (current && strcmp(current->address, address) == 0) {
cbm_mutex_unlock(&mutable_endpoint->generations_lock);
free(candidate->pipe_name);
free(candidate);
return true;
}
candidate->next = mutable_endpoint->generations;
mutable_endpoint->generations = candidate;
atomic_store_explicit(&mutable_endpoint->current_generation, candidate, memory_order_release);
cbm_mutex_unlock(&mutable_endpoint->generations_lock);
return true;
}
static win_rendezvous_status_t win_endpoint_refresh_rendezvous(
const cbm_daemon_ipc_endpoint_t *endpoint) {
if (!endpoint || !endpoint->user_sid || endpoint->user_sid_length == 0) {
return WIN_RENDEZVOUS_ERROR;
}
cbm_private_lock_directory_t *directory = NULL;
cbm_private_file_lock_t *record_lock = NULL;
cbm_private_file_lock_status_t directory_status =
cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory);
if (directory_status != CBM_PRIVATE_FILE_LOCK_OK) {
win_endpoint_generation_invalidate(endpoint);
return WIN_RENDEZVOUS_ERROR;
}
cbm_private_file_lock_status_t lock_status = cbm_private_file_lock_try_acquire(
directory, CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_FILE, CBM_PRIVATE_FILE_LOCK_SH, &record_lock);
if (lock_status != CBM_PRIVATE_FILE_LOCK_OK) {
win_private_lock_release_complete(&record_lock);
cbm_private_lock_directory_close(directory);
win_endpoint_generation_invalidate(endpoint);
return lock_status == CBM_PRIVATE_FILE_LOCK_BUSY ? WIN_RENDEZVOUS_BUSY
: WIN_RENDEZVOUS_ERROR;
}
uint8_t record[CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RECORD_SIZE];
size_t record_length = 0;
cbm_private_file_lock_status_t read_status =
cbm_private_file_lock_payload_read(record_lock, record, sizeof(record), &record_length);
uint8_t nonce[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE];
char address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP];
bool decoded =
read_status == CBM_PRIVATE_FILE_LOCK_OK &&
cbm_daemon_ipc_windows_rendezvous_record_decode(record, record_length, nonce, address);
char expected[CBM_DAEMON_IPC_WINDOWS_NAME_CAP];
bool bound =
decoded &&
cbm_daemon_ipc_windows_generation_address(endpoint->user_sid, endpoint->user_sid_length,
endpoint->instance_key, nonce, expected) &&
strcmp(expected, address) == 0;
win_rendezvous_status_t result;
if (read_status != CBM_PRIVATE_FILE_LOCK_OK) {
win_endpoint_generation_invalidate(endpoint);
result = WIN_RENDEZVOUS_ERROR;
} else if (record_length == 0) {
win_endpoint_generation_invalidate(endpoint);
result = WIN_RENDEZVOUS_ABSENT;
} else if (!bound) {
win_endpoint_generation_invalidate(endpoint);
result = WIN_RENDEZVOUS_CORRUPT;
} else if (!win_endpoint_generation_install(endpoint, address)) {
win_endpoint_generation_invalidate(endpoint);
result = WIN_RENDEZVOUS_ERROR;
} else {
result = WIN_RENDEZVOUS_VALID;
}
/* Keep the record lock through the in-memory snapshot update. Otherwise
* a reader of generation N could install/invalidate after a startup owner
* has published generation N+1, regressing this endpoint to stale state. */
win_private_lock_release_complete(&record_lock);
cbm_private_lock_directory_close(directory);
return result;
}
static bool win_generation_nonce(uint8_t nonce[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE]) {
return cbm_secure_random(nonce, CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE);
}
static int win_private_lock_probe(cbm_private_lock_directory_t *directory, const char *base_name) {
cbm_private_file_lock_t *probe = NULL;
cbm_private_file_lock_status_t status =
cbm_private_file_lock_try_acquire(directory, base_name, CBM_PRIVATE_FILE_LOCK_SH, &probe);
if (status == CBM_PRIVATE_FILE_LOCK_BUSY) {
return 1;
}
if (status != CBM_PRIVATE_FILE_LOCK_OK) {
win_private_lock_release_complete(&probe);
return -1;
}
win_private_lock_release_complete(&probe);
return 0;
}
static bool win_startup_publish_generation_locked(const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_private_file_lock_t *record_lock) {
uint8_t nonce[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE];
char address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP];
uint8_t record[CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RECORD_SIZE];
if (!record_lock || !win_generation_nonce(nonce) ||
!cbm_daemon_ipc_windows_generation_address(endpoint->user_sid, endpoint->user_sid_length,
endpoint->instance_key, nonce, address) ||
!cbm_daemon_ipc_windows_rendezvous_record_encode(nonce, address, record)) {
return false;
}
bool written = cbm_private_file_lock_payload_write(record_lock, record, sizeof(record)) ==
CBM_PRIVATE_FILE_LOCK_OK;
return written && win_endpoint_generation_install(endpoint, address);
}
/* Startup ownership serializes publishers, while the rendezvous EX lock is
* the bridge to lifetime ownership: a lifetime acquirer must finish its final
* rendezvous SH refresh before this probe can declare lifetime free. If it
* acquires lifetime after the probe, its final refresh waits for this publish
* and therefore binds the newly advertised generation. */
static int win_startup_prepare_generation(const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_private_lock_directory_t *directory) {
uint64_t now = ipc_now_ms();
uint64_t deadline = now > UINT64_MAX - CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RETRY_MS
? UINT64_MAX
: now + CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RETRY_MS;
for (;;) {
cbm_private_file_lock_t *record_lock = NULL;
cbm_private_file_lock_status_t status =
cbm_private_file_lock_try_acquire(directory, CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_FILE,
CBM_PRIVATE_FILE_LOCK_EX, &record_lock);
if (status == CBM_PRIVATE_FILE_LOCK_OK) {
int lifetime = win_private_lock_probe(directory, WIN_LIFETIME_LOCK_NAME);
bool published =
lifetime == 0 && win_startup_publish_generation_locked(endpoint, record_lock);
win_private_lock_release_complete(&record_lock);
if (lifetime != 0) {
return lifetime == 1 ? 0 : -1;
}
return published ? 1 : -1;
}
win_private_lock_release_complete(&record_lock);
if (status != CBM_PRIVATE_FILE_LOCK_BUSY) {
return -1;
}
if (ipc_now_ms() >= deadline) {
return 0;
}
Sleep(1);
}
}
int cbm_daemon_ipc_lifetime_reservation_try_acquire(
const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_daemon_ipc_lifetime_reservation_t **reservation_out) {
if (reservation_out) {
*reservation_out = NULL;
}
if (!endpoint || !reservation_out) {
return -1;
}
win_rendezvous_status_t rendezvous = win_endpoint_refresh_rendezvous(endpoint);
if (rendezvous == WIN_RENDEZVOUS_ABSENT || rendezvous == WIN_RENDEZVOUS_CORRUPT) {
cbm_daemon_ipc_startup_lock_t *startup = NULL;
int startup_status = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup);
bool prepared = startup_status == 1 && cbm_daemon_ipc_startup_lock_prepare_handoff(startup);
ipc_startup_lock_release_complete(&startup);
if (!prepared) {
return startup_status == 0 ? 0 : -1;
}
rendezvous = win_endpoint_refresh_rendezvous(endpoint);
}
if (rendezvous != WIN_RENDEZVOUS_VALID) {
return rendezvous == WIN_RENDEZVOUS_ABSENT || rendezvous == WIN_RENDEZVOUS_BUSY ? 0 : -1;
}
cbm_private_lock_directory_t *directory = NULL;
cbm_private_file_lock_t *lock = NULL;
if (cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory) !=
CBM_PRIVATE_FILE_LOCK_OK) {
return -1;
}
cbm_private_file_lock_status_t status = cbm_private_file_lock_try_acquire(
directory, WIN_LIFETIME_LOCK_NAME, CBM_PRIVATE_FILE_LOCK_EX, &lock);
if (status != CBM_PRIVATE_FILE_LOCK_OK) {
win_private_lock_release_complete(&lock);
cbm_private_lock_directory_close(directory);
return status == CBM_PRIVATE_FILE_LOCK_BUSY ? 0 : -1;
}
/* A starter may have won immediately before this lifetime lock. Its
* rendezvous publication must become visible before this owner listens;
* otherwise release rather than binding a stale generation address. */
if (win_endpoint_refresh_rendezvous(endpoint) != WIN_RENDEZVOUS_VALID) {
win_private_lock_release_complete(&lock);
cbm_private_lock_directory_close(directory);
return 0;
}
cbm_daemon_ipc_lifetime_reservation_t *reservation = calloc(1, sizeof(*reservation));
if (!reservation) {
win_private_lock_release_complete(&lock);
cbm_private_lock_directory_close(directory);
return -1;
}
reservation->endpoint = endpoint;
reservation->directory = directory;
reservation->lock = lock;
*reservation_out = reservation;
return 1;
}
void cbm_daemon_ipc_lifetime_reservation_release(
cbm_daemon_ipc_lifetime_reservation_t *reservation) {
if (!reservation) {
return;
}
win_private_lock_release_complete(&reservation->lock);
cbm_private_lock_directory_close(reservation->directory);
free(reservation);
}
static bool lifetime_reservation_matches_endpoint(
const cbm_daemon_ipc_endpoint_t *endpoint,
const cbm_daemon_ipc_lifetime_reservation_t *reservation) {
return endpoint && reservation && reservation->endpoint == endpoint && reservation->lock;
}
int cbm_daemon_ipc_lifetime_reservation_probe(const cbm_daemon_ipc_endpoint_t *endpoint) {
cbm_private_lock_directory_t *directory = NULL;
if (!endpoint || cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory) !=
CBM_PRIVATE_FILE_LOCK_OK) {
return -1;
}
int result = win_private_lock_probe(directory, WIN_LIFETIME_LOCK_NAME);
cbm_private_lock_directory_close(directory);
return result;
}
static int win_legacy_pipe_probe(const cbm_daemon_ipc_endpoint_t *endpoint) {
if (!endpoint || !endpoint->legacy_pipe_name || !endpoint->legacy_startup_mutex_name) {
return -1;
}
if (WaitNamedPipeW(endpoint->legacy_pipe_name, 0)) {
return 1;
}
DWORD pipe_error = GetLastError();
if (pipe_error == ERROR_SEM_TIMEOUT || pipe_error == ERROR_PIPE_BUSY) {
return 1;
}
if (pipe_error != ERROR_FILE_NOT_FOUND && pipe_error != ERROR_PATH_NOT_FOUND) {
return -1;
}
return 0;
}
int cbm_daemon_ipc_legacy_generation_probe(const cbm_daemon_ipc_endpoint_t *endpoint) {
int pipe = win_legacy_pipe_probe(endpoint);
if (pipe != 0) {
return pipe;
}
/* Open-only observation is sufficient: the legacy startup owner retains
* the sole named-object handle until it releases startup. Never create or
* wait on the mutex, so this compatibility path cannot become authority. */
HANDLE startup =
OpenMutexW(SYNCHRONIZE | READ_CONTROL, FALSE, endpoint->legacy_startup_mutex_name);
if (startup) {
win_security_t security;
bool safe =
win_security_init(&security) && win_kernel_mutex_current_user_only(&security, startup);
win_security_destroy(&security);
(void)SetHandleInformation(startup, HANDLE_FLAG_INHERIT, 0);
(void)CloseHandle(startup);
return safe ? 1 : -1;
}
DWORD startup_error = GetLastError();
return startup_error == ERROR_FILE_NOT_FOUND ? 0 : -1;
}
static HANDLE win_pipe_instance_new(const wchar_t *pipe_name, bool first_instance) {
win_security_t security;
if (!win_security_init(&security)) {
return INVALID_HANDLE_VALUE;
}
DWORD open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED;
if (first_instance) {
open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
}
HANDLE pipe = CreateNamedPipeW(
pipe_name, open_mode,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
PIPE_UNLIMITED_INSTANCES, 64U * 1024U, 64U * 1024U, 0, &security.attributes);
win_security_destroy(&security);
if (pipe != INVALID_HANDLE_VALUE) {
(void)SetHandleInformation(pipe, HANDLE_FLAG_INHERIT, 0);
}
return pipe;
}
/* This is a namespace reservation, never a protocol endpoint. Every current
* participant owns one idle instance. The first uses
* FILE_FLAG_FIRST_PIPE_INSTANCE; later participants use the same exact pipe
* parameters without that flag. Never calling ConnectNamedPipe means no
* sentinel thread or cancellable I/O survives a participant. */
static HANDLE win_legacy_sentinel_new(const cbm_daemon_ipc_endpoint_t *endpoint, bool first,
DWORD *error_out) {
if (error_out) {
*error_out = ERROR_INVALID_PARAMETER;
}
if (!endpoint || !endpoint->legacy_pipe_name || !error_out) {
return INVALID_HANDLE_VALUE;
}
win_security_t security;
if (!win_security_init(&security)) {
*error_out = ERROR_GEN_FAILURE;
return INVALID_HANDLE_VALUE;
}
SetLastError(ERROR_SUCCESS);
DWORD open_mode = PIPE_ACCESS_DUPLEX;
if (first) {
open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE;
}
HANDLE sentinel = CreateNamedPipeW(endpoint->legacy_pipe_name, open_mode,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT |
PIPE_REJECT_REMOTE_CLIENTS,
PIPE_UNLIMITED_INSTANCES, 1, 1, 0, &security.attributes);
DWORD create_error = sentinel == INVALID_HANDLE_VALUE ? GetLastError() : ERROR_SUCCESS;
win_security_destroy(&security);
if (sentinel != INVALID_HANDLE_VALUE &&
SetHandleInformation(sentinel, HANDLE_FLAG_INHERIT, 0) == 0) {
create_error = GetLastError();
(void)CloseHandle(sentinel);
sentinel = INVALID_HANDLE_VALUE;
}
*error_out = create_error;
return sentinel;
}
static int win_startup_v2_try_acquire(cbm_private_lock_directory_t *directory,
cbm_private_file_lock_t **lock_out) {
if (lock_out) {
*lock_out = NULL;
}
if (!directory || !lock_out) {
return -1;
}
cbm_private_file_lock_status_t status = cbm_private_file_lock_try_acquire(
directory, WIN_STARTUP_V2_LOCK_NAME, CBM_PRIVATE_FILE_LOCK_EX, lock_out);
if (status == CBM_PRIVATE_FILE_LOCK_OK) {
return 1;
}
win_private_lock_release_complete(lock_out);
return status == CBM_PRIVATE_FILE_LOCK_BUSY ? 0 : -1;
}
static bool win_legacy_sentinel_conflict(DWORD error) {
return error == ERROR_ACCESS_DENIED || error == ERROR_PIPE_BUSY ||
error == ERROR_ALREADY_EXISTS;
}
/* The validated legacy mutex serializes the group-file and deterministic-pipe
* observations. startup-v2 is retained by this process, or by the bootstrap
* parent when a child joins. The SH lock is acquired before the mutex is
* released, so group existence and at least one sentinel have no gap. */
static int win_participant_group_join_locked(const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_private_lock_directory_t *directory,
bool allow_first, cbm_private_file_lock_t **group_out,
HANDLE *sentinel_out) {
if (group_out) {
*group_out = NULL;
}
if (sentinel_out) {
*sentinel_out = INVALID_HANDLE_VALUE;
}
if (!endpoint || !directory || !group_out || !sentinel_out) {
return -1;
}
cbm_private_file_lock_t *exclusive_probe = NULL;
cbm_private_file_lock_status_t group_status = cbm_private_file_lock_try_acquire(
directory, WIN_PARTICIPANT_GROUP_LOCK_NAME, CBM_PRIVATE_FILE_LOCK_EX, &exclusive_probe);
bool first = group_status == CBM_PRIVATE_FILE_LOCK_OK;
if (!first && group_status != CBM_PRIVATE_FILE_LOCK_BUSY) {
win_private_lock_release_complete(&exclusive_probe);
return -1;
}
if (first && !allow_first) {
win_private_lock_release_complete(&exclusive_probe);
return 0;
}
int pipe = win_legacy_pipe_probe(endpoint);
if ((first && pipe != 0) || (!first && pipe != 1)) {
win_private_lock_release_complete(&exclusive_probe);
return pipe < 0 ? -1 : 0;
}
DWORD create_error = ERROR_SUCCESS;
HANDLE sentinel = win_legacy_sentinel_new(endpoint, first, &create_error);
if (sentinel == INVALID_HANDLE_VALUE) {
win_private_lock_release_complete(&exclusive_probe);
return win_legacy_sentinel_conflict(create_error) ? 0 : -1;
}
if (exclusive_probe) {
win_private_lock_release_complete(&exclusive_probe);
if (exclusive_probe) {
(void)CloseHandle(sentinel);
return -1;
}
}
cbm_private_file_lock_t *group = NULL;
group_status = cbm_private_file_lock_try_acquire(directory, WIN_PARTICIPANT_GROUP_LOCK_NAME,
CBM_PRIVATE_FILE_LOCK_SH, &group);
if (group_status != CBM_PRIVATE_FILE_LOCK_OK) {
win_private_lock_release_complete(&group);
(void)CloseHandle(sentinel);
return group_status == CBM_PRIVATE_FILE_LOCK_BUSY ? 0 : -1;
}
*group_out = group;
*sentinel_out = sentinel;
return 1;
}
/* Called only while startup-v2 and the validated legacy mutex are retained.
* Group SH disappears before this participant's sentinel; both gates exclude
* joiners from observing that required teardown intermediate state. */
static bool win_participant_group_release_locked(cbm_private_file_lock_t **group_io,
HANDLE *sentinel_io) {
if (!group_io || !sentinel_io) {
return false;
}
if (*group_io && cbm_private_file_lock_release(group_io) != CBM_PRIVATE_FILE_LOCK_OK) {
return false;
}
if (*sentinel_io != INVALID_HANDLE_VALUE) {
if (CloseHandle(*sentinel_io) == 0) {
return false;
}
*sentinel_io = INVALID_HANDLE_VALUE;
}
return true;
}
static void win_participant_group_release_complete(cbm_private_file_lock_t **group_io,
HANDLE *sentinel_io) {
uint64_t deadline = ipc_deadline_after(CBM_DAEMON_IPC_COORDINATION_CLEANUP_MS);
while ((group_io && *group_io) || (sentinel_io && *sentinel_io != INVALID_HANDLE_VALUE)) {
(void)win_participant_group_release_locked(group_io, sentinel_io);
if ((!group_io || !*group_io) && (!sentinel_io || *sentinel_io == INVALID_HANDLE_VALUE)) {
return;
}
if (ipc_now_ms() >= deadline) {
ipc_coordination_cleanup_fail_stop("participant_group_cleanup");
}
Sleep(1);
}
}
static bool win_participant_state_release(const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_private_lock_directory_t *directory,
cbm_private_file_lock_t **startup_v2_io,
win_legacy_mutex_guard_t **legacy_guard_io,
cbm_private_file_lock_t **group_io, HANDLE *sentinel_io) {
if (!endpoint || !directory || !startup_v2_io || !legacy_guard_io || !group_io ||
!sentinel_io) {
return false;
}
if (!*startup_v2_io) {
int startup = win_startup_v2_try_acquire(directory, startup_v2_io);
if (startup != 1) {
return false;
}
}
if (!*legacy_guard_io) {
int legacy = win_legacy_mutex_guard_try_acquire(endpoint, legacy_guard_io);
if (legacy != 1) {
(void)cbm_private_file_lock_release(startup_v2_io);
return false;
}
}
if (!win_participant_group_release_locked(group_io, sentinel_io)) {
return false;
}
if (!win_legacy_mutex_guard_release(legacy_guard_io)) {
return false;
}
return cbm_private_file_lock_release(startup_v2_io) == CBM_PRIVATE_FILE_LOCK_OK;
}
cbm_daemon_ipc_listener_t *cbm_daemon_ipc_listen_reserved(
const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_daemon_ipc_lifetime_reservation_t **reservation_io) {
cbm_daemon_ipc_lifetime_reservation_t *lifetime_reservation =
reservation_io ? *reservation_io : NULL;
if (!lifetime_reservation_matches_endpoint(endpoint, lifetime_reservation) ||
win_endpoint_refresh_rendezvous(endpoint) != WIN_RENDEZVOUS_VALID) {
return NULL;
}
win_generation_address_t *generation = win_endpoint_generation_snapshot(endpoint);
if (!generation || !generation->pipe_name) {
return NULL;
}
cbm_daemon_ipc_listener_t *listener = calloc(1, sizeof(*listener));
if (!listener) {
return NULL;
}
listener->pipe_name = wide_copy(generation->pipe_name);
listener->pipe = INVALID_HANDLE_VALUE;
listener->first_instance = true;
listener->lifetime_reservation = lifetime_reservation;
if (!listener->pipe_name) {
listener->lifetime_reservation = NULL;
cbm_daemon_ipc_listener_close(listener);
return NULL;
}
listener->pipe = win_pipe_instance_new(listener->pipe_name, true);
if (listener->pipe == INVALID_HANDLE_VALUE) {
listener->lifetime_reservation = NULL;
cbm_daemon_ipc_listener_close(listener);
return NULL;
}
listener->first_instance = false;
*reservation_io = NULL;
return listener;
}
cbm_daemon_ipc_listener_t *cbm_daemon_ipc_listen(const cbm_daemon_ipc_endpoint_t *endpoint) {
cbm_daemon_ipc_startup_lock_t *startup = NULL;
cbm_daemon_ipc_participant_guard_t *participant_guard = NULL;
cbm_daemon_ipc_lifetime_reservation_t *reservation = NULL;
if (cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) != 1 ||
!cbm_daemon_ipc_startup_lock_prepare_handoff(startup) ||
cbm_daemon_ipc_participant_guard_try_join(endpoint, &participant_guard) != 1 ||
cbm_daemon_ipc_lifetime_reservation_try_acquire(endpoint, &reservation) != 1) {
cbm_daemon_ipc_lifetime_reservation_release(reservation);
ipc_participant_guard_release_complete(&participant_guard);
ipc_startup_lock_release_complete(&startup);
return NULL;
}
cbm_daemon_ipc_listener_t *listener = cbm_daemon_ipc_listen_reserved(endpoint, &reservation);
if (listener) {
listener->participant_guard = participant_guard;
participant_guard = NULL;
}
cbm_daemon_ipc_lifetime_reservation_release(reservation);
ipc_participant_guard_release_complete(&participant_guard);
ipc_startup_lock_release_complete(&startup);
return listener;
}
void cbm_daemon_ipc_listener_close(cbm_daemon_ipc_listener_t *listener) {
if (!listener) {
return;
}
if (listener->pipe != INVALID_HANDLE_VALUE) {
(void)CancelIoEx(listener->pipe, NULL);
(void)DisconnectNamedPipe(listener->pipe);
(void)CloseHandle(listener->pipe);
}
if (listener->connect_event) {
(void)CloseHandle(listener->connect_event);
}
cbm_daemon_ipc_lifetime_reservation_release(listener->lifetime_reservation);
ipc_participant_guard_release_complete(&listener->participant_guard);
free(listener->pipe_name);
free(listener);
}
typedef struct {
HANDLE handle;
OVERLAPPED *overlapped;
} win_pending_io_t;
static cbm_ipc_pending_wait_status_t win_pending_wait(void *opaque, uint32_t timeout_ms) {
win_pending_io_t *pending = (win_pending_io_t *)opaque;
DWORD result = WaitForSingleObject(pending->overlapped->hEvent, timeout_ms);
if (result == WAIT_OBJECT_0) {
return CBM_IPC_PENDING_WAIT_SIGNALED;
}
return result == WAIT_TIMEOUT ? CBM_IPC_PENDING_WAIT_TIMEOUT : CBM_IPC_PENDING_WAIT_FAILED;
}
static void win_pending_cancel(void *opaque) {
win_pending_io_t *pending = (win_pending_io_t *)opaque;
(void)CancelIoEx(pending->handle, pending->overlapped);
}
static cbm_ipc_pending_finish_status_t win_pending_finish(void *opaque, bool blocking,
uint32_t *transferred_out) {
win_pending_io_t *pending = (win_pending_io_t *)opaque;
DWORD transferred = 0;
if (GetOverlappedResult(pending->handle, pending->overlapped, &transferred,
blocking ? TRUE : FALSE)) {
*transferred_out = transferred;
return CBM_IPC_PENDING_FINISH_COMPLETED;
}
return GetLastError() == ERROR_OPERATION_ABORTED ? CBM_IPC_PENDING_FINISH_CANCELLED
: CBM_IPC_PENDING_FINISH_FAILED;
}
static int win_overlapped_wait(HANDLE handle, OVERLAPPED *overlapped, DWORD timeout_ms,
DWORD *transferred_out) {
win_pending_io_t pending = {.handle = handle, .overlapped = overlapped};
cbm_ipc_pending_ops_t ops = {
.context = &pending,
.wait = win_pending_wait,
.cancel = win_pending_cancel,
.finish = win_pending_finish,
};
uint32_t transferred = 0;
int result = cbm_daemon_ipc_wait_pending(&ops, timeout_ms, &transferred);
if (result == 1) {
*transferred_out = (DWORD)transferred;
}
return result;
}
static bool win_pipe_client_is_current_user(HANDLE pipe) {
/* Validate the connected client by its process token, resolved from the
* pipe's CLIENT process id - the mirror of the client-side server check
* (win_pipe_server_is_current_user via GetNamedPipeServerProcessId). This
* needs neither impersonation nor a prior read on the pipe, so it works at
* accept time without changing accept's contract. ImpersonateNamedPipeClient
* was unusable here: it fails with ERROR_CANNOT_IMPERSONATE until the server
* has completed a read, which accept must not require. */
HMODULE kernel = GetModuleHandleW(L"kernel32.dll");
get_named_pipe_server_process_id_fn get_client_pid =
kernel ? (get_named_pipe_server_process_id_fn)(void (*)(void))GetProcAddress(
kernel, "GetNamedPipeClientProcessId")
: NULL;
if (!get_client_pid) {
cbm_log_warn("daemon.accept.client_identity", "step", "pid_fn_missing");
return false;
}
ULONG process_id = 0;
if (!get_client_pid(pipe, &process_id) || process_id == 0) {
char error_text[16];
(void)snprintf(error_text, sizeof(error_text), "%lu", (unsigned long)GetLastError());
cbm_log_warn("daemon.accept.client_identity", "step", "client_pid_query", "error",
error_text);
return false;
}
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);
if (!process) {
process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, process_id);
}
if (!process) {
char pid_text[16];
char error_text[16];
(void)snprintf(pid_text, sizeof(pid_text), "%lu", (unsigned long)process_id);
(void)snprintf(error_text, sizeof(error_text), "%lu", (unsigned long)GetLastError());
cbm_log_warn("daemon.accept.client_identity", "step", "process_open", "pid", pid_text,
"error", error_text);
return false;
}
win_security_t security;
bool initialized = win_security_init(&security);
HANDLE token = NULL;
bool opened = initialized && security.open_process_token(process, TOKEN_QUERY, &token) != 0;
PSID token_sid = NULL;
void *token_user = opened ? win_token_user_query(&security, token, &token_sid) : NULL;
bool same_user =
token_user && token_sid && security.equal_sid(token_sid, security.user_sid) != 0;
if (!same_user) {
const char *step = !initialized ? "security_init"
: !opened ? "token_open"
: !token_sid ? "token_query"
: "sid_mismatch";
cbm_log_warn("daemon.accept.client_identity", "step", step);
}
free(token_user);
if (token) {
(void)CloseHandle(token);
}
if (initialized) {
win_security_destroy(&security);
}
(void)CloseHandle(process);
return same_user;
}
int cbm_daemon_ipc_accept(cbm_daemon_ipc_listener_t *listener, uint32_t timeout_ms,
cbm_daemon_ipc_connection_t **connection_out) {
if (connection_out) {
*connection_out = NULL;
}
if (!listener || !connection_out) {
return -1;
}
if (listener->pipe == INVALID_HANDLE_VALUE) {
listener->pipe = win_pipe_instance_new(listener->pipe_name, listener->first_instance);
listener->first_instance = false;
listener->connect_pending = false;
if (listener->pipe == INVALID_HANDLE_VALUE) {
return -1;
}
}
if (!listener->connect_pending) {
if (!listener->connect_event) {
listener->connect_event = CreateEventW(NULL, TRUE, FALSE, NULL);
if (!listener->connect_event) {
return -1;
}
}
if (!ResetEvent(listener->connect_event)) {
return -1;
}
memset(&listener->connect_overlapped, 0, sizeof(listener->connect_overlapped));
listener->connect_overlapped.hEvent = listener->connect_event;
BOOL connected = ConnectNamedPipe(listener->pipe, &listener->connect_overlapped);
DWORD connect_error = connected ? ERROR_PIPE_CONNECTED : GetLastError();
if (connect_error == ERROR_IO_PENDING) {
listener->connect_pending = true;
} else if (connect_error != ERROR_PIPE_CONNECTED) {
(void)DisconnectNamedPipe(listener->pipe);
(void)CloseHandle(listener->pipe);
listener->pipe = INVALID_HANDLE_VALUE;
return -1;
}
}
if (listener->connect_pending) {
DWORD wait = WaitForSingleObject(listener->connect_event, timeout_ms);
if (wait == WAIT_TIMEOUT) {
/* Keep the instance and its pending connect armed: a listening
* pipe must stay available for clients between polls. */
return 0;
}
DWORD transferred = 0;
if (wait != WAIT_OBJECT_0 ||
!GetOverlappedResult(listener->pipe, &listener->connect_overlapped, &transferred,
FALSE)) {
DWORD connect_error = GetLastError();
listener->connect_pending = false;
if (connect_error != ERROR_PIPE_CONNECTED) {
(void)DisconnectNamedPipe(listener->pipe);
(void)CloseHandle(listener->pipe);
listener->pipe = INVALID_HANDLE_VALUE;
return -1;
}
}
listener->connect_pending = false;
}
cbm_daemon_ipc_connection_t *connection = malloc(sizeof(*connection));
if (!connection) {
(void)DisconnectNamedPipe(listener->pipe);
(void)CloseHandle(listener->pipe);
listener->pipe = INVALID_HANDLE_VALUE;
return -1;
}
connection->handle = listener->pipe;
atomic_init(&connection->poisoned, false);
connection->role = CBM_DAEMON_IPC_PIPE_ROLE_ACCEPTED_SERVER;
/* Validate the client's identity by its process token (no impersonation,
* no read) so accept keeps its immediate-return contract. */
if (!win_pipe_client_is_current_user(listener->pipe)) {
cbm_log_warn("daemon.accept.client_rejected", "stage", "client_identity");
free(connection);
(void)DisconnectNamedPipe(listener->pipe);
(void)CloseHandle(listener->pipe);
listener->pipe = INVALID_HANDLE_VALUE;
return -1;
}
listener->pipe = INVALID_HANDLE_VALUE;
*connection_out = connection;
return 1;
}
static bool win_pipe_server_is_current_user(HANDLE pipe) {
HMODULE kernel = GetModuleHandleW(L"kernel32.dll");
get_named_pipe_server_process_id_fn get_server_pid =
kernel ? (get_named_pipe_server_process_id_fn)(void (*)(void))GetProcAddress(
kernel, "GetNamedPipeServerProcessId")
: NULL;
ULONG process_id = 0;
if (!get_server_pid) {
cbm_log_warn("daemon.client.server_identity", "step", "pid_fn_missing");
return false;
}
if (!get_server_pid(pipe, &process_id) || process_id == 0) {
char error_text[16];
(void)snprintf(error_text, sizeof(error_text), "%lu", (unsigned long)GetLastError());
char pid_text[16];
(void)snprintf(pid_text, sizeof(pid_text), "%lu", (unsigned long)process_id);
cbm_log_warn("daemon.client.server_identity", "step", "server_pid_query", "error",
error_text, "pid", pid_text);
return false;
}
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);
if (!process) {
process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, process_id);
}
if (!process) {
/* A pipe whose server process cannot be opened is usually a dead
* server whose PID Windows already reaped or reused. */
char pid_text[16];
(void)snprintf(pid_text, sizeof(pid_text), "%lu", (unsigned long)process_id);
char error_text[16];
(void)snprintf(error_text, sizeof(error_text), "%lu", (unsigned long)GetLastError());
cbm_log_warn("daemon.client.server_identity", "step", "process_open", "pid", pid_text,
"error", error_text);
return false;
}
win_security_t security;
bool initialized = win_security_init(&security);
HANDLE token = NULL;
bool opened = initialized && security.open_process_token(process, TOKEN_QUERY, &token) != 0;
PSID token_sid = NULL;
void *token_user = opened ? win_token_user_query(&security, token, &token_sid) : NULL;
bool same_user =
token_user && token_sid && security.equal_sid(token_sid, security.user_sid) != 0;
if (!same_user) {
/* Name the failing step and the peer: a rejected server can be a dead
* PID that Windows already reused (often for a SYSTEM service), which
* looks identical to a hostile pipe without this classification. */
const char *step = !initialized ? "security_init"
: !opened ? "token_open"
: !token_sid ? "token_query"
: security.is_well_known_sid(token_sid, WinLocalSystemSid)
? "server_is_system"
: security.is_well_known_sid(token_sid, WinBuiltinAdministratorsSid)
? "server_is_admins"
: "server_other_account";
char pid_text[16];
(void)snprintf(pid_text, sizeof(pid_text), "%lu", (unsigned long)process_id);
cbm_log_warn("daemon.client.server_identity", "step", step, "pid", pid_text);
}
free(token_user);
if (token) {
(void)CloseHandle(token);
}
if (initialized) {
win_security_destroy(&security);
}
(void)CloseHandle(process);
return same_user;
}
static int win_current_generation_transport_probe(const cbm_daemon_ipc_endpoint_t *endpoint) {
win_rendezvous_status_t rendezvous = win_endpoint_refresh_rendezvous(endpoint);
if (rendezvous == WIN_RENDEZVOUS_ABSENT) {
return 0;
}
if (rendezvous != WIN_RENDEZVOUS_VALID) {
return -1;
}
win_generation_address_t *generation = win_endpoint_generation_snapshot(endpoint);
if (!generation || !generation->pipe_name) {
return -1;
}
HANDLE pipe = CreateFileW(generation->pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (pipe != INVALID_HANDLE_VALUE) {
(void)SetHandleInformation(pipe, HANDLE_FLAG_INHERIT, 0);
DWORD mode = PIPE_READMODE_BYTE;
bool authenticated = SetNamedPipeHandleState(pipe, &mode, NULL, NULL) != 0 &&
win_pipe_server_is_current_user(pipe);
(void)CloseHandle(pipe);
return authenticated ? 1 : -1;
}
DWORD error = GetLastError();
if (error == ERROR_PIPE_BUSY) {
return 1;
}
return error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND ? 0 : -1;
}
int cbm_daemon_ipc_endpoint_probe(const cbm_daemon_ipc_endpoint_t *endpoint, uint32_t timeout_ms) {
(void)timeout_ms;
if (!endpoint) {
return -1;
}
win_rendezvous_status_t rendezvous = win_endpoint_refresh_rendezvous(endpoint);
if (rendezvous != WIN_RENDEZVOUS_VALID) {
if (rendezvous == WIN_RENDEZVOUS_CORRUPT || rendezvous == WIN_RENDEZVOUS_ERROR) {
return -1;
}
cbm_private_lock_directory_t *directory = NULL;
if (cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory) !=
CBM_PRIVATE_FILE_LOCK_OK) {
return -1;
}
int startup = win_private_lock_probe(directory, WIN_STARTUP_V2_LOCK_NAME);
cbm_private_lock_directory_close(directory);
return startup;
}
win_generation_address_t *generation = win_endpoint_generation_snapshot(endpoint);
if (!generation) {
return -1;
}
HANDLE pipe = CreateFileW(generation->pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (pipe != INVALID_HANDLE_VALUE) {
(void)SetHandleInformation(pipe, HANDLE_FLAG_INHERIT, 0);
DWORD mode = PIPE_READMODE_BYTE;
bool authenticated = SetNamedPipeHandleState(pipe, &mode, NULL, NULL) != 0 &&
win_pipe_server_is_current_user(pipe);
(void)CloseHandle(pipe);
return authenticated ? 1 : -1;
}
DWORD open_error = GetLastError();
if (open_error == ERROR_PIPE_BUSY) {
/* Every server instance is occupied, which is itself positive
* liveness evidence. Treat capacity saturation as active. */
return 1;
}
if (open_error == ERROR_FILE_NOT_FOUND || open_error == ERROR_PATH_NOT_FOUND) {
int lifetime = cbm_daemon_ipc_lifetime_reservation_probe(endpoint);
if (lifetime != 0) {
return lifetime;
}
cbm_private_lock_directory_t *directory = NULL;
if (cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory) !=
CBM_PRIVATE_FILE_LOCK_OK) {
return -1;
}
int startup = win_private_lock_probe(directory, WIN_STARTUP_V2_LOCK_NAME);
cbm_private_lock_directory_close(directory);
return startup;
}
return -1;
}
cbm_daemon_ipc_connection_t *cbm_daemon_ipc_connect(const cbm_daemon_ipc_endpoint_t *endpoint,
uint32_t timeout_ms) {
if (!endpoint) {
return NULL;
}
uint64_t deadline_ms = ipc_deadline_after(timeout_ms);
HANDLE pipe = INVALID_HANDLE_VALUE;
bool wait_logged = false;
for (;;) {
win_rendezvous_status_t rendezvous = win_endpoint_refresh_rendezvous(endpoint);
if (rendezvous == WIN_RENDEZVOUS_CORRUPT || rendezvous == WIN_RENDEZVOUS_ERROR) {
/* Terminal refusal: without a reason here the caller can only
* report a generic connect timeout, which made owner-policy
* failures on the rendezvous namespace undiagnosable. */
cbm_log_warn("daemon.client.rendezvous_unreadable", "status",
rendezvous == WIN_RENDEZVOUS_CORRUPT ? "corrupt" : "unsafe_or_io");
return NULL;
}
win_generation_address_t *generation =
rendezvous == WIN_RENDEZVOUS_VALID ? win_endpoint_generation_snapshot(endpoint) : NULL;
if (!generation) {
if (win_retry_pause(deadline_ms)) {
if (!wait_logged) {
wait_logged = true;
cbm_log_warn("daemon.client.rendezvous_wait", "status",
rendezvous == WIN_RENDEZVOUS_ABSENT ? "absent"
: rendezvous == WIN_RENDEZVOUS_BUSY ? "busy"
: rendezvous == WIN_RENDEZVOUS_VALID ? "no_generation"
: "unknown");
}
continue;
}
cbm_log_warn("daemon.client.connect_deadline", "status",
rendezvous == WIN_RENDEZVOUS_ABSENT ? "absent"
: rendezvous == WIN_RENDEZVOUS_BUSY ? "busy"
: rendezvous == WIN_RENDEZVOUS_VALID ? "no_generation"
: "unknown");
return NULL;
}
pipe = CreateFileW(generation->pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (pipe != INVALID_HANDLE_VALUE) {
break;
}
DWORD open_error = GetLastError();
if ((open_error == ERROR_FILE_NOT_FOUND || open_error == ERROR_PATH_NOT_FOUND) &&
win_retry_pause(deadline_ms)) {
continue;
}
if (open_error != ERROR_PIPE_BUSY) {
return NULL;
}
DWORD remaining = win_deadline_remaining(deadline_ms);
if (remaining == 0) {
return NULL;
}
if (!WaitNamedPipeW(generation->pipe_name, remaining)) {
DWORD wait_error = GetLastError();
if ((wait_error == ERROR_FILE_NOT_FOUND || wait_error == ERROR_PATH_NOT_FOUND) &&
win_retry_pause(deadline_ms)) {
continue;
}
return NULL;
}
}
(void)SetHandleInformation(pipe, HANDLE_FLAG_INHERIT, 0);
DWORD mode = PIPE_READMODE_BYTE;
if (!SetNamedPipeHandleState(pipe, &mode, NULL, NULL) ||
!win_pipe_server_is_current_user(pipe)) {
cbm_log_warn("daemon.client.pipe_rejected", "stage", "server_identity");
(void)CloseHandle(pipe);
return NULL;
}
cbm_daemon_ipc_connection_t *connection = malloc(sizeof(*connection));
if (!connection) {
(void)CloseHandle(pipe);
return NULL;
}
connection->handle = pipe;
atomic_init(&connection->poisoned, false);
connection->role = CBM_DAEMON_IPC_PIPE_ROLE_CONNECTED_CLIENT;
return connection;
}
void cbm_daemon_ipc_connection_close(cbm_daemon_ipc_connection_t *connection) {
if (!connection) {
return;
}
if (connection->handle != INVALID_HANDLE_VALUE) {
(void)CancelIoEx(connection->handle, NULL);
(void)CloseHandle(connection->handle);
}
free(connection);
}
static int win_io_once(cbm_daemon_ipc_connection_t *connection, void *buffer, DWORD length,
bool writing, uint64_t deadline_ms, DWORD *transferred_out);
void cbm_daemon_ipc_connection_drain(cbm_daemon_ipc_connection_t *connection, uint32_t timeout_ms) {
if (!connection || connection->handle == INVALID_HANDLE_VALUE ||
connection->role != CBM_DAEMON_IPC_PIPE_ROLE_ACCEPTED_SERVER ||
atomic_load_explicit(&connection->poisoned, memory_order_acquire)) {
return;
}
/* Closing the server end of a named pipe discards data the client has not
* read yet, so a final response followed by an immediate close races the
* client's decode. Reading until peer EOF proves consumption: clients
* close only after draining their pending response. A poisoned or
* interrupted connection returns immediately above/below, so ordinary
* EOF-triggered teardown pays nothing here. */
uint64_t deadline_ms = ipc_deadline_after(timeout_ms);
for (;;) {
uint8_t discard[256];
DWORD transferred = 0;
int result =
win_io_once(connection, discard, sizeof(discard), false, deadline_ms, &transferred);
if (result != 1 || transferred == 0) {
return;
}
}
}
void cbm_daemon_ipc_connection_interrupt(cbm_daemon_ipc_connection_t *connection) {
if (!connection || connection->handle == INVALID_HANDLE_VALUE) {
return;
}
(void)CancelIoEx(connection->handle, NULL);
if (connection->role == CBM_DAEMON_IPC_PIPE_ROLE_ACCEPTED_SERVER) {
(void)DisconnectNamedPipe(connection->handle);
}
}
uint64_t cbm_daemon_ipc_connection_peer_pid(const cbm_daemon_ipc_connection_t *connection) {
if (!connection || !connection->handle || connection->handle == INVALID_HANDLE_VALUE) {
return 0;
}
HMODULE kernel = GetModuleHandleW(L"kernel32.dll");
ULONG process_id = 0;
if (connection->role == CBM_DAEMON_IPC_PIPE_ROLE_ACCEPTED_SERVER) {
get_named_pipe_client_process_id_fn get_client_pid =
kernel ? (get_named_pipe_client_process_id_fn)(void (*)(void))GetProcAddress(
kernel, "GetNamedPipeClientProcessId")
: NULL;
if (!get_client_pid || !get_client_pid(connection->handle, &process_id)) {
return 0;
}
} else if (connection->role == CBM_DAEMON_IPC_PIPE_ROLE_CONNECTED_CLIENT) {
get_named_pipe_server_process_id_fn get_server_pid =
kernel ? (get_named_pipe_server_process_id_fn)(void (*)(void))GetProcAddress(
kernel, "GetNamedPipeServerProcessId")
: NULL;
if (!get_server_pid || !get_server_pid(connection->handle, &process_id)) {
return 0;
}
} else {
return 0;
}
return process_id == 0 ? 0 : (uint64_t)process_id;
}
static int win_io_once(cbm_daemon_ipc_connection_t *connection, void *buffer, DWORD length,
bool writing, uint64_t deadline_ms, DWORD *transferred_out) {
OVERLAPPED overlapped;
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
if (!overlapped.hEvent) {
return -1;
}
DWORD transferred = 0;
BOOL completed = writing
? WriteFile(connection->handle, buffer, length, &transferred, &overlapped)
: ReadFile(connection->handle, buffer, length, &transferred, &overlapped);
int result = 1;
if (!completed) {
if (GetLastError() != ERROR_IO_PENDING) {
result = -1;
} else {
result = win_overlapped_wait(connection->handle, &overlapped,
win_deadline_remaining(deadline_ms), &transferred);
}
}
(void)CloseHandle(overlapped.hEvent);
if (result == 1) {
*transferred_out = transferred;
}
return result;
}
static int connection_read_full(cbm_daemon_ipc_connection_t *connection, void *buffer,
size_t length, uint64_t deadline_ms) {
if (!connection || atomic_load_explicit(&connection->poisoned, memory_order_acquire)) {
return -1;
}
size_t offset = 0;
while (offset < length) {
DWORD chunk = length - offset > MAXDWORD ? MAXDWORD : (DWORD)(length - offset);
DWORD transferred = 0;
int result = win_io_once(connection, (uint8_t *)buffer + offset, chunk, false, deadline_ms,
&transferred);
if (result != 1) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return result;
}
if (transferred == 0) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return -1;
}
offset += transferred;
}
return 1;
}
static int connection_write_full(cbm_daemon_ipc_connection_t *connection, const void *buffer,
size_t length, uint64_t deadline_ms) {
if (!connection || atomic_load_explicit(&connection->poisoned, memory_order_acquire)) {
return -1;
}
size_t offset = 0;
while (offset < length) {
DWORD chunk = length - offset > MAXDWORD ? MAXDWORD : (DWORD)(length - offset);
DWORD transferred = 0;
int result = win_io_once(connection, (void *)((const uint8_t *)buffer + offset), chunk,
true, deadline_ms, &transferred);
if (result != 1) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return result;
}
if (transferred == 0) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return -1;
}
offset += transferred;
}
return 1;
}
int cbm_daemon_ipc_startup_lock_try_acquire(const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_daemon_ipc_startup_lock_t **lock_out) {
if (lock_out) {
*lock_out = NULL;
}
if (!endpoint || !lock_out) {
return -1;
}
cbm_private_lock_directory_t *directory = NULL;
if (cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory) !=
CBM_PRIVATE_FILE_LOCK_OK) {
return -1;
}
cbm_private_file_lock_t *startup_v2 = NULL;
int startup_status = win_startup_v2_try_acquire(directory, &startup_v2);
if (startup_status != 1) {
cbm_private_lock_directory_close(directory);
return startup_status;
}
win_legacy_mutex_guard_t *legacy_guard = NULL;
int legacy_status = win_legacy_mutex_guard_try_acquire(endpoint, &legacy_guard);
if (legacy_status != 1) {
win_private_lock_release_complete(&startup_v2);
cbm_private_lock_directory_close(directory);
return legacy_status;
}
cbm_daemon_ipc_startup_lock_t *lock = calloc(1, sizeof(*lock));
if (!lock) {
win_legacy_mutex_guard_release_complete(&legacy_guard);
win_private_lock_release_complete(&startup_v2);
cbm_private_lock_directory_close(directory);
return -1;
}
lock->endpoint = endpoint;
lock->directory = directory;
lock->startup_v2_lock = startup_v2;
lock->legacy_guard = legacy_guard;
lock->legacy_sentinel = INVALID_HANDLE_VALUE;
*lock_out = lock;
/* The lock is held and the handoff has not run yet: the one point where a
* test can pin this interleaving deterministically. No-op in production. */
ipc_startup_gate_run();
return 1;
}
int cbm_daemon_ipc_generation_probe_under_startup_lock(
const cbm_daemon_ipc_endpoint_t *endpoint, const cbm_daemon_ipc_startup_lock_t *startup_lock) {
if (!endpoint || !startup_lock || startup_lock->endpoint != endpoint ||
!startup_lock->directory || !startup_lock->startup_v2_lock || !startup_lock->legacy_guard ||
startup_lock->prepared || startup_lock->group_lock ||
startup_lock->legacy_sentinel != INVALID_HANDLE_VALUE) {
return -1;
}
int lifetime = cbm_daemon_ipc_lifetime_reservation_probe(endpoint);
if (lifetime != 0) {
return lifetime;
}
int deterministic_pipe = win_legacy_pipe_probe(endpoint);
if (deterministic_pipe != 0) {
return deterministic_pipe;
}
return win_current_generation_transport_probe(endpoint);
}
bool cbm_daemon_ipc_startup_lock_prepare_handoff(cbm_daemon_ipc_startup_lock_t *lock) {
if (!lock || !lock->endpoint || !lock->directory || !lock->startup_v2_lock) {
return false;
}
if (lock->prepared) {
return lock->group_lock && lock->legacy_sentinel != INVALID_HANDLE_VALUE &&
!lock->legacy_guard;
}
if (!lock->legacy_guard || lock->group_lock || lock->legacy_sentinel != INVALID_HANDLE_VALUE) {
return false;
}
int joined = win_participant_group_join_locked(lock->endpoint, lock->directory, true,
&lock->group_lock, &lock->legacy_sentinel);
if (joined != 1) {
return false;
}
int generation = win_startup_prepare_generation(lock->endpoint, lock->directory);
if (generation != 1) {
(void)win_participant_group_release_locked(&lock->group_lock, &lock->legacy_sentinel);
return false;
}
if (!win_legacy_mutex_guard_release(&lock->legacy_guard)) {
return false;
}
lock->prepared = true;
return true;
}
bool cbm_daemon_ipc_startup_lock_release(cbm_daemon_ipc_startup_lock_t **lock_io) {
if (!lock_io) {
return false;
}
cbm_daemon_ipc_startup_lock_t *lock = *lock_io;
if (!lock) {
return true;
}
if (!win_participant_state_release(lock->endpoint, lock->directory, &lock->startup_v2_lock,
&lock->legacy_guard, &lock->group_lock,
&lock->legacy_sentinel)) {
return false;
}
cbm_private_lock_directory_close(lock->directory);
free(lock);
*lock_io = NULL;
return true;
}
int cbm_daemon_ipc_participant_guard_try_join(const cbm_daemon_ipc_endpoint_t *endpoint,
cbm_daemon_ipc_participant_guard_t **guard_out) {
if (guard_out) {
*guard_out = NULL;
}
if (!endpoint || !guard_out) {
return -1;
}
cbm_daemon_ipc_participant_guard_t *guard = calloc(1, sizeof(*guard));
if (!guard) {
return -1;
}
cbm_private_lock_directory_t *directory = NULL;
if (cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory) !=
CBM_PRIVATE_FILE_LOCK_OK) {
free(guard);
return -1;
}
win_legacy_mutex_guard_t *legacy_guard = NULL;
int legacy = win_legacy_mutex_guard_try_acquire(endpoint, &legacy_guard);
if (legacy != 1) {
cbm_private_lock_directory_close(directory);
free(guard);
return legacy;
}
cbm_private_file_lock_t *group = NULL;
HANDLE sentinel = INVALID_HANDLE_VALUE;
int joined = win_participant_group_join_locked(endpoint, directory, false, &group, &sentinel);
bool mutex_released = win_legacy_mutex_guard_release(&legacy_guard);
if (joined != 1 || !mutex_released) {
if (group || sentinel != INVALID_HANDLE_VALUE) {
win_participant_group_release_complete(&group, &sentinel);
}
win_legacy_mutex_guard_release_complete(&legacy_guard);
cbm_private_lock_directory_close(directory);
free(guard);
return joined == 0 && mutex_released ? 0 : -1;
}
guard->endpoint = endpoint;
guard->directory = directory;
guard->group_lock = group;
guard->legacy_sentinel = sentinel;
*guard_out = guard;
return 1;
}
bool cbm_daemon_ipc_participant_guard_release(cbm_daemon_ipc_participant_guard_t **guard_io) {
if (!guard_io) {
return false;
}
cbm_daemon_ipc_participant_guard_t *guard = *guard_io;
if (!guard) {
return true;
}
if (!win_participant_state_release(
guard->endpoint, guard->directory, &guard->teardown_startup_v2_lock,
&guard->teardown_legacy_guard, &guard->group_lock, &guard->legacy_sentinel)) {
return false;
}
cbm_private_lock_directory_close(guard->directory);
free(guard);
*guard_io = NULL;
return true;
}
int cbm_daemon_ipc_local_transition_try_acquire(
const cbm_daemon_ipc_endpoint_t *endpoint, cbm_daemon_ipc_local_transition_t **transition_out) {
if (transition_out) {
*transition_out = NULL;
}
if (!endpoint || !transition_out) {
return -1;
}
cbm_private_lock_directory_t *directory = NULL;
if (cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory) !=
CBM_PRIVATE_FILE_LOCK_OK) {
return -1;
}
cbm_private_file_lock_t *startup_v2 = NULL;
int startup = win_startup_v2_try_acquire(directory, &startup_v2);
if (startup != 1) {
cbm_private_lock_directory_close(directory);
return startup;
}
cbm_daemon_ipc_local_transition_t *transition = calloc(1, sizeof(*transition));
if (!transition) {
win_private_lock_release_complete(&startup_v2);
cbm_private_lock_directory_close(directory);
return -1;
}
transition->endpoint = endpoint;
transition->directory = directory;
transition->startup_v2_lock = startup_v2;
transition->legacy_sentinel = INVALID_HANDLE_VALUE;
*transition_out = transition;
return 1;
}
int cbm_daemon_ipc_local_transition_seal_legacy(cbm_daemon_ipc_local_transition_t *transition) {
if (!transition || !transition->endpoint || !transition->directory ||
!transition->startup_v2_lock || transition->work_begun) {
return -1;
}
if (transition->sealed) {
return transition->group_lock && transition->legacy_sentinel != INVALID_HANDLE_VALUE ? 1
: -1;
}
win_legacy_mutex_guard_t *legacy_guard = NULL;
int legacy = win_legacy_mutex_guard_try_acquire(transition->endpoint, &legacy_guard);
if (legacy != 1) {
return legacy;
}
int joined =
win_participant_group_join_locked(transition->endpoint, transition->directory, true,
&transition->group_lock, &transition->legacy_sentinel);
bool mutex_released = win_legacy_mutex_guard_release(&legacy_guard);
if (joined != 1 || !mutex_released) {
if (legacy_guard || transition->group_lock ||
transition->legacy_sentinel != INVALID_HANDLE_VALUE) {
transition->teardown_legacy_guard = legacy_guard;
}
return joined == 0 && mutex_released ? 0 : -1;
}
transition->sealed = true;
return 1;
}
int cbm_daemon_ipc_local_transition_lifetime_probe(
const cbm_daemon_ipc_endpoint_t *endpoint,
const cbm_daemon_ipc_local_transition_t *transition) {
if (!endpoint || !transition || transition->endpoint != endpoint || !transition->directory ||
!transition->startup_v2_lock || !transition->sealed || transition->work_begun ||
!transition->group_lock || transition->legacy_sentinel == INVALID_HANDLE_VALUE) {
return -1;
}
return cbm_daemon_ipc_lifetime_reservation_probe(endpoint);
}
bool cbm_daemon_ipc_local_transition_begin_work(cbm_daemon_ipc_local_transition_t *transition) {
if (!transition || !transition->endpoint || !transition->directory ||
!transition->startup_v2_lock || !transition->sealed || transition->work_begun ||
!transition->group_lock || transition->legacy_sentinel == INVALID_HANDLE_VALUE) {
return false;
}
if (cbm_private_file_lock_release(&transition->startup_v2_lock) != CBM_PRIVATE_FILE_LOCK_OK) {
return false;
}
transition->work_begun = true;
return true;
}
bool cbm_daemon_ipc_local_transition_release(cbm_daemon_ipc_local_transition_t **transition_io) {
if (!transition_io) {
return false;
}
cbm_daemon_ipc_local_transition_t *transition = *transition_io;
if (!transition) {
return true;
}
if (!transition->endpoint || !transition->directory) {
return false;
}
if (transition->group_lock || transition->legacy_sentinel != INVALID_HANDLE_VALUE ||
transition->teardown_legacy_guard) {
if (!win_participant_state_release(transition->endpoint, transition->directory,
&transition->startup_v2_lock,
&transition->teardown_legacy_guard,
&transition->group_lock, &transition->legacy_sentinel)) {
return false;
}
} else if (transition->startup_v2_lock &&
cbm_private_file_lock_release(&transition->startup_v2_lock) !=
CBM_PRIVATE_FILE_LOCK_OK) {
return false;
}
cbm_private_lock_directory_close(transition->directory);
transition->directory = NULL;
free(transition);
*transition_io = NULL;
return true;
}
#endif /* _WIN32 */
bool cbm_daemon_ipc_send_frame(cbm_daemon_ipc_connection_t *connection,
cbm_daemon_frame_type_t type, uint16_t flags, const void *payload,
uint32_t length) {
if (!connection || atomic_load_explicit(&connection->poisoned, memory_order_acquire) ||
(length > 0 && !payload)) {
return false;
}
uint8_t header[CBM_DAEMON_FRAME_HEADER_SIZE];
if (!cbm_daemon_frame_header_encode(header, type, flags, length)) {
return false;
}
uint64_t deadline_ms = ipc_deadline_after(CBM_DAEMON_IPC_SEND_TIMEOUT_MS);
if (connection_write_full(connection, header, sizeof(header), deadline_ms) != 1) {
return false;
}
if (length > 0 && connection_write_full(connection, payload, length, deadline_ms) != 1) {
/* The peer has already received a complete header and will interpret
* subsequent bytes as this payload, so this stream cannot be reused. */
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return false;
}
return true;
}
int cbm_daemon_ipc_receive_frame_bounded(cbm_daemon_ipc_connection_t *connection,
uint32_t timeout_ms, uint32_t max_payload_length,
cbm_daemon_frame_t *frame_out, uint8_t **payload_out) {
if (payload_out) {
*payload_out = NULL;
}
if (!connection || atomic_load_explicit(&connection->poisoned, memory_order_acquire) ||
!frame_out || !payload_out) {
return -1;
}
memset(frame_out, 0, sizeof(*frame_out));
uint64_t deadline_ms = ipc_deadline_after(timeout_ms);
uint8_t header[CBM_DAEMON_FRAME_HEADER_SIZE];
int result = connection_read_full(connection, header, sizeof(header), deadline_ms);
if (result != 1) {
return result;
}
cbm_daemon_frame_t frame;
if (!cbm_daemon_frame_header_decode(header, &frame)) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return -1;
}
if (frame.length > max_payload_length) {
/* The fixed unauthenticated envelope limit is enforced from the header
* before allocating or reading attacker-controlled payload bytes. The
* unread stream is necessarily unusable and is closed by its owner. */
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return -1;
}
uint8_t *payload = NULL;
if (frame.length > 0) {
payload = malloc(frame.length);
if (!payload) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return -1;
}
result = connection_read_full(connection, payload, frame.length, deadline_ms);
if (result != 1) {
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
free(payload);
return result;
}
}
*frame_out = frame;
*payload_out = payload;
return 1;
}
int cbm_daemon_ipc_receive_frame(cbm_daemon_ipc_connection_t *connection, uint32_t timeout_ms,
cbm_daemon_frame_t *frame_out, uint8_t **payload_out) {
return cbm_daemon_ipc_receive_frame_bounded(connection, timeout_ms, CBM_DAEMON_MAX_FRAME_SIZE,
frame_out, payload_out);
}
int cbm_daemon_ipc_receive_frame_after_failed_send(cbm_daemon_ipc_connection_t *connection,
uint32_t timeout_ms, uint32_t max_payload_length,
cbm_daemon_frame_t *frame_out,
uint8_t **payload_out) {
if (!connection) {
return cbm_daemon_ipc_receive_frame_bounded(NULL, timeout_ms, max_payload_length, frame_out,
payload_out);
}
/* The poison came from the failed send alone (nothing was read yet), and it
* guards the OUTBOUND frame boundary; the inbound one is intact. Lift it for
* this one receive, then restore it so no later send can reuse the stream. */
atomic_store_explicit(&connection->poisoned, false, memory_order_release);
int received = cbm_daemon_ipc_receive_frame_bounded(connection, timeout_ms, max_payload_length,
frame_out, payload_out);
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return received;
}