From 4a7d91dcac08293548f7e9e79ca57c84ab93883b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 24 Aug 2026 11:39:58 +0000 Subject: [PATCH] fix(gateway): stop HTTP thread before freeing auth context The lws thread still calls auth_validate_token after SIGTERM. Freeing auth_ctx first raced with in-flight /api and WebSocket auth checks. Co-authored-by: esadrianno --- src/core/bootstrap.c | 4 +++- src/core/bootstrap.h | 2 +- tests/test_gateway_http.c | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/core/bootstrap.c b/src/core/bootstrap.c index e7fe294..f0a17d1 100644 --- a/src/core/bootstrap.c +++ b/src/core/bootstrap.c @@ -294,12 +294,14 @@ int init_subsystems(config_t *cfg) void cleanup_subsystems(void) { #ifdef SHELLCLAW_GATEWAY + /* HTTP/WS callbacks still call auth_validate_token(ctx->auth). Join the + * lws thread before freeing auth_ctx (same order as tools_init failure). */ ws_shutdown_signal(); + http_stop(); if (g_auth_ctx) { auth_cleanup(g_auth_ctx); g_auth_ctx = NULL; } - http_stop(); ws_cleanup(); #endif tools_cleanup(); diff --git a/src/core/bootstrap.h b/src/core/bootstrap.h index e529492..0a89a54 100644 --- a/src/core/bootstrap.h +++ b/src/core/bootstrap.h @@ -27,7 +27,7 @@ int init_subsystems(config_t *cfg); /** Register tools from config (called from init_subsystems). */ int tools_init(const config_t *cfg); -/** Tear down all subsystems in reverse init order. */ +/** Tear down all subsystems in reverse init order (HTTP thread before auth_ctx). */ void cleanup_subsystems(void); config_t *bootstrap_get_cfg(void); diff --git a/tests/test_gateway_http.c b/tests/test_gateway_http.c index 5769a73..8001da1 100644 --- a/tests/test_gateway_http.c +++ b/tests/test_gateway_http.c @@ -562,6 +562,31 @@ static int test_api_asap_log_401(void) return 0; } +static int test_shutdown_does_not_crash(pid_t pid, const char *token) +{ + int i; + int status = 0; + + if (token && token[0]) { + for (i = 0; i < 16; i++) { + long code = 0; + char *body = NULL; + (void)http_get_auth(gw_url("/api/status"), token, &code, &body); + free(body); + } + } + ASSERT(kill(pid, SIGTERM) == 0); + ASSERT(waitpid(pid, &status, 0) == pid); + if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + if (sig == SIGSEGV || sig == SIGABRT || sig == SIGBUS || sig == SIGILL) { + fprintf(stderr, "FAIL: gateway crashed on shutdown with signal %d\n", sig); + return 1; + } + } + return 0; +} + static int test_api_asap_log(const char *token) { long code; @@ -690,8 +715,12 @@ int main(int argc, char **argv) if (test_api_sessions(token) != 0) { fprintf(stderr, "test_api_sessions failed\n"); failed++; } if (test_api_asap_log(token) != 0) { fprintf(stderr, "test_api_asap_log failed\n"); failed++; } } - kill(pid, SIGTERM); - waitpid(pid, NULL, 0); + if (test_shutdown_does_not_crash(pid, token) != 0) { + fprintf(stderr, "test_shutdown_does_not_crash failed\n"); + failed++; + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); + } unlink(config_path); unlink(tokens_path); unlink(pairing_file);