/* * test_daemon_ipc.c — RED guards for the local CBM daemon transport. * * These tests deliberately exercise the public transport boundary rather than * platform socket/pipe primitives. The same API is expected to use Unix * domain sockets on POSIX and current-user named pipes on Windows. */ #include "test_framework.h" #include "test_helpers.h" #include "daemon/daemon.h" #include "daemon/ipc.h" #include "daemon/ipc_internal.h" #include "foundation/compat.h" #include "foundation/compat_thread.h" #include "foundation/log.h" #include "foundation/platform.h" #include "foundation/private_file_lock_internal.h" #include "foundation/subprocess.h" #include #include #include #include #include #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include "foundation/win_utf8.h" #include #include #include #ifndef PIPE_REJECT_REMOTE_CLIENTS #define PIPE_REJECT_REMOTE_CLIENTS 0x00000008 #endif #define ipc_test_chdir _chdir #define ipc_test_getcwd _getcwd #else #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include /* #1830 userns real smoke: unshare(CLONE_NEWUSER). */ #endif #ifdef __APPLE__ #include #include #endif #define ipc_test_chdir chdir #define ipc_test_getcwd getcwd #endif enum { TEST_PATH_CAP = 1024 }; #ifndef _WIN32 static bool ipc_test_write_byte(const char *path, unsigned char byte); #endif static bool ipc_test_parent_new(char out[TEST_PATH_CAP], const char *tag) { /* Endpoint parents must carry production-shaped ancestry: the runtime * ancestry validation (correctly) refuses temp roots whose ancestors * grant mutation rights to Authenticated Users, e.g. C:/msys64/tmp and * GitHub-runner work directories. th_secure_runtime_parent_new anchors * under LocalAppData on Windows, like the production default parent. */ return th_secure_runtime_parent_new(out, TEST_PATH_CAP, tag); } static void ipc_test_copy_path(char out[TEST_PATH_CAP], const char *path) { if (!path) { out[0] = '\0'; return; } (void)snprintf(out, TEST_PATH_CAP, "%s", path); } static bool ipc_test_full_path(char out[TEST_PATH_CAP], const char *path) { #ifdef _WIN32 return path && _fullpath(out, path, TEST_PATH_CAP) != NULL; #else return path && realpath(path, out) != NULL; #endif } static bool ipc_test_path_is_absolute(const char *path) { if (!path || path[0] == '\0') { return false; } #ifdef _WIN32 return (path[0] == '\\' && path[1] == '\\') || (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')); #else return path[0] == '/'; #endif } static bool ipc_test_path_has_parent(const char *path, const char *parent) { if (!path || !parent) { return false; } size_t parent_length = strlen(parent); if (parent_length == 0) { return false; } for (size_t i = 0; i < parent_length; i++) { char actual = path[i]; char expected = parent[i]; #ifdef _WIN32 if (actual == '/') { actual = '\\'; } if (expected == '/') { expected = '\\'; } if (actual >= 'A' && actual <= 'Z') { actual = (char)(actual - 'A' + 'a'); } if (expected >= 'A' && expected <= 'Z') { expected = (char)(expected - 'A' + 'a'); } #endif if (actual == '\0' || actual != expected) { return false; } } if (parent[parent_length - 1] == '/' || parent[parent_length - 1] == '\\') { return true; } return path[parent_length] == '/' || path[parent_length] == '\\'; } /* Endpoint state is intentionally persistent while clients come and go. Tests * use isolated parents, so remove any lock/socket artifacts after all handles * have closed without following symlinks or spawning a cleanup shell. */ static void ipc_test_remove_flat_dir(const char *path) { if (!path || path[0] == '\0') { return; } #ifdef _WIN32 char pattern[TEST_PATH_CAP]; char child[TEST_PATH_CAP]; WIN32_FIND_DATAA entry; (void)snprintf(pattern, sizeof(pattern), "%s/*", path); HANDLE find = FindFirstFileA(pattern, &entry); if (find != INVALID_HANDLE_VALUE) { do { if (strcmp(entry.cFileName, ".") == 0 || strcmp(entry.cFileName, "..") == 0) { continue; } (void)snprintf(child, sizeof(child), "%s/%s", path, entry.cFileName); if ((entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { (void)RemoveDirectoryA(child); } else { (void)DeleteFileA(child); } } while (FindNextFileA(find, &entry)); (void)FindClose(find); } (void)RemoveDirectoryA(path); #else DIR *dir = opendir(path); if (dir) { struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } char child[TEST_PATH_CAP]; struct stat st; (void)snprintf(child, sizeof(child), "%s/%s", path, entry->d_name); if (lstat(child, &st) == 0 && S_ISDIR(st.st_mode)) { (void)rmdir(child); } else { (void)unlink(child); } } (void)closedir(dir); } (void)rmdir(path); #endif } static void ipc_test_remove_tree(const char *runtime_dir, const char *parent) { if (runtime_dir && parent && strcmp(runtime_dir, parent) != 0) { ipc_test_remove_flat_dir(runtime_dir); } ipc_test_remove_flat_dir(parent); } #ifdef __APPLE__ /* Return 1 when installed, 0 only when the backing filesystem does not support * Darwin extended ACLs, and -1 for every other fixture failure. */ static int ipc_test_macos_set_directory_acl(const char *path, bool inheritable, acl_tag_t tag) { if (!path || path[0] == '\0') { errno = EINVAL; return -1; } acl_t acl = acl_init(1); acl_entry_t entry = NULL; acl_permset_t permissions = NULL; acl_flagset_t flags = NULL; bool built = acl && acl_create_entry(&acl, &entry) == 0 && entry && (tag == ACL_EXTENDED_ALLOW || tag == ACL_EXTENDED_DENY) && acl_set_tag_type(entry, tag) == 0; /* wheel (0) and daemon (1) are stable real macOS system groups. Use the * one that is not the process's effective group so the fixture grants a * distinct principal directory-mutation rights. */ gid_t foreign_group = getegid() == (gid_t)0 ? (gid_t)1 : (gid_t)0; uuid_t foreign_group_uuid = {0}; int membership_result = built ? mbr_gid_to_uuid(foreign_group, foreign_group_uuid) : 0; if (built && membership_result != 0) { errno = membership_result; built = false; } built = built && acl_set_qualifier(entry, foreign_group_uuid) == 0 && acl_get_permset(entry, &permissions) == 0 && permissions && acl_clear_perms(permissions) == 0 && acl_add_perm(permissions, ACL_ADD_FILE) == 0 && acl_add_perm(permissions, ACL_ADD_SUBDIRECTORY) == 0 && acl_add_perm(permissions, ACL_DELETE_CHILD) == 0 && acl_set_permset(entry, permissions) == 0; if (built && inheritable) { built = acl_get_flagset_np(entry, &flags) == 0 && flags && acl_clear_flags_np(flags) == 0 && acl_add_flag_np(flags, ACL_ENTRY_FILE_INHERIT) == 0 && acl_add_flag_np(flags, ACL_ENTRY_DIRECTORY_INHERIT) == 0 && acl_set_flagset_np(entry, flags) == 0; } built = built && acl_valid(acl) == 0; errno = built ? 0 : errno; int result = built ? acl_set_file(path, ACL_TYPE_EXTENDED, acl) : -1; int saved_error = errno; if (acl && acl_free(acl) != 0 && result == 0) { result = -1; saved_error = errno; } errno = saved_error; if (result == 0) { return 1; } return saved_error == ENOTSUP || saved_error == EOPNOTSUPP ? 0 : -1; } static int ipc_test_macos_set_mutating_acl(const char *path, bool inheritable) { return ipc_test_macos_set_directory_acl(path, inheritable, ACL_EXTENDED_ALLOW); } static int ipc_test_macos_set_deny_acl(const char *path, bool inheritable) { return ipc_test_macos_set_directory_acl(path, inheritable, ACL_EXTENDED_DENY); } /* An ACL-less existing path is reported by Darwin either as an empty ACL or, * on some filesystems, ENOENT. Validate the path first so ENOENT cannot hide * a missing runtime directory. */ static int ipc_test_macos_extended_acl_entry_count(const char *path) { struct stat status; if (!path || lstat(path, &status) != 0) { return -1; } errno = 0; acl_t acl = acl_get_file(path, ACL_TYPE_EXTENDED); if (!acl) { return errno == ENOENT ? 0 : -1; } int count = 0; acl_entry_t entry = NULL; int entry_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); /* Darwin's acl_get_entry returns 0 for each entry and -1/EINVAL after * ACL_NEXT_ENTRY advances beyond the final entry (unlike the 1/0 return * convention used by several other POSIX ACL implementations). */ while (entry_result == 0) { count++; errno = 0; entry_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); } int entry_error = errno; int free_result = acl_free(acl); return entry_result == -1 && entry_error == EINVAL && free_result == 0 ? count : -1; } #endif #ifdef _WIN32 typedef struct { wchar_t *value; bool present; } ipc_test_win_env_t; static bool ipc_test_win_grant_everyone_rights(const char *path, DWORD rights) { wchar_t *wide_path = cbm_utf8_to_wide(path); BYTE world_buffer[SECURITY_MAX_SID_SIZE]; DWORD world_size = sizeof(world_buffer); PACL existing = NULL; PACL replacement = NULL; PSECURITY_DESCRIPTOR descriptor = NULL; bool ok = wide_path && CreateWellKnownSid(WinWorldSid, NULL, world_buffer, &world_size) != 0 && GetNamedSecurityInfoW(wide_path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &existing, NULL, &descriptor) == ERROR_SUCCESS; EXPLICIT_ACCESSW access; memset(&access, 0, sizeof(access)); access.grfAccessPermissions = rights; access.grfAccessMode = GRANT_ACCESS; access.grfInheritance = NO_INHERITANCE; access.Trustee.TrusteeForm = TRUSTEE_IS_SID; access.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; access.Trustee.ptstrName = (LPWSTR)world_buffer; if (ok) { ok = SetEntriesInAclW(1U, &access, existing, &replacement) == ERROR_SUCCESS && replacement && SetNamedSecurityInfoW(wide_path, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, replacement, NULL) == ERROR_SUCCESS; } if (replacement) { (void)LocalFree(replacement); } if (descriptor) { (void)LocalFree(descriptor); } free(wide_path); return ok; } static bool ipc_test_win_grant_everyone_mutation(const char *path) { return ipc_test_win_grant_everyone_rights(path, FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD | DELETE | WRITE_DAC | WRITE_OWNER); } static bool ipc_test_win_env_capture(const wchar_t *name, ipc_test_win_env_t *saved) { if (!name || !saved) { return false; } memset(saved, 0, sizeof(*saved)); SetLastError(ERROR_SUCCESS); DWORD needed = GetEnvironmentVariableW(name, NULL, 0); if (needed == 0) { DWORD error = GetLastError(); return error == ERROR_ENVVAR_NOT_FOUND || error == ERROR_SUCCESS; } saved->value = malloc((size_t)needed * sizeof(*saved->value)); if (!saved->value || GetEnvironmentVariableW(name, saved->value, needed) + 1U != needed) { free(saved->value); saved->value = NULL; return false; } saved->present = true; return true; } static bool ipc_test_win_env_restore(const wchar_t *name, ipc_test_win_env_t *saved) { if (!name || !saved) { return false; } bool restored = SetEnvironmentVariableW(name, saved->present ? saved->value : NULL) != 0; free(saved->value); memset(saved, 0, sizeof(*saved)); return restored; } static bool ipc_test_win_temp_set(const char *path) { wchar_t *wide = cbm_utf8_to_wide(path); bool changed = wide && SetEnvironmentVariableW(L"TMP", wide) != 0 && SetEnvironmentVariableW(L"TEMP", wide) != 0; free(wide); return changed; } static int ipc_test_win_lock_child(const char *kind, const char *key, const char *parent) { char self[MAX_PATH]; DWORD self_length = GetModuleFileNameA(NULL, self, sizeof(self)); if (!kind || !key || !parent || self_length == 0 || self_length >= sizeof(self)) { return -1; } const char *const argv[] = { self, "__cbm_daemon_ipc_lock_probe", kind, key, parent, NULL, }; cbm_proc_opts_t options = { .bin = self, .argv = argv, .quiet_timeout_ms = 5000, }; cbm_proc_result_t result; return cbm_subprocess_run(&options, &result) == 0 ? result.exit_code : -1; } typedef struct { const cbm_daemon_ipc_endpoint_t *endpoint; int result; atomic_bool finished; /* set once the startup call has fully returned */ } ipc_test_win_startup_call_t; typedef struct { atomic_bool acquired; atomic_bool may_proceed; } ipc_test_startup_gate_t; /* Runs on the startup thread INSIDE cbm_daemon_ipc_startup_lock_try_acquire, * with the startup lock held and the rendezvous handoff not yet attempted. * Announce that state, then park until the test has released its reader. This * pins the interleaving by construction; the previous version polled for the * lock being busy, which is unobservable whenever the handoff does not block. */ static void ipc_test_startup_gate(void *context) { ipc_test_startup_gate_t *gate = context; atomic_store(&gate->acquired, true); while (!atomic_load(&gate->may_proceed)) { cbm_usleep(200); } } /* Waits until the startup thread has either parked in the gate or returned * without reaching it. Both flags only ever go false -> true, and one of them * is always eventually set: before the gate the startup path only try-acquires * (no blocking wait on anything this test holds), and a thread parked in the * gate cannot return until the test releases it. So no attempt or time bound * is needed, and none may decide the verdict: the previous 50000 x * cbm_usleep(200) budget meant ~10 s on POSIX but, since cbm_usleep(200) is * Sleep(0) on Windows, only 50000 bare yields there -- a loaded CI runner * exhausted them before the thread reached the gate. A thread that never * started is caught by the caller's thread_started check. */ static bool ipc_test_startup_gate_wait_acquired(ipc_test_startup_gate_t *gate, ipc_test_win_startup_call_t *call) { while (!atomic_load(&gate->acquired) && !atomic_load(&call->finished)) { cbm_usleep(200); } return atomic_load(&gate->acquired); } static void *ipc_test_win_startup_call(void *opaque) { ipc_test_win_startup_call_t *call = opaque; cbm_daemon_ipc_startup_lock_t *startup = NULL; call->result = cbm_daemon_ipc_startup_lock_try_acquire(call->endpoint, &startup); if (call->result == 1 && !cbm_daemon_ipc_startup_lock_prepare_handoff(startup)) { call->result = 0; } cbm_daemon_ipc_startup_lock_release(&startup); atomic_store(&call->finished, true); return NULL; } static void ipc_test_win_lock_release(cbm_private_file_lock_t **lock_io) { while (lock_io && *lock_io && cbm_private_file_lock_release(lock_io) != CBM_PRIVATE_FILE_LOCK_OK) { cbm_usleep(1000); } } #endif typedef struct { cbm_ipc_pending_wait_status_t wait_status; cbm_ipc_pending_finish_status_t finish_status; uint32_t finish_bytes; char calls[8]; size_t call_count; bool finish_was_blocking; } ipc_pending_fake_t; static void ipc_pending_fake_record(ipc_pending_fake_t *fake, char call) { if (fake->call_count + 1 < sizeof(fake->calls)) { fake->calls[fake->call_count++] = call; fake->calls[fake->call_count] = '\0'; } } static cbm_ipc_pending_wait_status_t ipc_pending_fake_wait(void *opaque, uint32_t timeout_ms) { ipc_pending_fake_t *fake = (ipc_pending_fake_t *)opaque; (void)timeout_ms; ipc_pending_fake_record(fake, 'W'); return fake->wait_status; } static void ipc_pending_fake_cancel(void *opaque) { ipc_pending_fake_t *fake = (ipc_pending_fake_t *)opaque; ipc_pending_fake_record(fake, 'C'); } static cbm_ipc_pending_finish_status_t ipc_pending_fake_finish(void *opaque, bool blocking, uint32_t *transferred_out) { ipc_pending_fake_t *fake = (ipc_pending_fake_t *)opaque; ipc_pending_fake_record(fake, 'D'); fake->finish_was_blocking = blocking; if (fake->finish_status == CBM_IPC_PENDING_FINISH_COMPLETED) { *transferred_out = fake->finish_bytes; } return fake->finish_status; } static int ipc_pending_fake_run(ipc_pending_fake_t *fake, uint32_t *transferred_out) { cbm_ipc_pending_ops_t ops = { .context = fake, .wait = ipc_pending_fake_wait, .cancel = ipc_pending_fake_cancel, .finish = ipc_pending_fake_finish, }; return cbm_daemon_ipc_wait_pending(&ops, 17, transferred_out); } TEST(daemon_ipc_pending_timeout_race_returns_completed_io) { ipc_pending_fake_t fake = { .wait_status = CBM_IPC_PENDING_WAIT_TIMEOUT, .finish_status = CBM_IPC_PENDING_FINISH_COMPLETED, .finish_bytes = 6, }; uint32_t transferred = 0; int result = ipc_pending_fake_run(&fake, &transferred); ASSERT_EQ(result, 1); ASSERT_EQ(transferred, 6); ASSERT_STR_EQ(fake.calls, "WCD"); ASSERT_TRUE(fake.finish_was_blocking); PASS(); } TEST(daemon_ipc_pending_wait_failure_cancels_and_drains) { ipc_pending_fake_t fake = { .wait_status = CBM_IPC_PENDING_WAIT_FAILED, .finish_status = CBM_IPC_PENDING_FINISH_CANCELLED, }; uint32_t transferred = 0; int result = ipc_pending_fake_run(&fake, &transferred); ASSERT_EQ(result, -1); ASSERT_STR_EQ(fake.calls, "WCD"); ASSERT_TRUE(fake.finish_was_blocking); PASS(); } TEST(daemon_ipc_pending_timeout_cancelled_returns_timeout) { ipc_pending_fake_t fake = { .wait_status = CBM_IPC_PENDING_WAIT_TIMEOUT, .finish_status = CBM_IPC_PENDING_FINISH_CANCELLED, }; uint32_t transferred = 0; int result = ipc_pending_fake_run(&fake, &transferred); ASSERT_EQ(result, 0); ASSERT_STR_EQ(fake.calls, "WCD"); ASSERT_TRUE(fake.finish_was_blocking); PASS(); } TEST(daemon_ipc_windows_generation_address_binds_account_key_and_nonce) { /* S-1-5-21-305419896-2596069104-267242409 */ static const uint8_t sid_a[] = { 1, 4, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a, 0xa9, 0xcb, 0xed, 0x0f, }; /* Same account shape, different final sub-authority. */ static const uint8_t sid_b[] = { 1, 4, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a, 0xaa, 0xcb, 0xed, 0x0f, }; static const char key[] = "0123456789abcdef"; static const char other_key[] = "fedcba9876543210"; static const uint8_t nonce_a[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, }; static const uint8_t nonce_b[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, }; char address_a[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char address_same[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char address_b[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char address_other_key[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char address_other_nonce[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; bool first = cbm_daemon_ipc_windows_generation_address(sid_a, sizeof(sid_a), key, nonce_a, address_a); bool repeated = cbm_daemon_ipc_windows_generation_address(sid_a, sizeof(sid_a), key, nonce_a, address_same); bool other_user = cbm_daemon_ipc_windows_generation_address(sid_b, sizeof(sid_b), key, nonce_a, address_b); bool changed_key = cbm_daemon_ipc_windows_generation_address(sid_a, sizeof(sid_a), other_key, nonce_a, address_other_key); bool changed_nonce = cbm_daemon_ipc_windows_generation_address(sid_a, sizeof(sid_a), key, nonce_b, address_other_nonce); ASSERT_TRUE(first); ASSERT_TRUE(repeated); ASSERT_TRUE(other_user); ASSERT_TRUE(changed_key); ASSERT_TRUE(changed_nonce); ASSERT_STR_EQ(address_a, address_same); ASSERT_TRUE(strcmp(address_a, address_b) != 0); ASSERT_TRUE(strcmp(address_a, address_other_key) != 0); ASSERT_TRUE(strcmp(address_a, address_other_nonce) != 0); ASSERT_STR_EQ(address_a, "\\\\.\\pipe\\cbm-daemon-" "e861648d9f8bc786dce31bbb16eda2ab" "ffa330a770752832ab5f2e4feaa506f1"); ASSERT_TRUE(strstr(address_a, "S-1-") == NULL); ASSERT_TRUE(strstr(address_a, key) == NULL); PASS(); } TEST(daemon_ipc_windows_legacy_names_are_frozen_for_migration) { char pipe_name[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char startup_name[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; bool derived = cbm_daemon_ipc_windows_legacy_names("C:\\Users\\Alice\\AppData\\Local", "0123456789abcdef", pipe_name, startup_name); ASSERT_TRUE(derived); ASSERT_STR_EQ(pipe_name, "\\\\.\\pipe\\cbm-daemon-72380d6ef7f19c0c-" "0123456789abcdef"); ASSERT_STR_EQ(startup_name, "Local\\cbm-daemon-72380d6ef7f19c0c-" "0123456789abcdef-startup"); PASS(); } TEST(daemon_ipc_windows_rendezvous_record_is_exact_and_canonical) { static const uint8_t nonce[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE] = { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, }; static const char address[] = "\\\\.\\pipe\\cbm-daemon-" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef"; uint8_t record[CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RECORD_SIZE]; uint8_t decoded_nonce[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE] = {0}; char decoded_address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; bool encoded = cbm_daemon_ipc_windows_rendezvous_record_encode(nonce, address, record); bool decoded = encoded && cbm_daemon_ipc_windows_rendezvous_record_decode( record, sizeof(record), decoded_nonce, decoded_address); ASSERT_TRUE(encoded); ASSERT_TRUE(decoded); ASSERT_TRUE(memcmp(decoded_nonce, nonce, sizeof(nonce)) == 0); ASSERT_STR_EQ(decoded_address, address); uint8_t corrupt[sizeof(record)]; memcpy(corrupt, record, sizeof(corrupt)); corrupt[0] ^= 0xffU; ASSERT_FALSE(cbm_daemon_ipc_windows_rendezvous_record_decode(corrupt, sizeof(corrupt), decoded_nonce, decoded_address)); ASSERT_FALSE(cbm_daemon_ipc_windows_rendezvous_record_decode(record, sizeof(record) - 1U, decoded_nonce, decoded_address)); memcpy(corrupt, record, sizeof(corrupt)); corrupt[sizeof(corrupt) - 1U] = 1U; ASSERT_FALSE(cbm_daemon_ipc_windows_rendezvous_record_decode(corrupt, sizeof(corrupt), decoded_nonce, decoded_address)); memcpy(corrupt, record, sizeof(corrupt)); const size_t address_offset = 8U + CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE; corrupt[address_offset + strlen("\\\\.\\pipe\\cbm-daemon-")] = 'A'; ASSERT_FALSE(cbm_daemon_ipc_windows_rendezvous_record_decode(corrupt, sizeof(corrupt), decoded_nonce, decoded_address)); PASS(); } #ifdef _WIN32 TEST(daemon_ipc_windows_default_endpoint_ignores_temp_environment) { char key[17]; char parent_a[TEST_PATH_CAP] = {0}; char parent_b[TEST_PATH_CAP] = {0}; char runtime_a[TEST_PATH_CAP] = {0}; char runtime_b[TEST_PATH_CAP] = {0}; char address_a[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char address_b[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; ipc_test_win_env_t saved_tmp = {0}; ipc_test_win_env_t saved_temp = {0}; cbm_daemon_ipc_endpoint_t *endpoint_a = NULL; cbm_daemon_ipc_endpoint_t *endpoint_b = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; unsigned long long unique = ((unsigned long long)GetCurrentProcessId() << 32U) ^ GetTickCount64(); bool key_ok = snprintf(key, sizeof(key), "%016llx", unique) == 16; bool parents_ok = ipc_test_parent_new(parent_a, "win-temp-a") && ipc_test_parent_new(parent_b, "win-temp-b"); bool tmp_saved = ipc_test_win_env_capture(L"TMP", &saved_tmp); bool temp_saved = ipc_test_win_env_capture(L"TEMP", &saved_temp); bool saved = tmp_saved && temp_saved; bool first_temp = parents_ok && saved && ipc_test_win_temp_set(parent_a); if (key_ok && first_temp) { endpoint_a = cbm_daemon_ipc_endpoint_new(key, NULL); } if (endpoint_a) { int startup_status = cbm_daemon_ipc_startup_lock_try_acquire(endpoint_a, &startup); /* Publish the generation record: the address under test exists only * after a startup owner prepares the handoff. */ if (startup_status == 1 && !cbm_daemon_ipc_startup_lock_prepare_handoff(startup)) { startup_status = -1; } cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; if (startup_status != 1) { cbm_daemon_ipc_endpoint_free(endpoint_a); endpoint_a = NULL; } } if (endpoint_a) { ipc_test_copy_path(runtime_a, cbm_daemon_ipc_endpoint_runtime_dir(endpoint_a)); ipc_test_copy_path(address_a, cbm_daemon_ipc_endpoint_address(endpoint_a)); } bool second_temp = endpoint_a && ipc_test_win_temp_set(parent_b); if (second_temp) { endpoint_b = cbm_daemon_ipc_endpoint_new(key, NULL); } if (endpoint_b) { ipc_test_copy_path(runtime_b, cbm_daemon_ipc_endpoint_runtime_dir(endpoint_b)); ipc_test_copy_path(address_b, cbm_daemon_ipc_endpoint_address(endpoint_b)); } bool stable = endpoint_a && endpoint_b && strcmp(address_a, address_b) == 0 && address_a[0] != '\0' && strcmp(runtime_a, runtime_b) == 0 && !ipc_test_path_has_parent(runtime_a, parent_a) && !ipc_test_path_has_parent(runtime_a, parent_b); bool restored_tmp = tmp_saved && ipc_test_win_env_restore(L"TMP", &saved_tmp); bool restored_temp = temp_saved && ipc_test_win_env_restore(L"TEMP", &saved_temp); cbm_daemon_ipc_endpoint_free(endpoint_b); cbm_daemon_ipc_endpoint_free(endpoint_a); if (runtime_b[0] != '\0' && strcmp(runtime_b, runtime_a) != 0) { ipc_test_remove_flat_dir(runtime_b); } ipc_test_remove_flat_dir(runtime_a); ipc_test_remove_flat_dir(parent_b); ipc_test_remove_flat_dir(parent_a); ASSERT_TRUE(key_ok); ASSERT_TRUE(parents_ok); ASSERT_TRUE(saved); ASSERT_TRUE(first_temp); ASSERT_TRUE(second_temp); ASSERT_TRUE(restored_tmp); ASSERT_TRUE(restored_temp); ASSERT_TRUE(stable); PASS(); } TEST(daemon_ipc_windows_private_directory_rejects_untrusted_ancestor_acl) { char parent[TEST_PATH_CAP] = {0}; char unsafe[TEST_PATH_CAP] = {0}; char cache[TEST_PATH_CAP] = {0}; bool paths_ok = false; bool unsafe_created = false; bool acl_injected = false; bool rejected = false; bool cache_absent = false; if (ipc_test_parent_new(parent, "win-unsafe-ancestor")) { int unsafe_written = snprintf(unsafe, sizeof(unsafe), "%s/unsafe", parent); int cache_written = snprintf(cache, sizeof(cache), "%s/cache", unsafe); paths_ok = unsafe_written > 0 && unsafe_written < (int)sizeof(unsafe) && cache_written > 0 && cache_written < (int)sizeof(cache); } if (paths_ok) { unsafe_created = CreateDirectoryA(unsafe, NULL) != 0; } if (unsafe_created) { acl_injected = ipc_test_win_grant_everyone_mutation(unsafe); } if (acl_injected) { rejected = !cbm_daemon_ipc_private_directory_secure(cache); SetLastError(ERROR_SUCCESS); DWORD attributes = GetFileAttributesA(cache); DWORD error = GetLastError(); cache_absent = attributes == INVALID_FILE_ATTRIBUTES && (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND); } (void)RemoveDirectoryA(cache); (void)RemoveDirectoryA(unsafe); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(unsafe_created); ASSERT_TRUE(acl_injected); ASSERT_TRUE(rejected); ASSERT_TRUE(cache_absent); PASS(); } /* A private directory is a container, so its owner-only ACE must propagate. * Otherwise the protected DACL severs inheritance and every child is created * with an empty DACL, unreadable even by its owner. */ TEST(daemon_ipc_windows_private_directory_ace_is_inheritable) { char parent[TEST_PATH_CAP] = {0}; char cache[TEST_PATH_CAP] = {0}; bool paths_ok = false; bool secured = false; bool ace_read = false; bool ace_single = false; bool ace_inheritable = false; bool child_has_dacl = false; bool lock_directory_accepted = false; if (ipc_test_parent_new(parent, "win-inheritable-ace")) { int written = snprintf(cache, sizeof(cache), "%s/cache", parent); paths_ok = written > 0 && written < (int)sizeof(cache); } if (paths_ok) { secured = cbm_daemon_ipc_private_directory_secure(cache); } if (secured) { PACL dacl = NULL; PSECURITY_DESCRIPTOR descriptor = NULL; LPVOID opaque_ace = NULL; ACL_SIZE_INFORMATION information; memset(&information, 0, sizeof(information)); if (GetNamedSecurityInfoA(cache, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &dacl, NULL, &descriptor) == ERROR_SUCCESS && dacl && GetAclInformation(dacl, &information, sizeof(information), AclSizeInformation) && GetAce(dacl, 0, &opaque_ace) && opaque_ace) { ACCESS_ALLOWED_ACE *ace = (ACCESS_ALLOWED_ACE *)opaque_ace; ace_read = true; /* The owner-only validators require exactly one ACE. An * inheritable ACE built from GENERIC_ALL rather than specific * rights is split by Windows into an effective ACE plus an * INHERIT_ONLY one, which fails that check. */ ace_single = information.AceCount == 1; ace_inheritable = (ace->Header.AceFlags & CONTAINER_INHERIT_ACE) != 0 && (ace->Header.AceFlags & OBJECT_INHERIT_ACE) != 0; } if (descriptor) { (void)LocalFree(descriptor); } } if (ace_inheritable) { char child[TEST_PATH_CAP] = {0}; int written = snprintf(child, sizeof(child), "%s/child", cache); if (written > 0 && written < (int)sizeof(child) && CreateDirectoryA(child, NULL) != 0) { PACL child_dacl = NULL; PSECURITY_DESCRIPTOR child_descriptor = NULL; ACL_SIZE_INFORMATION information; memset(&information, 0, sizeof(information)); child_has_dacl = GetNamedSecurityInfoA(child, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &child_dacl, NULL, &child_descriptor) == ERROR_SUCCESS && child_dacl && GetAclInformation(child_dacl, &information, sizeof(information), AclSizeInformation) && information.AceCount > 0; if (child_descriptor) { (void)LocalFree(child_descriptor); } (void)RemoveDirectoryA(child); } } if (ace_single) { /* The owner-only DACL validator gates every project lock: a secured * directory it refuses takes the whole coordination layer down with * "secure CLI coordination could not be created (project-locks)". */ HANDLE handle = CreateFileA(cache, 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 (handle != INVALID_HANDLE_VALUE) { cbm_private_lock_directory_t *directory = NULL; lock_directory_accepted = cbm_private_lock_directory_adopt_windows( handle, cache, &directory) == CBM_PRIVATE_FILE_LOCK_OK && directory; if (directory) { cbm_private_lock_directory_close(directory); } else { (void)CloseHandle(handle); } } } (void)RemoveDirectoryA(cache); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(secured); ASSERT_TRUE(ace_read); ASSERT_TRUE(ace_single); ASSERT_TRUE(ace_inheritable); ASSERT_TRUE(child_has_dacl); ASSERT_TRUE(lock_directory_accepted); PASS(); } typedef struct { int calls; bool plant_file; /* false: play a racer that creates the DIRECTORY first */ bool planted; } ipc_test_win_create_racer_t; /* Plays the concurrent process that wins the creation of a path component the * walk has just observed absent. Runs inside the walk, at the exact point the * race lives, so the interleaving is pinned by construction: no threads, no * timing. */ static void ipc_test_win_create_racer(const wchar_t *path, void *context) { ipc_test_win_create_racer_t *racer = context; racer->calls++; if (racer->calls != 1) { return; } if (racer->plant_file) { HANDLE file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); racer->planted = file != INVALID_HANDLE_VALUE; if (racer->planted) { (void)CloseHandle(file); } } else { racer->planted = CreateDirectoryW(path, NULL) != 0; } } /* Cold-start race (test-windows-guards section_cold_storm, 2026-09-21; the * same thing a user gets when a host launches several MCP servers on its very * first run): N processes first-start against a runtime directory that does * not exist yet. All of them observe the final component absent, one * CreateDirectoryW wins, and every other process gets ERROR_ALREADY_EXISTS. * The walk treated that as a failure and recorded no detail, so the losers * exited with a bare "secure CLI coordination could not be created * (endpoint)". Losing the creation race is not a failure: the directory this * process wanted now exists, and the owner / DACL / not-a-reparse-point checks * that follow are exactly the ones a pre-existing directory gets. The POSIX * walk has always tolerated EEXIST here. */ TEST(daemon_ipc_windows_private_directory_survives_lost_creation_race) { char parent[TEST_PATH_CAP] = {0}; char cache[TEST_PATH_CAP] = {0}; bool paths_ok = false; bool secured = false; bool owner_only = false; ipc_test_win_create_racer_t racer = {0}; if (ipc_test_parent_new(parent, "win-create-race")) { int written = snprintf(cache, sizeof(cache), "%s/cache", parent); paths_ok = written > 0 && written < (int)sizeof(cache); } if (paths_ok) { cbm_daemon_ipc_win_directory_create_hook_set_for_test(ipc_test_win_create_racer, &racer); secured = cbm_daemon_ipc_private_directory_secure(cache); cbm_daemon_ipc_win_directory_create_hook_set_for_test(NULL, NULL); } if (secured) { /* The racer created the directory with an inherited DACL; the winner's * directory must still end up owner-only, like any adopted one. */ PACL dacl = NULL; PSECURITY_DESCRIPTOR descriptor = NULL; ACL_SIZE_INFORMATION information; memset(&information, 0, sizeof(information)); if (GetNamedSecurityInfoA(cache, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &dacl, NULL, &descriptor) == ERROR_SUCCESS && dacl && GetAclInformation(dacl, &information, sizeof(information), AclSizeInformation)) { owner_only = information.AceCount == 1; } if (descriptor) { (void)LocalFree(descriptor); } } (void)RemoveDirectoryA(cache); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); /* The seam sat on the path and the racer really won: without these two the * test could pass without ever exercising the race. */ ASSERT_EQ(racer.calls, 1); ASSERT_TRUE(racer.planted); ASSERT_TRUE(secured); ASSERT_TRUE(owner_only); PASS(); } /* The tolerance above must not admit anything: when what appeared at the path * is NOT a directory, the walk still refuses -- and it says why. A bare * "(endpoint)" is what four reporters in #1533/#1574 were left with; every * refusal on this path names the component and the rule. */ TEST(daemon_ipc_windows_private_directory_refuses_and_names_a_planted_file) { char parent[TEST_PATH_CAP] = {0}; char cache[TEST_PATH_CAP] = {0}; char detail[512] = {0}; bool paths_ok = false; bool refused = false; ipc_test_win_create_racer_t racer = {.plant_file = true}; if (ipc_test_parent_new(parent, "win-create-race-file")) { int written = snprintf(cache, sizeof(cache), "%s/cache", parent); paths_ok = written > 0 && written < (int)sizeof(cache); } if (paths_ok) { cbm_daemon_ipc_win_directory_create_hook_set_for_test(ipc_test_win_create_racer, &racer); refused = !cbm_daemon_ipc_private_directory_secure(cache); cbm_daemon_ipc_win_directory_create_hook_set_for_test(NULL, NULL); const char *why = cbm_daemon_ipc_validation_detail(); (void)snprintf(detail, sizeof(detail), "%s", why ? why : ""); } (void)DeleteFileA(cache); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_EQ(racer.calls, 1); ASSERT_TRUE(racer.planted); ASSERT_TRUE(refused); ASSERT_NOT_NULL(strstr(detail, "cache")); ASSERT_NOT_NULL(strstr(detail, "not a directory")); PASS(); } TEST(daemon_ipc_windows_private_directory_allows_add_subdirectory_only_ancestor) { char parent[TEST_PATH_CAP] = {0}; char add_only[TEST_PATH_CAP] = {0}; char cache[TEST_PATH_CAP] = {0}; bool paths_ok = false; bool ancestor_created = false; bool acl_injected = false; bool secured = false; bool cache_created = false; if (ipc_test_parent_new(parent, "win-add-only-ancestor")) { int ancestor_written = snprintf(add_only, sizeof(add_only), "%s/add-only", parent); int cache_written = snprintf(cache, sizeof(cache), "%s/cache", add_only); paths_ok = ancestor_written > 0 && ancestor_written < (int)sizeof(add_only) && cache_written > 0 && cache_written < (int)sizeof(cache); } if (paths_ok) { ancestor_created = CreateDirectoryA(add_only, NULL) != 0; } if (ancestor_created) { acl_injected = ipc_test_win_grant_everyone_rights(add_only, FILE_ADD_SUBDIRECTORY); } if (acl_injected) { secured = cbm_daemon_ipc_private_directory_secure(cache); DWORD attributes = GetFileAttributesA(cache); cache_created = attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0; } (void)RemoveDirectoryA(cache); (void)RemoveDirectoryA(add_only); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(ancestor_created); ASSERT_TRUE(acl_injected); ASSERT_TRUE(secured); ASSERT_TRUE(cache_created); PASS(); } /* Derive THIS machine's account-domain SID (S-1-5-21-a-b-c) from the current * process token (S-1-5-21-a-b-c-), independently of production's LSA-based * resolution. Caller frees with FreeSid. */ static PSID ipc_test_local_account_domain_sid(void) { HANDLE token = NULL; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { return NULL; } DWORD needed = 0; (void)GetTokenInformation(token, TokenUser, NULL, 0, &needed); PSID domain = NULL; if (needed > 0) { void *buffer = calloc(1, needed); if (buffer && GetTokenInformation(token, TokenUser, buffer, needed, &needed)) { PSID user = ((TOKEN_USER *)buffer)->User.Sid; if (user && IsValidSid(user)) { UCHAR count = *GetSidSubAuthorityCount(user); SID_IDENTIFIER_AUTHORITY *authority = GetSidIdentifierAuthority(user); /* Machine/domain account SID: NT authority (Value[5]==5), first * sub-authority 21, at least the three domain sub-authorities plus * the account RID. Drop the RID to recover the domain SID. */ if (count >= 4 && authority->Value[5] == 5 && *GetSidSubAuthority(user, 0) == 21U) { SID_IDENTIFIER_AUTHORITY nt = {SECURITY_NT_AUTHORITY}; (void)AllocateAndInitializeSid( &nt, 4, *GetSidSubAuthority(user, 0), *GetSidSubAuthority(user, 1), *GetSidSubAuthority(user, 2), *GetSidSubAuthority(user, 3), 0, 0, 0, 0, &domain); } } } free(buffer); } (void)CloseHandle(token); return domain; } /* Synthesize the RID-500 built-in Administrator SID for a given account-domain * SID, exactly as production does. Caller frees with free(). */ static PSID ipc_test_admin_sid_for_domain(PSID domain_sid) { if (!domain_sid) { return NULL; } DWORD needed = 0; (void)CreateWellKnownSid(WinAccountAdministratorSid, domain_sid, NULL, &needed); if (needed == 0) { return NULL; } PSID admin = malloc(needed); if (admin && CreateWellKnownSid(WinAccountAdministratorSid, domain_sid, admin, &needed) && IsValidSid(admin)) { return admin; } free(admin); return NULL; } /* #1705: THIS machine's built-in Administrator ACCOUNT (RID-500 under the local * account-domain SID) is a trusted directory owner/grantee, while a FOREIGN * S-1-5-21-*-500 (a domain administrator, or another machine's built-in * Administrator) must never be. * * RED without the fix: win_sid_trusted rejects the local RID-500 — on the VM this * process runs as `test`, whose RID is not 500, so the -500 SID matches neither * the current user nor SYSTEM, the Administrators GROUP, or TrustedInstaller — so * local_ok is false. GREEN with the fix. foreign_ok is false either way, which is * the exact cross-machine bypass this must never open. */ TEST(daemon_ipc_windows_sid_trust_accepts_local_admin_rejects_foreign_500) { PSID local_domain = ipc_test_local_account_domain_sid(); PSID local_admin = ipc_test_admin_sid_for_domain(local_domain); SID_IDENTIFIER_AUTHORITY nt = {SECURITY_NT_AUTHORITY}; PSID foreign_domain = NULL; (void)AllocateAndInitializeSid(&nt, 4, 21U, 111U, 222U, 333U, 0, 0, 0, 0, &foreign_domain); PSID foreign_admin = ipc_test_admin_sid_for_domain(foreign_domain); PSID builtin_admins = NULL; DWORD builtin_needed = 0; (void)CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, NULL, &builtin_needed); if (builtin_needed > 0) { builtin_admins = malloc(builtin_needed); if (builtin_admins && !CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, builtin_admins, &builtin_needed)) { free(builtin_admins); builtin_admins = NULL; } } bool local_built = local_admin != NULL; bool foreign_built = foreign_admin != NULL; bool builtin_built = builtin_admins != NULL; bool local_ok = local_built && cbm_daemon_ipc_win_sid_trusted_for_testing(local_admin); bool foreign_ok = foreign_built && cbm_daemon_ipc_win_sid_trusted_for_testing(foreign_admin); bool builtin_ok = builtin_built && cbm_daemon_ipc_win_sid_trusted_for_testing(builtin_admins); free(local_admin); free(foreign_admin); free(builtin_admins); if (local_domain) { FreeSid(local_domain); } if (foreign_domain) { FreeSid(foreign_domain); } ASSERT_TRUE(local_built); ASSERT_TRUE(foreign_built); ASSERT_TRUE(builtin_built); ASSERT_TRUE(builtin_ok); /* the seam works: an already-trusted SID passes */ ASSERT_TRUE(local_ok); /* #1705 fix: THIS machine's RID-500 is trusted */ ASSERT_FALSE(foreign_ok); /* safety: a foreign -500 is NEVER trusted */ PASS(); } TEST(daemon_ipc_windows_legacy_bridge_covers_handoff_and_lifetime) { static const char key[] = "91a2b3c4d5e6f708"; char parent[TEST_PATH_CAP] = {0}; char canonical_parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char legacy_pipe[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char legacy_startup[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_participant_guard_t *participant = NULL; cbm_daemon_ipc_lifetime_reservation_t *lifetime = NULL; HANDLE old_pipe = INVALID_HANDLE_VALUE; bool parent_ok = ipc_test_parent_new(parent, "win-legacy-bridge") && ipc_test_full_path(canonical_parent, parent); bool names_ok = parent_ok && cbm_daemon_ipc_windows_legacy_names(canonical_parent, key, legacy_pipe, legacy_startup); if (names_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int absent_before = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; int startup_status = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int visible_during_startup = startup ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; bool handoff = startup && cbm_daemon_ipc_startup_lock_prepare_handoff(startup); int visible_during_handoff = handoff ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; int participant_status = handoff ? cbm_daemon_ipc_participant_guard_try_join(endpoint, &participant) : -1; int lifetime_status = participant_status == 1 ? cbm_daemon_ipc_lifetime_reservation_try_acquire(endpoint, &lifetime) : -1; int visible_during_lifetime = lifetime ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; cbm_daemon_ipc_lifetime_reservation_release(lifetime); lifetime = NULL; /* Native Windows teardown is deliberately ordered through startup-v2. * A participant must remain retryable while a bootstrap handoff owner * retains that transition; releasing the bootstrap then unblocks it. */ bool participant_blocked_by_startup = participant && !cbm_daemon_ipc_participant_guard_release(&participant); bool participant_retained = participant != NULL; bool startup_released = cbm_daemon_ipc_startup_lock_release(&startup); bool participant_released = cbm_daemon_ipc_participant_guard_release(&participant); int absent_after_lifetime = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; typedef BOOL(WINAPI * ipc_test_initialize_sd_fn)(PSECURITY_DESCRIPTOR, DWORD); typedef BOOL(WINAPI * ipc_test_set_dacl_fn)(PSECURITY_DESCRIPTOR, BOOL, PACL, BOOL); HMODULE advapi = LoadLibraryW(L"advapi32.dll"); ipc_test_initialize_sd_fn initialize_sd = advapi ? (ipc_test_initialize_sd_fn)(void (*)(void))GetProcAddress( advapi, "InitializeSecurityDescriptor") : NULL; ipc_test_set_dacl_fn set_dacl = advapi ? (ipc_test_set_dacl_fn)(void (*)(void))GetProcAddress( advapi, "SetSecurityDescriptorDacl") : NULL; SECURITY_DESCRIPTOR unsafe_descriptor; bool unsafe_descriptor_ok = initialize_sd && set_dacl && initialize_sd(&unsafe_descriptor, SECURITY_DESCRIPTOR_REVISION) && set_dacl(&unsafe_descriptor, TRUE, NULL, FALSE); SECURITY_ATTRIBUTES unsafe_attributes = { .nLength = sizeof(unsafe_attributes), .lpSecurityDescriptor = unsafe_descriptor_ok ? &unsafe_descriptor : NULL, .bInheritHandle = FALSE, }; wchar_t *legacy_startup_wide = names_ok ? cbm_utf8_to_wide(legacy_startup) : NULL; HANDLE unsafe_mutex = unsafe_descriptor_ok && legacy_startup_wide ? CreateMutexW(&unsafe_attributes, FALSE, legacy_startup_wide) : NULL; free(legacy_startup_wide); int unsafe_probe = unsafe_mutex ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : 0; cbm_daemon_ipc_startup_lock_t *unsafe_startup = NULL; int unsafe_startup_status = unsafe_mutex ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &unsafe_startup) : 0; cbm_daemon_ipc_startup_lock_release(&unsafe_startup); if (unsafe_mutex) { (void)CloseHandle(unsafe_mutex); } if (advapi) { (void)FreeLibrary(advapi); } int absent_after_unsafe = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; wchar_t *old_name = names_ok ? cbm_utf8_to_wide(legacy_pipe) : NULL; if (old_name) { old_pipe = CreateNamedPipeW(old_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 0, NULL); } free(old_name); int visible_legacy_pipe = old_pipe != INVALID_HANDLE_VALUE ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; if (old_pipe != INVALID_HANDLE_VALUE) { (void)CloseHandle(old_pipe); } int absent_after_pipe = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; cbm_daemon_ipc_lifetime_reservation_release(lifetime); (void)cbm_daemon_ipc_participant_guard_release(&participant); cbm_daemon_ipc_startup_lock_release(&startup); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(names_ok); ASSERT_EQ(absent_before, 0); ASSERT_EQ(startup_status, 1); ASSERT_EQ(visible_during_startup, 1); ASSERT_TRUE(handoff); ASSERT_EQ(visible_during_handoff, 1); ASSERT_EQ(participant_status, 1); ASSERT_EQ(lifetime_status, 1); ASSERT_EQ(visible_during_lifetime, 1); ASSERT_TRUE(participant_blocked_by_startup); ASSERT_TRUE(participant_retained); ASSERT_TRUE(startup_released); ASSERT_NULL(startup); ASSERT_EQ(absent_after_lifetime, 0); ASSERT_TRUE(participant_released); ASSERT_NULL(participant); ASSERT_TRUE(unsafe_descriptor_ok); ASSERT_TRUE(unsafe_mutex != NULL); ASSERT_EQ(unsafe_probe, -1); ASSERT_EQ(unsafe_startup_status, -1); ASSERT_EQ(absent_after_unsafe, 0); ASSERT_TRUE(old_pipe != INVALID_HANDLE_VALUE); ASSERT_EQ(visible_legacy_pipe, 1); ASSERT_EQ(absent_after_pipe, 0); PASS(); } TEST(daemon_ipc_windows_local_transition_atomically_reserves_legacy_pipe) { static const char key[] = "71a2b3c4d5e6f809"; char parent[TEST_PATH_CAP] = {0}; char canonical_parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char legacy_pipe[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char legacy_startup[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_local_transition_t *transition_wins = NULL; cbm_daemon_ipc_local_transition_t *legacy_wins = NULL; HANDLE old_after_sentinel = INVALID_HANDLE_VALUE; HANDLE old_first = INVALID_HANDLE_VALUE; bool parent_ok = ipc_test_parent_new(parent, "win-local-transition") && ipc_test_full_path(canonical_parent, parent); bool names_ok = parent_ok && cbm_daemon_ipc_windows_legacy_names(canonical_parent, key, legacy_pipe, legacy_startup); if (names_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int transition_wins_result = endpoint ? cbm_daemon_ipc_local_transition_try_acquire(endpoint, &transition_wins) : -1; int sentinel_result = transition_wins_result == 1 ? cbm_daemon_ipc_local_transition_seal_legacy(transition_wins) : -1; int sentinel_visible = sentinel_result == 1 ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; wchar_t *legacy_name = names_ok ? cbm_utf8_to_wide(legacy_pipe) : NULL; if (legacy_name && sentinel_result == 1) { old_after_sentinel = CreateNamedPipeW( legacy_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 0, NULL); } bool work_begun = sentinel_result == 1 && cbm_daemon_ipc_local_transition_begin_work(transition_wins); bool transition_wins_released = cbm_daemon_ipc_local_transition_release(&transition_wins); int absent_after_sentinel = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; if (legacy_name) { old_first = CreateNamedPipeW( legacy_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 0, NULL); } int legacy_wins_result = old_first != INVALID_HANDLE_VALUE ? cbm_daemon_ipc_local_transition_try_acquire(endpoint, &legacy_wins) : -1; int rejected_after_legacy_win = legacy_wins_result == 1 ? cbm_daemon_ipc_local_transition_seal_legacy(legacy_wins) : -1; bool legacy_wins_released = cbm_daemon_ipc_local_transition_release(&legacy_wins); if (old_after_sentinel != INVALID_HANDLE_VALUE) { (void)CloseHandle(old_after_sentinel); } if (old_first != INVALID_HANDLE_VALUE) { (void)CloseHandle(old_first); } free(legacy_name); int absent_after_old = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; (void)cbm_daemon_ipc_local_transition_release(&legacy_wins); (void)cbm_daemon_ipc_local_transition_release(&transition_wins); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(names_ok); ASSERT_EQ(transition_wins_result, 1); ASSERT_EQ(sentinel_result, 1); ASSERT_EQ(sentinel_visible, 1); ASSERT_TRUE(old_after_sentinel == INVALID_HANDLE_VALUE); ASSERT_TRUE(work_begun); ASSERT_TRUE(transition_wins_released); ASSERT_NULL(transition_wins); ASSERT_EQ(absent_after_sentinel, 0); ASSERT_TRUE(old_first != INVALID_HANDLE_VALUE); ASSERT_EQ(legacy_wins_result, 1); ASSERT_EQ(rejected_after_legacy_win, 0); ASSERT_TRUE(legacy_wins_released); ASSERT_NULL(legacy_wins); ASSERT_EQ(absent_after_old, 0); PASS(); } TEST(daemon_ipc_windows_startup_retries_transient_rendezvous_reader) { static const char key[] = "81b2c3d4e5f60719"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_private_lock_directory_t *directory = NULL; cbm_private_file_lock_t *record_reader = NULL; cbm_thread_t thread; bool thread_started = false; bool startup_observed = false; int join_status = -1; ipc_test_win_startup_call_t call = {.result = -1}; atomic_init(&call.finished, false); ipc_test_startup_gate_t gate; atomic_init(&gate.acquired, false); atomic_init(&gate.may_proceed, false); cbm_daemon_ipc_startup_gate_set_for_test(ipc_test_startup_gate, &gate); bool parent_ok = ipc_test_parent_new(parent, "win-record-reader"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { call.endpoint = endpoint; ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } cbm_private_file_lock_status_t directory_status = endpoint ? cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory) : CBM_PRIVATE_FILE_LOCK_IO; cbm_private_file_lock_status_t record_status = directory_status == CBM_PRIVATE_FILE_LOCK_OK ? cbm_private_file_lock_try_acquire(directory, CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_FILE, CBM_PRIVATE_FILE_LOCK_SH, &record_reader) : CBM_PRIVATE_FILE_LOCK_IO; if (record_status == CBM_PRIVATE_FILE_LOCK_OK) { thread_started = cbm_thread_create(&thread, 0, ipc_test_win_startup_call, &call) == 0; } if (thread_started) { startup_observed = ipc_test_startup_gate_wait_acquired(&gate, &call); } ipc_test_win_lock_release(&record_reader); atomic_store(&gate.may_proceed, true); if (thread_started) { join_status = cbm_thread_join(&thread); } if (endpoint) { ipc_test_copy_path(address, cbm_daemon_ipc_endpoint_address(endpoint)); } cbm_daemon_ipc_startup_gate_set_for_test(NULL, NULL); ipc_test_win_lock_release(&record_reader); cbm_private_lock_directory_close(directory); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(directory_status, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_EQ(record_status, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_TRUE(thread_started); if (!startup_observed) { /* The startup thread returned without reaching the gate; its * try-acquire status is the true reason. */ char reason[96]; (void)snprintf(reason, sizeof(reason), "startup call returned %d without reaching the gate", call.result); FAIL(reason); } ASSERT_EQ(join_status, 0); ASSERT_EQ(call.result, 1); ASSERT_TRUE(address[0] != '\0'); PASS(); } TEST(daemon_ipc_windows_rendezvous_bridges_concurrent_lifetime_owner) { static const char key[] = "71b2c3d4e5f60729"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char before[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char after[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *initial_startup = NULL; cbm_private_lock_directory_t *directory = NULL; cbm_private_file_lock_t *record_reader = NULL; cbm_private_file_lock_t *lifetime_owner = NULL; cbm_thread_t thread; bool thread_started = false; bool startup_observed = false; int join_status = -1; ipc_test_win_startup_call_t call = {.result = -1}; atomic_init(&call.finished, false); bool parent_ok = ipc_test_parent_new(parent, "win-lifetime-bridge"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { call.endpoint = endpoint; ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int initial_status = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &initial_startup) : -1; if (initial_status == 1 && !cbm_daemon_ipc_startup_lock_prepare_handoff(initial_startup)) { initial_status = -1; } if (initial_status == 1) { ipc_test_copy_path(before, cbm_daemon_ipc_endpoint_address(endpoint)); } cbm_daemon_ipc_startup_lock_release(&initial_startup); initial_startup = NULL; ipc_test_startup_gate_t gate; atomic_init(&gate.acquired, false); atomic_init(&gate.may_proceed, false); cbm_daemon_ipc_startup_gate_set_for_test(ipc_test_startup_gate, &gate); cbm_private_file_lock_status_t directory_status = endpoint ? cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory) : CBM_PRIVATE_FILE_LOCK_IO; cbm_private_file_lock_status_t record_status = directory_status == CBM_PRIVATE_FILE_LOCK_OK ? cbm_private_file_lock_try_acquire(directory, CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_FILE, CBM_PRIVATE_FILE_LOCK_SH, &record_reader) : CBM_PRIVATE_FILE_LOCK_IO; if (record_status == CBM_PRIVATE_FILE_LOCK_OK) { thread_started = cbm_thread_create(&thread, 0, ipc_test_win_startup_call, &call) == 0; } if (thread_started) { startup_observed = ipc_test_startup_gate_wait_acquired(&gate, &call); } /* Claim the lifetime lock while startup is parked in the gate, so the * concurrent-owner bridge under test is exercised deterministically. */ cbm_private_file_lock_status_t lifetime_status = startup_observed ? cbm_private_file_lock_try_acquire(directory, "cbm-lifetime.lock", CBM_PRIVATE_FILE_LOCK_EX, &lifetime_owner) : CBM_PRIVATE_FILE_LOCK_IO; ipc_test_win_lock_release(&record_reader); atomic_store(&gate.may_proceed, true); if (thread_started) { join_status = cbm_thread_join(&thread); } if (endpoint) { ipc_test_copy_path(after, cbm_daemon_ipc_endpoint_address(endpoint)); } ipc_test_win_lock_release(&lifetime_owner); cbm_daemon_ipc_startup_gate_set_for_test(NULL, NULL); ipc_test_win_lock_release(&record_reader); ipc_test_win_lock_release(&lifetime_owner); cbm_private_lock_directory_close(directory); cbm_daemon_ipc_startup_lock_release(&initial_startup); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(initial_status, 1); ASSERT_TRUE(before[0] != '\0'); ASSERT_EQ(directory_status, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_EQ(record_status, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_TRUE(thread_started); if (!startup_observed) { /* The startup thread returned without reaching the gate; its * try-acquire status is the true reason. */ char reason[96]; (void)snprintf(reason, sizeof(reason), "startup call returned %d without reaching the gate", call.result); FAIL(reason); } ASSERT_EQ(lifetime_status, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_EQ(join_status, 0); ASSERT_EQ(call.result, 0); ASSERT_STR_EQ(after, before); PASS(); } TEST(daemon_ipc_windows_generation_rotates_and_escapes_occupied_old_pipe) { static const char key[] = "a1b2c3d4e5f60718"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char first_address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char first_peer_address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char second_address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char second_peer_address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_endpoint_t *peer = NULL; cbm_daemon_ipc_startup_lock_t *first_startup = NULL; cbm_daemon_ipc_startup_lock_t *second_startup = NULL; cbm_daemon_ipc_participant_guard_t *participant = NULL; cbm_daemon_ipc_lifetime_reservation_t *lifetime = NULL; cbm_daemon_ipc_listener_t *listener = NULL; HANDLE old_pipe = INVALID_HANDLE_VALUE; bool parent_ok = ipc_test_parent_new(parent, "win-generation"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); peer = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int first_status = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &first_startup) : -1; if (first_status == 1 && !cbm_daemon_ipc_startup_lock_prepare_handoff(first_startup)) { first_status = -1; } if (first_status == 1) { ipc_test_copy_path(first_address, cbm_daemon_ipc_endpoint_address(endpoint)); ipc_test_copy_path(first_peer_address, cbm_daemon_ipc_endpoint_address(peer)); } cbm_daemon_ipc_startup_lock_release(&first_startup); first_startup = NULL; wchar_t *old_name = first_address[0] ? cbm_utf8_to_wide(first_address) : NULL; if (old_name) { old_pipe = CreateNamedPipeW(old_name, PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, 1, 4096, 4096, 0, NULL); } free(old_name); int second_status = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &second_startup) : -1; bool handoff_prepared = second_status == 1 && cbm_daemon_ipc_startup_lock_prepare_handoff(second_startup); int participant_status = handoff_prepared ? cbm_daemon_ipc_participant_guard_try_join(endpoint, &participant) : -1; int lifetime_status = participant_status == 1 ? cbm_daemon_ipc_lifetime_reservation_try_acquire(endpoint, &lifetime) : -1; if (handoff_prepared) { ipc_test_copy_path(second_address, cbm_daemon_ipc_endpoint_address(endpoint)); ipc_test_copy_path(second_peer_address, cbm_daemon_ipc_endpoint_address(peer)); listener = lifetime_status == 1 ? cbm_daemon_ipc_listen_reserved(endpoint, &lifetime) : NULL; } cbm_daemon_ipc_startup_lock_release(&second_startup); second_startup = NULL; bool first_shared = first_address[0] && strcmp(first_address, first_peer_address) == 0; bool rotated = second_address[0] && strcmp(first_address, second_address) != 0; bool second_shared = strcmp(second_address, second_peer_address) == 0; bool old_occupied = old_pipe != INVALID_HANDLE_VALUE; bool new_listened = listener != NULL; cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_lifetime_reservation_release(lifetime); bool participant_released = cbm_daemon_ipc_participant_guard_release(&participant); if (old_pipe != INVALID_HANDLE_VALUE) { (void)CloseHandle(old_pipe); } cbm_daemon_ipc_startup_lock_release(&second_startup); cbm_daemon_ipc_startup_lock_release(&first_startup); cbm_daemon_ipc_endpoint_free(peer); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(first_status, 1); ASSERT_TRUE(first_shared); ASSERT_TRUE(old_occupied); ASSERT_EQ(second_status, 1); ASSERT_TRUE(handoff_prepared); ASSERT_EQ(participant_status, 1); ASSERT_EQ(lifetime_status, 1); ASSERT_TRUE(rotated); ASSERT_TRUE(second_shared); ASSERT_TRUE(new_listened); ASSERT_TRUE(participant_released); PASS(); } TEST(daemon_ipc_windows_corrupt_rendezvous_fails_closed_until_startup_repairs) { static const char key[] = "b1c2d3e4f5061728"; static const uint8_t partial[] = {'C', 'B', 'M', 'R', 'D', 'V', '1'}; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char original_address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char repaired_address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char rebound_address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_endpoint_t *reader = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_private_lock_directory_t *directory = NULL; cbm_private_file_lock_t *record_lock = NULL; cbm_private_file_lock_status_t directory_status = CBM_PRIVATE_FILE_LOCK_IO; cbm_private_file_lock_status_t record_status = CBM_PRIVATE_FILE_LOCK_IO; cbm_private_file_lock_status_t write_status = CBM_PRIVATE_FILE_LOCK_IO; cbm_private_file_lock_status_t read_status = CBM_PRIVATE_FILE_LOCK_IO; uint8_t readback[CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_RECORD_SIZE] = {0}; size_t readback_length = 0; bool parent_ok = ipc_test_parent_new(parent, "win-corrupt-record"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int initial_status = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; if (initial_status == 1 && !cbm_daemon_ipc_startup_lock_prepare_handoff(startup)) { initial_status = -1; } if (initial_status == 1) { ipc_test_copy_path(original_address, cbm_daemon_ipc_endpoint_address(endpoint)); } cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; if (endpoint) { directory_status = cbm_daemon_ipc_private_lock_directory_new(endpoint, &directory); } if (directory_status == CBM_PRIVATE_FILE_LOCK_OK) { record_status = cbm_private_file_lock_try_acquire(directory, CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_FILE, CBM_PRIVATE_FILE_LOCK_EX, &record_lock); } if (record_status == CBM_PRIVATE_FILE_LOCK_OK) { write_status = cbm_private_file_lock_payload_write(record_lock, partial, sizeof(partial)); } if (record_lock) { (void)cbm_private_file_lock_release(&record_lock); } cbm_private_lock_directory_close(directory); directory = NULL; if (write_status == CBM_PRIVATE_FILE_LOCK_OK) { reader = cbm_daemon_ipc_endpoint_new(key, parent); } cbm_daemon_ipc_connection_t *corrupt_connection = reader ? cbm_daemon_ipc_connect(reader, 1) : NULL; bool failed_closed = reader && cbm_daemon_ipc_endpoint_address(reader) == NULL && corrupt_connection == NULL; cbm_daemon_ipc_connection_close(corrupt_connection); if (reader && cbm_daemon_ipc_private_lock_directory_new(reader, &directory) == CBM_PRIVATE_FILE_LOCK_OK && cbm_private_file_lock_try_acquire(directory, CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_FILE, CBM_PRIVATE_FILE_LOCK_SH, &record_lock) == CBM_PRIVATE_FILE_LOCK_OK) { read_status = cbm_private_file_lock_payload_read(record_lock, readback, sizeof(readback), &readback_length); } if (record_lock) { (void)cbm_private_file_lock_release(&record_lock); } cbm_private_lock_directory_close(directory); directory = NULL; bool reader_did_not_repair = read_status == CBM_PRIVATE_FILE_LOCK_OK && readback_length == sizeof(partial) && memcmp(readback, partial, sizeof(partial)) == 0; int repair_status = reader ? cbm_daemon_ipc_startup_lock_try_acquire(reader, &startup) : -1; if (repair_status == 1 && !cbm_daemon_ipc_startup_lock_prepare_handoff(startup)) { repair_status = -1; } if (repair_status == 1) { ipc_test_copy_path(repaired_address, cbm_daemon_ipc_endpoint_address(reader)); } cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; bool repaired = repaired_address[0] && strcmp(original_address, repaired_address) != 0; uint8_t mismatched_nonce[CBM_DAEMON_IPC_WINDOWS_NONCE_SIZE] = {0}; char mismatched_address[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; size_t valid_record_length = 0; cbm_private_file_lock_status_t mismatch_read = CBM_PRIVATE_FILE_LOCK_IO; cbm_private_file_lock_status_t mismatch_write = CBM_PRIVATE_FILE_LOCK_IO; bool mismatch_encoded = false; if (reader && cbm_daemon_ipc_private_lock_directory_new(reader, &directory) == CBM_PRIVATE_FILE_LOCK_OK && cbm_private_file_lock_try_acquire(directory, CBM_DAEMON_IPC_WINDOWS_RENDEZVOUS_FILE, CBM_PRIVATE_FILE_LOCK_EX, &record_lock) == CBM_PRIVATE_FILE_LOCK_OK) { mismatch_read = cbm_private_file_lock_payload_read(record_lock, readback, sizeof(readback), &valid_record_length); bool decoded = mismatch_read == CBM_PRIVATE_FILE_LOCK_OK && cbm_daemon_ipc_windows_rendezvous_record_decode( readback, valid_record_length, mismatched_nonce, mismatched_address); if (decoded) { mismatched_nonce[0] ^= 0x80U; mismatch_encoded = cbm_daemon_ipc_windows_rendezvous_record_encode( mismatched_nonce, mismatched_address, readback); } if (mismatch_encoded) { mismatch_write = cbm_private_file_lock_payload_write(record_lock, readback, sizeof(readback)); } } ipc_test_win_lock_release(&record_lock); cbm_private_lock_directory_close(directory); directory = NULL; bool mismatch_failed_closed = mismatch_write == CBM_PRIVATE_FILE_LOCK_OK && cbm_daemon_ipc_endpoint_address(reader) == NULL; int rebound_status = reader ? cbm_daemon_ipc_startup_lock_try_acquire(reader, &startup) : -1; if (rebound_status == 1 && !cbm_daemon_ipc_startup_lock_prepare_handoff(startup)) { rebound_status = -1; } if (rebound_status == 1) { ipc_test_copy_path(rebound_address, cbm_daemon_ipc_endpoint_address(reader)); } cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; bool rebound = rebound_address[0] && strcmp(rebound_address, repaired_address) != 0; cbm_daemon_ipc_startup_lock_release(&startup); if (record_lock) { (void)cbm_private_file_lock_release(&record_lock); } cbm_private_lock_directory_close(directory); cbm_daemon_ipc_endpoint_free(reader); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(initial_status, 1); ASSERT_TRUE(original_address[0] != '\0'); ASSERT_EQ(directory_status, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_EQ(record_status, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_EQ(write_status, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_TRUE(failed_closed); ASSERT_TRUE(reader_did_not_repair); ASSERT_EQ(repair_status, 1); ASSERT_TRUE(repaired); ASSERT_EQ(mismatch_read, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_TRUE(mismatch_encoded); ASSERT_EQ(mismatch_write, CBM_PRIVATE_FILE_LOCK_OK); ASSERT_TRUE(mismatch_failed_closed); ASSERT_EQ(rebound_status, 1); ASSERT_TRUE(rebound); PASS(); } TEST(daemon_ipc_windows_startup_and_lifetime_locks_are_cross_process) { static const char key[] = "c1d2e3f405162738"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_startup_lock_t *lifetime_startup = NULL; cbm_daemon_ipc_participant_guard_t *participant = NULL; cbm_daemon_ipc_lifetime_reservation_t *lifetime = NULL; bool parent_ok = ipc_test_parent_new(parent, "win-process-locks"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int startup_status = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int child_startup_while_held = startup_status == 1 ? ipc_test_win_lock_child("startup", key, parent) : -1; cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; int child_startup_after_release = endpoint ? ipc_test_win_lock_child("startup", key, parent) : -1; int lifetime_startup_status = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &lifetime_startup) : -1; bool lifetime_prepared = lifetime_startup_status == 1 && cbm_daemon_ipc_startup_lock_prepare_handoff(lifetime_startup); int participant_status = lifetime_prepared ? cbm_daemon_ipc_participant_guard_try_join(endpoint, &participant) : -1; int lifetime_status = participant_status == 1 ? cbm_daemon_ipc_lifetime_reservation_try_acquire(endpoint, &lifetime) : -1; cbm_daemon_ipc_startup_lock_release(&lifetime_startup); lifetime_startup = NULL; int child_lifetime_while_held = lifetime_status == 1 ? ipc_test_win_lock_child("lifetime", key, parent) : -1; cbm_daemon_ipc_lifetime_reservation_release(lifetime); lifetime = NULL; int child_lifetime_after_release = endpoint ? ipc_test_win_lock_child("lifetime", key, parent) : -1; bool participant_released = cbm_daemon_ipc_participant_guard_release(&participant); cbm_daemon_ipc_lifetime_reservation_release(lifetime); cbm_daemon_ipc_startup_lock_release(&startup); cbm_daemon_ipc_startup_lock_release(&lifetime_startup); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(startup_status, 1); ASSERT_EQ(child_startup_while_held, 20); ASSERT_EQ(child_startup_after_release, 0); ASSERT_EQ(lifetime_startup_status, 1); ASSERT_TRUE(lifetime_prepared); ASSERT_EQ(participant_status, 1); ASSERT_EQ(lifetime_status, 1); ASSERT_EQ(child_lifetime_while_held, 20); ASSERT_EQ(child_lifetime_after_release, 0); ASSERT_TRUE(participant_released); ASSERT_NULL(participant); PASS(); } #endif TEST(daemon_ipc_endpoint_is_namespaced_by_instance_key) { static const char key_a[] = "0123456789abcdef"; static const char key_b[] = "fedcba9876543210"; char parent[TEST_PATH_CAP] = {0}; char canonical_parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char same_runtime_dir[TEST_PATH_CAP] = {0}; char other_runtime_dir[TEST_PATH_CAP] = {0}; bool same_key_same_address = false; bool other_key_other_address = false; bool address_contains_key = false; bool runtime_is_private_child = false; bool invalid_key_rejected = false; cbm_daemon_ipc_endpoint_t *a = NULL; cbm_daemon_ipc_endpoint_t *same = NULL; cbm_daemon_ipc_endpoint_t *other = NULL; cbm_daemon_ipc_endpoint_t *invalid = NULL; cbm_daemon_ipc_startup_lock_t *a_startup = NULL; cbm_daemon_ipc_startup_lock_t *other_startup = NULL; bool parent_ok = ipc_test_parent_new(parent, "namespace") && ipc_test_full_path(canonical_parent, parent); if (parent_ok) { a = cbm_daemon_ipc_endpoint_new(key_a, parent); same = cbm_daemon_ipc_endpoint_new(key_a, parent); other = cbm_daemon_ipc_endpoint_new(key_b, parent); invalid = cbm_daemon_ipc_endpoint_new("../../not-a-key", parent); invalid_key_rejected = invalid == NULL; } #ifdef _WIN32 int a_startup_status = a ? cbm_daemon_ipc_startup_lock_try_acquire(a, &a_startup) : -1; int other_startup_status = other ? cbm_daemon_ipc_startup_lock_try_acquire(other, &other_startup) : -1; /* Windows addresses are generation-bound and exist only once a startup * owner publishes the rendezvous record; drive the documented flow. */ if (a_startup_status == 1 && !cbm_daemon_ipc_startup_lock_prepare_handoff(a_startup)) { a_startup_status = -1; } if (other_startup_status == 1 && !cbm_daemon_ipc_startup_lock_prepare_handoff(other_startup)) { other_startup_status = -1; } cbm_daemon_ipc_startup_lock_release(&a_startup); cbm_daemon_ipc_startup_lock_release(&other_startup); a_startup = NULL; other_startup = NULL; if (a_startup_status != 1 || other_startup_status != 1) { cbm_daemon_ipc_endpoint_free(other); cbm_daemon_ipc_endpoint_free(same); other = NULL; same = NULL; } #endif if (a && same && other) { const char *a_address = cbm_daemon_ipc_endpoint_address(a); const char *same_address = cbm_daemon_ipc_endpoint_address(same); const char *other_address = cbm_daemon_ipc_endpoint_address(other); const char *runtime = cbm_daemon_ipc_endpoint_runtime_dir(a); same_key_same_address = a_address && same_address && strcmp(a_address, same_address) == 0; other_key_other_address = a_address && other_address && strcmp(a_address, other_address) != 0; #ifdef _WIN32 address_contains_key = a_address && strncmp(a_address, "\\\\.\\pipe\\cbm-daemon-", strlen("\\\\.\\pipe\\cbm-daemon-")) == 0 && strstr(a_address, key_a) == NULL && other_address && strstr(other_address, key_b) == NULL; #else address_contains_key = a_address && strstr(a_address, key_a) != NULL && other_address && strstr(other_address, key_b) != NULL; #endif runtime_is_private_child = runtime && strcmp(runtime, canonical_parent) != 0 && ipc_test_path_has_parent(runtime, canonical_parent); ipc_test_copy_path(runtime_dir, runtime); ipc_test_copy_path(same_runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(same)); ipc_test_copy_path(other_runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(other)); } cbm_daemon_ipc_endpoint_free(invalid); cbm_daemon_ipc_startup_lock_release(&other_startup); cbm_daemon_ipc_startup_lock_release(&a_startup); cbm_daemon_ipc_endpoint_free(other); cbm_daemon_ipc_endpoint_free(same); cbm_daemon_ipc_endpoint_free(a); if (same_runtime_dir[0] != '\0' && strcmp(same_runtime_dir, runtime_dir) != 0) { ipc_test_remove_flat_dir(same_runtime_dir); } if (other_runtime_dir[0] != '\0' && strcmp(other_runtime_dir, runtime_dir) != 0 && strcmp(other_runtime_dir, same_runtime_dir) != 0) { ipc_test_remove_flat_dir(other_runtime_dir); } ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(same_key_same_address); ASSERT_TRUE(other_key_other_address); ASSERT_TRUE(address_contains_key); ASSERT_TRUE(runtime_is_private_child); ASSERT_TRUE(invalid_key_rejected); /* only exact 16-hex keys enter endpoint names */ PASS(); } TEST(daemon_ipc_relative_runtime_parent_is_canonical_and_stable) { static const char key[] = "0a1b2c3d4e5f6071"; char parent[TEST_PATH_CAP] = {0}; char canonical_parent[TEST_PATH_CAP] = {0}; char saved_cwd[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char cleanup_runtime_dir[TEST_PATH_CAP] = {0}; char address[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; cbm_daemon_ipc_connection_t *client = NULL; bool parent_ok = false; bool cwd_saved = false; bool entered_parent = false; bool cwd_restored = false; bool runtime_canonical = false; bool address_absolute = false; bool usable_after_chdir = false; parent_ok = ipc_test_parent_new(parent, "relative-parent") && ipc_test_full_path(canonical_parent, parent); cwd_saved = parent_ok && ipc_test_getcwd(saved_cwd, sizeof(saved_cwd)) != NULL; entered_parent = cwd_saved && ipc_test_chdir(parent) == 0; if (entered_parent) { endpoint = cbm_daemon_ipc_endpoint_new(key, "."); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); (void)ipc_test_full_path(cleanup_runtime_dir, runtime_dir); runtime_canonical = ipc_test_path_is_absolute(runtime_dir) && ipc_test_path_has_parent(runtime_dir, canonical_parent); } if (entered_parent) { cwd_restored = ipc_test_chdir(saved_cwd) == 0; } if (endpoint && cwd_restored) { listener = cbm_daemon_ipc_listen(endpoint); ipc_test_copy_path(address, cbm_daemon_ipc_endpoint_address(endpoint)); #ifdef _WIN32 address_absolute = strncmp(address, "\\\\.\\pipe\\", 9) == 0; #else address_absolute = ipc_test_path_is_absolute(address); #endif if (listener) { client = cbm_daemon_ipc_connect(endpoint, 500); } usable_after_chdir = listener != NULL && client != NULL; } cbm_daemon_ipc_connection_close(client); cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_endpoint_free(endpoint); if (entered_parent && !cwd_restored) { cwd_restored = ipc_test_chdir(saved_cwd) == 0; } ipc_test_remove_tree(cleanup_runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(cwd_saved); ASSERT_TRUE(entered_parent); ASSERT_TRUE(cwd_restored); ASSERT_TRUE(runtime_canonical); ASSERT_TRUE(address_absolute); ASSERT_TRUE(usable_after_chdir); PASS(); } TEST(daemon_ipc_rejects_uppercase_instance_key) { static const char uppercase_key[] = "ABCDEF0123456789"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; bool parent_ok = ipc_test_parent_new(parent, "uppercase-key"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(uppercase_key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } bool rejected = endpoint == NULL; cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(rejected); PASS(); } TEST(daemon_ipc_no_spawn_probe_distinguishes_absent_active_and_busy) { static const char key[] = "2468ace02468ace0"; enum { PROBE_CLIENT_CAP = 64 }; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; cbm_daemon_ipc_connection_t *clients[PROBE_CLIENT_CAP] = {0}; size_t client_count = 0; int absent_before = -1; int active_or_busy = -1; int absent_after = -1; if (ipc_test_parent_new(parent, "no-spawn-probe")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); absent_before = cbm_daemon_ipc_endpoint_probe(endpoint, 1); listener = cbm_daemon_ipc_listen(endpoint); } /* Do not accept: on Windows the sole pipe instance becomes BUSY; on * POSIX this fills the listen backlog. Probe must still report ACTIVE. */ while (listener && client_count < PROBE_CLIENT_CAP) { cbm_daemon_ipc_connection_t *client = cbm_daemon_ipc_connect(endpoint, 1); if (!client) { break; } clients[client_count++] = client; } if (listener) { active_or_busy = cbm_daemon_ipc_endpoint_probe(endpoint, 1); } for (size_t i = 0; i < client_count; i++) { cbm_daemon_ipc_connection_close(clients[i]); } cbm_daemon_ipc_listener_close(listener); listener = NULL; if (endpoint) { absent_after = cbm_daemon_ipc_endpoint_probe(endpoint, 1); } cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_EQ(absent_before, 0); ASSERT(client_count > 0); ASSERT_EQ(active_or_busy, 1); ASSERT_EQ(absent_after, 0); PASS(); } TEST(daemon_ipc_lifetime_reservation_survives_saturated_second_listen) { static const char key[] = "13579bdf13579bdf"; enum { PROBE_CLIENT_CAP = 64 }; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *first = NULL; cbm_daemon_ipc_listener_t *second = NULL; cbm_daemon_ipc_listener_t *after_close = NULL; cbm_daemon_ipc_connection_t *clients[PROBE_CLIENT_CAP] = {0}; size_t client_count = 0; bool first_started = false; bool after_close_started = false; int free_before = -1; int held_while_listening = -1; int held_after_second_attempt = -1; int endpoint_after_second_attempt = -1; int free_after_close = -1; if (ipc_test_parent_new(parent, "lifetime-reservation")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); free_before = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); first = cbm_daemon_ipc_listen(endpoint); first_started = first != NULL; } if (first) { held_while_listening = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); } /* Keep the first listener from accepting and fill as much transport * capacity as the platform exposes. On BSD-derived kernels a subsequent * connect can report ECONNREFUSED even though this listener is live. */ while (first && client_count < PROBE_CLIENT_CAP) { cbm_daemon_ipc_connection_t *client = cbm_daemon_ipc_connect(endpoint, 1); if (!client) { break; } clients[client_count++] = client; } if (first) { second = cbm_daemon_ipc_listen(endpoint); held_after_second_attempt = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); endpoint_after_second_attempt = cbm_daemon_ipc_endpoint_probe(endpoint, 1); } for (size_t i = 0; i < client_count; i++) { cbm_daemon_ipc_connection_close(clients[i]); } cbm_daemon_ipc_listener_close(second); cbm_daemon_ipc_listener_close(first); first = NULL; if (endpoint) { free_after_close = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); after_close = cbm_daemon_ipc_listen(endpoint); after_close_started = after_close != NULL; } cbm_daemon_ipc_listener_close(after_close); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_EQ(free_before, 0); ASSERT_TRUE(first_started); ASSERT_TRUE(client_count > 0); ASSERT_EQ(held_while_listening, 1); ASSERT_TRUE(second == NULL); ASSERT_EQ(held_after_second_attempt, 1); ASSERT_EQ(endpoint_after_second_attempt, 1); ASSERT_EQ(free_after_close, 0); ASSERT_TRUE(after_close_started); PASS(); } /* #2178: an age-based temp cleaner unlinked the live daemon's lifetime lock * and identity marker. The heartbeat must keep every listener-owned file * young, and report loss (unlink, or replacement by a fresh file at the same * name) so the daemon exits instead of serving under a lifetime file that * coordinates nothing or leaving an unremovable socket pair behind at close. */ TEST(daemon_ipc_listener_touch_detects_lost_runtime_files) { #ifdef _WIN32 SKIP_PLATFORM("unlink-while-held of runtime files is POSIX behavior"); #else enum { TOUCH_CASES = 5 }; enum { LOSS_UNLINK, LOSS_REPLACE_FILE, LOSS_REPLACE_SYMLINK }; static const char key[] = "2178a2178a2178a0"; static const struct { const char *suffix; int loss; } cases[TOUCH_CASES] = { {"lifetime.lock", LOSS_UNLINK}, /* lifetime reservation */ {"lock", LOSS_UNLINK}, /* listener-owned participant guard */ {"sock.identity", LOSS_UNLINK}, /* identity marker, unlinked */ {"sock.identity", LOSS_REPLACE_FILE}, /* ... replaced by a fresh inode */ {"sock.identity", LOSS_REPLACE_SYMLINK} /* ... replaced by a symlink */, }; const time_t stale = 1000000000; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; bool started[TOUCH_CASES] = {false}; int touched_intact[TOUCH_CASES] = {0}; bool refreshed[TOUCH_CASES] = {false}; bool lost[TOUCH_CASES] = {false}; int touched_after_loss[TOUCH_CASES] = {1, 1, 1, 1, 1}; int unreadable_touch = 0; int readable_again_touch = 0; if (ipc_test_parent_new(parent, "listener-touch")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } /* A still-valid marker that merely cannot be reopened (EACCES) is a * transient refresh failure, not loss: the daemon must keep serving. */ if (endpoint) { cbm_daemon_ipc_listener_t *listener = cbm_daemon_ipc_listen(endpoint); char marker[TEST_PATH_CAP]; int written = snprintf(marker, sizeof(marker), "%s/cbm-%s.sock.identity", runtime_dir, key); if (listener && written > 0 && written < (int)sizeof(marker) && chmod(marker, 0) == 0) { unreadable_touch = cbm_daemon_ipc_listener_touch(listener, NULL); readable_again_touch = chmod(marker, 0600) == 0 ? cbm_daemon_ipc_listener_touch(listener, NULL) : -2; } cbm_daemon_ipc_listener_close(listener); } for (size_t i = 0; endpoint && i < TOUCH_CASES; i++) { cbm_daemon_ipc_listener_t *listener = cbm_daemon_ipc_listen(endpoint); started[i] = listener != NULL; char path[TEST_PATH_CAP]; int written = snprintf(path, sizeof(path), "%s/cbm-%s.%s", runtime_dir, key, cases[i].suffix); bool path_ok = started[i] && written > 0 && written < (int)sizeof(path); struct timespec stale_times[2] = {{.tv_sec = stale}, {.tv_sec = stale}}; bool aged = path_ok && utimensat(AT_FDCWD, path, stale_times, AT_SYMLINK_NOFOLLOW) == 0; touched_intact[i] = aged ? cbm_daemon_ipc_listener_touch(listener, NULL) : -2; struct stat after; refreshed[i] = touched_intact[i] == 1 && stat(path, &after) == 0 && after.st_atime > stale && after.st_mtime > stale; lost[i] = refreshed[i] && unlink(path) == 0; if (lost[i] && cases[i].loss == LOSS_REPLACE_FILE) { int fd = open(path, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0600); lost[i] = fd >= 0 && close(fd) == 0; } else if (lost[i] && cases[i].loss == LOSS_REPLACE_SYMLINK) { lost[i] = symlink("/dev/null", path) == 0; } touched_after_loss[i] = lost[i] ? cbm_daemon_ipc_listener_touch(listener, NULL) : -2; cbm_daemon_ipc_listener_close(listener); /* A tampered marker makes close (correctly) keep the socket pair; clear * this case's simulated leftovers so the next case can listen. */ static const char *const leftovers[] = {"sock.identity", "sock", "anc"}; for (size_t j = 0; j < sizeof(leftovers) / sizeof(leftovers[0]); j++) { written = snprintf(path, sizeof(path), "%s/cbm-%s.%s", runtime_dir, key, leftovers[j]); if (written > 0 && written < (int)sizeof(path)) { (void)unlink(path); } } } cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); /* Root bypasses mode 0, so the reopen succeeds there and the touch is 1. */ ASSERT_EQ(unreadable_touch, geteuid() == 0 ? 1 : -1); ASSERT_EQ(readable_again_touch, 1); for (size_t i = 0; i < TOUCH_CASES; i++) { ASSERT_TRUE(started[i]); ASSERT_EQ(touched_intact[i], 1); ASSERT_TRUE(refreshed[i]); ASSERT_TRUE(lost[i]); ASSERT_EQ(touched_after_loss[i], 0); } PASS(); #endif } TEST(daemon_ipc_lifetime_reservation_transfers_without_unlock_window) { static const char key[] = "1029384756abcdef"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_participant_guard_t *participant = NULL; cbm_daemon_ipc_lifetime_reservation_t *reservation = NULL; cbm_daemon_ipc_listener_t *listener = NULL; cbm_daemon_ipc_listener_t *contender = NULL; cbm_daemon_ipc_connection_t *client = NULL; int acquired = -1; int held_before_transfer = -1; int held_after_transfer = -1; int free_after_close = -1; bool transfer_consumed = false; bool connected = false; if (ipc_test_parent_new(parent, "lifetime-transfer")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); int startup_result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup); bool prepared = startup_result == 1 && cbm_daemon_ipc_startup_lock_prepare_handoff(startup); int participant_result = prepared ? cbm_daemon_ipc_participant_guard_try_join(endpoint, &participant) : -1; acquired = participant_result == 1 ? cbm_daemon_ipc_lifetime_reservation_try_acquire(endpoint, &reservation) : -1; cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; } if (reservation) { held_before_transfer = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); contender = cbm_daemon_ipc_listen(endpoint); listener = cbm_daemon_ipc_listen_reserved(endpoint, &reservation); transfer_consumed = listener != NULL && reservation == NULL; } if (listener) { held_after_transfer = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); client = cbm_daemon_ipc_connect(endpoint, 500); connected = client != NULL; } cbm_daemon_ipc_connection_close(client); cbm_daemon_ipc_listener_close(contender); cbm_daemon_ipc_listener_close(listener); listener = NULL; cbm_daemon_ipc_lifetime_reservation_release(reservation); reservation = NULL; bool participant_released = cbm_daemon_ipc_participant_guard_release(&participant); if (endpoint) { free_after_close = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); } cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_EQ(acquired, 1); ASSERT_EQ(held_before_transfer, 1); ASSERT_TRUE(contender == NULL); ASSERT_TRUE(transfer_consumed); ASSERT_TRUE(connected); ASSERT_EQ(held_after_transfer, 1); ASSERT_EQ(free_after_close, 0); ASSERT_TRUE(participant_released); ASSERT_NULL(participant); PASS(); } typedef struct { cbm_daemon_ipc_listener_t *listener; int result; uint64_t peer_pid; } ipc_roundtrip_server_t; static uint64_t ipc_test_process_id(void) { #ifdef _WIN32 return (uint64_t)GetCurrentProcessId(); #else return (uint64_t)getpid(); #endif } static void *ipc_roundtrip_server(void *opaque) { static const uint8_t expected_request[] = {0x00, 'r', 'e', 'q', 0xff}; static const uint8_t response[] = {'o', 'k', 0x00, 0x7f}; ipc_roundtrip_server_t *server = (ipc_roundtrip_server_t *)opaque; cbm_daemon_ipc_connection_t *connection = NULL; cbm_daemon_frame_t frame = {0}; uint8_t *payload = NULL; server->result = 1; if (cbm_daemon_ipc_accept(server->listener, 2000, &connection) != 1 || !connection) { goto done; } server->peer_pid = cbm_daemon_ipc_connection_peer_pid(connection); if (server->peer_pid == 0) { goto done; } if (cbm_daemon_ipc_receive_frame(connection, 2000, &frame, &payload) != 1) { goto done; } if (frame.type != CBM_DAEMON_FRAME_REQUEST || frame.flags != 0x1234 || frame.length != sizeof(expected_request) || !payload || memcmp(payload, expected_request, sizeof(expected_request)) != 0) { goto done; } if (!cbm_daemon_ipc_send_frame(connection, CBM_DAEMON_FRAME_RESPONSE, 0x00a5, response, (uint32_t)sizeof(response))) { goto done; } server->result = 0; done: free(payload); /* receive_frame returns a malloc-owned payload */ cbm_daemon_ipc_connection_close(connection); return NULL; } TEST(daemon_ipc_local_frame_roundtrip) { static const char key[] = "1111222233334444"; static const uint8_t request[] = {0x00, 'r', 'e', 'q', 0xff}; static const uint8_t expected_response[] = {'o', 'k', 0x00, 0x7f}; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; cbm_daemon_ipc_connection_t *client = NULL; cbm_thread_t thread; bool thread_started = false; ipc_roundtrip_server_t server = {0}; cbm_daemon_frame_t response_frame = {0}; uint8_t *response_payload = NULL; uint64_t client_peer_pid = 0; int result = 1; if (!ipc_test_parent_new(parent, "roundtrip")) { goto cleanup; } endpoint = cbm_daemon_ipc_endpoint_new(key, parent); if (!endpoint) { goto cleanup; } ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); listener = cbm_daemon_ipc_listen(endpoint); if (!listener) { goto cleanup; } server.listener = listener; server.result = 1; if (cbm_thread_create(&thread, 0, ipc_roundtrip_server, &server) != 0) { goto cleanup; } thread_started = true; client = cbm_daemon_ipc_connect(endpoint, 2000); client_peer_pid = cbm_daemon_ipc_connection_peer_pid(client); if (!client || client_peer_pid == 0 || !cbm_daemon_ipc_send_frame(client, CBM_DAEMON_FRAME_REQUEST, 0x1234, request, (uint32_t)sizeof(request)) || cbm_daemon_ipc_receive_frame(client, 2000, &response_frame, &response_payload) != 1) { goto cleanup; } if (response_frame.type != CBM_DAEMON_FRAME_RESPONSE || response_frame.flags != 0x00a5 || response_frame.length != sizeof(expected_response) || !response_payload || memcmp(response_payload, expected_response, sizeof(expected_response)) != 0) { goto cleanup; } result = 0; cleanup: free(response_payload); cbm_daemon_ipc_connection_close(client); if (thread_started) { int join_result = cbm_thread_join(&thread); if (join_result != 0 || server.result != 0) { result = 1; } } cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_EQ(result, 0); ASSERT_EQ(client_peer_pid, ipc_test_process_id()); ASSERT_EQ(server.peer_pid, ipc_test_process_id()); PASS(); } TEST(daemon_ipc_bounded_receive_rejects_oversize_before_payload) { static const char key[] = "2021222324252627"; static const uint8_t oversized_payload[] = {'o', 'v', 'e', 'r', '!'}; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; cbm_daemon_ipc_connection_t *client = NULL; cbm_daemon_ipc_connection_t *server = NULL; cbm_daemon_frame_t frame = {0}; uint8_t *received_payload = NULL; bool parent_ok = ipc_test_parent_new(parent, "bounded-frame"); int accepted = -1; bool sent = false; int bounded_result = 1; int reuse_result = 1; bool bounded_payload_absent = false; bool bounded_frame_empty = false; if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); listener = cbm_daemon_ipc_listen(endpoint); } if (listener) { client = cbm_daemon_ipc_connect(endpoint, 500); } if (client) { accepted = cbm_daemon_ipc_accept(listener, 500, &server); } if (accepted == 1 && server) { sent = cbm_daemon_ipc_send_frame(client, CBM_DAEMON_FRAME_REQUEST, 0x2021, oversized_payload, (uint32_t)sizeof(oversized_payload)); } if (sent) { bounded_result = cbm_daemon_ipc_receive_frame_bounded( server, 500, (uint32_t)sizeof(oversized_payload) - 1, &frame, &received_payload); bounded_payload_absent = received_payload == NULL; bounded_frame_empty = frame.length == 0; free(received_payload); received_payload = NULL; reuse_result = cbm_daemon_ipc_receive_frame(server, 1, &frame, &received_payload); } free(received_payload); cbm_daemon_ipc_connection_close(server); cbm_daemon_ipc_connection_close(client); cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(accepted, 1); ASSERT_TRUE(sent); ASSERT_EQ(bounded_result, -1); ASSERT_TRUE(bounded_payload_absent); ASSERT_TRUE(bounded_frame_empty); ASSERT_EQ(reuse_result, -1); PASS(); } typedef struct { cbm_daemon_ipc_listener_t *listener; cbm_daemon_ipc_connection_t *connection; atomic_bool waiting; atomic_bool finished; /* set once the server thread has returned */ int receive_result; } ipc_forever_wait_server_t; /* The connection is deliberately NOT closed here. The test interrupts this * thread's wait through server->connection, and `receive_frame` can also return * on its own (a torn connection, an early frame) before that call lands. * Closing from this thread therefore freed the connection while the test was * still dereferencing the pointer it had already loaded — a use-after-free that * crashed roughly one Windows run in five. Ownership stays with the test, which * closes it after joining, so `interrupt` can only ever see a live connection. */ static void *ipc_forever_wait_server(void *opaque) { ipc_forever_wait_server_t *server = (ipc_forever_wait_server_t *)opaque; cbm_daemon_frame_t frame = {0}; uint8_t *payload = NULL; server->receive_result = -2; if (cbm_daemon_ipc_accept(server->listener, 2000, &server->connection) == 1 && server->connection) { atomic_store_explicit(&server->waiting, true, memory_order_release); server->receive_result = cbm_daemon_ipc_receive_frame( server->connection, CBM_DAEMON_IPC_WAIT_FOREVER, &frame, &payload); } free(payload); atomic_store_explicit(&server->finished, true, memory_order_release); return NULL; } TEST(daemon_ipc_wait_forever_is_interruptible) { static const char key[] = "1212121212121212"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; cbm_daemon_ipc_connection_t *client = NULL; cbm_thread_t thread; bool thread_started = false; bool reached_wait = false; int join_result = -1; ipc_forever_wait_server_t server = {0}; atomic_init(&server.waiting, false); atomic_init(&server.finished, false); if (ipc_test_parent_new(parent, "wait-forever")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); listener = cbm_daemon_ipc_listen(endpoint); } if (listener) { server.listener = listener; thread_started = cbm_thread_create(&thread, 0, ipc_forever_wait_server, &server) == 0; } if (thread_started) { client = cbm_daemon_ipc_connect(endpoint, 2000); } /* Wait for the event itself, never a deadline: the server either enters * its forever-wait or returns (its accept is bounded by the call's own * timeout), and both flags only ever go false -> true. */ while (client && !atomic_load_explicit(&server.waiting, memory_order_acquire) && !atomic_load_explicit(&server.finished, memory_order_acquire)) { cbm_usleep(1000); } reached_wait = atomic_load_explicit(&server.waiting, memory_order_acquire); if (reached_wait) { cbm_daemon_ipc_connection_interrupt(server.connection); } if (thread_started) { join_result = cbm_thread_join(&thread); } /* Safe only after the join: the server thread no longer touches it. */ cbm_daemon_ipc_connection_close(server.connection); server.connection = NULL; cbm_daemon_ipc_connection_close(client); cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(thread_started); ASSERT_TRUE(client != NULL); ASSERT_TRUE(reached_wait); ASSERT_EQ(join_result, 0); ASSERT_EQ(server.receive_result, -1); PASS(); } typedef struct { const cbm_daemon_ipc_endpoint_t *endpoint; cbm_daemon_ipc_listener_t *listener; atomic_bool delay_started; } ipc_delayed_listener_t; static void *ipc_delayed_listener_start(void *opaque) { ipc_delayed_listener_t *delayed = (ipc_delayed_listener_t *)opaque; struct timespec delay = {.tv_sec = 0, .tv_nsec = 150000000}; atomic_store_explicit(&delayed->delay_started, true, memory_order_release); (void)cbm_nanosleep(&delay, NULL); delayed->listener = cbm_daemon_ipc_listen(delayed->endpoint); return NULL; } TEST(daemon_ipc_connect_waits_for_delayed_listener) { static const char key[] = "2222333344445555"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_connection_t *client = NULL; cbm_thread_t thread; bool thread_started = false; bool delay_observed = false; int join_result = -1; ipc_delayed_listener_t delayed = {0}; atomic_init(&delayed.delay_started, false); bool parent_ok = ipc_test_parent_new(parent, "delayed-listener"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); delayed.endpoint = endpoint; thread_started = cbm_thread_create(&thread, 0, ipc_delayed_listener_start, &delayed) == 0; } if (thread_started) { /* `delay_started` is the thread's first store, so it cannot return * without setting it: wait for that event with no attempt bound. */ struct timespec poll_delay = {.tv_sec = 0, .tv_nsec = 1000000}; while (!atomic_load_explicit(&delayed.delay_started, memory_order_acquire)) { (void)cbm_nanosleep(&poll_delay, NULL); } delay_observed = true; } if (delay_observed) { client = cbm_daemon_ipc_connect(endpoint, 1500); } if (thread_started) { join_result = cbm_thread_join(&thread); } bool connected_after_wait = client != NULL && delayed.listener != NULL; cbm_daemon_ipc_connection_close(client); cbm_daemon_ipc_listener_close(delayed.listener); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(thread_started); ASSERT_TRUE(delay_observed); ASSERT_EQ(join_result, 0); ASSERT_TRUE(connected_after_wait); PASS(); } typedef struct { const cbm_daemon_ipc_endpoint_t *endpoint; int result; } ipc_lock_contender_t; static void *ipc_lock_contender(void *opaque) { ipc_lock_contender_t *contender = (ipc_lock_contender_t *)opaque; cbm_daemon_ipc_startup_lock_t *lock = NULL; contender->result = cbm_daemon_ipc_startup_lock_try_acquire(contender->endpoint, &lock); cbm_daemon_ipc_startup_lock_release(&lock); return NULL; } TEST(daemon_ipc_startup_lock_has_one_winner) { static const char key[] = "5555666677778888"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *first = NULL; cbm_daemon_ipc_startup_lock_t *after_release = NULL; cbm_thread_t thread; bool thread_started = false; ipc_lock_contender_t contender = {0}; int first_result = -1; int reacquire_result = -1; int join_result = -1; if (ipc_test_parent_new(parent, "startup-lock")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); first_result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &first); } if (first_result == 1 && first) { contender.endpoint = endpoint; contender.result = -1; if (cbm_thread_create(&thread, 0, ipc_lock_contender, &contender) == 0) { thread_started = true; join_result = cbm_thread_join(&thread); thread_started = false; } } cbm_daemon_ipc_startup_lock_release(&first); first = NULL; if (endpoint) { reacquire_result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &after_release); } cbm_daemon_ipc_startup_lock_release(&after_release); if (thread_started) { (void)cbm_thread_join(&thread); } cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_EQ(first_result, 1); ASSERT_EQ(join_result, 0); ASSERT_EQ(contender.result, 0); /* held, not an OS/error failure */ ASSERT_EQ(reacquire_result, 1); PASS(); } TEST(daemon_ipc_activation_probe_ignores_matching_startup_claim_only) { static const char key[] = "5c5c6d6d7e7e8f8f"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_endpoint_t *wrong_endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_listener_t *listener = NULL; bool parent_ok = ipc_test_parent_new(parent, "activation-probe"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); wrong_endpoint = cbm_daemon_ipc_endpoint_new("5c5c6d6d7e7e8f90", parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int without_lock = endpoint ? cbm_daemon_ipc_generation_probe_under_startup_lock(endpoint, NULL) : 0; int startup_result = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int generic_self = startup ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; int matching = startup ? cbm_daemon_ipc_generation_probe_under_startup_lock(endpoint, startup) : -2; int mismatched = startup && wrong_endpoint ? cbm_daemon_ipc_generation_probe_under_startup_lock(wrong_endpoint, startup) : -2; bool prepared = startup && cbm_daemon_ipc_startup_lock_prepare_handoff(startup); int after_prepare = prepared ? cbm_daemon_ipc_generation_probe_under_startup_lock(endpoint, startup) : -2; cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; int after_release = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; listener = endpoint ? cbm_daemon_ipc_listen(endpoint) : NULL; bool listener_started = listener != NULL; int active_startup = listener ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int active_generation = active_startup == 1 ? cbm_daemon_ipc_generation_probe_under_startup_lock(endpoint, startup) : -1; cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_endpoint_free(wrong_endpoint); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(without_lock, -1); ASSERT_EQ(startup_result, 1); ASSERT_EQ(generic_self, 1); ASSERT_EQ(matching, 0); ASSERT_EQ(mismatched, -1); ASSERT_TRUE(prepared); ASSERT_EQ(after_prepare, -1); ASSERT_EQ(after_release, 0); ASSERT_TRUE(listener_started); ASSERT_EQ(active_startup, 1); ASSERT_EQ(active_generation, 1); PASS(); } TEST(daemon_ipc_local_transition_coexists_with_active_daemon_lifetime) { static const char key[] = "5d5d6e6e7f7f8080"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_startup_lock_t *startup_during_local = NULL; cbm_daemon_ipc_startup_lock_t *startup_after = NULL; cbm_daemon_ipc_lifetime_reservation_t *lifetime = NULL; cbm_daemon_ipc_participant_guard_t *daemon_participant = NULL; cbm_daemon_ipc_local_transition_t *transition = NULL; bool parent_ok = ipc_test_parent_new(parent, "local-transition"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int startup_result = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; bool handoff = startup_result == 1 && cbm_daemon_ipc_startup_lock_prepare_handoff(startup); int participant_result = handoff ? cbm_daemon_ipc_participant_guard_try_join(endpoint, &daemon_participant) : -1; int lifetime_result = participant_result == 1 ? cbm_daemon_ipc_lifetime_reservation_try_acquire(endpoint, &lifetime) : -1; cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; int transition_result = lifetime_result == 1 ? cbm_daemon_ipc_local_transition_try_acquire(endpoint, &transition) : -1; int unsealed_lifetime = transition_result == 1 ? cbm_daemon_ipc_local_transition_lifetime_probe(endpoint, transition) : 0; int seal_result = transition_result == 1 ? cbm_daemon_ipc_local_transition_seal_legacy(transition) : -1; int sealed_lifetime = seal_result == 1 ? cbm_daemon_ipc_local_transition_lifetime_probe(endpoint, transition) : -1; bool work_begun = sealed_lifetime == 1 && cbm_daemon_ipc_local_transition_begin_work(transition); int startup_during_result = work_begun ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup_during_local) : -1; cbm_daemon_ipc_startup_lock_release(&startup_during_local); startup_during_local = NULL; bool transition_released = cbm_daemon_ipc_local_transition_release(&transition); int lifetime_after_transition = endpoint ? cbm_daemon_ipc_lifetime_reservation_probe(endpoint) : -1; cbm_daemon_ipc_lifetime_reservation_release(lifetime); lifetime = NULL; bool participant_released = cbm_daemon_ipc_participant_guard_release(&daemon_participant); int startup_after_result = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup_after) : -1; cbm_daemon_ipc_startup_lock_release(&startup_after); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(startup_result, 1); ASSERT_TRUE(handoff); ASSERT_EQ(participant_result, 1); ASSERT_EQ(lifetime_result, 1); ASSERT_EQ(transition_result, 1); ASSERT_EQ(unsealed_lifetime, -1); ASSERT_EQ(seal_result, 1); ASSERT_EQ(sealed_lifetime, 1); ASSERT_TRUE(work_begun); ASSERT_EQ(startup_during_result, 1); ASSERT_TRUE(transition_released); ASSERT_NULL(transition); ASSERT_EQ(lifetime_after_transition, 1); ASSERT_TRUE(participant_released); ASSERT_NULL(daemon_participant); ASSERT_EQ(startup_after_result, 1); PASS(); } TEST(daemon_ipc_local_participants_overlap_and_allow_modern_startup) { static const char key[] = "5e5e6f6f70708181"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_local_transition_t *first = NULL; cbm_daemon_ipc_local_transition_t *second = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; bool parent_ok = ipc_test_parent_new(parent, "local-overlap"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int first_acquired = endpoint ? cbm_daemon_ipc_local_transition_try_acquire(endpoint, &first) : -1; int first_sealed = first_acquired == 1 ? cbm_daemon_ipc_local_transition_seal_legacy(first) : -1; int first_presence = first_sealed == 1 ? cbm_daemon_ipc_local_transition_lifetime_probe(endpoint, first) : -1; bool first_begun = first_presence == 0 && cbm_daemon_ipc_local_transition_begin_work(first); int second_acquired = first_begun ? cbm_daemon_ipc_local_transition_try_acquire(endpoint, &second) : -1; int second_sealed = second_acquired == 1 ? cbm_daemon_ipc_local_transition_seal_legacy(second) : -1; int second_presence = second_sealed == 1 ? cbm_daemon_ipc_local_transition_lifetime_probe(endpoint, second) : -1; bool second_begun = second_presence == 0 && cbm_daemon_ipc_local_transition_begin_work(second); int startup_during = second_begun ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; int legacy_while_both = second_begun ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; bool first_released = cbm_daemon_ipc_local_transition_release(&first); int legacy_while_second = first_released ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; bool second_released = cbm_daemon_ipc_local_transition_release(&second); int legacy_after = second_released ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; (void)cbm_daemon_ipc_local_transition_release(&second); (void)cbm_daemon_ipc_local_transition_release(&first); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(first_acquired, 1); ASSERT_EQ(first_sealed, 1); ASSERT_EQ(first_presence, 0); ASSERT_TRUE(first_begun); ASSERT_EQ(second_acquired, 1); ASSERT_EQ(second_sealed, 1); ASSERT_EQ(second_presence, 0); ASSERT_TRUE(second_begun); ASSERT_EQ(startup_during, 1); ASSERT_EQ(legacy_while_both, 1); ASSERT_TRUE(first_released); ASSERT_EQ(legacy_while_second, 1); ASSERT_TRUE(second_released); ASSERT_EQ(legacy_after, 0); PASS(); } TEST(daemon_ipc_windows_local_transition_release_reports_live_gate_contention) { #ifndef _WIN32 PASS(); #else static const char key[] = "6060717182829393"; char parent[TEST_PATH_CAP] = {0}; char canonical_parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char legacy_pipe[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; char legacy_startup[CBM_DAEMON_IPC_WINDOWS_NAME_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_local_transition_t *transition = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; bool parent_ok = ipc_test_parent_new(parent, "local-release-busy") && ipc_test_full_path(canonical_parent, parent); bool names_ok = parent_ok && cbm_daemon_ipc_windows_legacy_names(canonical_parent, key, legacy_pipe, legacy_startup); if (names_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int acquired = endpoint ? cbm_daemon_ipc_local_transition_try_acquire(endpoint, &transition) : -1; int sealed = acquired == 1 ? cbm_daemon_ipc_local_transition_seal_legacy(transition) : -1; int lifetime = sealed == 1 ? cbm_daemon_ipc_local_transition_lifetime_probe(endpoint, transition) : -1; bool begun = lifetime == 0 && cbm_daemon_ipc_local_transition_begin_work(transition); int startup_acquired = begun ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int blocked = cbm_daemon_ipc_local_transition_try_release(&transition); bool retained_while_busy = transition != NULL; bool boolean_blocked = cbm_daemon_ipc_local_transition_release(&transition); /* Keep the already-validated named mutex alive after the startup owner * exits. Holding just this mutex exercises the second contention path. */ wchar_t *legacy_name = names_ok ? cbm_utf8_to_wide(legacy_startup) : NULL; HANDLE legacy_mutex = startup_acquired == 1 && legacy_name ? OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, legacy_name) : NULL; free(legacy_name); bool startup_released = cbm_daemon_ipc_startup_lock_release(&startup); DWORD mutex_wait = startup_released && legacy_mutex ? WaitForSingleObject(legacy_mutex, 0) : WAIT_FAILED; bool mutex_owned = mutex_wait == WAIT_OBJECT_0 || mutex_wait == WAIT_ABANDONED; int legacy_blocked = mutex_owned ? cbm_daemon_ipc_local_transition_try_release(&transition) : -1; bool retained_while_legacy_busy = transition != NULL; bool mutex_released = mutex_owned && ReleaseMutex(legacy_mutex); if (legacy_mutex) { (void)CloseHandle(legacy_mutex); } int legacy_retained = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; int released = cbm_daemon_ipc_local_transition_try_release(&transition); bool consumed = transition == NULL; int legacy_after = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; (void)cbm_daemon_ipc_startup_lock_release(&startup); (void)cbm_daemon_ipc_local_transition_release(&transition); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(names_ok); ASSERT_EQ(acquired, 1); ASSERT_EQ(sealed, 1); ASSERT_EQ(lifetime, 0); ASSERT_TRUE(begun); ASSERT_EQ(startup_acquired, 1); ASSERT_EQ(blocked, 0); ASSERT_TRUE(retained_while_busy); ASSERT_FALSE(boolean_blocked); ASSERT_TRUE(startup_released); ASSERT_EQ(mutex_wait, WAIT_OBJECT_0); ASSERT_EQ(legacy_blocked, 0); ASSERT_TRUE(retained_while_legacy_busy); ASSERT_TRUE(mutex_released); ASSERT_EQ(legacy_retained, 1); ASSERT_EQ(released, 1); ASSERT_TRUE(consumed); ASSERT_EQ(legacy_after, 0); PASS(); #endif } TEST(daemon_ipc_windows_local_transition_release_retries_retained_mutex) { #ifndef _WIN32 PASS(); #else static const char key[] = "5f5f606071718282"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_local_transition_t *transition = NULL; cbm_daemon_ipc_startup_lock_t *after = NULL; bool parent_ok = ipc_test_parent_new(parent, "local-release-retry"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int acquired = endpoint ? cbm_daemon_ipc_local_transition_try_acquire(endpoint, &transition) : -1; int sealed = acquired == 1 ? cbm_daemon_ipc_local_transition_seal_legacy(transition) : -1; int lifetime = sealed == 1 ? cbm_daemon_ipc_local_transition_lifetime_probe(endpoint, transition) : -1; bool begun = lifetime == 0 && cbm_daemon_ipc_local_transition_begin_work(transition); if (begun) { cbm_daemon_ipc_windows_legacy_guard_release_failures_set_for_test(1); } int first_release = cbm_daemon_ipc_local_transition_try_release(&transition); bool retained_after_failure = transition != NULL; bool retry_release = cbm_daemon_ipc_local_transition_release(&transition); cbm_daemon_ipc_windows_legacy_guard_release_failures_set_for_test(0); int legacy_after = endpoint ? cbm_daemon_ipc_legacy_generation_probe(endpoint) : -1; int startup_after = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &after) : -1; cbm_daemon_ipc_startup_lock_release(&after); (void)cbm_daemon_ipc_local_transition_release(&transition); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(acquired, 1); ASSERT_EQ(sealed, 1); ASSERT_EQ(lifetime, 0); ASSERT_TRUE(begun); ASSERT_EQ(first_release, -1); ASSERT_TRUE(retained_after_failure); ASSERT_TRUE(retry_release); ASSERT_NULL(transition); ASSERT_EQ(legacy_after, 0); ASSERT_EQ(startup_after, 1); PASS(); #endif } TEST(daemon_ipc_windows_startup_release_retains_retry_authority) { #ifndef _WIN32 PASS(); #else static const char key[] = "6f6f707081819292"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_startup_lock_t *blocked = NULL; cbm_daemon_ipc_startup_lock_t *after = NULL; bool parent_ok = ipc_test_parent_new(parent, "startup-release-retry"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int acquired = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; if (acquired == 1) { cbm_daemon_ipc_windows_legacy_guard_release_failures_set_for_test(1); } bool first_release = cbm_daemon_ipc_startup_lock_release(&startup); bool retained_after_failure = startup != NULL; int blocked_while_retained = retained_after_failure ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &blocked) : -1; bool retry_release = cbm_daemon_ipc_startup_lock_release(&startup); cbm_daemon_ipc_windows_legacy_guard_release_failures_set_for_test(0); int acquired_after = retry_release ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &after) : -1; bool after_released = cbm_daemon_ipc_startup_lock_release(&after); (void)cbm_daemon_ipc_startup_lock_release(&blocked); (void)cbm_daemon_ipc_startup_lock_release(&startup); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(acquired, 1); ASSERT_FALSE(first_release); ASSERT_TRUE(retained_after_failure); ASSERT_EQ(blocked_while_retained, 0); ASSERT_TRUE(retry_release); ASSERT_NULL(startup); ASSERT_EQ(acquired_after, 1); ASSERT_TRUE(after_released); PASS(); #endif } /* A frame deadline is a fail-stop boundary. In particular, Windows * overlapped I/O can complete while cancellation races a timeout, so an * offset-zero timeout does not prove that the byte stream is untouched. * Reusing that connection could interpret trailing bytes as a new header. */ TEST(daemon_ipc_frame_timeout_poisons_connection) { static const char key[] = "5a5a6b6b7c7c8d8d"; static const uint8_t payload[] = {'a', 'f', 't', 'e', 'r'}; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; cbm_daemon_ipc_connection_t *client = NULL; cbm_daemon_ipc_connection_t *server = NULL; cbm_daemon_frame_t frame = {0}; uint8_t *received_payload = NULL; int accept_result = -1; int timeout_result = -1; int reuse_result = 1; bool sent_after_timeout = false; bool parent_ok = ipc_test_parent_new(parent, "frame-timeout"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); listener = cbm_daemon_ipc_listen(endpoint); } if (listener) { client = cbm_daemon_ipc_connect(endpoint, 500); } if (client) { accept_result = cbm_daemon_ipc_accept(listener, 500, &server); } if (accept_result == 1 && server) { timeout_result = cbm_daemon_ipc_receive_frame(server, 20, &frame, &received_payload); } free(received_payload); received_payload = NULL; memset(&frame, 0, sizeof(frame)); if (timeout_result == 0) { sent_after_timeout = cbm_daemon_ipc_send_frame(client, CBM_DAEMON_FRAME_REQUEST, 0x1357, payload, (uint32_t)sizeof(payload)); } if (sent_after_timeout) { reuse_result = cbm_daemon_ipc_receive_frame(server, 500, &frame, &received_payload); } free(received_payload); cbm_daemon_ipc_connection_close(server); cbm_daemon_ipc_connection_close(client); cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(accept_result, 1); ASSERT_EQ(timeout_result, 0); ASSERT_TRUE(sent_after_timeout); ASSERT_EQ(reuse_result, -1); PASS(); } #ifndef _WIN32 static bool ipc_test_fd_write_all(int fd, const void *buffer, size_t length) { size_t offset = 0; while (offset < length) { ssize_t written = write(fd, (const uint8_t *)buffer + offset, length - offset); if (written > 0) { offset += (size_t)written; continue; } if (written < 0 && errno == EINTR) { continue; } return false; } return true; } static bool ipc_test_fd_read_all(int fd, void *buffer, size_t length) { size_t offset = 0; while (offset < length) { ssize_t received = read(fd, (uint8_t *)buffer + offset, length - offset); if (received > 0) { offset += (size_t)received; continue; } if (received < 0 && errno == EINTR) { continue; } return false; } return true; } static bool ipc_test_socket_send_all(int fd, const void *buffer, size_t length) { size_t offset = 0; while (offset < length) { #ifdef MSG_NOSIGNAL ssize_t written = send(fd, (const uint8_t *)buffer + offset, length - offset, MSG_NOSIGNAL); #else ssize_t written = send(fd, (const uint8_t *)buffer + offset, length - offset, 0); #endif if (written > 0) { offset += (size_t)written; continue; } if (written < 0 && errno == EINTR) { continue; } return false; } return true; } static bool ipc_test_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 ipc_test_socket_identity_path(char out[TEST_PATH_CAP], const char *socket_path) { int written = socket_path ? snprintf(out, TEST_PATH_CAP, "%s.identity", socket_path) : -1; return written > 0 && written < TEST_PATH_CAP; } static bool ipc_test_socket_anchor_path(char out[TEST_PATH_CAP], const char *runtime_dir, const char *key) { int written = runtime_dir && key ? snprintf(out, TEST_PATH_CAP, "%s/cbm-%s.anc", runtime_dir, key) : -1; return written > 0 && written < TEST_PATH_CAP; } static bool ipc_test_socket_pending_path(char out[TEST_PATH_CAP], const char *socket_path) { int written = socket_path ? snprintf(out, TEST_PATH_CAP, "%s.pending", socket_path) : -1; return written > 0 && written < TEST_PATH_CAP; } #ifndef _WIN32 static bool ipc_test_record_temp_path(char out[TEST_PATH_CAP], const char *runtime_dir, const char *record_path) { if (!out || !runtime_dir || !record_path) { return false; } out[0] = '\0'; const char *base = strrchr(record_path, '/'); base = base ? base + 1 : record_path; char prefix[TEST_PATH_CAP]; int prefix_length = snprintf(prefix, sizeof(prefix), "%s.tmp", base); if (prefix_length <= 0 || prefix_length >= (int)sizeof(prefix)) { return false; } DIR *directory = opendir(runtime_dir); if (!directory) { return false; } bool found = false; bool ambiguous = false; struct dirent *entry; while ((entry = readdir(directory)) != NULL) { if (strncmp(entry->d_name, prefix, (size_t)prefix_length) != 0) { continue; } if (found) { ambiguous = true; break; } int written = snprintf(out, TEST_PATH_CAP, "%s/%s", runtime_dir, entry->d_name); found = written > 0 && written < TEST_PATH_CAP; if (!found) { break; } } (void)closedir(directory); if (ambiguous || !found) { out[0] = '\0'; return false; } return true; } #endif static void ipc_test_publication_crash_hook(cbm_daemon_ipc_posix_publication_stage_t stage, void *opaque) { const cbm_daemon_ipc_posix_publication_stage_t *target = opaque; if (target && stage == *target) { _exit(40 + (int)stage); } } static int ipc_test_cross_process_lock_child(const cbm_daemon_ipc_endpoint_t *endpoint, int command_fd, int result_fd) { for (int attempt = 0; attempt < 2; attempt++) { uint8_t command = 0; if (!ipc_test_fd_read_all(command_fd, &command, sizeof(command)) || command != (uint8_t)(attempt + 1)) { return 10 + attempt; } cbm_daemon_ipc_startup_lock_t *lock = NULL; int result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &lock); cbm_daemon_ipc_startup_lock_release(&lock); if (!ipc_test_fd_write_all(result_fd, &result, sizeof(result))) { return 20 + attempt; } } return 0; } TEST(daemon_ipc_posix_startup_lock_is_cross_process) { static const char key[] = "6666777788889999"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *parent_lock = NULL; int commands[2] = {-1, -1}; int results[2] = {-1, -1}; pid_t child = -1; int child_status = -1; int parent_result = -1; int child_while_held = -1; int child_after_release = -1; bool pipes_ok = false; bool first_exchange_ok = false; bool second_exchange_ok = false; bool parent_ok = ipc_test_parent_new(parent, "process-lock"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); if (pipe(commands) == 0) { if (pipe(results) == 0) { pipes_ok = true; } else { (void)close(commands[0]); (void)close(commands[1]); commands[0] = commands[1] = -1; } } } if (pipes_ok) { child = fork(); } if (child == 0) { (void)close(commands[1]); (void)close(results[0]); int child_result = ipc_test_cross_process_lock_child(endpoint, commands[0], results[1]); (void)close(commands[0]); (void)close(results[1]); _exit(child_result); } if (child > 0) { (void)close(commands[0]); commands[0] = -1; (void)close(results[1]); results[1] = -1; parent_result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &parent_lock); uint8_t command = 1; first_exchange_ok = ipc_test_fd_write_all(commands[1], &command, sizeof(command)) && ipc_test_fd_read_all(results[0], &child_while_held, sizeof(child_while_held)); cbm_daemon_ipc_startup_lock_release(&parent_lock); parent_lock = NULL; if (first_exchange_ok) { command = 2; second_exchange_ok = ipc_test_fd_write_all(commands[1], &command, sizeof(command)) && ipc_test_fd_read_all(results[0], &child_after_release, sizeof(child_after_release)); } (void)close(commands[1]); commands[1] = -1; (void)close(results[0]); results[0] = -1; while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {} } cbm_daemon_ipc_startup_lock_release(&parent_lock); for (size_t i = 0; i < 2; i++) { if (commands[i] >= 0) { (void)close(commands[i]); } if (results[i] >= 0) { (void)close(results[i]); } } cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(pipes_ok); ASSERT_GT(child, 0); ASSERT_EQ(parent_result, 1); ASSERT_TRUE(first_exchange_ok); ASSERT_EQ(child_while_held, 0); ASSERT_TRUE(second_exchange_ok); ASSERT_EQ(child_after_release, 1); ASSERT_TRUE(WIFEXITED(child_status)); ASSERT_EQ(WEXITSTATUS(child_status), 0); PASS(); } TEST(daemon_ipc_posix_lifetime_reservation_rejects_fork_inheritance) { static const char key[] = "6a6a7b7b8c8c9d9d"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_lifetime_reservation_t *reservation = NULL; cbm_daemon_ipc_listener_t *parent_listener = NULL; int result_pipe[2] = {-1, -1}; pid_t child = -1; uint8_t child_result = 0; int child_status = -1; int acquired = -1; int held_after_child = -1; bool parent_transfer_consumed = false; int free_after_close = -1; if (ipc_test_parent_new(parent, "forked-lifetime")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); acquired = cbm_daemon_ipc_lifetime_reservation_try_acquire(endpoint, &reservation); } if (reservation && pipe(result_pipe) == 0) { child = fork(); } if (child == 0) { (void)close(result_pipe[0]); cbm_daemon_ipc_lifetime_reservation_t *inherited = reservation; cbm_daemon_ipc_listener_t *unexpected = cbm_daemon_ipc_listen_reserved(endpoint, &inherited); child_result = unexpected == NULL && inherited == reservation ? 1 : 0; cbm_daemon_ipc_listener_close(unexpected); cbm_daemon_ipc_lifetime_reservation_release(inherited); bool reported = ipc_test_fd_write_all(result_pipe[1], &child_result, sizeof(child_result)); (void)close(result_pipe[1]); _exit(reported && child_result == 1 ? 0 : 1); } if (child > 0) { (void)close(result_pipe[1]); result_pipe[1] = -1; bool received = ipc_test_fd_read_all(result_pipe[0], &child_result, sizeof(child_result)); (void)close(result_pipe[0]); result_pipe[0] = -1; if (!received) { child_result = 0; } while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {} } if (child_result == 1 && WIFEXITED(child_status) && WEXITSTATUS(child_status) == 0) { held_after_child = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); parent_listener = cbm_daemon_ipc_listen_reserved(endpoint, &reservation); parent_transfer_consumed = parent_listener != NULL && reservation == NULL; } cbm_daemon_ipc_listener_close(parent_listener); cbm_daemon_ipc_lifetime_reservation_release(reservation); if (endpoint) { free_after_close = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); } for (size_t index = 0; index < 2; index++) { if (result_pipe[index] >= 0) { (void)close(result_pipe[index]); } } cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_EQ(acquired, 1); ASSERT_GT(child, 0); ASSERT_EQ(child_result, 1); ASSERT_TRUE(WIFEXITED(child_status)); ASSERT_EQ(WEXITSTATUS(child_status), 0); ASSERT_EQ(held_after_child, 1); ASSERT_TRUE(parent_transfer_consumed); ASSERT_EQ(free_after_close, 0); PASS(); } TEST(daemon_ipc_posix_child_participant_handoff_retains_legacy_bridge) { #ifdef _WIN32 PASS(); #else static const char key[] = "6b6b7c7c8d8d9e9e"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char legacy_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; int parent_to_child[2] = {-1, -1}; int child_to_parent[2] = {-1, -1}; pid_t child = -1; int child_status = -1; uint8_t joined = 0; uint8_t released = 0; int legacy_fd = -1; int blocked_while_child = -1; int acquired_after_child = -1; bool parent_ok = ipc_test_parent_new(parent, "participant-handoff"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); int path_length = snprintf(legacy_path, sizeof(legacy_path), "%s/cbm-%s.lock", runtime_dir, key); parent_ok = path_length > 0 && path_length < (int)sizeof(legacy_path); } int startup_result = parent_ok ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; bool prepared = startup_result == 1 && cbm_daemon_ipc_startup_lock_prepare_handoff(startup); bool pipes_ok = prepared && pipe(parent_to_child) == 0 && pipe(child_to_parent) == 0; if (pipes_ok) { child = fork(); } if (child == 0) { (void)close(parent_to_child[1]); (void)close(child_to_parent[0]); cbm_daemon_ipc_participant_guard_t *guard = NULL; int join_result = cbm_daemon_ipc_participant_guard_try_join(endpoint, &guard); uint8_t join_byte = join_result == 1 && guard ? 1 : 0; bool reported = ipc_test_fd_write_all(child_to_parent[1], &join_byte, sizeof(join_byte)); uint8_t command = 0; bool commanded = reported && ipc_test_fd_read_all(parent_to_child[0], &command, sizeof(command)); bool guard_released = cbm_daemon_ipc_participant_guard_release(&guard); uint8_t release_byte = commanded && command == 1 && guard_released && !guard ? 1 : 0; bool release_reported = ipc_test_fd_write_all(child_to_parent[1], &release_byte, sizeof(release_byte)); (void)close(parent_to_child[0]); (void)close(child_to_parent[1]); _exit(join_byte == 1 && release_reported && release_byte == 1 ? 0 : 1); } if (child > 0) { (void)close(parent_to_child[0]); parent_to_child[0] = -1; (void)close(child_to_parent[1]); child_to_parent[1] = -1; bool joined_read = ipc_test_fd_read_all(child_to_parent[0], &joined, sizeof(joined)); cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; legacy_fd = open(legacy_path, O_RDWR | O_CLOEXEC | O_NOFOLLOW); if (joined_read && joined == 1 && legacy_fd >= 0) { blocked_while_child = flock(legacy_fd, LOCK_EX | LOCK_NB) == 0 ? 0 : (errno == EWOULDBLOCK || errno == EAGAIN ? 1 : -1); } uint8_t command = 1; bool commanded = ipc_test_fd_write_all(parent_to_child[1], &command, sizeof(command)); bool released_read = commanded && ipc_test_fd_read_all(child_to_parent[0], &released, sizeof(released)); (void)close(parent_to_child[1]); parent_to_child[1] = -1; (void)close(child_to_parent[0]); child_to_parent[0] = -1; while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {} if (released_read && released == 1 && WIFEXITED(child_status) && legacy_fd >= 0) { acquired_after_child = flock(legacy_fd, LOCK_EX | LOCK_NB) == 0 ? 1 : 0; } } cbm_daemon_ipc_startup_lock_release(&startup); if (legacy_fd >= 0) { (void)flock(legacy_fd, LOCK_UN); (void)close(legacy_fd); } for (size_t index = 0; index < 2; index++) { if (parent_to_child[index] >= 0) { (void)close(parent_to_child[index]); } if (child_to_parent[index] >= 0) { (void)close(child_to_parent[index]); } } cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(startup_result, 1); ASSERT_TRUE(prepared); ASSERT_TRUE(pipes_ok); ASSERT_GT(child, 0); ASSERT_EQ(joined, 1); ASSERT_EQ(blocked_while_child, 1); ASSERT_EQ(released, 1); ASSERT_EQ(acquired_after_child, 1); ASSERT_TRUE(WIFEXITED(child_status)); ASSERT_EQ(WEXITSTATUS(child_status), 0); PASS(); #endif } TEST(daemon_ipc_posix_publication_boundaries_recover_from_crash) { #ifdef _WIN32 PASS(); #else static const char key[] = "9f8e7d6c5b4a3210"; static cbm_daemon_ipc_posix_publication_stage_t stages[] = { CBM_DAEMON_IPC_POSIX_PUBLICATION_ANCHOR_DURABLE, CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_DURABLE, CBM_DAEMON_IPC_POSIX_PUBLICATION_STABLE_DURABLE, CBM_DAEMON_IPC_POSIX_PUBLICATION_MARKER_DURABLE, CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_REMOVED, }; bool stage_ok[sizeof(stages) / sizeof(stages[0])] = {0}; for (size_t index = 0; index < sizeof(stages) / sizeof(stages[0]); index++) { char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char anchor_path[TEST_PATH_CAP] = {0}; char identity_path[TEST_PATH_CAP] = {0}; char pending_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; struct stat socket_status = {0}; struct stat anchor_status = {0}; struct stat record_status = {0}; pid_t child = -1; int child_status = -1; bool parent_ok = ipc_test_parent_new(parent, "publish-boundary"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); } bool paths_ok = endpoint && ipc_test_socket_anchor_path(anchor_path, runtime_dir, key) && ipc_test_socket_identity_path(identity_path, socket_path) && ipc_test_socket_pending_path(pending_path, socket_path); if (paths_ok) { cbm_daemon_ipc_posix_publication_hook_set_for_test(ipc_test_publication_crash_hook, &stages[index]); child = fork(); } if (child == 0) { cbm_daemon_ipc_listener_t *listener = cbm_daemon_ipc_listen(endpoint); _exit(listener ? 90 : 91); } cbm_daemon_ipc_posix_publication_hook_set_for_test(NULL, NULL); if (child > 0) { while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {} } bool crashed_at_boundary = child > 0 && WIFEXITED(child_status) && WEXITSTATUS(child_status) == 40 + (int)stages[index]; bool anchor_present = lstat(anchor_path, &anchor_status) == 0 && S_ISSOCK(anchor_status.st_mode); bool stable_present = lstat(socket_path, &socket_status) == 0 && S_ISSOCK(socket_status.st_mode); bool pending_present = lstat(pending_path, &record_status) == 0 && S_ISREG(record_status.st_mode); bool marker_present = lstat(identity_path, &record_status) == 0 && S_ISREG(record_status.st_mode); bool linked_shape = stages[index] < CBM_DAEMON_IPC_POSIX_PUBLICATION_STABLE_DURABLE ? anchor_present && anchor_status.st_nlink == 1 && !stable_present : anchor_present && stable_present && anchor_status.st_nlink == 2 && socket_status.st_nlink == 2 && anchor_status.st_dev == socket_status.st_dev && anchor_status.st_ino == socket_status.st_ino; bool expected_records = pending_present == (stages[index] >= CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_DURABLE && stages[index] < CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_REMOVED) && marker_present == (stages[index] >= CBM_DAEMON_IPC_POSIX_PUBLICATION_MARKER_DURABLE); int startup_result = crashed_at_boundary ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int cleanup_result = startup_result == 1 ? cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup) : -1; cbm_daemon_ipc_startup_lock_release(&startup); errno = 0; bool stable_removed = lstat(socket_path, &record_status) != 0 && errno == ENOENT; errno = 0; bool anchor_removed = lstat(anchor_path, &record_status) != 0 && errno == ENOENT; errno = 0; bool pending_removed = lstat(pending_path, &record_status) != 0 && errno == ENOENT; errno = 0; bool marker_removed = lstat(identity_path, &record_status) != 0 && errno == ENOENT; stage_ok[index] = parent_ok && paths_ok && crashed_at_boundary && linked_shape && expected_records && startup_result == 1 && cleanup_result == 1 && stable_removed && anchor_removed && pending_removed && marker_removed; cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); } for (size_t index = 0; index < sizeof(stage_ok) / sizeof(stage_ok[0]); index++) { ASSERT_TRUE(stage_ok[index]); } PASS(); #endif } TEST(daemon_ipc_posix_record_publication_windows_recover_from_crash) { #ifdef _WIN32 PASS(); #else static const char key[] = "1a2b3c4d5e6f7081"; static const struct { cbm_daemon_ipc_posix_publication_stage_t stage; bool marker; bool linked; } cases[] = { {CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_TEMP_SYNCED, false, false}, {CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_RECORD_LINKED, false, true}, {CBM_DAEMON_IPC_POSIX_PUBLICATION_MARKER_TEMP_SYNCED, true, false}, {CBM_DAEMON_IPC_POSIX_PUBLICATION_MARKER_RECORD_LINKED, true, true}, }; bool case_ok[sizeof(cases) / sizeof(cases[0])] = {0}; for (size_t index = 0; index < sizeof(cases) / sizeof(cases[0]); index++) { char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char anchor_path[TEST_PATH_CAP] = {0}; char identity_path[TEST_PATH_CAP] = {0}; char pending_path[TEST_PATH_CAP] = {0}; char temp_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; struct stat socket_status = {0}; struct stat anchor_status = {0}; struct stat pending_status = {0}; struct stat marker_status = {0}; struct stat record_status = {0}; struct stat temp_status = {0}; struct stat absent_status = {0}; pid_t child = -1; int child_status = -1; bool parent_ok = ipc_test_parent_new(parent, "record-window"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); } bool paths_ok = endpoint && ipc_test_socket_anchor_path(anchor_path, runtime_dir, key) && ipc_test_socket_identity_path(identity_path, socket_path) && ipc_test_socket_pending_path(pending_path, socket_path); if (paths_ok) { cbm_daemon_ipc_posix_publication_hook_set_for_test(ipc_test_publication_crash_hook, (void *)&cases[index].stage); child = fork(); } if (child == 0) { cbm_daemon_ipc_listener_t *listener = cbm_daemon_ipc_listen(endpoint); _exit(listener ? 90 : 91); } cbm_daemon_ipc_posix_publication_hook_set_for_test(NULL, NULL); if (child > 0) { while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {} } bool crashed = child > 0 && WIFEXITED(child_status) && WEXITSTATUS(child_status) == 40 + (int)cases[index].stage; const char *record_path = cases[index].marker ? identity_path : pending_path; bool temp_found = crashed && ipc_test_record_temp_path(temp_path, runtime_dir, record_path) && lstat(temp_path, &temp_status) == 0 && S_ISREG(temp_status.st_mode) && temp_status.st_uid == geteuid() && (temp_status.st_mode & 0777) == 0600 && temp_status.st_nlink == (cases[index].linked ? 2 : 1); errno = 0; bool record_shape = cases[index].linked ? lstat(record_path, &record_status) == 0 && S_ISREG(record_status.st_mode) && record_status.st_nlink == 2 && temp_found && record_status.st_dev == temp_status.st_dev && record_status.st_ino == temp_status.st_ino : lstat(record_path, &absent_status) != 0 && errno == ENOENT; bool socket_shape = cases[index].marker ? lstat(socket_path, &socket_status) == 0 && S_ISSOCK(socket_status.st_mode) && socket_status.st_nlink == 2 && lstat(anchor_path, &anchor_status) == 0 && S_ISSOCK(anchor_status.st_mode) && anchor_status.st_nlink == 2 && socket_status.st_dev == anchor_status.st_dev && socket_status.st_ino == anchor_status.st_ino : lstat(anchor_path, &anchor_status) == 0 && S_ISSOCK(anchor_status.st_mode) && anchor_status.st_nlink == 1 && lstat(socket_path, &absent_status) != 0 && errno == ENOENT; errno = 0; bool preceding_record_shape = cases[index].marker ? lstat(pending_path, &pending_status) == 0 && S_ISREG(pending_status.st_mode) && pending_status.st_nlink == 1 : lstat(identity_path, &marker_status) != 0 && errno == ENOENT; int startup_result = crashed ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int cleanup_result = startup_result == 1 ? cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup) : -1; cbm_daemon_ipc_startup_lock_release(&startup); errno = 0; bool stable_removed = lstat(socket_path, &absent_status) != 0 && errno == ENOENT; errno = 0; bool anchor_removed = lstat(anchor_path, &absent_status) != 0 && errno == ENOENT; errno = 0; bool pending_removed = lstat(pending_path, &absent_status) != 0 && errno == ENOENT; errno = 0; bool marker_removed = lstat(identity_path, &absent_status) != 0 && errno == ENOENT; errno = 0; bool temp_removed = temp_path[0] != '\0' && lstat(temp_path, &absent_status) != 0 && errno == ENOENT; case_ok[index] = parent_ok && paths_ok && crashed && temp_found && record_shape && socket_shape && preceding_record_shape && startup_result == 1 && cleanup_result == 1 && stable_removed && anchor_removed && pending_removed && marker_removed && temp_removed; cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); } for (size_t index = 0; index < sizeof(case_ok) / sizeof(case_ok[0]); index++) { ASSERT_TRUE(case_ok[index]); } PASS(); #endif } TEST(daemon_ipc_posix_unknown_record_temp_pair_is_preserved) { #ifdef _WIN32 PASS(); #else static const char key[] = "2b3c4d5e6f708192"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char pending_path[TEST_PATH_CAP] = {0}; char temp_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; struct stat pending_before = {0}; struct stat temp_before = {0}; struct stat pending_after = {0}; struct stat temp_after = {0}; bool parent_ok = ipc_test_parent_new(parent, "unknown-record-temp"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); } bool pending_path_ok = endpoint && ipc_test_socket_pending_path(pending_path, socket_path); int temp_written = pending_path_ok ? snprintf(temp_path, sizeof(temp_path), "%s.tmp", pending_path) : -1; bool paths_ok = pending_path_ok && temp_written > 0 && temp_written < (int)sizeof(temp_path); bool pair_created = paths_ok && ipc_test_write_byte(temp_path, 0x5a) && chmod(temp_path, 0600) == 0 && link(temp_path, pending_path) == 0 && lstat(temp_path, &temp_before) == 0 && lstat(pending_path, &pending_before) == 0 && S_ISREG(temp_before.st_mode) && temp_before.st_nlink == 2 && pending_before.st_dev == temp_before.st_dev && pending_before.st_ino == temp_before.st_ino; int startup_result = pair_created ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int cleanup_result = startup_result == 1 ? cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup) : -1; cbm_daemon_ipc_startup_lock_release(&startup); bool pair_preserved = lstat(temp_path, &temp_after) == 0 && lstat(pending_path, &pending_after) == 0 && temp_after.st_dev == temp_before.st_dev && temp_after.st_ino == temp_before.st_ino && temp_after.st_nlink == 2 && pending_after.st_nlink == 2 && pending_after.st_dev == pending_before.st_dev && pending_after.st_ino == pending_before.st_ino && temp_after.st_size == 1 && pending_after.st_size == 1; (void)unlink(pending_path); (void)unlink(temp_path); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(paths_ok); ASSERT_TRUE(pair_created); ASSERT_EQ(startup_result, 1); ASSERT_EQ(cleanup_result, 0); ASSERT_TRUE(pair_preserved); PASS(); #endif } TEST(daemon_ipc_posix_recovery_preserves_replaced_stable_socket) { #ifdef _WIN32 PASS(); #else static const char key[] = "8e7d6c5b4a392817"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char anchor_path[TEST_PATH_CAP] = {0}; char identity_path[TEST_PATH_CAP] = {0}; char pending_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_posix_publication_stage_t crash_stage = CBM_DAEMON_IPC_POSIX_PUBLICATION_PENDING_REMOVED; struct stat replacement_before = {0}; struct stat replacement_after = {0}; struct stat status = {0}; struct sockaddr_un address; socklen_t address_length = 0; pid_t child = -1; int child_status = -1; int replacement = -1; bool parent_ok = ipc_test_parent_new(parent, "stable-replacement"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); } bool paths_ok = endpoint && ipc_test_socket_anchor_path(anchor_path, runtime_dir, key) && ipc_test_socket_identity_path(identity_path, socket_path) && ipc_test_socket_pending_path(pending_path, socket_path) && ipc_test_unix_address_set(&address, socket_path, &address_length); if (paths_ok) { cbm_daemon_ipc_posix_publication_hook_set_for_test(ipc_test_publication_crash_hook, &crash_stage); child = fork(); } if (child == 0) { cbm_daemon_ipc_listener_t *listener = cbm_daemon_ipc_listen(endpoint); _exit(listener ? 90 : 91); } cbm_daemon_ipc_posix_publication_hook_set_for_test(NULL, NULL); if (child > 0) { while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {} } bool crashed_committed = child > 0 && WIFEXITED(child_status) && WEXITSTATUS(child_status) == 40 + (int)crash_stage; bool stable_unlinked = crashed_committed && unlink(socket_path) == 0; if (stable_unlinked) { replacement = socket(AF_UNIX, SOCK_STREAM, 0); } bool replacement_ready = replacement >= 0 && bind(replacement, (const struct sockaddr *)&address, address_length) == 0 && chmod(socket_path, 0600) == 0 && listen(replacement, 1) == 0 && lstat(socket_path, &replacement_before) == 0 && S_ISSOCK(replacement_before.st_mode); int startup_result = replacement_ready ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int cleanup_result = startup_result == 1 ? cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup) : -1; cbm_daemon_ipc_startup_lock_release(&startup); bool replacement_preserved = lstat(socket_path, &replacement_after) == 0 && S_ISSOCK(replacement_after.st_mode) && replacement_after.st_dev == replacement_before.st_dev && replacement_after.st_ino == replacement_before.st_ino; errno = 0; bool anchor_removed = lstat(anchor_path, &status) != 0 && errno == ENOENT; errno = 0; bool pending_removed = lstat(pending_path, &status) != 0 && errno == ENOENT; errno = 0; bool marker_removed = lstat(identity_path, &status) != 0 && errno == ENOENT; if (replacement >= 0) { (void)close(replacement); } (void)unlink(socket_path); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(paths_ok); ASSERT_TRUE(crashed_committed); ASSERT_TRUE(stable_unlinked); ASSERT_TRUE(replacement_ready); ASSERT_EQ(startup_result, 1); ASSERT_EQ(cleanup_result, 0); ASSERT_TRUE(replacement_preserved); ASSERT_TRUE(anchor_removed); ASSERT_TRUE(pending_removed); ASSERT_TRUE(marker_removed); PASS(); #endif } TEST(daemon_ipc_posix_pending_without_anchor_never_deletes_stable) { #ifdef _WIN32 PASS(); #else static const char key[] = "7d6c5b4a39281706"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char anchor_path[TEST_PATH_CAP] = {0}; char pending_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_posix_publication_stage_t crash_stage = CBM_DAEMON_IPC_POSIX_PUBLICATION_STABLE_DURABLE; struct stat stable_before = {0}; struct stat stable_after = {0}; struct stat status = {0}; pid_t child = -1; int child_status = -1; bool parent_ok = ipc_test_parent_new(parent, "pending-no-anchor"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); } bool paths_ok = endpoint && ipc_test_socket_anchor_path(anchor_path, runtime_dir, key) && ipc_test_socket_pending_path(pending_path, socket_path); if (paths_ok) { cbm_daemon_ipc_posix_publication_hook_set_for_test(ipc_test_publication_crash_hook, &crash_stage); child = fork(); } if (child == 0) { cbm_daemon_ipc_listener_t *listener = cbm_daemon_ipc_listen(endpoint); _exit(listener ? 90 : 91); } cbm_daemon_ipc_posix_publication_hook_set_for_test(NULL, NULL); if (child > 0) { while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {} } bool crashed_before_marker = child > 0 && WIFEXITED(child_status) && WEXITSTATUS(child_status) == 40 + (int)crash_stage; bool anchor_removed = crashed_before_marker && lstat(socket_path, &stable_before) == 0 && S_ISSOCK(stable_before.st_mode) && unlink(anchor_path) == 0; int startup_result = anchor_removed ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int cleanup_result = startup_result == 1 ? cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup) : -1; cbm_daemon_ipc_startup_lock_release(&startup); bool stable_preserved = lstat(socket_path, &stable_after) == 0 && S_ISSOCK(stable_after.st_mode) && stable_after.st_dev == stable_before.st_dev && stable_after.st_ino == stable_before.st_ino; errno = 0; bool pending_removed = lstat(pending_path, &status) != 0 && errno == ENOENT; (void)unlink(socket_path); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(paths_ok); ASSERT_TRUE(crashed_before_marker); ASSERT_TRUE(anchor_removed); ASSERT_EQ(startup_result, 1); ASSERT_EQ(cleanup_result, 0); ASSERT_TRUE(stable_preserved); ASSERT_TRUE(pending_removed); PASS(); #endif } TEST(daemon_ipc_posix_current_generation_crash_cleanup_requires_startup_lock) { static const char key[] = "a1b2c3d4e5f60718"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char anchor_path[TEST_PATH_CAP] = {0}; char identity_path[TEST_PATH_CAP] = {0}; char pending_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_endpoint_t *wrong_endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_startup_lock_t *wrong_startup = NULL; int ready_pipe[2] = {-1, -1}; pid_t child = -1; int child_status = -1; uint8_t ready = 0; struct stat status = {0}; bool paths_ok = false; bool child_ready = false; bool artifacts_survived_crash = false; int lifetime_after_crash = -1; int cleanup_without_lock = -2; int wrong_startup_result = -1; int cleanup_with_wrong_lock = -2; int startup_result = -1; int cleanup_result = -1; bool artifacts_removed = false; if (ipc_test_parent_new(parent, "crash-identity")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); paths_ok = ipc_test_socket_anchor_path(anchor_path, runtime_dir, key) && ipc_test_socket_identity_path(identity_path, socket_path) && ipc_test_socket_pending_path(pending_path, socket_path) && pipe(ready_pipe) == 0; } if (paths_ok) { child = fork(); } if (child == 0) { (void)close(ready_pipe[0]); cbm_daemon_ipc_listener_t *listener = cbm_daemon_ipc_listen(endpoint); uint8_t result = listener ? 'R' : 'E'; bool reported = ipc_test_fd_write_all(ready_pipe[1], &result, sizeof(result)); (void)close(ready_pipe[1]); /* Deliberately bypass listener_close: the kernel releases descriptors * and locks, while the current-generation socket identity remains. */ _exit(listener && reported ? 0 : 1); } if (child > 0) { (void)close(ready_pipe[1]); ready_pipe[1] = -1; child_ready = ipc_test_fd_read_all(ready_pipe[0], &ready, sizeof(ready)) && ready == 'R'; (void)close(ready_pipe[0]); ready_pipe[0] = -1; while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {} } if (child_ready && WIFEXITED(child_status) && WEXITSTATUS(child_status) == 0) { artifacts_survived_crash = lstat(socket_path, &status) == 0 && S_ISSOCK(status.st_mode) && status.st_nlink == 2 && lstat(anchor_path, &status) == 0 && S_ISSOCK(status.st_mode) && status.st_nlink == 2 && lstat(identity_path, &status) == 0 && S_ISREG(status.st_mode); lifetime_after_crash = cbm_daemon_ipc_lifetime_reservation_probe(endpoint); cleanup_without_lock = cbm_daemon_ipc_stale_generation_cleanup(endpoint, NULL); wrong_endpoint = cbm_daemon_ipc_endpoint_new("a1b2c3d4e5f60719", parent); wrong_startup_result = wrong_endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(wrong_endpoint, &wrong_startup) : -1; cleanup_with_wrong_lock = wrong_startup ? cbm_daemon_ipc_stale_generation_cleanup(endpoint, wrong_startup) : -2; cbm_daemon_ipc_startup_lock_release(&wrong_startup); wrong_startup = NULL; startup_result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup); } if (startup) { cleanup_result = cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup); errno = 0; bool socket_removed = lstat(socket_path, &status) != 0 && errno == ENOENT; errno = 0; bool identity_removed = lstat(identity_path, &status) != 0 && errno == ENOENT; errno = 0; bool anchor_removed = lstat(anchor_path, &status) != 0 && errno == ENOENT; errno = 0; bool pending_removed = lstat(pending_path, &status) != 0 && errno == ENOENT; artifacts_removed = socket_removed && anchor_removed && identity_removed && pending_removed; } cbm_daemon_ipc_startup_lock_release(&startup); cbm_daemon_ipc_startup_lock_release(&wrong_startup); for (size_t index = 0; index < 2; index++) { if (ready_pipe[index] >= 0) { (void)close(ready_pipe[index]); } } cbm_daemon_ipc_endpoint_free(wrong_endpoint); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(paths_ok); ASSERT_GT(child, 0); ASSERT_TRUE(child_ready); ASSERT_TRUE(WIFEXITED(child_status)); ASSERT_EQ(WEXITSTATUS(child_status), 0); ASSERT_TRUE(artifacts_survived_crash); ASSERT_EQ(lifetime_after_crash, 0); ASSERT_EQ(cleanup_without_lock, -1); ASSERT_EQ(wrong_startup_result, 1); ASSERT_EQ(cleanup_with_wrong_lock, -1); ASSERT_EQ(startup_result, 1); ASSERT_EQ(cleanup_result, 1); ASSERT_TRUE(artifacts_removed); PASS(); } TEST(daemon_ipc_posix_unknown_socket_without_identity_refuses_cleanup) { static const char key[] = "b1c2d3e4f5061728"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char identity_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_local_transition_t *transition = NULL; int raw_listener = -1; struct sockaddr_un address; socklen_t address_length = 0; struct stat before = {0}; struct stat after = {0}; bool paths_ok = false; bool unknown_created = false; int startup_result = -1; int cleanup_result = -1; int transition_result = -1; int transition_seal_result = -1; bool unknown_unchanged = false; bool identity_absent = false; if (ipc_test_parent_new(parent, "unknown-socket")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); paths_ok = ipc_test_socket_identity_path(identity_path, socket_path) && ipc_test_unix_address_set(&address, socket_path, &address_length); } if (paths_ok) { raw_listener = socket(AF_UNIX, SOCK_STREAM, 0); } if (raw_listener >= 0) { unknown_created = bind(raw_listener, (const struct sockaddr *)&address, address_length) == 0 && chmod(socket_path, 0600) == 0 && listen(raw_listener, 1) == 0 && lstat(socket_path, &before) == 0 && S_ISSOCK(before.st_mode) && before.st_uid == geteuid() && (before.st_mode & 0777) == 0600; (void)close(raw_listener); raw_listener = -1; } if (unknown_created) { startup_result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup); } if (startup) { cleanup_result = cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup); unknown_unchanged = lstat(socket_path, &after) == 0 && S_ISSOCK(after.st_mode) && after.st_dev == before.st_dev && after.st_ino == before.st_ino; errno = 0; identity_absent = lstat(identity_path, &after) != 0 && errno == ENOENT; } cbm_daemon_ipc_startup_lock_release(&startup); startup = NULL; if (unknown_unchanged) { transition_result = cbm_daemon_ipc_local_transition_try_acquire(endpoint, &transition); } if (transition) { transition_seal_result = cbm_daemon_ipc_local_transition_seal_legacy(transition); } (void)cbm_daemon_ipc_local_transition_release(&transition); if (raw_listener >= 0) { (void)close(raw_listener); } (void)unlink(socket_path); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(unknown_created); ASSERT_EQ(startup_result, 1); ASSERT_EQ(cleanup_result, 0); ASSERT_EQ(transition_result, 1); ASSERT_EQ(transition_seal_result, 0); ASSERT_TRUE(unknown_unchanged); ASSERT_TRUE(identity_absent); PASS(); } TEST(daemon_ipc_posix_markerless_linked_socket_recovers_under_startup_lock) { static const char key[] = "d1e2f30415263748"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char anchor_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; int raw_listener = -1; struct sockaddr_un address; socklen_t address_length = 0; struct stat socket_status = {0}; struct stat anchor_status = {0}; struct stat absent_status = {0}; bool paths_ok = false; bool orphan_created = false; int startup_result = -1; int cleanup_result = -1; int endpoint_after_cleanup = -1; bool names_removed = false; if (ipc_test_parent_new(parent, "markerless-linked-socket")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); paths_ok = ipc_test_socket_anchor_path(anchor_path, runtime_dir, key) && ipc_test_unix_address_set(&address, socket_path, &address_length); } if (paths_ok) { raw_listener = socket(AF_UNIX, SOCK_STREAM, 0); } if (raw_listener >= 0) { orphan_created = bind(raw_listener, (const struct sockaddr *)&address, address_length) == 0 && chmod(socket_path, 0600) == 0 && listen(raw_listener, 1) == 0 && link(socket_path, anchor_path) == 0 && lstat(socket_path, &socket_status) == 0 && lstat(anchor_path, &anchor_status) == 0 && S_ISSOCK(socket_status.st_mode) && S_ISSOCK(anchor_status.st_mode) && socket_status.st_nlink == 2 && anchor_status.st_nlink == 2 && socket_status.st_dev == anchor_status.st_dev && socket_status.st_ino == anchor_status.st_ino; (void)close(raw_listener); raw_listener = -1; } if (orphan_created) { startup_result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup); } if (startup) { cleanup_result = cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup); endpoint_after_cleanup = cbm_daemon_ipc_endpoint_probe(endpoint, 0); errno = 0; bool socket_removed = lstat(socket_path, &absent_status) != 0 && errno == ENOENT; errno = 0; bool anchor_removed = lstat(anchor_path, &absent_status) != 0 && errno == ENOENT; names_removed = socket_removed && anchor_removed; } cbm_daemon_ipc_startup_lock_release(&startup); if (raw_listener >= 0) { (void)close(raw_listener); } (void)unlink(anchor_path); (void)unlink(socket_path); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(orphan_created); ASSERT_EQ(startup_result, 1); ASSERT_EQ(cleanup_result, 1); ASSERT_EQ(endpoint_after_cleanup, 0); ASSERT_TRUE(names_removed); PASS(); } TEST(daemon_ipc_posix_markerless_mismatched_socket_and_anchor_are_preserved) { static const char key[] = "e1f2031425364758"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char anchor_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; int stable_listener = -1; int anchor_listener = -1; struct sockaddr_un stable_address; struct sockaddr_un anchor_address; socklen_t stable_address_length = 0; socklen_t anchor_address_length = 0; struct stat stable_before = {0}; struct stat anchor_before = {0}; struct stat stable_after = {0}; struct stat anchor_after = {0}; bool paths_ok = false; bool mismatch_created = false; int startup_result = -1; int cleanup_result = -1; bool mismatch_preserved = false; if (ipc_test_parent_new(parent, "markerless-mismatch")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); paths_ok = ipc_test_socket_anchor_path(anchor_path, runtime_dir, key) && ipc_test_unix_address_set(&stable_address, socket_path, &stable_address_length) && ipc_test_unix_address_set(&anchor_address, anchor_path, &anchor_address_length); } if (paths_ok) { stable_listener = socket(AF_UNIX, SOCK_STREAM, 0); anchor_listener = socket(AF_UNIX, SOCK_STREAM, 0); } if (stable_listener >= 0 && anchor_listener >= 0) { mismatch_created = bind(stable_listener, (const struct sockaddr *)&stable_address, stable_address_length) == 0 && chmod(socket_path, 0600) == 0 && listen(stable_listener, 1) == 0 && bind(anchor_listener, (const struct sockaddr *)&anchor_address, anchor_address_length) == 0 && chmod(anchor_path, 0600) == 0 && listen(anchor_listener, 1) == 0 && lstat(socket_path, &stable_before) == 0 && lstat(anchor_path, &anchor_before) == 0 && S_ISSOCK(stable_before.st_mode) && S_ISSOCK(anchor_before.st_mode) && stable_before.st_ino != anchor_before.st_ino; (void)close(stable_listener); stable_listener = -1; (void)close(anchor_listener); anchor_listener = -1; } if (mismatch_created) { startup_result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup); } if (startup) { cleanup_result = cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup); mismatch_preserved = lstat(socket_path, &stable_after) == 0 && lstat(anchor_path, &anchor_after) == 0 && stable_after.st_dev == stable_before.st_dev && stable_after.st_ino == stable_before.st_ino && anchor_after.st_dev == anchor_before.st_dev && anchor_after.st_ino == anchor_before.st_ino; } cbm_daemon_ipc_startup_lock_release(&startup); if (stable_listener >= 0) { (void)close(stable_listener); } if (anchor_listener >= 0) { (void)close(anchor_listener); } (void)unlink(anchor_path); (void)unlink(socket_path); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(mismatch_created); ASSERT_EQ(startup_result, 1); ASSERT_EQ(cleanup_result, 0); ASSERT_TRUE(mismatch_preserved); PASS(); } TEST(daemon_ipc_posix_active_listener_is_never_cleaned_under_queue_pressure) { static const char key[] = "c1d2e3f405162738"; enum { CLIENT_CAP = 64 }; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char anchor_path[TEST_PATH_CAP] = {0}; char identity_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; cbm_daemon_ipc_connection_t *clients[CLIENT_CAP] = {0}; size_t client_count = 0; struct stat socket_before = {0}; struct stat anchor_before = {0}; struct stat identity_before = {0}; struct stat socket_after = {0}; struct stat anchor_after = {0}; struct stat identity_after = {0}; bool paths_ok = false; bool artifacts_before = false; int startup_result = -1; int cleanup_result = -1; bool artifacts_unchanged = false; int endpoint_after_cleanup = -1; if (ipc_test_parent_new(parent, "active-cleanup")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); paths_ok = ipc_test_socket_anchor_path(anchor_path, runtime_dir, key) && ipc_test_socket_identity_path(identity_path, socket_path); listener = cbm_daemon_ipc_listen(endpoint); } if (listener) { artifacts_before = lstat(socket_path, &socket_before) == 0 && S_ISSOCK(socket_before.st_mode) && lstat(anchor_path, &anchor_before) == 0 && S_ISSOCK(anchor_before.st_mode) && anchor_before.st_dev == socket_before.st_dev && anchor_before.st_ino == socket_before.st_ino && lstat(identity_path, &identity_before) == 0 && S_ISREG(identity_before.st_mode); } /* Never accept: pressure the listen queue into the BSD case where a live * listener can reject further connects with ECONNREFUSED. Cleanup must * rely only on the retained lifetime reservation, never on connect(). */ while (listener && client_count < CLIENT_CAP) { cbm_daemon_ipc_connection_t *client = cbm_daemon_ipc_connect(endpoint, 1); if (!client) { break; } clients[client_count++] = client; } if (listener) { startup_result = cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup); } if (startup) { cleanup_result = cbm_daemon_ipc_stale_generation_cleanup(endpoint, startup); artifacts_unchanged = lstat(socket_path, &socket_after) == 0 && S_ISSOCK(socket_after.st_mode) && socket_after.st_dev == socket_before.st_dev && socket_after.st_ino == socket_before.st_ino && lstat(anchor_path, &anchor_after) == 0 && S_ISSOCK(anchor_after.st_mode) && anchor_after.st_dev == anchor_before.st_dev && anchor_after.st_ino == anchor_before.st_ino && lstat(identity_path, &identity_after) == 0 && S_ISREG(identity_after.st_mode) && identity_after.st_dev == identity_before.st_dev && identity_after.st_ino == identity_before.st_ino; endpoint_after_cleanup = cbm_daemon_ipc_endpoint_probe(endpoint, 1); } cbm_daemon_ipc_startup_lock_release(&startup); for (size_t index = 0; index < client_count; index++) { cbm_daemon_ipc_connection_close(clients[index]); } cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(artifacts_before); ASSERT_TRUE(client_count > 0); ASSERT_EQ(startup_result, 1); ASSERT_EQ(cleanup_result, 0); ASSERT_TRUE(artifacts_unchanged); ASSERT_EQ(endpoint_after_cleanup, 1); PASS(); } TEST(daemon_ipc_posix_partial_frame_timeout_poisons_connection) { static const char key[] = "777788889999aaaa"; static const uint8_t payload[] = {'n', 'e', 'w'}; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; cbm_daemon_ipc_connection_t *server_connection = NULL; int raw_client = -1; uint8_t partial_header[CBM_DAEMON_FRAME_HEADER_SIZE] = {0}; uint8_t full_header[CBM_DAEMON_FRAME_HEADER_SIZE] = {0}; cbm_daemon_frame_t frame = {0}; uint8_t *received_payload = NULL; bool raw_connected = false; bool partial_sent = false; bool full_frame_sent = false; int accept_result = -1; int partial_receive_result = -1; int reuse_result = 1; bool parent_ok = ipc_test_parent_new(parent, "partial-frame"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); listener = cbm_daemon_ipc_listen(endpoint); } if (listener) { raw_client = socket(AF_UNIX, SOCK_STREAM, 0); } if (raw_client >= 0) { #ifdef SO_NOSIGPIPE int no_sigpipe = 1; (void)setsockopt(raw_client, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe)); #endif struct sockaddr_un address; socklen_t address_length = 0; if (ipc_test_unix_address_set(&address, cbm_daemon_ipc_endpoint_address(endpoint), &address_length)) { int connect_result; do { connect_result = connect(raw_client, (const struct sockaddr *)&address, address_length); } while (connect_result != 0 && errno == EINTR); raw_connected = connect_result == 0; } } if (raw_connected) { accept_result = cbm_daemon_ipc_accept(listener, 500, &server_connection); } if (accept_result == 1 && server_connection && cbm_daemon_frame_header_encode(partial_header, CBM_DAEMON_FRAME_REQUEST, 0, 0)) { partial_sent = ipc_test_socket_send_all(raw_client, partial_header, CBM_DAEMON_FRAME_HEADER_SIZE / 2); } if (partial_sent) { partial_receive_result = cbm_daemon_ipc_receive_frame(server_connection, 50, &frame, &received_payload); } free(received_payload); received_payload = NULL; memset(&frame, 0, sizeof(frame)); if (partial_receive_result == 0 && cbm_daemon_frame_header_encode(full_header, CBM_DAEMON_FRAME_REQUEST, 0x55aa, (uint32_t)sizeof(payload))) { full_frame_sent = ipc_test_socket_send_all(raw_client, full_header, sizeof(full_header)) && ipc_test_socket_send_all(raw_client, payload, sizeof(payload)); } if (full_frame_sent) { reuse_result = cbm_daemon_ipc_receive_frame(server_connection, 500, &frame, &received_payload); } free(received_payload); if (raw_client >= 0) { (void)close(raw_client); } cbm_daemon_ipc_connection_close(server_connection); cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(raw_connected); ASSERT_EQ(accept_result, 1); ASSERT_TRUE(partial_sent); ASSERT_EQ(partial_receive_result, 0); ASSERT_TRUE(full_frame_sent); ASSERT_EQ(reuse_result, -1); /* a timed-out partial frame permanently poisons the stream */ PASS(); } #ifdef __APPLE__ TEST(daemon_ipc_macos_clears_inherited_deny_acl_from_new_runtime) { static const char key[] = "aa11bb22cc33dd44"; char parent[TEST_PATH_CAP] = {0}; char control[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; struct stat parent_status = {0}; struct stat control_status = {0}; struct stat runtime_status = {0}; bool parent_ok = ipc_test_parent_new(parent, "mac-acl-inherit"); int acl_fixture = parent_ok ? ipc_test_macos_set_deny_acl(parent, true) : -1; if (acl_fixture == 0) { ipc_test_remove_flat_dir(parent); SKIP_PLATFORM("macOS fixture filesystem has no extended ACL support"); } bool parent_mode_stayed_private = acl_fixture == 1 && lstat(parent, &parent_status) == 0 && S_ISDIR(parent_status.st_mode) && (parent_status.st_mode & 0777) == 0700; int control_written = parent_mode_stayed_private ? snprintf(control, sizeof(control), "%s/inheritance-control", parent) : -1; bool control_created = control_written > 0 && control_written < (int)sizeof(control) && mkdir(control, 0700) == 0; bool control_mode_stayed_private = control_created && lstat(control, &control_status) == 0 && S_ISDIR(control_status.st_mode) && (control_status.st_mode & 0777) == 0700; int control_acl_entries = control_created ? ipc_test_macos_extended_acl_entry_count(control) : -1; bool control_removed = !control_created || rmdir(control) == 0; /* A deny-only ancestor is safe to traverse, but its inherited deny entry * still must be stripped from CBM's final private directory. The control * child proves that this filesystem propagated the ACL. */ if (control_removed && control_acl_entries > 0) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } bool runtime_mode_stayed_private = runtime_dir[0] != '\0' && lstat(runtime_dir, &runtime_status) == 0 && S_ISDIR(runtime_status.st_mode) && (runtime_status.st_mode & 0777) == 0700; int runtime_acl_entries = runtime_dir[0] != '\0' ? ipc_test_macos_extended_acl_entry_count(runtime_dir) : -1; bool endpoint_created = endpoint != NULL; cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(acl_fixture, 1); ASSERT_TRUE(parent_mode_stayed_private); ASSERT_TRUE(control_created); ASSERT_TRUE(control_mode_stayed_private); ASSERT_GT(control_acl_entries, 0); ASSERT_TRUE(control_removed); ASSERT_TRUE(endpoint_created); ASSERT_TRUE(runtime_mode_stayed_private); ASSERT_EQ(runtime_acl_entries, 0); PASS(); } TEST(daemon_ipc_macos_rejects_allow_acl_on_ancestor_without_mutation) { static const char key[] = "bb22cc33dd44ee55"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; struct stat before = {0}; struct stat after = {0}; bool parent_ok = ipc_test_parent_new(parent, "mac-acl-ancestor-allow"); int runtime_written = parent_ok ? snprintf(runtime_dir, sizeof(runtime_dir), "%s/cbm-daemon-%lu", parent, (unsigned long)geteuid()) : -1; int acl_fixture = parent_ok ? ipc_test_macos_set_mutating_acl(parent, false) : -1; if (acl_fixture == 0) { ipc_test_remove_flat_dir(parent); SKIP_PLATFORM("macOS fixture filesystem has no extended ACL support"); } int entries_before = acl_fixture == 1 ? ipc_test_macos_extended_acl_entry_count(parent) : -1; bool snapshot_before = entries_before > 0 && lstat(parent, &before) == 0; cbm_daemon_ipc_endpoint_t *endpoint = snapshot_before ? cbm_daemon_ipc_endpoint_new(key, parent) : NULL; int entries_after = ipc_test_macos_extended_acl_entry_count(parent); bool parent_unchanged = lstat(parent, &after) == 0 && before.st_dev == after.st_dev && before.st_ino == after.st_ino && (before.st_mode & 07777) == (after.st_mode & 07777) && entries_after == entries_before; errno = 0; bool runtime_absent = runtime_written > 0 && runtime_written < (int)sizeof(runtime_dir) && lstat(runtime_dir, &after) != 0 && errno == ENOENT; cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(acl_fixture, 1); ASSERT_TRUE(snapshot_before); ASSERT_NULL(endpoint); ASSERT_TRUE(parent_unchanged); ASSERT_TRUE(runtime_absent); PASS(); } TEST(daemon_ipc_macos_repairs_or_rejects_existing_runtime_mutating_acl) { static const char key[] = "ee55aa66bb77cc88"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *seed = NULL; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; struct stat runtime_status = {0}; bool parent_ok = ipc_test_parent_new(parent, "mac-acl-existing"); if (parent_ok) { seed = cbm_daemon_ipc_endpoint_new(key, parent); } if (seed) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(seed)); } bool runtime_seeded = runtime_dir[0] != '\0'; cbm_daemon_ipc_endpoint_free(seed); seed = NULL; int acl_fixture = runtime_seeded ? ipc_test_macos_set_mutating_acl(runtime_dir, false) : -1; if (acl_fixture == 0) { ipc_test_remove_tree(runtime_dir, parent); SKIP_PLATFORM("macOS fixture filesystem has no extended ACL support"); } bool unsafe_mode_stayed_private = acl_fixture == 1 && lstat(runtime_dir, &runtime_status) == 0 && S_ISDIR(runtime_status.st_mode) && (runtime_status.st_mode & 0777) == 0700; int acl_entries_before = acl_fixture == 1 ? ipc_test_macos_extended_acl_entry_count(runtime_dir) : -1; if (acl_entries_before > 0) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } int startup_status = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int acl_entries_after = ipc_test_macos_extended_acl_entry_count(runtime_dir); /* A constructor rejection or a lock-use rejection is fail-closed. If use * succeeds, the dedicated directory must already have had its ACL cleared * and revalidated. Mode 0700 alone is deliberately insufficient. */ bool rejected_before_use = endpoint == NULL || startup_status == -1; bool repaired_before_use = endpoint != NULL && startup_status == 1 && acl_entries_after == 0; cbm_daemon_ipc_startup_lock_release(&startup); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(runtime_seeded); ASSERT_EQ(acl_fixture, 1); ASSERT_TRUE(unsafe_mode_stayed_private); ASSERT_GT(acl_entries_before, 0); ASSERT_TRUE(rejected_before_use || repaired_before_use); PASS(); } TEST(daemon_ipc_macos_runtime_acl_injection_invalidates_existing_endpoint) { static const char key[] = "aa77bb88cc99dd00"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; struct stat runtime_status = {0}; bool parent_ok = ipc_test_parent_new(parent, "mac-acl-injected"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int acl_fixture = runtime_dir[0] != '\0' ? ipc_test_macos_set_mutating_acl(runtime_dir, false) : -1; if (acl_fixture == 0) { cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); SKIP_PLATFORM("macOS fixture filesystem has no extended ACL support"); } bool mode_stayed_private = acl_fixture == 1 && lstat(runtime_dir, &runtime_status) == 0 && S_ISDIR(runtime_status.st_mode) && (runtime_status.st_mode & 07777) == 0700; int startup_status = mode_stayed_private ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : 0; cbm_daemon_ipc_startup_lock_release(&startup); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_TRUE(runtime_dir[0] != '\0'); ASSERT_EQ(acl_fixture, 1); ASSERT_TRUE(mode_stayed_private); ASSERT_EQ(startup_status, -1); ASSERT_NULL(startup); PASS(); } TEST(daemon_ipc_macos_lock_acl_injection_invalidates_retained_startup) { static const char key[] = "ee11dd22cc33bb44"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char startup_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_startup_lock_t *startup = NULL; struct stat startup_status = {0}; bool parent_ok = ipc_test_parent_new(parent, "mac-acl-lock"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); } int startup_result = endpoint ? cbm_daemon_ipc_startup_lock_try_acquire(endpoint, &startup) : -1; int path_written = startup_result == 1 ? snprintf(startup_path, sizeof(startup_path), "%s/cbm-%s.startup-v2.lock", runtime_dir, key) : -1; bool path_ok = path_written > 0 && path_written < (int)sizeof(startup_path); int acl_fixture = path_ok ? ipc_test_macos_set_mutating_acl(startup_path, false) : -1; if (acl_fixture == 0) { cbm_daemon_ipc_startup_lock_release(&startup); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); SKIP_PLATFORM("macOS fixture filesystem has no extended ACL support"); } bool mode_stayed_private = acl_fixture == 1 && lstat(startup_path, &startup_status) == 0 && S_ISREG(startup_status.st_mode) && (startup_status.st_mode & 07777) == 0600; bool prepared = mode_stayed_private && cbm_daemon_ipc_startup_lock_prepare_handoff(startup); cbm_daemon_ipc_startup_lock_release(&startup); cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(parent_ok); ASSERT_EQ(startup_result, 1); ASSERT_TRUE(path_ok); ASSERT_EQ(acl_fixture, 1); ASSERT_TRUE(mode_stayed_private); ASSERT_FALSE(prepared); PASS(); } #endif TEST(daemon_ipc_posix_runtime_and_socket_are_owner_only) { static const char key[] = "9999aaaabbbbcccc"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char anchor_path[TEST_PATH_CAP] = {0}; char identity_path[TEST_PATH_CAP] = {0}; char pending_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; struct stat runtime_stat = {0}; struct stat socket_stat = {0}; struct stat anchor_stat = {0}; struct stat identity_stat = {0}; bool runtime_ok = false; bool socket_ok = false; bool anchor_ok = false; bool identity_ok = false; bool socket_removed = false; bool anchor_removed = false; bool pending_absent = false; bool identity_removed = false; if (ipc_test_parent_new(parent, "permissions")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); (void)ipc_test_socket_anchor_path(anchor_path, runtime_dir, key); (void)ipc_test_socket_identity_path(identity_path, socket_path); (void)ipc_test_socket_pending_path(pending_path, socket_path); runtime_ok = lstat(runtime_dir, &runtime_stat) == 0 && S_ISDIR(runtime_stat.st_mode) && (runtime_stat.st_mode & 0777) == 0700 && runtime_stat.st_uid == geteuid(); listener = cbm_daemon_ipc_listen(endpoint); } if (listener) { socket_ok = lstat(socket_path, &socket_stat) == 0 && S_ISSOCK(socket_stat.st_mode) && (socket_stat.st_mode & 0777) == 0600 && socket_stat.st_uid == geteuid() && socket_stat.st_nlink == 2; anchor_ok = lstat(anchor_path, &anchor_stat) == 0 && S_ISSOCK(anchor_stat.st_mode) && (anchor_stat.st_mode & 0777) == 0600 && anchor_stat.st_uid == geteuid() && anchor_stat.st_nlink == 2 && anchor_stat.st_dev == socket_stat.st_dev && anchor_stat.st_ino == socket_stat.st_ino; identity_ok = lstat(identity_path, &identity_stat) == 0 && S_ISREG(identity_stat.st_mode) && (identity_stat.st_mode & 0777) == 0600 && identity_stat.st_uid == geteuid() && identity_stat.st_nlink == 1; errno = 0; pending_absent = lstat(pending_path, &identity_stat) != 0 && errno == ENOENT; } cbm_daemon_ipc_listener_close(listener); if (socket_path[0] != '\0') { errno = 0; socket_removed = lstat(socket_path, &socket_stat) != 0 && errno == ENOENT; } if (identity_path[0] != '\0') { errno = 0; identity_removed = lstat(identity_path, &identity_stat) != 0 && errno == ENOENT; } if (anchor_path[0] != '\0') { errno = 0; anchor_removed = lstat(anchor_path, &anchor_stat) != 0 && errno == ENOENT; } cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(runtime_ok); ASSERT_TRUE(socket_ok); ASSERT_TRUE(anchor_ok); ASSERT_TRUE(identity_ok); ASSERT_TRUE(pending_absent); ASSERT_TRUE(socket_removed); ASSERT_TRUE(anchor_removed); ASSERT_TRUE(identity_removed); PASS(); } static bool ipc_test_write_byte(const char *path, unsigned char byte) { FILE *file = fopen(path, "wb"); if (!file) { return false; } bool ok = fwrite(&byte, 1, 1, file) == 1; return fclose(file) == 0 && ok; } TEST(daemon_ipc_posix_private_log_creates_first_ever_cache_tree_safely) { char parent[TEST_PATH_CAP] = {0}; char first[TEST_PATH_CAP] = {0}; char cache[TEST_PATH_CAP] = {0}; char logs[TEST_PATH_CAP] = {0}; char log_path[TEST_PATH_CAP] = {0}; FILE *log = NULL; struct stat status = {0}; bool paths_ok = false; bool absent_before = false; bool opened = false; bool tree_private = false; bool file_private = false; if (ipc_test_parent_new(parent, "private-log-first-cache")) { int first_written = snprintf(first, sizeof(first), "%s/first", parent); int cache_written = snprintf(cache, sizeof(cache), "%s/cache", first); int logs_written = snprintf(logs, sizeof(logs), "%s/logs", cache); int log_written = snprintf(log_path, sizeof(log_path), "%s/daemon.log", logs); paths_ok = first_written > 0 && first_written < (int)sizeof(first) && cache_written > 0 && cache_written < (int)sizeof(cache) && logs_written > 0 && logs_written < (int)sizeof(logs) && log_written > 0 && log_written < (int)sizeof(log_path); } if (paths_ok) { errno = 0; absent_before = lstat(first, &status) != 0 && errno == ENOENT; log = cbm_daemon_ipc_private_log_open(logs, "daemon.log", 4096); opened = log != NULL; } if (log) { (void)fputs("first startup\n", log); (void)fclose(log); log = NULL; } if (opened) { struct stat first_status; struct stat cache_status; struct stat logs_status; tree_private = lstat(first, &first_status) == 0 && S_ISDIR(first_status.st_mode) && (first_status.st_mode & 0777) == 0700 && lstat(cache, &cache_status) == 0 && S_ISDIR(cache_status.st_mode) && (cache_status.st_mode & 0777) == 0700 && lstat(logs, &logs_status) == 0 && S_ISDIR(logs_status.st_mode) && (logs_status.st_mode & 0777) == 0700; file_private = lstat(log_path, &status) == 0 && S_ISREG(status.st_mode) && status.st_uid == geteuid() && (status.st_mode & 0777) == 0600; } (void)unlink(log_path); (void)rmdir(logs); (void)rmdir(cache); (void)rmdir(first); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(absent_before); ASSERT_TRUE(opened); ASSERT_TRUE(tree_private); ASSERT_TRUE(file_private); PASS(); } TEST(daemon_ipc_posix_private_directory_hardens_existing_cache_root) { char parent[TEST_PATH_CAP] = {0}; char cache[TEST_PATH_CAP] = {0}; struct stat status = {0}; bool paths_ok = false; bool deliberately_public = false; bool hardened = false; if (ipc_test_parent_new(parent, "private-cache-root")) { int cache_written = snprintf(cache, sizeof(cache), "%s/cache", parent); paths_ok = cache_written > 0 && cache_written < (int)sizeof(cache) && mkdir(cache, 0700) == 0; } if (paths_ok) { deliberately_public = chmod(cache, 0755) == 0; } if (deliberately_public && cbm_daemon_ipc_private_directory_secure(cache)) { hardened = lstat(cache, &status) == 0 && S_ISDIR(status.st_mode) && status.st_uid == geteuid() && (status.st_mode & 07777) == 0700; } (void)rmdir(cache); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(deliberately_public); ASSERT_TRUE(hardened); PASS(); } TEST(daemon_ipc_posix_private_directory_rejects_world_writable_ancestor) { char parent[TEST_PATH_CAP] = {0}; char unsafe[TEST_PATH_CAP] = {0}; char cache[TEST_PATH_CAP] = {0}; struct stat status = {0}; bool paths_ok = false; bool unsafe_mode_set = false; bool rejected = false; bool ancestor_unchanged = false; bool cache_absent = false; if (ipc_test_parent_new(parent, "private-cache-unsafe-ancestor")) { int unsafe_written = snprintf(unsafe, sizeof(unsafe), "%s/unsafe", parent); int cache_written = snprintf(cache, sizeof(cache), "%s/cache", unsafe); paths_ok = unsafe_written > 0 && unsafe_written < (int)sizeof(unsafe) && cache_written > 0 && cache_written < (int)sizeof(cache) && mkdir(unsafe, 0700) == 0; } if (paths_ok) { unsafe_mode_set = chmod(unsafe, 0777) == 0; } if (unsafe_mode_set) { rejected = !cbm_daemon_ipc_private_directory_secure(cache); ancestor_unchanged = lstat(unsafe, &status) == 0 && S_ISDIR(status.st_mode) && (status.st_mode & 07777) == 0777; errno = 0; cache_absent = lstat(cache, &status) != 0 && errno == ENOENT; } (void)rmdir(cache); (void)chmod(unsafe, 0700); (void)rmdir(unsafe); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(unsafe_mode_set); ASSERT_TRUE(rejected); ASSERT_TRUE(ancestor_unchanged); ASSERT_TRUE(cache_absent); PASS(); } TEST(daemon_ipc_posix_private_log_rejects_symlinks_and_is_owner_only) { char parent[TEST_PATH_CAP] = {0}; char logs[TEST_PATH_CAP] = {0}; char log_path[TEST_PATH_CAP] = {0}; char victim_dir[TEST_PATH_CAP] = {0}; char victim_file[TEST_PATH_CAP] = {0}; FILE *log = NULL; struct stat status = {0}; bool paths_ok = false; bool directory_symlink_created = false; bool directory_symlink_rejected = false; bool secure_log_opened = false; bool directory_owner_only = false; bool file_owner_only = false; bool file_symlink_created = false; bool file_symlink_rejected = false; bool victim_unchanged = false; if (ipc_test_parent_new(parent, "private-log")) { int logs_written = snprintf(logs, sizeof(logs), "%s/logs", parent); int log_written = snprintf(log_path, sizeof(log_path), "%s/daemon.log", logs); int victim_dir_written = snprintf(victim_dir, sizeof(victim_dir), "%s/victim", parent); int victim_file_written = snprintf(victim_file, sizeof(victim_file), "%s/sentinel", parent); paths_ok = logs_written > 0 && logs_written < (int)sizeof(logs) && log_written > 0 && log_written < (int)sizeof(log_path) && victim_dir_written > 0 && victim_dir_written < (int)sizeof(victim_dir) && victim_file_written > 0 && victim_file_written < (int)sizeof(victim_file) && mkdir(victim_dir, 0700) == 0 && ipc_test_write_byte(victim_file, 0x4d); } if (paths_ok) { directory_symlink_created = symlink(victim_dir, logs) == 0; } if (directory_symlink_created) { log = cbm_daemon_ipc_private_log_open(logs, "daemon.log", 4096); directory_symlink_rejected = log == NULL; if (log) { (void)fclose(log); log = NULL; } (void)unlink(logs); } if (directory_symlink_rejected) { log = cbm_daemon_ipc_private_log_open(logs, "daemon.log", 4096); secure_log_opened = log != NULL; if (log) { (void)fputs("secure\n", log); (void)fclose(log); log = NULL; } directory_owner_only = lstat(logs, &status) == 0 && S_ISDIR(status.st_mode) && status.st_uid == geteuid() && (status.st_mode & 0777) == 0700; file_owner_only = lstat(log_path, &status) == 0 && S_ISREG(status.st_mode) && status.st_uid == geteuid() && (status.st_mode & 0777) == 0600; } if (file_owner_only) { (void)unlink(log_path); file_symlink_created = symlink(victim_file, log_path) == 0; } if (file_symlink_created) { log = cbm_daemon_ipc_private_log_open(logs, "daemon.log", 4096); file_symlink_rejected = log == NULL; if (log) { (void)fclose(log); log = NULL; } status.st_size = -1; victim_unchanged = stat(victim_file, &status) == 0 && S_ISREG(status.st_mode) && status.st_size == 1; } (void)unlink(log_path); (void)unlink(victim_file); (void)rmdir(logs); (void)rmdir(victim_dir); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(directory_symlink_created); ASSERT_TRUE(directory_symlink_rejected); ASSERT_TRUE(secure_log_opened); ASSERT_TRUE(directory_owner_only); ASSERT_TRUE(file_owner_only); ASSERT_TRUE(file_symlink_created); ASSERT_TRUE(file_symlink_rejected); ASSERT_TRUE(victim_unchanged); PASS(); } TEST(daemon_ipc_posix_rejects_non_socket_and_symlink_endpoints) { static const char key[] = "ddddeeeeffff0000"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char sentinel_path[TEST_PATH_CAP] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *unexpected_listener = NULL; struct stat st = {0}; bool regular_created = false; bool regular_rejected_unchanged = false; bool sentinel_created = false; bool symlink_created = false; bool symlink_rejected_unchanged = false; bool sentinel_unchanged = false; if (ipc_test_parent_new(parent, "stale-path")) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); (void)snprintf(sentinel_path, sizeof(sentinel_path), "%s/sentinel", parent); regular_created = ipc_test_write_byte(socket_path, 0x42); } if (regular_created) { unexpected_listener = cbm_daemon_ipc_listen(endpoint); regular_rejected_unchanged = unexpected_listener == NULL && lstat(socket_path, &st) == 0 && S_ISREG(st.st_mode) && st.st_size == 1; cbm_daemon_ipc_listener_close(unexpected_listener); unexpected_listener = NULL; (void)unlink(socket_path); sentinel_created = ipc_test_write_byte(sentinel_path, 0x7e); } if (sentinel_created) { symlink_created = symlink(sentinel_path, socket_path) == 0; } if (symlink_created) { unexpected_listener = cbm_daemon_ipc_listen(endpoint); symlink_rejected_unchanged = unexpected_listener == NULL && lstat(socket_path, &st) == 0 && S_ISLNK(st.st_mode); sentinel_unchanged = stat(sentinel_path, &st) == 0 && S_ISREG(st.st_mode) && st.st_size == 1; cbm_daemon_ipc_listener_close(unexpected_listener); } if (socket_path[0] != '\0') { (void)unlink(socket_path); } if (sentinel_path[0] != '\0') { (void)unlink(sentinel_path); } cbm_daemon_ipc_endpoint_free(endpoint); ipc_test_remove_tree(runtime_dir, parent); ASSERT_TRUE(regular_created); ASSERT_TRUE(regular_rejected_unchanged); ASSERT_TRUE(sentinel_created); ASSERT_TRUE(symlink_created); ASSERT_TRUE(symlink_rejected_unchanged); ASSERT_TRUE(sentinel_unchanged); PASS(); } #endif /* !_WIN32 */ #ifndef _WIN32 /* #1537: the daemon's private-directory ancestry walk refused ANY group-write * bit on an ancestor — the same rule #1535 removed on the activation side, and * the sibling the consolidated strictness decision covers but that never got * changed. A group-writable ~ or ~/.cache is ordinary (WSL2 ships 0775, so do * several distro skeletons and any shared-primary-group site), so the daemon * was unusable there. World-writable must still be refused: any local user * could swap a path component. And the private directory itself must still end * up owner-private, which is what makes admitting the ancestor safe. */ TEST(daemon_ipc_posix_group_writable_ancestor_is_admitted_issue1537) { char parent[TEST_PATH_CAP]; char ancestor[TEST_PATH_CAP]; char cache[TEST_PATH_CAP]; bool paths_ok = false; bool ancestor_ready = false; bool secured = false; bool leaf_owner_private = false; if (ipc_test_parent_new(parent, "posix-group-writable-ancestor")) { int a = snprintf(ancestor, sizeof(ancestor), "%s/group-writable", parent); int c = snprintf(cache, sizeof(cache), "%s/cache", ancestor); paths_ok = a > 0 && a < (int)sizeof(ancestor) && c > 0 && c < (int)sizeof(cache); } if (paths_ok) { ancestor_ready = mkdir(ancestor, 0775) == 0 && chmod(ancestor, 0775) == 0; } if (ancestor_ready) { secured = cbm_daemon_ipc_private_directory_secure(cache); struct stat leaf; leaf_owner_private = stat(cache, &leaf) == 0 && S_ISDIR(leaf.st_mode) && (leaf.st_mode & 07777) == 0700; } (void)rmdir(cache); (void)rmdir(ancestor); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(ancestor_ready); ASSERT_TRUE(secured); ASSERT_TRUE(leaf_owner_private); PASS(); } /* The other half of the same decision: world-writable stays refused. A rule * that admitted everything would "fix" #1537 by removing the protection. */ TEST(daemon_ipc_posix_world_writable_ancestor_still_refused_issue1537) { char parent[TEST_PATH_CAP]; char ancestor[TEST_PATH_CAP]; char cache[TEST_PATH_CAP]; bool paths_ok = false; bool ancestor_ready = false; bool refused = false; if (ipc_test_parent_new(parent, "posix-world-writable-ancestor")) { int a = snprintf(ancestor, sizeof(ancestor), "%s/world-writable", parent); int c = snprintf(cache, sizeof(cache), "%s/cache", ancestor); paths_ok = a > 0 && a < (int)sizeof(ancestor) && c > 0 && c < (int)sizeof(cache); } if (paths_ok) { ancestor_ready = mkdir(ancestor, 0777) == 0 && chmod(ancestor, 0777) == 0; } if (ancestor_ready) { refused = !cbm_daemon_ipc_private_directory_secure(cache); } (void)rmdir(cache); (void)rmdir(ancestor); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(ancestor_ready); ASSERT_TRUE(refused); PASS(); } #ifdef CBM_ENABLE_TEST_SEAMS /* #1687: on a WSL DrvFs mount (/mnt/e, default 0777) the ancestor refusal is * correct but used to leave the user guessing. The gate must still refuse, and * the detail must now name the containing directory AND the WSL remedy. */ TEST(daemon_ipc_posix_wsl_drvfs_refusal_names_remedy_issue1687) { char parent[TEST_PATH_CAP]; char ancestor[TEST_PATH_CAP]; char cache[TEST_PATH_CAP]; char detail[512] = {0}; char plain_detail[512] = {0}; bool paths_ok = false; bool ancestor_ready = false; bool refused = false; bool plain_refused = false; if (ipc_test_parent_new(parent, "posix-wsl-drvfs")) { int a = snprintf(ancestor, sizeof(ancestor), "%s/e", parent); int c = snprintf(cache, sizeof(cache), "%s/codebase_memory_cache", ancestor); paths_ok = a > 0 && a < (int)sizeof(ancestor) && c > 0 && c < (int)sizeof(cache); } if (paths_ok) { ancestor_ready = mkdir(ancestor, 0777) == 0 && chmod(ancestor, 0777) == 0; } if (ancestor_ready) { cbm_daemon_ipc_posix_force_wsl_drvfs_for_test(true); refused = !cbm_daemon_ipc_private_directory_secure(cache); cbm_daemon_ipc_posix_force_wsl_drvfs_for_test(false); (void)snprintf(detail, sizeof(detail), "%s", cbm_daemon_ipc_validation_detail()); plain_refused = !cbm_daemon_ipc_private_directory_secure(cache); (void)snprintf(plain_detail, sizeof(plain_detail), "%s", cbm_daemon_ipc_validation_detail()); } (void)rmdir(cache); (void)rmdir(ancestor); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(paths_ok); ASSERT_TRUE(ancestor_ready); ASSERT_TRUE(refused); /* policy unchanged: still refused */ ASSERT_NOT_NULL(strstr(detail, "CONTAINING 'codebase_memory_cache'")); ASSERT_NOT_NULL(strstr(detail, "/etc/wsl.conf")); ASSERT_NOT_NULL(strstr(detail, "metadata,umask=22,fmask=11")); ASSERT_NOT_NULL(strstr(detail, "wsl --shutdown")); ASSERT_NOT_NULL(strstr(detail, "~/.cache")); /* The remedy must fit whole, not be clipped by the detail buffer. */ ASSERT_NOT_NULL(strstr(detail, "(e.g. ~/.cache)")); /* Off DrvFs the generic message stays and carries no WSL advice. */ ASSERT_TRUE(plain_refused); ASSERT_NOT_NULL(strstr(plain_detail, "CONTAINING 'codebase_memory_cache'")); ASSERT_NULL(strstr(plain_detail, "wsl.conf")); PASS(); } /* #1717: an ancestor owned by ANOTHER non-root account (a shared filesystem * whose parent directory belongs to a colleague) is refused -- that account can * rename or replace everything below it, so the policy stays. What was wrong is * the message: the refusal set no detail, and the caller printed * "ancestry component validation failed (errno 17)", a stale EEXIST from * mkdirat() that named neither the directory nor the reason. The reporter * could not tell which directory, or why. The refusal must name the refusing * directory, its owner uid, and the remedy. The seam reports one directory as * foreign-owned because an unprivileged test cannot chown to another account. */ enum { IPC_TEST_FOREIGN_MAX_DEPTH = 16 }; typedef struct { bool paths_ok; bool seam_set; bool refused; bool cache_created; bool secured_without_seam; size_t foreign_path_length; char detail[512]; } ipc_test_foreign_result_t; /* Build //shared-fs, mark shared-fs as owned by * foreign_uid through the seam, and ask for <...>/shared-fs/cache. Deeper * chains produce a path long enough that the detail must be shortened. */ static void ipc_test_foreign_ancestor_run(const char *tag, int depth, unsigned long foreign_uid, ipc_test_foreign_result_t *result) { char parent[TEST_PATH_CAP]; char chain[IPC_TEST_FOREIGN_MAX_DEPTH + 1][TEST_PATH_CAP]; char cache[TEST_PATH_CAP]; int made = 0; memset(result, 0, sizeof(*result)); result->paths_ok = depth <= IPC_TEST_FOREIGN_MAX_DEPTH && ipc_test_parent_new(parent, tag); const char *base = parent; for (int level = 0; result->paths_ok && level <= depth; level++) { const char *leaf = level == depth ? "shared-fs" : "level-component-24-chars"; int w = snprintf(chain[level], sizeof(chain[level]), "%s/%s", base, leaf); result->paths_ok = w > 0 && w < (int)sizeof(chain[level]) && mkdir(chain[level], 0755) == 0; made += result->paths_ok ? 1 : 0; base = chain[level]; } if (result->paths_ok) { int c = snprintf(cache, sizeof(cache), "%s/cache", chain[depth]); result->paths_ok = c > 0 && c < (int)sizeof(cache); result->foreign_path_length = strlen(chain[depth]); } if (result->paths_ok) { result->seam_set = cbm_daemon_ipc_posix_set_foreign_owned_dir_for_test(chain[depth], foreign_uid); } if (result->seam_set) { result->refused = !cbm_daemon_ipc_private_directory_secure(cache); const char *why = cbm_daemon_ipc_validation_detail(); (void)snprintf(result->detail, sizeof(result->detail), "%s", why ? why : ""); struct stat probe; result->cache_created = lstat(cache, &probe) == 0; cbm_daemon_ipc_posix_clear_foreign_owned_dir_for_test(); /* Control: with real ownership the same path is accepted, so the * refusal above is the foreign owner and nothing else. */ result->secured_without_seam = cbm_daemon_ipc_private_directory_secure(cache); (void)rmdir(cache); } cbm_daemon_ipc_posix_clear_foreign_owned_dir_for_test(); while (made > 0) { (void)rmdir(chain[--made]); } if (depth <= IPC_TEST_FOREIGN_MAX_DEPTH) { ipc_test_remove_flat_dir(parent); } } TEST(daemon_ipc_posix_foreign_owned_ancestor_refusal_names_directory_issue1717) { /* A uid no account resolves to: the detail names the uid alone. */ unsigned long foreign_uid = (unsigned long)geteuid() + 4242UL; ASSERT_TRUE(getpwuid((uid_t)foreign_uid) == NULL); /* precondition */ ipc_test_foreign_result_t result; ipc_test_foreign_ancestor_run("posix-foreign-ancestor", 0, foreign_uid, &result); char owner_text[64]; (void)snprintf(owner_text, sizeof(owner_text), "owned by uid %lu,", foreign_uid); ASSERT_TRUE(result.paths_ok); ASSERT_TRUE(result.seam_set); ASSERT_TRUE(result.refused); ASSERT_TRUE(!result.cache_created); /* nothing is created below a foreign ancestor */ ASSERT_NOT_NULL(strstr(result.detail, "/shared-fs'")); ASSERT_NOT_NULL(strstr(result.detail, owner_text)); ASSERT_NOT_NULL(strstr(result.detail, "CBM_CACHE_DIR")); ASSERT_TRUE(strstr(result.detail, "errno") == NULL); ASSERT_TRUE(result.secured_without_seam); PASS(); } /* #1717: the owner is named when the account resolves. uid 1 is a local system * account in /etc/passwd on macOS and on the Linux images we test ("daemon" on * both; Alpine calls it "bin"), so the expected name is taken from the same * local database rather than hardcoded. */ TEST(daemon_ipc_posix_foreign_owned_ancestor_names_owner_account_issue1717) { const unsigned long named_uid = 1; struct passwd *expected = getpwuid((uid_t)named_uid); if (!expected || !expected->pw_name || !expected->pw_name[0]) { SKIP_PLATFORM("uid 1 has no local account name on this system"); } char owner_text[96]; (void)snprintf(owner_text, sizeof(owner_text), "owned by uid %lu (%s),", named_uid, expected->pw_name); ipc_test_foreign_result_t result; ipc_test_foreign_ancestor_run("posix-foreign-named", 0, named_uid, &result); ASSERT_TRUE(result.paths_ok); ASSERT_TRUE(result.seam_set); ASSERT_TRUE(result.refused); ASSERT_NOT_NULL(strstr(result.detail, owner_text)); ASSERT_NOT_NULL(strstr(result.detail, "/shared-fs'")); ASSERT_TRUE(result.secured_without_seam); PASS(); } /* #1717: the detail buffer is fixed (384 bytes). A long path must not push the * remedy out: the path is shortened from the left, so the refusing component * and the whole remedy survive. */ TEST(daemon_ipc_posix_foreign_owned_ancestor_long_path_keeps_remedy_issue1717) { const unsigned long named_uid = 1; ipc_test_foreign_result_t result; ipc_test_foreign_ancestor_run("posix-foreign-long", IPC_TEST_FOREIGN_MAX_DEPTH, named_uid, &result); static const char remedy[] = "Use a location whose parent directories are yours or root's (e.g. set CBM_CACHE_DIR)"; ASSERT_TRUE(result.paths_ok); ASSERT_TRUE(result.foreign_path_length > 384); /* the input really overflows */ ASSERT_TRUE(result.refused); ASSERT_TRUE(strlen(result.detail) < 384); ASSERT_TRUE(strncmp(result.detail, "'...", 4) == 0); ASSERT_NOT_NULL(strstr(result.detail, "/shared-fs' is owned by uid 1")); size_t detail_length = strlen(result.detail); ASSERT_TRUE(detail_length >= sizeof(remedy) - 1); ASSERT_STR_EQ(result.detail + detail_length - (sizeof(remedy) - 1), remedy); PASS(); } #endif #ifdef CBM_ENABLE_TEST_SEAMS /* #1830: /proc/self/uid_map single-uid detection. A single-uid map is exactly * one line " 1" whose inside id is our euid; anything else — * the init map, a count other than 1, a foreign inside id, extra lines, or junk * — must read as "not single-uid" so the overflow tolerance never engages. */ TEST(daemon_ipc_posix_uid_map_single_uid_parse_issue1830) { const unsigned long me = 1000; ASSERT_TRUE(cbm_daemon_ipc_posix_uid_map_is_single_uid_for_test("1000 1000 1\n", me)); ASSERT_TRUE(cbm_daemon_ipc_posix_uid_map_is_single_uid_for_test("1000 0 1", me)); ASSERT_TRUE(!cbm_daemon_ipc_posix_uid_map_is_single_uid_for_test("0 0 4294967295\n", me)); ASSERT_TRUE(!cbm_daemon_ipc_posix_uid_map_is_single_uid_for_test("1000 1000 2\n", me)); ASSERT_TRUE(!cbm_daemon_ipc_posix_uid_map_is_single_uid_for_test("42 1000 1\n", me)); ASSERT_TRUE(!cbm_daemon_ipc_posix_uid_map_is_single_uid_for_test("1000 1000 1\n0 0 1\n", me)); ASSERT_TRUE(!cbm_daemon_ipc_posix_uid_map_is_single_uid_for_test("", me)); ASSERT_TRUE(!cbm_daemon_ipc_posix_uid_map_is_single_uid_for_test("not a map", me)); PASS(); } /* #1830: the ancestor accept/refuse decision. The overflow uid is tolerable for * an ancestor ONLY when the single-uid tolerance is engaged, and even then only * where a root owner would be: not world-writable unless sticky, and never * group-writable (the unmapped owner's host group is unknown). With tolerance * OFF (init/multi-uid/unreadable map) an overflow-owned ancestor is refused * exactly as before #1830 — that OFF row is what a revert of the tolerance * would collapse the ON rows to, so it pins the fix. euid/root/foreign owners * behave identically with tolerance on or off. */ TEST(daemon_ipc_posix_overflow_ancestor_tolerated_only_in_single_uid_ns_issue1830) { const unsigned long euid = 1000; const unsigned long ovf = 65534; const unsigned long foreign = 4242; #define ANCESTOR_OK(owner, mode, on, ovfuid) \ cbm_daemon_ipc_posix_ancestor_stat_ok_for_test((owner), (mode), euid, (on), (ovfuid)) /* Overflow-owned ancestors, single-uid tolerance ENGAGED. */ ASSERT_TRUE(ANCESTOR_OK(ovf, 0755, true, ovf)); ASSERT_TRUE(ANCESTOR_OK(ovf, 0700, true, ovf)); ASSERT_TRUE(ANCESTOR_OK(ovf, 01777, true, ovf)); /* sticky /tmp shape */ ASSERT_TRUE(!ANCESTOR_OK(ovf, 0777, true, ovf)); /* world-writable, no sticky */ ASSERT_TRUE(!ANCESTOR_OK(ovf, 0775, true, ovf)); /* group-writable overflow owner */ /* Same ancestors, tolerance OFF — the pre-#1830 refusal (and the multi-uid / * unreadable-map path). A revert of the fix makes the ON rows read like these. */ ASSERT_TRUE(!ANCESTOR_OK(ovf, 0755, false, 0)); ASSERT_TRUE(!ANCESTOR_OK(ovf, 01777, false, 0)); /* Controls unaffected by the tolerance. */ ASSERT_TRUE(ANCESTOR_OK(euid, 0755, true, ovf)); ASSERT_TRUE(ANCESTOR_OK(euid, 0700, false, 0)); ASSERT_TRUE(ANCESTOR_OK(0, 01777, true, ovf)); /* root-owned sticky /tmp */ ASSERT_TRUE(!ANCESTOR_OK(0, 0777, true, ovf)); /* root world-writable, no sticky */ ASSERT_TRUE(!ANCESTOR_OK(foreign, 0755, true, ovf)); /* a mapped-out foreign uid */ #undef ANCESTOR_OK PASS(); } /* #1830 regression guard, and the test that would have caught the original bug * with no namespace at all. The overflow uid used to be memoised per process * via pthread_once. That made a SECURITY decision sticky: pthread_once state * survives fork(), unshare(CLONE_NEWUSER) rewrites /proc/self/uid_map, so a * process that forked and then entered a namespace kept the parent verdict. * Nothing in the suite could see it, because every deterministic #1830 test * drives the override seam rather than the real derivation -- they bind the * decision TABLE, not the WIRING. * * So assert the wiring directly: two ancestor checks must perform two real * derivations. Re-introduce any cache and the second call derives zero times * and this fails by name, on every platform, with no namespace required. */ TEST(daemon_ipc_posix_overflow_uid_is_never_cached_issue1830) { #if defined(__linux__) char parent[TEST_PATH_CAP]; if (!ipc_test_parent_new(parent, "overflow-nocache")) { FAIL("could not create the probe parent directory"); } char probe[TEST_PATH_CAP]; (void)snprintf(probe, sizeof(probe), "%s/x", parent); /* The override seam short-circuits derivation, so it must be OFF here or * the test would measure nothing. */ cbm_daemon_ipc_posix_set_ancestor_overflow_uid_for_test(false, 0); unsigned before = cbm_daemon_ipc_posix_overflow_compute_count_for_test(); (void)cbm_daemon_ipc_private_directory_secure(probe); unsigned after_first = cbm_daemon_ipc_posix_overflow_compute_count_for_test(); (void)cbm_daemon_ipc_private_directory_secure(probe); unsigned after_second = cbm_daemon_ipc_posix_overflow_compute_count_for_test(); (void)rmdir(probe); ipc_test_remove_flat_dir(parent); ASSERT_TRUE(after_first > before); /* The decisive half: the SECOND call must derive again. */ ASSERT_TRUE(after_second > after_first); PASS(); #else SKIP_PLATFORM("the overflow-uid derivation is Linux-only"); #endif } /* #1830 real end-to-end smoke: inside a single-uid user namespace the * root-owned ancestors (/, /tmp) are overflow-owned, and the daemon must still * create its private directory there. An overflow-owned ancestor cannot be * fabricated unprivileged, so this runs the real path only when a user * namespace is actually available. O10 whitelisted-skip everywhere it is not: * macOS has no user namespaces (compile-gated out); Docker's default seccomp * blocks unshare(CLONE_NEWUSER) on the Colima container leg; some kernels ship * user namespaces disabled. NOT on that list any more: Ubuntu 23.10+ restricts * unprivileged userns via AppArmor, which used to confine this test to the two * ubuntu-22.04 legs -- broad-matrix only, so it ran in no PR and sat on * GitHub's 2027-04-17 retirement clock. _test.yml now lifts that with * kernel.apparmor_restrict_unprivileged_userns=0 on every ubuntu leg, so this * runs on the CORE matrix. WHAT WAS TRIED when it skips: fork + unshare * CLONE_NEWUSER + a 1:1 uid_map write, which the sandbox denied (EPERM). The * deterministic decision coverage above is what binds the fix. * * TO REPRODUCE IT LOCALLY two things are needed, and the second is easy to * miss: run the container with --security-opt seccomp=unconfined so unshare is * permitted, AND run the suite as a NON-ROOT uid. As root the mapped range * covers uid 0, so root-owned /tmp stays 1:1 inside the namespace, never turns * overflow, and this test passes without ever reaching the branch it exists to * cover -- it passes just as happily with the fix reverted. Under `su tester` * (uid 1001, the shape CI's runner user has) the ancestors do go overflow and * the assertion becomes real. */ TEST(daemon_ipc_posix_single_uid_userns_real_smoke_issue1830) { #if defined(__linux__) uid_t host_uid = geteuid(); char probe[TEST_PATH_CAP]; (void)snprintf(probe, sizeof(probe), "/tmp/cbm-userns-smoke-%ld/x", (long)getpid()); char probe_dir[TEST_PATH_CAP]; (void)snprintf(probe_dir, sizeof(probe_dir), "/tmp/cbm-userns-smoke-%ld", (long)getpid()); pid_t child = fork(); if (child == 0) { /* Child: enter a single-uid user namespace mapping host_uid 1:1. */ if (unshare(CLONE_NEWUSER) != 0) { _exit(2); /* userns unavailable → signal skip to the parent. */ } int sg = open("/proc/self/setgroups", O_WRONLY | O_CLOEXEC); if (sg >= 0) { (void)!write(sg, "deny", 4); (void)close(sg); } int mf = open("/proc/self/uid_map", O_WRONLY | O_CLOEXEC); char line[64]; int n = snprintf(line, sizeof(line), "%ld %ld 1", (long)host_uid, (long)host_uid); bool mapped = mf >= 0 && n > 0 && write(mf, line, (size_t)n) == n; if (mf >= 0) { (void)close(mf); } if (!mapped) { _exit(2); } /* Inside the ns / and /tmp now show the overflow uid. With #1830 the * daemon can still build its private tree there; without it, refused. * * EXEC, do not just call. The overflow uid is derived once per process * (pthread_once) from /proc/self/uid_map, and that state survives * fork(): seven earlier call sites in this suite prime it with the HOST * answer, so a forked child keeps "no overflow uid" and refuses no * matter what its namespace says. That made this test fail on the only * platform where it actually runs (ubuntu-22.04; macOS compile-gates it * out, Colima's seccomp blocks unshare, and 23.10+ restricts * unprivileged userns) -- while looking like a product regression. * Re-exec so the decision is made by a process that STARTED here, which * is also the only shape production ever takes. */ (void)execl("/proc/self/exe", "test-runner", "--userns-secure-probe", probe, (char *)NULL); _exit(3); /* exec failed -- distinct from secure(0)/refused(1)/skip(2) */ } if (child < 0) { FAIL("fork failed for userns smoke"); } int status = 0; while (waitpid(child, &status, 0) < 0 && errno == EINTR) {} (void)rmdir(probe); (void)rmdir(probe_dir); if (WIFEXITED(status) && WEXITSTATUS(status) == 2) { SKIP_PLATFORM("user namespaces unavailable (no CLONE_NEWUSER / seccomp-blocked)"); } /* A failed re-exec must never read as a product refusal: 3 is its own code * so a broken probe is a loud harness failure, not a quiet "refused". */ if (WIFEXITED(status) && WEXITSTATUS(status) == 3) { FAIL("userns smoke could not re-exec /proc/self/exe for the probe"); } /* {0,1,2,3} is the whole protocol. Any other code -- a sanitizer exit (LSan * defaults to 23), an abort, a signal -- is a broken harness, not a refusal, * and must say so with the code it actually saw. */ if (!WIFEXITED(status)) { FAIL("userns probe did not exit normally (signalled) -- not a security verdict"); } if (WEXITSTATUS(status) != 0 && WEXITSTATUS(status) != 1) { char unexpected[128]; (void)snprintf(unexpected, sizeof(unexpected), "userns probe exited %d -- not a security verdict", WEXITSTATUS(status)); FAIL(unexpected); } ASSERT_EQ(0, WEXITSTATUS(status)); PASS(); #else SKIP_PLATFORM("user namespaces are Linux-only"); #endif } #endif /* CBM_ENABLE_TEST_SEAMS */ #endif /* !_WIN32 */ #ifndef _WIN32 enum { IPC_TEST_LOG_CAPTURE_CAP = 16384 }; static char ipc_test_log_capture[IPC_TEST_LOG_CAPTURE_CAP]; static size_t ipc_test_log_capture_used; static void ipc_test_log_capture_sink(const char *line) { if (!line) { return; } size_t length = strlen(line); if (ipc_test_log_capture_used + length + 2 > sizeof(ipc_test_log_capture)) { return; } memcpy(ipc_test_log_capture + ipc_test_log_capture_used, line, length); ipc_test_log_capture_used += length; ipc_test_log_capture[ipc_test_log_capture_used++] = '\n'; ipc_test_log_capture[ipc_test_log_capture_used] = '\0'; } #ifdef CBM_ENABLE_TEST_SEAMS /* #1717: a cache root on a network filesystem (shared HPC home, NFS/SMB mount) * is ACCEPTED, but SQLite WAL is unreliable there, so startup logs ONE warning * with the local-storage advice. The seam supplies the statfs f_type on every * platform (real detection is Linux-only), so the mapping and the * once-per-process rule are bound deterministically here. */ static bool ipc_test_network_warning_emitted(unsigned long magic, const char *fs_name) { ipc_test_log_capture_used = 0; ipc_test_log_capture[0] = '\0'; cbm_daemon_ipc_set_fs_magic_for_test(true, magic); cbm_log_set_sink_ex(ipc_test_log_capture_sink, CBM_LOG_SINK_REPLACE); bool warned = cbm_daemon_ipc_warn_if_network_cache_root("/shared/fs/me/.cache/cbm"); cbm_log_set_sink(NULL); char expected[32]; (void)snprintf(expected, sizeof(expected), "fs=%s", fs_name); const char *line = strstr(ipc_test_log_capture, "daemon.cache_root_network_fs"); return warned && line && strstr(line, expected) && strstr(line, "CBM_CACHE_DIR") && strstr(line, "path=/shared/fs/me/.cache/cbm"); } TEST(daemon_ipc_network_cache_root_warns_once_issue1717) { CBMLogLevel saved_level = cbm_log_get_level(); cbm_log_set_level(CBM_LOG_WARN); bool nfs = ipc_test_network_warning_emitted(0x6969UL, "NFS"); bool cifs = ipc_test_network_warning_emitted(0xFF534D42UL, "CIFS"); bool smb2 = ipc_test_network_warning_emitted(0xFE534D42UL, "SMB2"); bool lustre = ipc_test_network_warning_emitted(0x0BD00BD0UL, "Lustre"); bool gpfs = ipc_test_network_warning_emitted(0x47504653UL, "GPFS"); /* Once per process: a second startup check stays silent. */ cbm_daemon_ipc_set_fs_magic_for_test(true, 0x6969UL); bool first = cbm_daemon_ipc_warn_if_network_cache_root("/nfs/cache"); bool second = cbm_daemon_ipc_warn_if_network_cache_root("/nfs/cache"); /* Local filesystems (ext4, xfs, tmpfs) never warn. */ cbm_daemon_ipc_set_fs_magic_for_test(true, 0xEF53UL); bool ext4 = cbm_daemon_ipc_warn_if_network_cache_root("/home/me/.cache/cbm"); cbm_daemon_ipc_set_fs_magic_for_test(true, 0x58465342UL); bool xfs = cbm_daemon_ipc_warn_if_network_cache_root("/home/me/.cache/cbm"); cbm_daemon_ipc_set_fs_magic_for_test(true, 0x01021994UL); bool tmpfs = cbm_daemon_ipc_warn_if_network_cache_root("/home/me/.cache/cbm"); cbm_daemon_ipc_set_fs_magic_for_test(false, 0); cbm_log_set_level(saved_level); ASSERT_TRUE(nfs); ASSERT_TRUE(cifs); ASSERT_TRUE(smb2); ASSERT_TRUE(lustre); ASSERT_TRUE(gpfs); ASSERT_TRUE(first); ASSERT_TRUE(!second); ASSERT_TRUE(!ext4); ASSERT_TRUE(!xfs); ASSERT_TRUE(!tmpfs); PASS(); } #endif /* #1828: a full /tmp made every daemon start die at pending publication, and * the only durable trace was `daemon.ipc.listen_failed stage=pending_publication` * -- no syscall, no errno, no path. The reporter needed hours (and a wrong * `df` on the wrong mount) to find the cause. The failure line must name the * errno and the exact artifact path that could not be written. */ TEST(daemon_ipc_listen_failure_names_errno_and_path) { static const char key[] = "1828000000000001"; char parent[TEST_PATH_CAP] = {0}; char runtime_dir[TEST_PATH_CAP] = {0}; char socket_path[TEST_PATH_CAP] = {0}; char pending_path[TEST_PATH_CAP] = {0}; char expected_path[TEST_PATH_CAP + 16] = {0}; cbm_daemon_ipc_endpoint_t *endpoint = NULL; cbm_daemon_ipc_listener_t *listener = NULL; bool parent_ok = ipc_test_parent_new(parent, "enospc-diag"); if (parent_ok) { endpoint = cbm_daemon_ipc_endpoint_new(key, parent); } if (endpoint) { ipc_test_copy_path(runtime_dir, cbm_daemon_ipc_endpoint_runtime_dir(endpoint)); ipc_test_copy_path(socket_path, cbm_daemon_ipc_endpoint_address(endpoint)); } bool paths_ok = endpoint && ipc_test_socket_pending_path(pending_path, socket_path) && snprintf(expected_path, sizeof(expected_path), "path=%s.tmp", pending_path) > 0; if (paths_ok) { ipc_test_log_capture_used = 0; ipc_test_log_capture[0] = '\0'; cbm_daemon_ipc_posix_record_write_failure_set_for_test(ENOSPC); cbm_log_set_sink_ex(ipc_test_log_capture_sink, CBM_LOG_SINK_REPLACE); listener = cbm_daemon_ipc_listen(endpoint); cbm_log_set_sink(NULL); cbm_daemon_ipc_posix_record_write_failure_set_for_test(0); } const char *failed = strstr(ipc_test_log_capture, "msg=daemon.ipc.listen_failed"); bool stage_named = failed && strstr(failed, "stage=pending_publication") != NULL; bool errno_named = failed && strstr(failed, "errno=ENOSPC") != NULL; bool path_named = failed && strstr(failed, expected_path) != NULL; struct stat leftover; bool namespace_clean = paths_ok && lstat(socket_path, &leftover) != 0 && errno == ENOENT && lstat(pending_path, &leftover) != 0 && errno == ENOENT; cbm_daemon_ipc_listener_close(listener); cbm_daemon_ipc_endpoint_free(endpoint); th_cleanup(parent_ok ? parent : NULL); ASSERT_TRUE(parent_ok); ASSERT_TRUE(paths_ok); ASSERT_TRUE(listener == NULL); ASSERT_TRUE(failed != NULL); ASSERT_TRUE(stage_named); ASSERT_TRUE(errno_named); ASSERT_TRUE(path_named); ASSERT_TRUE(namespace_clean); PASS(); } #endif SUITE(daemon_ipc) { RUN_TEST(daemon_ipc_pending_timeout_race_returns_completed_io); RUN_TEST(daemon_ipc_pending_wait_failure_cancels_and_drains); RUN_TEST(daemon_ipc_pending_timeout_cancelled_returns_timeout); RUN_TEST(daemon_ipc_windows_generation_address_binds_account_key_and_nonce); RUN_TEST(daemon_ipc_windows_legacy_names_are_frozen_for_migration); RUN_TEST(daemon_ipc_windows_rendezvous_record_is_exact_and_canonical); #ifdef _WIN32 RUN_TEST(daemon_ipc_windows_default_endpoint_ignores_temp_environment); RUN_TEST(daemon_ipc_windows_private_directory_rejects_untrusted_ancestor_acl); RUN_TEST(daemon_ipc_windows_private_directory_ace_is_inheritable); RUN_TEST(daemon_ipc_windows_private_directory_survives_lost_creation_race); RUN_TEST(daemon_ipc_windows_private_directory_refuses_and_names_a_planted_file); RUN_TEST(daemon_ipc_windows_private_directory_allows_add_subdirectory_only_ancestor); RUN_TEST(daemon_ipc_windows_sid_trust_accepts_local_admin_rejects_foreign_500); RUN_TEST(daemon_ipc_windows_legacy_bridge_covers_handoff_and_lifetime); RUN_TEST(daemon_ipc_windows_local_transition_atomically_reserves_legacy_pipe); RUN_TEST(daemon_ipc_windows_startup_retries_transient_rendezvous_reader); RUN_TEST(daemon_ipc_windows_rendezvous_bridges_concurrent_lifetime_owner); RUN_TEST(daemon_ipc_windows_generation_rotates_and_escapes_occupied_old_pipe); RUN_TEST(daemon_ipc_windows_corrupt_rendezvous_fails_closed_until_startup_repairs); RUN_TEST(daemon_ipc_windows_startup_and_lifetime_locks_are_cross_process); #endif RUN_TEST(daemon_ipc_endpoint_is_namespaced_by_instance_key); RUN_TEST(daemon_ipc_relative_runtime_parent_is_canonical_and_stable); RUN_TEST(daemon_ipc_rejects_uppercase_instance_key); RUN_TEST(daemon_ipc_no_spawn_probe_distinguishes_absent_active_and_busy); RUN_TEST(daemon_ipc_lifetime_reservation_survives_saturated_second_listen); RUN_TEST(daemon_ipc_listener_touch_detects_lost_runtime_files); RUN_TEST(daemon_ipc_lifetime_reservation_transfers_without_unlock_window); RUN_TEST(daemon_ipc_local_frame_roundtrip); RUN_TEST(daemon_ipc_bounded_receive_rejects_oversize_before_payload); RUN_TEST(daemon_ipc_wait_forever_is_interruptible); RUN_TEST(daemon_ipc_connect_waits_for_delayed_listener); RUN_TEST(daemon_ipc_startup_lock_has_one_winner); RUN_TEST(daemon_ipc_activation_probe_ignores_matching_startup_claim_only); RUN_TEST(daemon_ipc_local_transition_coexists_with_active_daemon_lifetime); RUN_TEST(daemon_ipc_local_participants_overlap_and_allow_modern_startup); RUN_TEST(daemon_ipc_windows_local_transition_release_reports_live_gate_contention); RUN_TEST(daemon_ipc_windows_local_transition_release_retries_retained_mutex); RUN_TEST(daemon_ipc_windows_startup_release_retains_retry_authority); RUN_TEST(daemon_ipc_frame_timeout_poisons_connection); #ifndef _WIN32 RUN_TEST(daemon_ipc_posix_group_writable_ancestor_is_admitted_issue1537); RUN_TEST(daemon_ipc_posix_world_writable_ancestor_still_refused_issue1537); #ifdef CBM_ENABLE_TEST_SEAMS RUN_TEST(daemon_ipc_posix_wsl_drvfs_refusal_names_remedy_issue1687); RUN_TEST(daemon_ipc_posix_foreign_owned_ancestor_refusal_names_directory_issue1717); RUN_TEST(daemon_ipc_posix_foreign_owned_ancestor_names_owner_account_issue1717); RUN_TEST(daemon_ipc_posix_foreign_owned_ancestor_long_path_keeps_remedy_issue1717); RUN_TEST(daemon_ipc_posix_uid_map_single_uid_parse_issue1830); RUN_TEST(daemon_ipc_posix_overflow_ancestor_tolerated_only_in_single_uid_ns_issue1830); RUN_TEST(daemon_ipc_posix_overflow_uid_is_never_cached_issue1830); RUN_TEST(daemon_ipc_posix_single_uid_userns_real_smoke_issue1830); #endif RUN_TEST(daemon_ipc_posix_startup_lock_is_cross_process); RUN_TEST(daemon_ipc_posix_lifetime_reservation_rejects_fork_inheritance); RUN_TEST(daemon_ipc_posix_child_participant_handoff_retains_legacy_bridge); RUN_TEST(daemon_ipc_posix_publication_boundaries_recover_from_crash); RUN_TEST(daemon_ipc_posix_record_publication_windows_recover_from_crash); RUN_TEST(daemon_ipc_posix_unknown_record_temp_pair_is_preserved); RUN_TEST(daemon_ipc_posix_recovery_preserves_replaced_stable_socket); RUN_TEST(daemon_ipc_posix_pending_without_anchor_never_deletes_stable); RUN_TEST(daemon_ipc_posix_current_generation_crash_cleanup_requires_startup_lock); RUN_TEST(daemon_ipc_posix_unknown_socket_without_identity_refuses_cleanup); RUN_TEST(daemon_ipc_posix_markerless_linked_socket_recovers_under_startup_lock); RUN_TEST(daemon_ipc_posix_markerless_mismatched_socket_and_anchor_are_preserved); RUN_TEST(daemon_ipc_posix_active_listener_is_never_cleaned_under_queue_pressure); RUN_TEST(daemon_ipc_posix_partial_frame_timeout_poisons_connection); #ifdef __APPLE__ RUN_TEST(daemon_ipc_macos_clears_inherited_deny_acl_from_new_runtime); RUN_TEST(daemon_ipc_macos_rejects_allow_acl_on_ancestor_without_mutation); RUN_TEST(daemon_ipc_macos_repairs_or_rejects_existing_runtime_mutating_acl); RUN_TEST(daemon_ipc_macos_runtime_acl_injection_invalidates_existing_endpoint); RUN_TEST(daemon_ipc_macos_lock_acl_injection_invalidates_retained_startup); #endif RUN_TEST(daemon_ipc_posix_runtime_and_socket_are_owner_only); RUN_TEST(daemon_ipc_posix_private_log_creates_first_ever_cache_tree_safely); RUN_TEST(daemon_ipc_posix_private_directory_hardens_existing_cache_root); RUN_TEST(daemon_ipc_posix_private_directory_rejects_world_writable_ancestor); RUN_TEST(daemon_ipc_posix_private_log_rejects_symlinks_and_is_owner_only); RUN_TEST(daemon_ipc_posix_rejects_non_socket_and_symlink_endpoints); #ifndef _WIN32 RUN_TEST(daemon_ipc_listen_failure_names_errno_and_path); #ifdef CBM_ENABLE_TEST_SEAMS RUN_TEST(daemon_ipc_network_cache_root_warns_once_issue1717); #endif #endif #endif }