Repository navigation
Description
Summary
msgpack_vrefbuffer_migrate() in src/vrefbuffer.c contains a bounds check that compares a pointer derived from the destination buffer against the end pointer of the source buffer — two unrelated heap allocations. When this cross-object comparison happens to evaluate true... (incorrectly skipping growth), the function proceeds to memcpy() more data into the destination's array than it has capacity for, causing a heap buffer overflow.
File: src/vrefbuffer.c
- Function:
msgpack_vrefbuffer_migrate - Line: 201 (condition), 227 (overflowing
memcpy) - Affected version:
c_masterbranch, HEAD82a6cc03(and likely all releases containing this code path) - Bug class: Heap buffer overflow (CWE-787 Out-of-bounds Write), via undefined-behavior pointer comparison (CWE-758)
Root cause
// src/vrefbuffer.c, msgpack_vrefbuffer_migrate()
if(to->tail + nused < vbuf->end) { // line 201 — BUG: compares against vbuf->end, not to->end
... grow/realloc to->array ... // lines 202-224 — skipped when condition is false
}
memcpy(to->tail, vbuf->array, sizeof(msgpack_iovec)*nused); // line 227 — writes past to->array's real allocationThe condition mixes a pointer derived from to (the buffer about to be written into) with vbuf->end (the end pointer of a completely unrelated, independently heap-allocated source buffer). Comparing pointers from two unrelated allocations is undefined behavior per the C standard (C11 §6.5.8p5), and in practice its truth value depends on where the two heap blocks happen to land relative to each other — not on whether to->array actually has room for nused more iovecs.
When the comparison evaluates to false even though to->array does not have enough real capacity, the growth/realloc block is skipped entirely, and the very next line (227) writes past the end of to->array's real allocation.
The correct, same-object comparison pattern already exists elsewhere in this same file — msgpack_vrefbuffer_append_ref() at line 113 compares vbuf->tail == vbuf->end (same object on both sides).
Trigger / reproduction
Reachable through ordinary public API calls only:
msgpack_vrefbuffer_init(&vbuf, 0, 0)andmsgpack_vrefbuffer_init(&to, 0, 0)— two independent vrefbuffers.msgpack_vrefbuffer_append_ref(&vbuf, ...)called 20 times, growingvbuf's internal iovec array tonused == 20.msgpack_vrefbuffer_append_ref(&to, ...)called 5 times (growingto's array to a real capacity of only 8 iovecs), followed by the publicmsgpack_vrefbuffer_clear(&to)(an ordinary buffer-reuse operation — resetsto->tailbut does not shrink the allocation).msgpack_vrefbuffer_migrate(&vbuf, &to).
At this point to->array's real allocation is sized for 8 iovecs, but migrate() is about to copy in 20 — a 192-byte overflow.
A second, independently discovered trigger shape reproduces reliably under AddressSanitizer (see Variant Evidence below): two sequential msgpack_vrefbuffer_migrate() calls between the same two buffers, with moderate accumulated growth, deterministically pushes to->tail one iovec slot (16 bytes) past to->end.
Minimal PoC (plain glibc malloc — demonstrates the overflow directly)
#define _GNU_SOURCE
#include <msgpack/vrefbuffer.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
static const char PAYLOAD[16] = "XXXXXXXXXXXXXXX";
#define CANARY_SIZE 256
int main(void) {
msgpack_vrefbuffer vbuf, to;
int i;
msgpack_vrefbuffer_init(&vbuf, 0, 0);
msgpack_vrefbuffer_init(&to, 0, 0);
for (i = 0; i < 20; i++) msgpack_vrefbuffer_append_ref(&vbuf, PAYLOAD, 8);
for (i = 0; i < 5; i++) msgpack_vrefbuffer_append_ref(&to, PAYLOAD, 8);
msgpack_vrefbuffer_clear(&to);
unsigned char *heap_canary = (unsigned char*)malloc(CANARY_SIZE);
memset(heap_canary, 0xAA, CANARY_SIZE);
int rc = msgpack_vrefbuffer_migrate(&vbuf, &to);
printf("migrate() returned %d\n", rc);
int corrupted = 0;
for (i = 0; i < CANARY_SIZE; i++) if (heap_canary[i] != 0xAA) { corrupted = 1; break; }
printf("canary sentinel: %s\n", corrupted ? "CORRUPTED (heap overflow confirmed)" : "intact");
free(heap_canary);
msgpack_vrefbuffer_destroy(&vbuf);
msgpack_vrefbuffer_destroy(&to);
return corrupted ? 1 : 0;
}Build and run (plain glibc malloc — the build that reproduces the overflow):
clang -g -O1 -I include poc.c src/vrefbuffer.c src/zone.c -o poc_positive
./poc_positiveObserved output on this allocator:
Before migrate:
vbuf nused = 20 iovecs
to->array real usable size = 136 bytes (malloc_usable_size)
bytes migrate() will memcpy = 320 bytes
overflow margin if unsafe = 184 bytes
migrate() returned 0
canary sentinel after migrate: CORRUPTED - heap-buffer-overflow confirmed
free(): invalid size
Aborted (core dumped)
Exit code 134 (SIGABRT) is the expected/successful result — glibc's own allocator detects the corrupted heap metadata on a later free().
Why the single-migrate PoC doesn't reproduce under ASan, and the variant that does
The single-migrate trigger above depends on the two allocations (to->array and the canary) landing adjacently on the heap — true under plain glibc malloc, but not under ASan's allocator, which segregates allocations by size class rather than allocation order. This is expected, not a sign the bug is fake: the underlying defect (UB pointer comparison) is independent of which allocator exposes it.
An ASan-reproducible variant was found independently via AFL++ fuzzing of the public buffer API directly: two sequential msgpack_vrefbuffer_migrate() calls between the same two buffer instances. Minimized crashing input (22 bytes, via afl-tmin) confirms:
AddressSanitizer: heap-buffer-overflow ... in __asan_memcpy
#1 msgpack_vrefbuffer_migrate src/vrefbuffer.c:227:9
allocated region traced to realloc at src/vrefbuffer.c:215:36 (inside a PRIOR migrate call's growth branch)
A standalone debug-trace program confirms the exact mechanics with printf-instrumented capacity/used dumps:
A before migrate1: cap=8 used=5
B before migrate1: cap=4 used=0
migrate1 rc=0
A after migrate1: cap=8 used=0
B after migrate1: cap=8 used=5
A before migrate2: cap=8 used=4
B before migrate2: cap=8 used=5
migrate2 rc=0
A after migrate2: cap=8 used=0
B after migrate2: cap=8 used=9 <-- 9 > capacity 8: confirmed overflow condition, ASan-reproducible
Impact
Heap buffer overflow (out-of-bounds write) in the destination buffer's backing allocation, with attacker-influenced content (iovec structs — pointer + length pairs copied from the source buffer). Depending on heap layout, this corrupts adjacent heap metadata or adjacent allocations, leading to a crash (DoS) or, under favorable heap layouts, potential memory corruption exploitable for further impact. msgpack_vrefbuffer is used by msgpack_packer-style streaming/zero-copy serialization paths that reference external buffers without copying them — any application calling msgpack_vrefbuffer_migrate() (directly, or via higher-level buffer-merging logic built on top of it) with imbalanced buffer sizes is affected.
Suggested fix
- if(to->tail + nused < vbuf->end) {
+ if(to->tail + nused < to->end) {Compare against to's own end pointer — the object actually being bounds-checked — not the unrelated source buffer's end pointer.
Activity
AmirWilayah commentedon Oct 28, 2026
The screenshot in my WSL by running the overflow and safe PoC

I also ran a negative control. In this test, the destination array to->array is allocated with sufficient capacity (520 bytes / 32 iovecs) to hold the 20 iovecs.
check destination capacity in msgpack_vrefbuffer_migrate
AmirWilayah commentedon Oct 30, 2026
@redboltz LGTM. Can we close this, as it has been fixed? If closed, can it be submitted for a CVE?