Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
 
root / src / builder / b-ws-server.c
Author[]Andy Green <andy@warmcat.com> 2026-09-18 18:40 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-09-18 18:40 UTC
Treeaf77b02dc14fe44f0a3cb7d6e3a25c7f18fbf759   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
Page fetched 0s ago, creation time: 3ms (vhost etag hits: 0%, cache hits: 0%)