Author: Andy Green Date: Fri Sep 18 19:41:25 2026 +0100 server: bound builder loadreport reassembly and free it on close, fixes F-019 Same defect class as F-017 on the builder link: fragmented loadreports are stashed in pss->onward_reassembly until the message completes, with no cap on the total buffered bytes and no destroy of a half-filled buflist when the connection closes. A builder that sends a message header and endless continuation fragments grows sai-server's heap without bound; a builder conn closed mid-loadreport leaks its buflist. Use sais_buflist_append_bounded() (shared with the power-link fix) with a 64KiB cap at both append sites and drop the connection over it or on OOM; destroy onward_reassembly on CLOSED next to power_rx_cache. diff --git a/src/server/s-comms.c b/src/server/s-comms.c index ac638a1..afdb1cc 100644 --- a/src/server/s-comms.c +++ b/src/server/s-comms.c @@ -517,6 +517,7 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user, /* a conn closed mid-message must not leak its reassembly */ lws_buflist_destroy_all_segments(&pss->power_rx_cache); + lws_buflist_destroy_all_segments(&pss->onward_reassembly); { const unsigned char *cp = lws_get_close_payload(wsi); diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c index a37ad5f..8637b6d 100644 --- a/src/server/s-ws-builder.c +++ b/src/server/s-ws-builder.c @@ -34,6 +34,13 @@ #include "s-private.h" +/* + * Sanity cap on the per-builder loadreport reassembly. Loadreports are + * small; the reassembly exists so fragmented ones can be forwarded to the + * web side as an atomic message. + */ +#define SAIS_LOADREPORT_REASSEMBLY_MAX (64 * 1024) + const lws_struct_map_t lsm_schema_map_ta[] = { LSM_SCHEMA (sai_task_t, NULL, lsm_task, "com-warmcat-sai-ta"), }; @@ -804,10 +811,15 @@ sais_ws_json_rx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t b */ *((unsigned int *)(buf - sizeof(int))) = ss_flags; - if (lws_buflist_append_segment(&pss->onward_reassembly, - buf - sizeof(int), - bl + sizeof(int)) < 0) + if (sais_buflist_append_bounded( + &pss->onward_reassembly, + buf - sizeof(int), + bl + sizeof(int), + SAIS_LOADREPORT_REASSEMBLY_MAX)) { + lwsl_err("%s: loadreport reassembly over cap / OOM\n", + __func__); return -1; + } } pss->frag = 1; @@ -1144,10 +1156,15 @@ sais_ws_json_rx_builder(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t b */ *((unsigned int *)(buf - sizeof(int))) = ss_flags; - if (lws_buflist_append_segment(&pss->onward_reassembly, - buf - sizeof(int), - bl + sizeof(int)) < 0) + if (sais_buflist_append_bounded( + &pss->onward_reassembly, + buf - sizeof(int), + bl + sizeof(int), + SAIS_LOADREPORT_REASSEMBLY_MAX)) { + lwsl_err("%s: loadreport reassembly over cap / OOM\n", + __func__); return -1; + } /* * Then let's forward the whole reassembly buflist on