Author: Andy Green Date: Sat Sep 12 16:09:14 2026 +0100 web: tolerate NULL pss in the http and ws close callbacks lws delivers LWS_CALLBACK_CLOSED_HTTP, LWS_CALLBACK_HTTP_DROP_PROTOCOL and LWS_CALLBACK_CLOSED with wsi->user_space as the user pointer, which is NULL when the connection went away before per-session storage was allocated, eg, a wsi that never bound to a protocol and closed on error or timeout. saiw_close_artifact() dereferenced that unconditionally, giving an invalid read at the artifact field offset (0x9e0) under valgrind after a while. Bail early on NULL pss there, and guard the ws CLOSED case the same way before it touches the buflists and subscription list. Co-Authored-By: Claude Fable 5.1 diff --git a/src/web/w-comms.c b/src/web/w-comms.c index 3daf952..d3c6395 100644 --- a/src/web/w-comms.c +++ b/src/web/w-comms.c @@ -145,6 +145,15 @@ saiw_event_db_close_all_now(struct vhd *vhd) static void saiw_close_artifact(struct pss *pss) { + /* + * lws hands the close callbacks wsi->user_space, which is NULL if the + * connection went away before per-session storage was allocated (eg, + * a wsi that never bound to a protocol, closed on error or timeout). + * There can be no artifact state without a pss, so nothing to do. + */ + if (!pss) + return; + if (pss->blob_artifact) { sqlite3_blob_close(pss->blob_artifact); pss->blob_artifact = NULL; @@ -811,6 +820,8 @@ http_resp: case LWS_CALLBACK_CLOSED: lwsl_wsi_info(wsi, "CLOSED browse conn"); + if (!pss) + break; lws_buflist2_destroy_all_segments(&pss->raw_tx); lws_buflist_destroy_all_segments(&pss->rx_reasm); saiw_browser_state_changed(pss, 0);