From 1257ea4dc522f92ae016d78d7cb202769321ad72 Mon Sep 17 00:00:00 2001 From: MUSTARDTIGERFPV Date: Tue, 8 Sep 2026 16:40:33 -0700 Subject: [PATCH] Free OtaContext-owned buffers when the context is torn down OtaContext owns serve_buf, serve_self_leaves and serve_self_proof as raw malloc'd pointers, and nothing freed them on teardown: self-serve only frees them when it re-allocates, and reset_session() does not touch them. That was harmless while the context was a permanent .bss singleton, but both dynamic storage modes destroy it between operations - OTA_HEAP_CONTEXT deletes it, and the Companion's borrowed queue runs ~OtaContext() in place. A self-serving node reaches release with both self-serve buffers populated, so every acquire/release cycle leaked them. Give OtaContext a destructor that releases what it owns, and delete the copy operations so the raw pointers cannot be double-freed. --- src/helpers/ota/OtaContext.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/helpers/ota/OtaContext.h b/src/helpers/ota/OtaContext.h index 826c081e..a2052593 100644 --- a/src/helpers/ota/OtaContext.h +++ b/src/helpers/ota/OtaContext.h @@ -144,6 +144,22 @@ struct OtaContext { // count (the manager's fixed 4 KiB scratch covers <=1024 blocks, about 2 MiB at the new default). uint8_t* serve_self_leaves = nullptr; uint8_t* serve_self_proof = nullptr; + + // These raw buffers are owned by the context and are otherwise only freed + // when self-serve re-allocates them. A dynamic-storage build destroys the + // context between operations (OTA_HEAP_CONTEXT deletes it; the Companion's + // borrowed queue runs ~OtaContext() in place), so teardown has to release + // them or every acquire/release cycle leaks. A self-serving node reaches + // release with both buffers populated, so this is the normal path, not an + // edge case. + ~OtaContext() { + releaseServeBuffer(); // no-op where serve_buf is a fixed array + free(serve_self_leaves); + free(serve_self_proof); + } + OtaContext() = default; + OtaContext(const OtaContext&) = delete; // would double-free above + OtaContext& operator=(const OtaContext&) = delete; uint8_t serve_self_manifest[MOTA_MFL]; // fixed-layout full+unsigned manifest-minus-leaves (197 B) ApplyState apply_st; // pending apply (P6)