| Author | Andy Green <andy@warmcat.com> 2026-09-18 18:40 UTC | | Committer | Andy Green <andy@warmcat.com> 2026-09-18 18:40 UTC | | Tree | af77b02dc14fe44f0a3cb7d6e3a25c7f18fbf759 Raw Patch | | | server: bound the /power link rx cache and free it on close, fixes F-017 | server: bound the /power link rx cache and free it on close, fixes F-017
sais_power_rx() cached every ws fragment in pss->power_rx_cache with no
size cap. The pcon_energy passthrough deliberately buffers the whole
message before forwarding it, so a peer that sends a message header and
then an endless stream of continuation fragments (no EOM) grows the
buflist, and so the coordinator heap, without bound. The connection
close path also never destroyed a half-filled cache, leaking it for
every conn closed mid-message.
Append through a new shared sais_buflist_append_bounded() helper with a
64KiB per-message cap (real messages on this link are all small), and
drop the connection when it is exceeded: the RECEIVE handler now acts
on sais_power_rx() failing instead of ignoring it. power_rx_cache is
destroyed on CLOSED alongside the other per-conn state.
|
diff --git a/src/server/s-comms.c b/src/server/s-comms.c
index e1ffb51..ac638a1 100644
--- a/src/server/s-comms.c
+++ b/src/server/s-comms.c
@@ -515,6 +515,9 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
case LWS_CALLBACK_CLOSED:
lwsac_free(&pss->query_ac);
+ /* a conn closed mid-message must not leak its reassembly */
+ lws_buflist_destroy_all_segments(&pss->power_rx_cache);
+
{
const unsigned char *cp = lws_get_close_payload(wsi);
int clen = lws_get_close_length(wsi);
@@ -562,7 +565,11 @@ s_callback_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
*/
if (pss->is_power) {
- sais_power_rx(vhd, pss, in, len, ssf);
+ if (sais_power_rx(vhd, pss, in, len, ssf)) {
+ lwsl_err("%s: sais_power_rx returned error, dropping connection\n",
+ __func__);
+ return -1;
+ }
break;
}
diff --git a/src/server/s-helpers.c b/src/server/s-helpers.c
index d4907e9..091bf06 100644
--- a/src/server/s-helpers.c
+++ b/src/server/s-helpers.c
@@ -65,6 +65,29 @@ reject:
return 1;
}
+/*
+ * Append a rx fragment to a per-connection reassembly / forwarding buflist,
+ * enforcing a sanity cap on the total bytes buffered for the message. A peer
+ * that sends SOM and then an endless stream of continuation fragments must
+ * not be able to grow server memory without bound waiting for an EOM that
+ * never comes.
+ *
+ * Returns 0 if appended, else nonzero (over the cap or OOM): callers should
+ * drop the connection.
+ */
+int
+sais_buflist_append_bounded(struct lws_buflist **head, const uint8_t *buf,
+ size_t len, size_t cap)
+{
+ if (len > cap || lws_buflist_total_len(head) > cap - len)
+ return 1;
+
+ if (lws_buflist_append_segment(head, buf, len) < 0)
+ return 1;
+
+ return 0;
+}
+
int
sql3_get_integer_cb(void *user, int cols, char **values, char **name)
{
diff --git a/src/server/s-power.c b/src/server/s-power.c
index 5db2a5a..55a4cd9 100644
--- a/src/server/s-power.c
+++ b/src/server/s-power.c
@@ -37,6 +37,13 @@
#include "s-private.h"
+/*
+ * Sanity cap on the per-message power-link reassembly. Real messages on
+ * this link (state updates, PCON topology, energy reports) are all small;
+ * the passthrough path buffers the whole message before forwarding.
+ */
+#define SAIS_POWER_RX_CACHE_MAX (64 * 1024)
+
#if 0
/*
* (Structs and maps removed - now in common/include/private.h and common/struct-metadata.c)
@@ -97,8 +104,9 @@ sais_power_rx(struct vhd *vhd, struct pss *pss, uint8_t *buf,
}
/* We always cache the fragment until we know what it is */
- if (lws_buflist_append_segment(&pss->power_rx_cache, buf, bl) < 0) {
- lwsl_err("%s: failed to append to power_rx_cache\n", __func__);
+ if (sais_buflist_append_bounded(&pss->power_rx_cache, buf, bl,
+ SAIS_POWER_RX_CACHE_MAX)) {
+ lwsl_err("%s: power link rx cache over cap / OOM\n", __func__);
return -1;
}
diff --git a/src/server/s-private.h b/src/server/s-private.h
index 31bc323..f57b4f8 100644
--- a/src/server/s-private.h
+++ b/src/server/s-private.h
@@ -295,6 +295,10 @@ int
sais_validate_id(const char *id, int reqlen);
int
+sais_buflist_append_bounded(struct lws_buflist **head, const uint8_t *buf,
+ size_t len, size_t cap);
+
+int
saiw_ws_json_tx_browser(struct vhd *vhd, struct pss *pss, uint8_t *buf, size_t bl);
int
|