diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index 6029e3801..ce9437bae 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -4460,29 +4460,29 @@ daemon_file_logger( #if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) int -sentry__crash_daemon_main( - pid_t app_pid, uint64_t app_tid, int notify_eventfd, int ready_eventfd) +sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, int notify_eventfd, + int ready_eventfd, int message_fd) #elif defined(SENTRY_PLATFORM_MACOS) int sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, int notify_pipe_read, - int ready_pipe_write, int shm_fd) + int ready_pipe_write, int shm_fd, int message_fd) #elif defined(SENTRY_PLATFORM_WINDOWS) int sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, - HANDLE ready_event_handle) + HANDLE ready_event_handle, HANDLE message_read_handle) #endif { // Initialize IPC first (attach to shared memory created by parent) // We need this to get the database path for logging #if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) sentry_crash_ipc_t *ipc = sentry__crash_ipc_init_daemon( - app_pid, app_tid, notify_eventfd, ready_eventfd); + app_pid, app_tid, notify_eventfd, ready_eventfd, message_fd); #elif defined(SENTRY_PLATFORM_MACOS) - sentry_crash_ipc_t *ipc = sentry__crash_ipc_init_daemon( - app_pid, app_tid, notify_pipe_read, ready_pipe_write, shm_fd); + sentry_crash_ipc_t *ipc = sentry__crash_ipc_init_daemon(app_pid, app_tid, + notify_pipe_read, ready_pipe_write, shm_fd, message_fd); #elif defined(SENTRY_PLATFORM_WINDOWS) - sentry_crash_ipc_t *ipc = sentry__crash_ipc_init_daemon( - app_pid, app_tid, event_handle, ready_event_handle); + sentry_crash_ipc_t *ipc = sentry__crash_ipc_init_daemon(app_pid, app_tid, + event_handle, ready_event_handle, message_read_handle); #endif if (!ipc) { return 1; @@ -4647,6 +4647,7 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, // Don't overwrite it with the parent's handle (handles are per-process) (void)event_handle; (void)ready_event_handle; + (void)message_read_handle; #endif #if defined(SENTRY_PLATFORM_WINDOWS) @@ -4823,19 +4824,21 @@ main(int argc, char **argv) { // Expected arguments: // Linux: + // // macOS: + // # if defined(SENTRY_PLATFORM_MACOS) - if (argc < 6) { + if (argc < 7) { fprintf(stderr, "Usage: sentry-crash " - " \n"); + " \n"); return 1; } # else - if (argc < 5) { + if (argc < 6) { fprintf(stderr, "Usage: sentry-crash " - "\n"); + " \n"); return 1; } # endif @@ -4847,19 +4850,23 @@ main(int argc, char **argv) # if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) int notify_eventfd = atoi(argv[3]); int ready_eventfd = atoi(argv[4]); + int message_fd = atoi(argv[5]); return sentry__crash_daemon_main( - app_pid, app_tid, notify_eventfd, ready_eventfd); + app_pid, app_tid, notify_eventfd, ready_eventfd, message_fd); # elif defined(SENTRY_PLATFORM_MACOS) int notify_pipe_read = atoi(argv[3]); int ready_pipe_write = atoi(argv[4]); int shm_fd_arg = atoi(argv[5]); - return sentry__crash_daemon_main( - app_pid, app_tid, notify_pipe_read, ready_pipe_write, shm_fd_arg); + int message_fd = atoi(argv[6]); + return sentry__crash_daemon_main(app_pid, app_tid, notify_pipe_read, + ready_pipe_write, shm_fd_arg, message_fd); # elif defined(SENTRY_PLATFORM_WINDOWS) unsigned long long event_handle_val = strtoull(argv[3], NULL, 10); unsigned long long ready_event_val = strtoull(argv[4], NULL, 10); + unsigned long long message_handle_val = strtoull(argv[5], NULL, 10); HANDLE event_handle = (HANDLE)(uintptr_t)event_handle_val; HANDLE ready_event_handle = (HANDLE)(uintptr_t)ready_event_val; + HANDLE message_read_handle = (HANDLE)(uintptr_t)message_handle_val; # if defined(SENTRY_PLATFORM_XBOX) // Required before any XNetworking call the transport makes at @@ -4872,8 +4879,8 @@ main(int argc, char **argv) NULL, 0, sentry__xbox_network_prewarm_thread_proc, NULL, 0, NULL); # endif - int rv = sentry__crash_daemon_main( - app_pid, app_tid, event_handle, ready_event_handle); + int rv = sentry__crash_daemon_main(app_pid, app_tid, event_handle, + ready_event_handle, message_read_handle); # if defined(SENTRY_PLATFORM_XBOX) if (network_prewarm_thread) { diff --git a/src/backends/native/sentry_crash_daemon.h b/src/backends/native/sentry_crash_daemon.h index e689b39f8..e7a6473d5 100644 --- a/src/backends/native/sentry_crash_daemon.h +++ b/src/backends/native/sentry_crash_daemon.h @@ -22,14 +22,14 @@ struct sentry_options_s; * @param ready_handle Ready signal handle to signal parent */ #if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) -int sentry__crash_daemon_main( - pid_t app_pid, uint64_t app_tid, int notify_eventfd, int ready_eventfd); +int sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, + int notify_eventfd, int ready_eventfd, int message_fd); #elif defined(SENTRY_PLATFORM_MACOS) int sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, - int notify_pipe_read, int ready_pipe_write, int shm_fd); + int notify_pipe_read, int ready_pipe_write, int shm_fd, int message_fd); #elif defined(SENTRY_PLATFORM_WINDOWS) int sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, - HANDLE event_handle, HANDLE ready_event_handle); + HANDLE event_handle, HANDLE ready_event_handle, HANDLE message_read_handle); #endif /** diff --git a/src/backends/native/sentry_crash_ipc.c b/src/backends/native/sentry_crash_ipc.c index deafe3ca5..94cdd0eef 100644 --- a/src/backends/native/sentry_crash_ipc.c +++ b/src/backends/native/sentry_crash_ipc.c @@ -7,12 +7,151 @@ #include #include +static void +write_u16_le(char *buf, uint16_t value) +{ + buf[0] = (char)(value & 0xffu); + buf[1] = (char)((value >> 8) & 0xffu); +} + +static void +write_u32_le(char *buf, uint32_t value) +{ + buf[0] = (char)(value & 0xffu); + buf[1] = (char)((value >> 8) & 0xffu); + buf[2] = (char)((value >> 16) & 0xffu); + buf[3] = (char)((value >> 24) & 0xffu); +} + +static void +write_u64_le(char *buf, uint64_t value) +{ + for (size_t i = 0; i < 8; i++) { + buf[i] = (char)((value >> (i * 8)) & 0xffu); + } +} + +static uint16_t +read_u16_le(const char *buf) +{ + return (uint16_t)((uint8_t)buf[0] | ((uint16_t)(uint8_t)buf[1] << 8)); +} + +static uint32_t +read_u32_le(const char *buf) +{ + return (uint32_t)(uint8_t)buf[0] | ((uint32_t)(uint8_t)buf[1] << 8) + | ((uint32_t)(uint8_t)buf[2] << 16) | ((uint32_t)(uint8_t)buf[3] << 24); +} + +static uint64_t +read_u64_le(const char *buf) +{ + uint64_t value = 0; + for (size_t i = 0; i < 8; i++) { + value |= (uint64_t)(uint8_t)buf[i] << (i * 8); + } + return value; +} + +bool +sentry__crash_ipc_message_type_is_known(uint16_t type) +{ + return type >= SENTRY_CRASH_IPC_MESSAGE_SCOPE_SNAPSHOT + && type <= SENTRY_CRASH_IPC_MESSAGE_SHUTDOWN; +} + +sentry_crash_ipc_message_result_t +sentry__crash_ipc_message_encode(uint16_t type, uint16_t flags, + uint64_t sequence, const char *payload, size_t payload_len, char **out_buf, + size_t *out_len) +{ + if (!out_buf || !out_len || (payload_len && !payload) + || !sentry__crash_ipc_message_type_is_known(type)) { + return !sentry__crash_ipc_message_type_is_known(type) + ? SENTRY_CRASH_IPC_MESSAGE_UNKNOWN_TYPE + : SENTRY_CRASH_IPC_MESSAGE_INVALID; + } + + if (payload_len > SENTRY_CRASH_IPC_MESSAGE_MAX_LEN + - SENTRY_CRASH_IPC_MESSAGE_MIN_LEN + || payload_len > SIZE_MAX - SENTRY_CRASH_IPC_MESSAGE_HEADER_SIZE) { + return SENTRY_CRASH_IPC_MESSAGE_OVERSIZED; + } + + uint32_t message_len + = (uint32_t)(SENTRY_CRASH_IPC_MESSAGE_MIN_LEN + payload_len); + size_t total_len + = SENTRY_CRASH_IPC_MESSAGE_PREFIX_SIZE + (size_t)message_len; + char *buf = sentry_malloc(total_len); + if (!buf) { + return SENTRY_CRASH_IPC_MESSAGE_OOM; + } + + write_u32_le(buf, message_len); + write_u16_le(buf + 4, type); + write_u16_le(buf + 6, flags); + write_u64_le(buf + 8, sequence); + if (payload_len) { + memcpy( + buf + SENTRY_CRASH_IPC_MESSAGE_HEADER_SIZE, payload, payload_len); + } + + *out_buf = buf; + *out_len = total_len; + return SENTRY_CRASH_IPC_MESSAGE_OK; +} + +sentry_crash_ipc_message_result_t +sentry__crash_ipc_message_decode( + const char *buf, size_t buf_len, sentry_crash_ipc_message_t *message) +{ + if (!buf || !message) { + return SENTRY_CRASH_IPC_MESSAGE_INVALID; + } + if (buf_len < SENTRY_CRASH_IPC_MESSAGE_PREFIX_SIZE) { + return SENTRY_CRASH_IPC_MESSAGE_PARTIAL; + } + + uint32_t message_len = read_u32_le(buf); + if (message_len < SENTRY_CRASH_IPC_MESSAGE_MIN_LEN) { + return SENTRY_CRASH_IPC_MESSAGE_INVALID; + } +#if SIZE_MAX <= UINT32_MAX + if (message_len > SIZE_MAX - SENTRY_CRASH_IPC_MESSAGE_PREFIX_SIZE) { + return SENTRY_CRASH_IPC_MESSAGE_OVERSIZED; + } +#endif + + size_t total_len + = SENTRY_CRASH_IPC_MESSAGE_PREFIX_SIZE + (size_t)message_len; + if (buf_len < total_len) { + return SENTRY_CRASH_IPC_MESSAGE_PARTIAL; + } + if (buf_len > total_len) { + return SENTRY_CRASH_IPC_MESSAGE_INVALID; + } + + uint16_t type = read_u16_le(buf + 4); + if (!sentry__crash_ipc_message_type_is_known(type)) { + return SENTRY_CRASH_IPC_MESSAGE_UNKNOWN_TYPE; + } + + message->type = type; + message->flags = read_u16_le(buf + 6); + message->sequence = read_u64_le(buf + 8); + message->payload = buf + SENTRY_CRASH_IPC_MESSAGE_HEADER_SIZE; + message->payload_len = message_len - SENTRY_CRASH_IPC_MESSAGE_MIN_LEN; + return SENTRY_CRASH_IPC_MESSAGE_OK; +} + #if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) # include # include # include # include +# include # include # include @@ -25,6 +164,8 @@ sentry__crash_ipc_init_app(sem_t *init_sem) } ipc->is_daemon = false; ipc->init_sem = init_sem; // Use provided semaphore (managed by backend) + ipc->message_fd[0] = -1; + ipc->message_fd[1] = -1; // Create shared memory with unique name based on PID and thread ID // macOS has a 31-character limit for POSIX shared memory names (PSEMNAMLEN) @@ -150,6 +291,24 @@ sentry__crash_ipc_init_app(sem_t *init_sem) return NULL; } + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc->message_fd) + < 0) { + SENTRY_WARNF( + "failed to create message socketpair: %s", strerror(errno)); + close(ipc->ready_fd); + close(ipc->notify_fd); + munmap(ipc->shmem, SENTRY_CRASH_SHM_SIZE); + close(ipc->shm_fd); + if (!shm_exists) { + shm_unlink(ipc->shm_name); + } + if (ipc->init_sem) { + sem_post(ipc->init_sem); + } + sentry_free(ipc); + return NULL; + } + // Initialize shared memory only if newly created if (!shm_exists) { sentry__crash_context_init(ipc->shmem); @@ -167,14 +326,16 @@ sentry__crash_ipc_init_app(sem_t *init_sem) } sentry_crash_ipc_t * -sentry__crash_ipc_init_daemon( - pid_t app_pid, uint64_t app_tid, int notify_eventfd, int ready_eventfd) +sentry__crash_ipc_init_daemon(pid_t app_pid, uint64_t app_tid, + int notify_eventfd, int ready_eventfd, int message_fd) { sentry_crash_ipc_t *ipc = SENTRY_MAKE(sentry_crash_ipc_t); if (!ipc) { return NULL; } ipc->is_daemon = true; + ipc->message_fd[0] = -1; + ipc->message_fd[1] = message_fd; // Open existing shared memory created by app (using PID and thread ID) // Must match the format in sentry__crash_ipc_init_app @@ -299,6 +460,13 @@ sentry__crash_ipc_free(sentry_crash_ipc_t *ipc) close(ipc->ready_fd); } + if (ipc->message_fd[0] >= 0) { + close(ipc->message_fd[0]); + } + if (ipc->message_fd[1] >= 0) { + close(ipc->message_fd[1]); + } + sentry_free(ipc); } @@ -309,6 +477,7 @@ sentry__crash_ipc_free(sentry_crash_ipc_t *ipc) # include # include # include +# include # include # include @@ -321,6 +490,8 @@ sentry__crash_ipc_init_app(sentry_mutex_t *init_mutex) } ipc->is_daemon = false; ipc->init_mutex = init_mutex; + ipc->message_fd[0] = -1; + ipc->message_fd[1] = -1; // Build a file path for shared memory using the system temp directory. // Unlike shm_open(), regular files in $TMPDIR work inside App Sandbox. @@ -445,6 +616,27 @@ sentry__crash_ipc_init_app(sentry_mutex_t *init_mutex) return NULL; } + if (socketpair(AF_UNIX, SOCK_STREAM, 0, ipc->message_fd) < 0) { + SENTRY_WARNF( + "failed to create message socketpair: %s", strerror(errno)); + close(ipc->ready_pipe[0]); + close(ipc->ready_pipe[1]); + close(ipc->notify_pipe[0]); + close(ipc->notify_pipe[1]); + munmap(ipc->shmem, SENTRY_CRASH_SHM_SIZE); + close(ipc->shm_fd); + if (!shm_exists) { + unlink(ipc->shm_path); + } + if (ipc->init_mutex) { + sentry__mutex_unlock(ipc->init_mutex); + } + sentry_free(ipc); + return NULL; + } + fcntl(ipc->message_fd[0], F_SETFD, FD_CLOEXEC); + fcntl(ipc->message_fd[1], F_SETFD, FD_CLOEXEC); + if (!shm_exists) { sentry__crash_context_init(ipc->shmem); } @@ -461,7 +653,7 @@ sentry__crash_ipc_init_app(sentry_mutex_t *init_mutex) sentry_crash_ipc_t * sentry__crash_ipc_init_daemon(pid_t app_pid, uint64_t app_tid, - int notify_pipe_read, int ready_pipe_write, int shm_fd) + int notify_pipe_read, int ready_pipe_write, int shm_fd, int message_fd) { (void)app_pid; (void)app_tid; @@ -471,6 +663,8 @@ sentry__crash_ipc_init_daemon(pid_t app_pid, uint64_t app_tid, return NULL; } ipc->is_daemon = true; + ipc->message_fd[0] = -1; + ipc->message_fd[1] = message_fd; // Use the inherited shm_fd directly (no shm_open needed, sandbox-safe) ipc->shm_fd = shm_fd; @@ -584,6 +778,13 @@ sentry__crash_ipc_free(sentry_crash_ipc_t *ipc) close(ipc->ready_pipe[1]); } + if (ipc->message_fd[0] >= 0) { + close(ipc->message_fd[0]); + } + if (ipc->message_fd[1] >= 0) { + close(ipc->message_fd[1]); + } + if (!ipc->is_daemon) { sentry__crash_ipc_unlink(ipc); } @@ -697,6 +898,25 @@ sentry__crash_ipc_init_app(HANDLE init_mutex) return NULL; } + SECURITY_ATTRIBUTES pipe_attrs; + ZeroMemory(&pipe_attrs, sizeof(pipe_attrs)); + pipe_attrs.nLength = sizeof(pipe_attrs); + pipe_attrs.bInheritHandle = TRUE; + if (!CreatePipe(&ipc->message_read_handle, &ipc->message_write_handle, + &pipe_attrs, 0)) { + SENTRY_WARNF("failed to create message pipe: %lu", GetLastError()); + CloseHandle(ipc->ready_event_handle); + CloseHandle(ipc->event_handle); + UnmapViewOfFile(ipc->shmem); + CloseHandle(ipc->shm_handle); + if (ipc->init_mutex) { + ReleaseMutex(ipc->init_mutex); + } + sentry_free(ipc); + return NULL; + } + SetHandleInformation(ipc->message_write_handle, HANDLE_FLAG_INHERIT, 0); + // Initialize shared memory only if newly created if (!shm_exists) { sentry__crash_context_init(ipc->shmem); @@ -714,7 +934,7 @@ sentry__crash_ipc_init_app(HANDLE init_mutex) sentry_crash_ipc_t * sentry__crash_ipc_init_daemon(pid_t app_pid, uint64_t app_tid, - HANDLE event_handle, HANDLE ready_event_handle) + HANDLE event_handle, HANDLE ready_event_handle, HANDLE message_read_handle) { // On Windows, we open events by name, so handles from parent are not used // (handles are per-process and cannot be directly inherited) @@ -726,6 +946,7 @@ sentry__crash_ipc_init_daemon(pid_t app_pid, uint64_t app_tid, return NULL; } ipc->is_daemon = true; + ipc->message_read_handle = message_read_handle; // Open existing shared memory (using PID and thread ID) swprintf(ipc->shm_name, SENTRY_CRASH_IPC_NAME_SIZE, @@ -855,6 +1076,13 @@ sentry__crash_ipc_free(sentry_crash_ipc_t *ipc) CloseHandle(ipc->ready_event_handle); } + if (ipc->message_read_handle) { + CloseHandle(ipc->message_read_handle); + } + if (ipc->message_write_handle) { + CloseHandle(ipc->message_write_handle); + } + if (ipc->parent_handle) { CloseHandle(ipc->parent_handle); } diff --git a/src/backends/native/sentry_crash_ipc.h b/src/backends/native/sentry_crash_ipc.h index ad0e4fa6f..dbefd8b3b 100644 --- a/src/backends/native/sentry_crash_ipc.h +++ b/src/backends/native/sentry_crash_ipc.h @@ -4,6 +4,9 @@ #include "sentry_boot.h" #include "sentry_crash_context.h" +#include +#include + #if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) # include # include @@ -16,6 +19,57 @@ # include #endif +#define SENTRY_CRASH_IPC_MESSAGE_PREFIX_SIZE 4u +#define SENTRY_CRASH_IPC_MESSAGE_HEADER_SIZE 16u +#define SENTRY_CRASH_IPC_MESSAGE_MIN_LEN 12u +#define SENTRY_CRASH_IPC_MESSAGE_MAX_LEN UINT32_MAX + +typedef enum { + SENTRY_CRASH_IPC_MESSAGE_SCOPE_SNAPSHOT = 1, + SENTRY_CRASH_IPC_MESSAGE_SET_RELEASE = 2, + SENTRY_CRASH_IPC_MESSAGE_SET_ENVIRONMENT = 3, + SENTRY_CRASH_IPC_MESSAGE_SET_TRANSACTION = 4, + SENTRY_CRASH_IPC_MESSAGE_SET_FINGERPRINT = 5, + SENTRY_CRASH_IPC_MESSAGE_SET_LEVEL = 6, + SENTRY_CRASH_IPC_MESSAGE_SET_USER = 7, + SENTRY_CRASH_IPC_MESSAGE_SET_TAG = 8, + SENTRY_CRASH_IPC_MESSAGE_REMOVE_TAG = 9, + SENTRY_CRASH_IPC_MESSAGE_SET_EXTRA = 10, + SENTRY_CRASH_IPC_MESSAGE_REMOVE_EXTRA = 11, + SENTRY_CRASH_IPC_MESSAGE_SET_CONTEXT = 12, + SENTRY_CRASH_IPC_MESSAGE_REMOVE_CONTEXT = 13, + SENTRY_CRASH_IPC_MESSAGE_ADD_BREADCRUMB = 14, + SENTRY_CRASH_IPC_MESSAGE_SET_ATTACHMENT_LIST = 15, + SENTRY_CRASH_IPC_MESSAGE_CRASH = 16, + SENTRY_CRASH_IPC_MESSAGE_SHUTDOWN = 17, +} sentry_crash_ipc_message_type_t; + +typedef enum { + SENTRY_CRASH_IPC_MESSAGE_OK = 0, + SENTRY_CRASH_IPC_MESSAGE_PARTIAL = 1, + SENTRY_CRASH_IPC_MESSAGE_INVALID = 2, + SENTRY_CRASH_IPC_MESSAGE_OVERSIZED = 3, + SENTRY_CRASH_IPC_MESSAGE_UNKNOWN_TYPE = 4, + SENTRY_CRASH_IPC_MESSAGE_OOM = 5, +} sentry_crash_ipc_message_result_t; + +typedef struct { + uint16_t type; + uint16_t flags; + uint64_t sequence; + const char *payload; + size_t payload_len; +} sentry_crash_ipc_message_t; + +bool sentry__crash_ipc_message_type_is_known(uint16_t type); + +sentry_crash_ipc_message_result_t sentry__crash_ipc_message_encode( + uint16_t type, uint16_t flags, uint64_t sequence, const char *payload, + size_t payload_len, char **out_buf, size_t *out_len); + +sentry_crash_ipc_message_result_t sentry__crash_ipc_message_decode( + const char *buf, size_t buf_len, sentry_crash_ipc_message_t *message); + #if defined(SENTRY_PLATFORM_WINDOWS) typedef HANDLE sentry_process_handle_t; #else @@ -32,6 +86,8 @@ typedef struct { int shm_fd; int notify_fd; // Eventfd for crash notifications int ready_fd; // Eventfd for daemon ready signal + // Anonymous app-to-daemon message stream: app writes, daemon reads. + int message_fd[2]; char shm_name[SENTRY_CRASH_IPC_NAME_SIZE]; sem_t *init_sem; // Named semaphore for initialization synchronization char sem_name[SENTRY_CRASH_IPC_NAME_SIZE]; @@ -39,14 +95,19 @@ typedef struct { int shm_fd; int notify_pipe[2]; // Pipe for crash notifications (fork-safe) int ready_pipe[2]; // Pipe for daemon ready signal (fork-safe) + int message_fd[2]; // Anonymous app-to-daemon message stream: app writes, + // daemon reads char shm_path[SENTRY_CRASH_MAX_PATH]; // File-backed shm path (sandbox-safe) sentry_mutex_t *init_mutex; // Process-wide mutex (sandbox-safe, no sem_open) #elif defined(SENTRY_PLATFORM_WINDOWS) HANDLE shm_handle; HANDLE event_handle; // Event for crash notifications (parent -> daemon) - HANDLE - ready_event_handle; // Event for daemon ready signal (daemon -> parent) + HANDLE ready_event_handle; // Event for ready signal (daemon -> parent) + HANDLE message_read_handle; // Anonymous app-to-daemon message stream read + // end + HANDLE message_write_handle; // Anonymous app-to-daemon message stream write + // end wchar_t shm_name[SENTRY_CRASH_IPC_NAME_SIZE]; wchar_t event_name[SENTRY_CRASH_IPC_NAME_SIZE]; wchar_t ready_event_name[SENTRY_CRASH_IPC_NAME_SIZE]; @@ -85,14 +146,16 @@ sentry_crash_ipc_t *sentry__crash_ipc_init_app(void); * Linux, pipe fd on macOS, event on Windows) */ #if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) -sentry_crash_ipc_t *sentry__crash_ipc_init_daemon( - pid_t app_pid, uint64_t app_tid, int notify_eventfd, int ready_eventfd); +sentry_crash_ipc_t *sentry__crash_ipc_init_daemon(pid_t app_pid, + uint64_t app_tid, int notify_eventfd, int ready_eventfd, int message_fd); #elif defined(SENTRY_PLATFORM_MACOS) sentry_crash_ipc_t *sentry__crash_ipc_init_daemon(pid_t app_pid, - uint64_t app_tid, int notify_pipe_read, int ready_pipe_write, int shm_fd); + uint64_t app_tid, int notify_pipe_read, int ready_pipe_write, int shm_fd, + int message_fd); #elif defined(SENTRY_PLATFORM_WINDOWS) sentry_crash_ipc_t *sentry__crash_ipc_init_daemon(pid_t app_pid, - uint64_t app_tid, HANDLE event_handle, HANDLE ready_event_handle); + uint64_t app_tid, HANDLE event_handle, HANDLE ready_event_handle, + HANDLE message_read_handle); #endif /** diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 6bf029278..64dc467dc 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -333,15 +333,16 @@ native_backend_process_old_run(sentry_backend_t *backend, #if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) static pid_t daemon_start(pid_t app_pid, uint64_t app_tid, int notify_eventfd, - int ready_eventfd, const char *handler_path) + int ready_eventfd, int message_fd, const char *handler_path) #elif defined(SENTRY_PLATFORM_MACOS) static pid_t daemon_start(pid_t app_pid, uint64_t app_tid, int notify_pipe_read, - int ready_pipe_write, int shm_fd, const char *handler_path) + int ready_pipe_write, int shm_fd, int message_fd, const char *handler_path) #elif defined(SENTRY_PLATFORM_WINDOWS) static pid_t daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, - HANDLE ready_event_handle, const char *handler_path) + HANDLE ready_event_handle, HANDLE message_read_handle, + const char *handler_path) #endif { #if defined(SENTRY_PLATFORM_MACOS) @@ -374,16 +375,19 @@ daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, strcpy(daemon_path + dir_len, "sentry-crash"); } - // Build argument strings (6 args: pid, tid, notify_fd, ready_fd, shm_fd) - char pid_str[32], tid_str[32], notify_str[32], ready_str[32], shm_str[32]; + // Build argument strings (pid, tid, notify_fd, ready_fd, shm_fd, + // message_fd) + char pid_str[32], tid_str[32], notify_str[32], ready_str[32], shm_str[32], + message_str[32]; snprintf(pid_str, sizeof(pid_str), "%d", (int)app_pid); snprintf(tid_str, sizeof(tid_str), "%" PRIx64, app_tid); snprintf(notify_str, sizeof(notify_str), "%d", notify_pipe_read); snprintf(ready_str, sizeof(ready_str), "%d", ready_pipe_write); snprintf(shm_str, sizeof(shm_str), "%d", shm_fd); + snprintf(message_str, sizeof(message_str), "%d", message_fd); char *spawn_argv[] = { "sentry-crash", pid_str, tid_str, notify_str, - ready_str, shm_str, NULL }; + ready_str, shm_str, message_str, NULL }; // Set up posix_spawn attributes posix_spawnattr_t attr; @@ -399,6 +403,7 @@ daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, posix_spawn_file_actions_addinherit_np(&file_actions, notify_pipe_read); posix_spawn_file_actions_addinherit_np(&file_actions, ready_pipe_write); posix_spawn_file_actions_addinherit_np(&file_actions, shm_fd); + posix_spawn_file_actions_addinherit_np(&file_actions, message_fd); // Open /dev/null on stdin/stdout/stderr so the daemon starts with valid // standard fds. Without this, POSIX_SPAWN_CLOEXEC_DEFAULT closes them, // and the first fopen() in the daemon would get fd 0, which the daemon's @@ -409,7 +414,7 @@ daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, int std_modes[3] = { O_RDONLY, O_WRONLY, O_WRONLY }; for (int i = 0; i < 3; i++) { if (std_fds[i] != notify_pipe_read && std_fds[i] != ready_pipe_write - && std_fds[i] != shm_fd) { + && std_fds[i] != shm_fd && std_fds[i] != message_fd) { posix_spawn_file_actions_addopen( &file_actions, std_fds[i], "/dev/null", std_modes[i], 0); } @@ -450,16 +455,22 @@ daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, if (ready_flags != -1) { fcntl(ready_eventfd, F_SETFD, ready_flags & ~FD_CLOEXEC); } + int message_flags = fcntl(message_fd, F_GETFD); + if (message_flags != -1) { + fcntl(message_fd, F_SETFD, message_flags & ~FD_CLOEXEC); + } // Convert arguments to strings for exec - char pid_str[32], tid_str[32], notify_str[32], ready_str[32]; + char pid_str[32], tid_str[32], notify_str[32], ready_str[32], + message_str[32]; snprintf(pid_str, sizeof(pid_str), "%d", (int)app_pid); snprintf(tid_str, sizeof(tid_str), "%" PRIx64, app_tid); snprintf(notify_str, sizeof(notify_str), "%d", notify_eventfd); snprintf(ready_str, sizeof(ready_str), "%d", ready_eventfd); + snprintf(message_str, sizeof(message_str), "%d", message_fd); - char *argv[] - = { "sentry-crash", pid_str, tid_str, notify_str, ready_str, NULL }; + char *argv[] = { "sentry-crash", pid_str, tid_str, notify_str, + ready_str, message_str, NULL }; if (!sentry__string_empty(handler_path)) { execv(handler_path, argv); @@ -542,13 +553,14 @@ daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, } // Build command line: sentry-crash.exe - // - wchar_t cmd_line[SENTRY_CRASH_MAX_PATH + 128]; + // + wchar_t cmd_line[SENTRY_CRASH_MAX_PATH + 192]; int cmd_len = _snwprintf(cmd_line, sizeof(cmd_line) / sizeof(wchar_t), - L"\"%s\" %lu %llx %llu %llu", daemon_path_w, (unsigned long)app_pid, - (unsigned long long)app_tid, + L"\"%s\" %lu %llx %llu %llu %llu", daemon_path_w, + (unsigned long)app_pid, (unsigned long long)app_tid, (unsigned long long)(uintptr_t)event_handle, - (unsigned long long)(uintptr_t)ready_event_handle); + (unsigned long long)(uintptr_t)ready_event_handle, + (unsigned long long)(uintptr_t)message_read_handle); if (cmd_len < 0 || cmd_len >= (int)(sizeof(cmd_line) / sizeof(wchar_t))) { SENTRY_WARN("Command line too long for daemon spawn"); @@ -937,16 +949,17 @@ native_backend_startup( # if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) uint64_t tid = (uint64_t)pthread_self(); state->daemon_pid = daemon_start(getpid(), tid, state->ipc->notify_fd, - state->ipc->ready_fd, daemon_handler_path); + state->ipc->ready_fd, state->ipc->message_fd[1], daemon_handler_path); # elif defined(SENTRY_PLATFORM_MACOS) uint64_t tid = (uint64_t)pthread_self(); state->daemon_pid = daemon_start(getpid(), tid, state->ipc->notify_pipe[0], - state->ipc->ready_pipe[1], state->ipc->shm_fd, daemon_handler_path); + state->ipc->ready_pipe[1], state->ipc->shm_fd, + state->ipc->message_fd[1], daemon_handler_path); # elif defined(SENTRY_PLATFORM_WINDOWS) uint64_t tid = (uint64_t)GetCurrentThreadId(); - state->daemon_pid - = daemon_start(GetCurrentProcessId(), tid, state->ipc->event_handle, - state->ipc->ready_event_handle, daemon_handler_path); + state->daemon_pid = daemon_start(GetCurrentProcessId(), tid, + state->ipc->event_handle, state->ipc->ready_event_handle, + state->ipc->message_read_handle, daemon_handler_path); # endif // On Windows, pid_t is DWORD (unsigned), so (pid_t)-1 == 0xFFFFFFFF. @@ -970,11 +983,16 @@ native_backend_startup( // Close unused pipe ends in parent process close(state->ipc->notify_pipe[0]); // Daemon reads from this close(state->ipc->ready_pipe[1]); // Daemon writes to this + close(state->ipc->message_fd[1]); // Daemon reads from this state->ipc->notify_pipe[0] = -1; state->ipc->ready_pipe[1] = -1; + state->ipc->message_fd[1] = -1; # endif # if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) + close(state->ipc->message_fd[1]); // Daemon reads from this + state->ipc->message_fd[1] = -1; + // Close unused eventfd ends in parent process // (eventfds are bidirectional, but we only use one direction per fd) // Parent writes to notify_fd, daemon reads from it - parent can close for @@ -994,6 +1012,11 @@ native_backend_startup( } # endif +# if defined(SENTRY_PLATFORM_WINDOWS) + CloseHandle(state->ipc->message_read_handle); + state->ipc->message_read_handle = NULL; +# endif + // Wait for daemon to signal it's ready if (!sentry__crash_ipc_wait_for_ready( state->ipc, SENTRY_CRASH_DAEMON_READY_TIMEOUT_MS)) { diff --git a/tests/unit/test_native_backend.c b/tests/unit/test_native_backend.c index 1395c5c90..f91860946 100644 --- a/tests/unit/test_native_backend.c +++ b/tests/unit/test_native_backend.c @@ -16,6 +16,7 @@ // Include native backend headers # include "../../src/backends/native/minidump/sentry_minidump_format.h" # include "../../src/backends/native/sentry_crash_context.h" +# include "../../src/backends/native/sentry_crash_ipc.h" static void noop_crashed_last_run(const sentry_envelope_t *envelope, void *user_data) @@ -813,3 +814,172 @@ SENTRY_TEST(elf_header_entry_sizes) TEST_CHECK(!sentry__elf_has_phdr_size(e_ident, phdr_size)); #endif } + +SENTRY_TEST(crash_ipc_message_roundtrip) +{ +#ifndef SENTRY_BACKEND_NATIVE + SKIP_TEST(); +#else + const char payload[] = "\x81\xa3key\xa5value"; + char *buf = NULL; + size_t buf_len = 0; + + sentry_crash_ipc_message_result_t result + = sentry__crash_ipc_message_encode(SENTRY_CRASH_IPC_MESSAGE_SET_TAG, 7, + 42, payload, sizeof(payload) - 1, &buf, &buf_len); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_OK); + TEST_CHECK(buf != NULL); + TEST_CHECK_INT_EQUAL( + buf_len, SENTRY_CRASH_IPC_MESSAGE_HEADER_SIZE + sizeof(payload) - 1); + + sentry_crash_ipc_message_t message; + result = sentry__crash_ipc_message_decode(buf, buf_len, &message); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_OK); + TEST_CHECK_INT_EQUAL(message.type, SENTRY_CRASH_IPC_MESSAGE_SET_TAG); + TEST_CHECK_INT_EQUAL(message.flags, 7); + TEST_CHECK_INT_EQUAL(message.sequence, 42); + TEST_CHECK_INT_EQUAL(message.payload_len, sizeof(payload) - 1); + TEST_CHECK(memcmp(message.payload, payload, sizeof(payload) - 1) == 0); + + sentry_free(buf); +#endif +} + +SENTRY_TEST(crash_ipc_message_partial) +{ +#ifndef SENTRY_BACKEND_NATIVE + SKIP_TEST(); +#else + char *buf = NULL; + size_t buf_len = 0; + + sentry_crash_ipc_message_result_t result = sentry__crash_ipc_message_encode( + SENTRY_CRASH_IPC_MESSAGE_CRASH, 0, 9, NULL, 0, &buf, &buf_len); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_OK); + + sentry_crash_ipc_message_t message; + result = sentry__crash_ipc_message_decode(buf, 3, &message); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_PARTIAL); + + result = sentry__crash_ipc_message_decode(buf, buf_len - 1, &message); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_PARTIAL); + + sentry_free(buf); +#endif +} + +SENTRY_TEST(crash_ipc_message_invalid_lengths) +{ +#ifndef SENTRY_BACKEND_NATIVE + SKIP_TEST(); +#else + char too_small_len[4] = { 11, 0, 0, 0 }; + sentry_crash_ipc_message_t message; + + sentry_crash_ipc_message_result_t result = sentry__crash_ipc_message_decode( + too_small_len, sizeof(too_small_len), &message); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_INVALID); + + char *buf = NULL; + size_t buf_len = 0; + result = sentry__crash_ipc_message_encode( + SENTRY_CRASH_IPC_MESSAGE_SHUTDOWN, 0, 1, NULL, 0, &buf, &buf_len); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_OK); + + char extra[32]; + memcpy(extra, buf, buf_len); + extra[buf_len] = 0; + result = sentry__crash_ipc_message_decode(extra, buf_len + 1, &message); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_INVALID); + + sentry_free(buf); +#endif +} + +SENTRY_TEST(crash_ipc_message_unknown_type) +{ +#ifndef SENTRY_BACKEND_NATIVE + SKIP_TEST(); +#else + char *buf = NULL; + size_t buf_len = 0; + + sentry_crash_ipc_message_result_t result = sentry__crash_ipc_message_encode( + SENTRY_CRASH_IPC_MESSAGE_CRASH, 0, 1, NULL, 0, &buf, &buf_len); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_OK); + buf[4] = 0xff; + buf[5] = 0x7f; + + sentry_crash_ipc_message_t message; + result = sentry__crash_ipc_message_decode(buf, buf_len, &message); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_UNKNOWN_TYPE); + + sentry_free(buf); +#endif +} + +SENTRY_TEST(crash_ipc_message_oversized) +{ +#ifndef SENTRY_BACKEND_NATIVE + SKIP_TEST(); +#else + char oversized_len[4] = { 0 }; + uint32_t len = SENTRY_CRASH_IPC_MESSAGE_MAX_LEN; + oversized_len[0] = (char)(len & 0xffu); + oversized_len[1] = (char)((len >> 8) & 0xffu); + oversized_len[2] = (char)((len >> 16) & 0xffu); + oversized_len[3] = (char)((len >> 24) & 0xffu); + + sentry_crash_ipc_message_t message; + sentry_crash_ipc_message_result_t result = sentry__crash_ipc_message_decode( + oversized_len, sizeof(oversized_len), &message); +# if SIZE_MAX <= UINT32_MAX + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_OVERSIZED); +# else + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_PARTIAL); +# endif + + char payload = 0; + char *buf = NULL; + size_t buf_len = 0; + result = sentry__crash_ipc_message_encode( + SENTRY_CRASH_IPC_MESSAGE_SCOPE_SNAPSHOT, 0, 0, &payload, + SENTRY_CRASH_IPC_MESSAGE_MAX_LEN, &buf, &buf_len); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_OVERSIZED); + result = sentry__crash_ipc_message_encode( + SENTRY_CRASH_IPC_MESSAGE_SCOPE_SNAPSHOT, 0, 0, &payload, + SIZE_MAX - SENTRY_CRASH_IPC_MESSAGE_HEADER_SIZE + 1, &buf, &buf_len); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_OVERSIZED); +#endif +} + +SENTRY_TEST(crash_ipc_message_large_roundtrip) +{ +#ifndef SENTRY_BACKEND_NATIVE + SKIP_TEST(); +#else + size_t payload_len = 200u * 1024u * 1024u; + char *payload = sentry_malloc(payload_len); + TEST_ASSERT(payload != NULL); + memset(payload, 0xa5, payload_len); + char *buf = NULL; + size_t buf_len = 0; + + sentry_crash_ipc_message_result_t result = sentry__crash_ipc_message_encode( + SENTRY_CRASH_IPC_MESSAGE_SCOPE_SNAPSHOT, 0, 42, payload, payload_len, + &buf, &buf_len); + TEST_ASSERT(result == SENTRY_CRASH_IPC_MESSAGE_OK); + TEST_CHECK(buf_len == SENTRY_CRASH_IPC_MESSAGE_HEADER_SIZE + payload_len); + + sentry_crash_ipc_message_t message; + result = sentry__crash_ipc_message_decode(buf, buf_len - 1, &message); + TEST_CHECK_INT_EQUAL(result, SENTRY_CRASH_IPC_MESSAGE_PARTIAL); + result = sentry__crash_ipc_message_decode(buf, buf_len, &message); + TEST_ASSERT(result == SENTRY_CRASH_IPC_MESSAGE_OK); + TEST_CHECK(message.payload_len == payload_len); + TEST_CHECK(memcmp(message.payload, payload, payload_len) == 0); + + sentry_free(buf); + sentry_free(payload); +#endif +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 157cba929..4af136712 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -142,6 +142,12 @@ XX(crash_context_init) XX(crash_context_null_options) XX(crash_context_options_propagation) XX(crash_context_transport_fields) +XX(crash_ipc_message_invalid_lengths) +XX(crash_ipc_message_large_roundtrip) +XX(crash_ipc_message_oversized) +XX(crash_ipc_message_partial) +XX(crash_ipc_message_roundtrip) +XX(crash_ipc_message_unknown_type) XX(crash_marker) XX(crashed_last_run) XX(custom_logger)