pm3_grabbed_output_get null-terminated the captured output at
g_grabbed_output.size, but size is the allocated capacity, not the number of
bytes written -- ui.c grows it by MAX_PRINT_BUFFER at a time and tracks the
used length separately in idx. Every call therefore wrote one zero byte past
the end of the heap allocation.
The CLI mostly survives it because it calls the function once or twice and
then exits, so the corrupted malloc metadata is never reused. A long-running
libpm3 client that captures output on every command does not: the damage
surfaces later, far from the cause. It was found as a SIGTRAP inside an
unrelated AppKit autorelease pool.
Reproduced against real hardware:
MALLOC_STRICT_SIZE=1 MallocGuardEdges=1 \
DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib ./pm3run <port> "hw version"
which crashes with SIGSEGV inside pm3_grabbed_output_get before this change
and exits cleanly with identical captured output after it.
Terminating at idx is always in bounds: the grabber guarantees at least
MAX_PRINT_BUFFER bytes of headroom before each write, so idx is strictly less
than size.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjGgtnsvWJHAMYEksAiYuf