Skip to content

manage_adr get path: use-after-free causes MCP client to hang indefinitely #125

Description

@halindrome

Summary

The manage_adr tool's get path (default mode) contains a use-after-free bug in handle_manage_adr (src/mcp/mcp.c) that causes the MCP client to hang indefinitely when an adr.md file exists.

Root Cause

// Buggy code in handle_manage_adr (get branch):
char *buf = malloc(sz + 1);
size_t n = fread(buf, 1, sz, fp);
buf[n] = '\0';
(void)fclose(fp);
yyjson_mut_obj_add_str(doc, root_obj, "content", buf);
free(buf);  // BUG: yyjson stores the raw pointer, not a copy
// ... later:
char *json = yy_doc_to_str(doc);  // reads freed memory → garbage JSON

yyjson_mut_obj_add_str stores the raw pointer without copying. After free(buf), the yyjson document holds a dangling pointer into freed heap memory. When yy_doc_to_str(doc) runs, the allocator has overwritten that memory with bookkeeping bytes, producing invalid JSON. cbm_jsonrpc_format_response then calls yyjson_read() on the corrupted string; the parse fails silently (res_doc == NULL), so no "result" field is added.

Symptom

The MCP response becomes {"jsonrpc":"2.0","id":N} — missing result. The MCP client (Claude Code and others) waits indefinitely for a valid response.

Affected Scope

  • Only the get branch (default when mode is absent or "get")
  • Only when a .codebase-memory/adr.md file exists for the project
  • Projects without an ADR file are unaffected (no_adr path uses string literals)

Fix

Hoist adr_buf to function scope (initialized NULL), remove the premature free, and free it after yy_doc_to_str has serialized the document:

char *adr_buf = NULL; /* freed after yy_doc_to_str — yyjson holds pointer, not copy */
// ...
adr_buf = malloc(sz + 1);
// ...
yyjson_mut_obj_add_str(doc, root_obj, "content", adr_buf);
/* do NOT free adr_buf here: yyjson stores the pointer, not a copy */
// ...
char *json = yy_doc_to_str(doc);
yyjson_mut_doc_free(doc);
free(adr_buf); /* safe to free now — doc has been serialized */

Total diff: ~7 lines in handle_manage_adr.

Regression Test

A regression test tool_manage_adr_get_with_existing_adr is included that:

  1. Creates a temp directory with .codebase-memory/adr.md
  2. Registers it as a project in an in-memory store
  3. Calls manage_adr(mode="get") via cbm_mcp_server_handle
  4. Asserts the JSON-RPC response contains "result" with the ADR content

This test fails on main (before the fix) and passes after.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstability/performanceServer crashes, OOM, hangs, high CPU/memory

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions