From 6ced0f80098bb3bf0f33b0cabd8d28c04bd8e53f Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Wed, 6 May 2026 14:11:40 -0500 Subject: [PATCH] fix: double-free in engram_neighbors_json BFS + rebuild engram.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit el_strdup tracks pointers in the arena. The BFS arrays in engram_neighbors_json are manually freed — using el_strdup caused a double-free when the arena was later popped. Changed to plain strdup for those allocations. engram/dist/engram.c rebuilt from engram/src/server.el with current elc (minor codegen diff: parenthesisation and _argc/_argv rename). --- engram/dist/engram.c | 6 +++--- lang/el-compiler/runtime/el_runtime.c | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/engram/dist/engram.c b/engram/dist/engram.c index b5fe865..275c694 100644 --- a/engram/dist/engram.c +++ b/engram/dist/engram.c @@ -308,7 +308,7 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) { if (str_eq(method, EL_STR("POST")) && (str_eq(clean, EL_STR("/api/nodes")) || str_eq(clean, EL_STR("/nodes")))) { return route_create_node(method, path, body); } - if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/nodes")) || str_eq(clean, EL_STR("/nodes")) || str_eq(clean, EL_STR("/nodes/list")) || str_eq(clean, EL_STR("/api/nodes/list")))) { + if (str_eq(method, EL_STR("GET")) && (((str_eq(clean, EL_STR("/api/nodes")) || str_eq(clean, EL_STR("/nodes"))) || str_eq(clean, EL_STR("/nodes/list"))) || str_eq(clean, EL_STR("/api/nodes/list")))) { return route_scan_nodes(method, path, body); } if (str_eq(method, EL_STR("GET")) && (str_eq(clean, EL_STR("/api/edges")) || str_eq(clean, EL_STR("/edges")))) { @@ -351,8 +351,8 @@ el_val_t handle_request(el_val_t method, el_val_t path, el_val_t body) { return 0; } -int main(int argc, char** argv) { - el_runtime_init_args(argc, argv); +int main(int _argc, char** _argv) { + el_runtime_init_args(_argc, _argv); bind_str = env(EL_STR("ENGRAM_BIND")); if (str_eq(bind_str, EL_STR(""))) { bind_str = EL_STR(":8742"); diff --git a/lang/el-compiler/runtime/el_runtime.c b/lang/el-compiler/runtime/el_runtime.c index 9df9847..d3d2aaa 100644 --- a/lang/el-compiler/runtime/el_runtime.c +++ b/lang/el-compiler/runtime/el_runtime.c @@ -7321,8 +7321,10 @@ el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t di free(frontier); free(frontier_h); free(visited); jb_putc(&b, ']'); return el_wrap_str(b.buf); } - frontier[fc] = el_strdup(sid); frontier_h[fc] = 0; fc++; - visited[vc++] = el_strdup(sid); + /* Use plain strdup (not el_strdup) so arena doesn't track these pointers. + * The BFS loop manually frees them below — arena would double-free them. */ + frontier[fc] = strdup(sid); frontier_h[fc] = 0; fc++; + visited[vc++] = strdup(sid); int first = 1; while (fc > 0) { @@ -7351,8 +7353,8 @@ el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t di char tmp[64]; snprintf(tmp, sizeof(tmp), ",\"hops\":%lld}", (long long)(h + 1)); jb_puts(&b, tmp); first = 0; - if (vc < 1024) visited[vc++] = el_strdup(peer); - if (fc < 1024 && h + 1 < depth) { frontier[fc] = el_strdup(peer); frontier_h[fc] = h + 1; fc++; } + if (vc < 1024) visited[vc++] = strdup(peer); + if (fc < 1024 && h + 1 < depth) { frontier[fc] = strdup(peer); frontier_h[fc] = h + 1; fc++; } } free(cur); }