Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rabbitmq-c for openSUSE:Factory checked in at 2026-08-28 19:50:01 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rabbitmq-c (Old) and /work/SRC/openSUSE:Factory/.rabbitmq-c.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rabbitmq-c" Fri Aug 28 19:50:01 2026 rev:14 rq:1374201 version:0.17.0 Changes: -------- --- /work/SRC/openSUSE:Factory/rabbitmq-c/rabbitmq-c.changes 2025-11-09 21:09:15.494549026 +0100 +++ /work/SRC/openSUSE:Factory/.rabbitmq-c.new.1265/rabbitmq-c.changes 2026-08-28 19:51:02.148638642 +0200 @@ -1,0 +2,23 @@ +Wed Aug 26 03:43:12 UTC 2026 - Simon Lees + Gemini <[email protected]> + +- Update to 0.17.0: + * Security: fix size_t overflow in bounds check leading to OOB + read (GHSA-jgjf-7fwf-f3c7) + * Security: fix heap buffer overflow in frame serialization + (GHSA-hfjv-vcp3-39wh) + * librabbitmq-tools: fall back to AMQP_URL env var if connection + options are omitted + * Tools: enable default SSL verification paths by default unless + --no-default-cert-paths is passed +- Also includes changes from 0.16.0: + * Security: fix out-of-bounds read via undersized frames + (GHSA-9mmv-r8g3-qp46) + * Security: fix crash on too-low frame_max negotiation + (GHSA-jh48-qjf5-fx5v) + * Add amqp_bytes_from_buffer macro + * Fix several pointer safety and file stream leak bugs + * Minimum build requirement: CMake 3.22 and C99 compiler + * TLSv1.2 is now the minimum supported SSL protocol version +- Rebase rabbitmq-c-default-cacert-location.patch to zero fuzz + +------------------------------------------------------------------- Old: ---- rabbitmq-c-0.15.0.tar.gz New: ---- rabbitmq-c-0.17.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rabbitmq-c.spec ++++++ --- /var/tmp/diff_new_pack.OqH35T/_old 2026-08-28 19:51:04.387716907 +0200 +++ /var/tmp/diff_new_pack.OqH35T/_new 2026-08-28 19:51:04.392717082 +0200 @@ -20,11 +20,10 @@ %global libname librabbitmq %global majsonum 4 Name: rabbitmq-c -Version: 0.15.0 +Version: 0.17.0 Release: 0 Summary: Client library for AMQP License: MIT -Group: Development/Libraries/C and C++ URL: https://github.com/alanxz/rabbitmq-c Source0: https://github.com/alanxz/rabbitmq-c/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz # [bsc#1232541], https://github.com/alanxz/rabbitmq-c/issues/846 @@ -32,10 +31,11 @@ BuildRequires: cmake > 2.8.12 BuildRequires: gcc BuildRequires: openssl-devel -# For tools -BuildRequires: popt-devel +BuildRequires: pkgconfig # For man page BuildRequires: xmlto +# For tools +BuildRequires: pkgconfig(popt) # SECTION test requirements BuildRequires: rabbitmq-server BuildRequires: rabbitmq-server-plugins @@ -47,7 +47,6 @@ %package -n %{libname}%{majsonum} Summary: Client library for AMQP -Group: Development/Libraries/C and C++ %description -n %{libname}%{majsonum} This is a C-language AMQP client library for use with AMQP servers @@ -55,7 +54,6 @@ %package -n %{libname}-devel Summary: Header files and development libraries for %{name} -Group: Development/Libraries/C and C++ Requires: %{libname}%{majsonum} = %{version} %description -n %{libname}-devel @@ -64,7 +62,6 @@ %package -n %{name}-tools Summary: Example tools built using the librabbitmq package -Group: Development/Libraries/C and C++ %description -n %{name}-tools This package contains example tools built using %{name}. It provides: @@ -108,7 +105,7 @@ export RABBITMQ_SCHEMA_DIR=/home/abuild/rabbitmq/schema export RABBITMQ_GENERATED_CONFIG_DIR=/home/abuild/rabbitmq/config %endif -sbin_base=/usr/lib*/rabbitmq/lib/rabbitmq_server-*/sbin +sbin_base=%{_prefix}/lib*/rabbitmq/lib/rabbitmq_server-*/sbin $sbin_base/rabbitmq-server& sleep 10 $sbin_base/rabbitmqctl await_startup ++++++ rabbitmq-c-0.15.0.tar.gz -> rabbitmq-c-0.17.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/CMakeLists.txt new/rabbitmq-c-0.17.0/CMakeLists.txt --- old/rabbitmq-c-0.15.0/CMakeLists.txt 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/CMakeLists.txt 2026-07-02 05:45:59.000000000 +0200 @@ -14,7 +14,7 @@ # 4. If any interfaces have been removed since the last public release, then set age to 0. set(RMQ_SOVERSION_CURRENT 11) -set(RMQ_SOVERSION_REVISION 0) +set(RMQ_SOVERSION_REVISION 2) set(RMQ_SOVERSION_AGE 7) include(VersionFunctions) @@ -167,7 +167,7 @@ endif() if(BUILD_TOOLS) - find_package(POPT REQUIRED) + find_package(POPT 1.14 REQUIRED) if(BUILD_TOOLS_DOCS) find_package(XMLTO REQUIRED) endif() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/ChangeLog.md new/rabbitmq-c-0.17.0/ChangeLog.md --- old/rabbitmq-c-0.15.0/ChangeLog.md 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/ChangeLog.md 2026-07-02 05:45:59.000000000 +0200 @@ -1,4 +1,40 @@ # Change Log +## v0.17.0 - 2026-07-01 + +### Security +- Fix size_t overflow in `amqp_decode_bytes` bounds check leading to out-of-bounds read (GHSA-jgjf-7fwf-f3c7, #888) +- Fix heap buffer overflow in `amqp_frame_to_bytes` for oversized body frames (GHSA-hfjv-vcp3-39wh, #892) + +### Added +- `librabbitmq-tools` fall back to the `AMQP_URL` environment variable when no connection options are given on the command line (#887) + +### Fixed +- Fix undefined behavior in `amqp_decode_properties` when decoding content-header property flags (#883, #885) +- Fix `ioctlsocket` type mismatch on Windows (#890) +- Document buffer lifetime requirement of `amqp_decode_table`'s encoded buffer to prevent use-after-free misuse (#895) + +### Changed +- `librabbitmq-tools` now enable default SSL certificate verification paths unless `--no-default-cert-paths` is passed (fixes #868, #893) +- Building the tools now requires POPT v1.14 or newer (#889) + +## v0.16.0 - 2026-06-08 + +### Security +- Fix out-of-bounds read via undersized frames in `amqp_handle_input` (GHSA-9mmv-r8g3-qp46, #878) +- Fix client crash when server negotiates `frame_max` below the AMQP protocol minimum (GHSA-jh48-qjf5-fx5v) + +### Added +- Add `amqp_bytes_from_buffer` macro to create `amqp_bytes_t` from an arbitrary byte buffer with explicit length (#856, #866) + +### Fixed +- Fix NULL pointer dereferences on allocation failure in `tools/publish.c` (#860, #861) +- Fix NULL pointer dereference in `tools/consume.c` `stringify_bytes()` on allocation failure (#858) +- Fix file stream leak in `tools/common.c` `read_authfile()` (#859) +- Fix handling of absolute `CMAKE_INSTALL_INCLUDEDIR` in exported CMake targets (#849) + +### Changed +- `amqp_literal_bytes` macro now uses an explicit `(void *)` cast (#853) + ## v0.15.0 - 2024-11-19 ## Fixed - Warning on MacOS about incompatible function declaration diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/README.md new/rabbitmq-c-0.17.0/README.md --- old/rabbitmq-c-0.15.0/README.md 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/README.md 2026-07-02 05:45:59.000000000 +0200 @@ -177,4 +177,4 @@ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/cmake/FindPOPT.cmake new/rabbitmq-c-0.17.0/cmake/FindPOPT.cmake --- old/rabbitmq-c-0.15.0/cmake/FindPOPT.cmake 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/cmake/FindPOPT.cmake 2026-07-02 05:45:59.000000000 +0200 @@ -4,6 +4,12 @@ # POPT_FOUND - System has popt # POPT_INCLUDE_DIR - The popt include directory # POPT_LIBRARY - The libraries needed to use popt +# POPT_VERSION - The version of popt that was found +# +# This module also supports version requirements, e.g. +# find_package(POPT 1.14 REQUIRED). popt does not expose its release +# version in popt.h, so the version is obtained from pkg-config. A version +# requirement therefore needs pkg-config and the popt.pc file to be available. # use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/examples/amqp_confirm_select.c new/rabbitmq-c-0.17.0/examples/amqp_confirm_select.c --- old/rabbitmq-c-0.15.0/examples/amqp_confirm_select.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/examples/amqp_confirm_select.c 2026-07-02 05:45:59.000000000 +0200 @@ -26,7 +26,7 @@ #define SUMMARY_EVERY_US 5000 -static void send_batch(amqp_connection_state_t conn, char const *queue_name, +static void send_batch(amqp_connection_state_t conn, amqp_bytes_t queue_name, int rate_limit, int message_count) { uint64_t start_time = now_microseconds(); int i; @@ -48,9 +48,8 @@ for (i = 0; i < message_count; i++) { uint64_t now = now_microseconds(); - die_on_error(amqp_basic_publish(conn, 1, amqp_cstring_bytes("amq.direct"), - amqp_cstring_bytes(queue_name), 0, 0, NULL, - message_bytes), + die_on_error(amqp_basic_publish(conn, 1, amqp_literal_bytes("amq.direct"), + queue_name, 0, 0, NULL, message_bytes), "Publishing"); sent++; if (now > next_summary_time) { @@ -177,7 +176,7 @@ amqp_confirm_select(conn, 1); die_on_amqp_error(amqp_get_rpc_reply(conn), "Enable confirm-select"); - send_batch(conn, "test queue", rate_limit, message_count); + send_batch(conn, amqp_literal_bytes("test queue"), rate_limit, message_count); wait_for_acks(conn); die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/examples/amqp_producer.c new/rabbitmq-c-0.17.0/examples/amqp_producer.c --- old/rabbitmq-c-0.15.0/examples/amqp_producer.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/examples/amqp_producer.c 2026-07-02 05:45:59.000000000 +0200 @@ -13,7 +13,7 @@ #define SUMMARY_EVERY_US 1000000 -static void send_batch(amqp_connection_state_t conn, char const *queue_name, +static void send_batch(amqp_connection_state_t conn, amqp_bytes_t queue_name, int rate_limit, int message_count) { uint64_t start_time = now_microseconds(); int i; @@ -35,9 +35,8 @@ for (i = 0; i < message_count; i++) { uint64_t now = now_microseconds(); - die_on_error(amqp_basic_publish(conn, 1, amqp_cstring_bytes("amq.direct"), - amqp_cstring_bytes(queue_name), 0, 0, NULL, - message_bytes), + die_on_error(amqp_basic_publish(conn, 1, amqp_literal_bytes("amq.direct"), + queue_name, 0, 0, NULL, message_bytes), "Publishing"); sent++; if (now > next_summary_time) { @@ -107,7 +106,7 @@ amqp_channel_open(conn, 1); die_on_amqp_error(amqp_get_rpc_reply(conn), "Opening channel"); - send_batch(conn, "test queue", rate_limit, message_count); + send_batch(conn, amqp_literal_bytes("test queue"), rate_limit, message_count); die_on_amqp_error(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), "Closing channel"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/examples/amqp_rpc_sendstring_client.c new/rabbitmq-c-0.17.0/examples/amqp_rpc_sendstring_client.c --- old/rabbitmq-c-0.15.0/examples/amqp_rpc_sendstring_client.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/examples/amqp_rpc_sendstring_client.c 2026-07-02 05:45:59.000000000 +0200 @@ -85,14 +85,14 @@ props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG | AMQP_BASIC_REPLY_TO_FLAG | AMQP_BASIC_CORRELATION_ID_FLAG; - props.content_type = amqp_cstring_bytes("text/plain"); + props.content_type = amqp_literal_bytes("text/plain"); props.delivery_mode = 2; /* persistent delivery mode */ props.reply_to = amqp_bytes_malloc_dup(reply_to_queue); if (props.reply_to.bytes == NULL) { fprintf(stderr, "Out of memory while copying queue name"); return 1; } - props.correlation_id = amqp_cstring_bytes("1"); + props.correlation_id = amqp_literal_bytes("1"); /* publish diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/examples/amqp_sendstring.c new/rabbitmq-c-0.17.0/examples/amqp_sendstring.c --- old/rabbitmq-c-0.15.0/examples/amqp_sendstring.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/examples/amqp_sendstring.c 2026-07-02 05:45:59.000000000 +0200 @@ -54,7 +54,7 @@ { amqp_basic_properties_t props; props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG; - props.content_type = amqp_cstring_bytes("text/plain"); + props.content_type = amqp_literal_bytes("text/plain"); props.delivery_mode = 2; /* persistent delivery mode */ die_on_error(amqp_basic_publish(conn, 1, amqp_cstring_bytes(exchange), amqp_cstring_bytes(routingkey), 0, 0, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/fuzz/CMakeLists.txt new/rabbitmq-c-0.17.0/fuzz/CMakeLists.txt --- old/rabbitmq-c-0.15.0/fuzz/CMakeLists.txt 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/fuzz/CMakeLists.txt 2026-07-02 05:45:59.000000000 +0200 @@ -17,4 +17,16 @@ add_executable(fuzz_server fuzz_server.c) target_link_libraries(fuzz_server rabbitmq-static) + + add_executable(fuzz_method_decode fuzz_method_decode.c) + target_link_libraries(fuzz_method_decode rabbitmq-static) + + add_executable(fuzz_properties_decode fuzz_properties_decode.c) + target_link_libraries(fuzz_properties_decode rabbitmq-static) + + add_executable(fuzz_handle_input fuzz_handle_input.c) + target_link_libraries(fuzz_handle_input rabbitmq-static) + + add_executable(fuzz_codec fuzz_codec.c) + target_link_libraries(fuzz_codec rabbitmq-static) endif () diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/fuzz/fuzz_codec.c new/rabbitmq-c-0.17.0/fuzz/fuzz_codec.c --- old/rabbitmq-c-0.15.0/fuzz/fuzz_codec.c 1970-01-01 01:00:00.000000000 +0100 +++ new/rabbitmq-c-0.17.0/fuzz/fuzz_codec.c 2026-07-02 05:45:59.000000000 +0200 @@ -0,0 +1,132 @@ +// Copyright 2007 - 2026, Arthur Chan and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <rabbitmq-c/amqp.h> +#include <rabbitmq-c/framing.h> + +// Round-trips the AMQP codecs. The existing fuzzers only decode; the generated +// serializers (amqp_encode_method/properties/table) and amqp_table_clone are +// unreachable from hand-written input. Each mode decodes attacker bytes into the +// in-memory struct, then re-encodes/clones it -- the decoder supplies the +// adversarial-but-valid structs the encoders would otherwise never see. + +#define ENC_BUF_SIZE (1u << 20) + +static void roundtrip_method(const uint8_t *p, size_t n) { + amqp_pool_t pool; + void *decoded = NULL; + amqp_method_number_t method_id; + amqp_bytes_t encoded; + + if (n < 4) { + return; + } + method_id = ((amqp_method_number_t)p[0] << 24) | + ((amqp_method_number_t)p[1] << 16) | + ((amqp_method_number_t)p[2] << 8) | (amqp_method_number_t)p[3]; + encoded.len = n - 4; + encoded.bytes = (void *)(p + 4); + + init_amqp_pool(&pool, 4096); + if (amqp_decode_method(method_id, &pool, encoded, &decoded) == + AMQP_STATUS_OK && + decoded != NULL) { + void *buf = malloc(ENC_BUF_SIZE); + if (buf != NULL) { + amqp_bytes_t out; + out.len = ENC_BUF_SIZE; + out.bytes = buf; + amqp_encode_method(method_id, decoded, out); + free(buf); + } + } + empty_amqp_pool(&pool); +} + +static void roundtrip_properties(const uint8_t *p, size_t n) { + amqp_pool_t pool; + void *decoded = NULL; + uint16_t class_id; + amqp_bytes_t encoded; + + if (n < 2) { + return; + } + class_id = ((uint16_t)p[0] << 8) | (uint16_t)p[1]; + encoded.len = n - 2; + encoded.bytes = (void *)(p + 2); + + init_amqp_pool(&pool, 4096); + if (amqp_decode_properties(class_id, &pool, encoded, &decoded) == + AMQP_STATUS_OK && + decoded != NULL) { + void *buf = malloc(ENC_BUF_SIZE); + if (buf != NULL) { + amqp_bytes_t out; + out.len = ENC_BUF_SIZE; + out.bytes = buf; + amqp_encode_properties(class_id, decoded, out); + free(buf); + } + } + empty_amqp_pool(&pool); +} + +static void roundtrip_table(const uint8_t *p, size_t n) { + amqp_pool_t pool; + amqp_table_t decoded; + amqp_bytes_t encoded; + size_t offset = 0; + + encoded.len = n; + encoded.bytes = (void *)p; + + init_amqp_pool(&pool, 4096); + memset(&decoded, 0, sizeof(decoded)); + if (amqp_decode_table(encoded, &pool, &decoded, &offset) == AMQP_STATUS_OK) { + void *buf = malloc(ENC_BUF_SIZE); + if (buf != NULL) { + amqp_bytes_t out; + size_t out_off = 0; + out.len = ENC_BUF_SIZE; + out.bytes = buf; + amqp_encode_table(out, &decoded, &out_off); + free(buf); + } + { + amqp_pool_t clone_pool; + amqp_table_t clone; + init_amqp_pool(&clone_pool, 4096); + memset(&clone, 0, sizeof(clone)); + amqp_table_clone(&decoded, &clone, &clone_pool); + empty_amqp_pool(&clone_pool); + } + } + empty_amqp_pool(&pool); +} + +// First byte selects the codec; the remainder is the payload, framed like the +// matching decode fuzzer (method-id / class-id prefix where applicable). +extern int LLVMFuzzerTestOneInput(const char *data, size_t size) { + const uint8_t *bytes = (const uint8_t *)data; + + if (size < 1) { + return 0; + } + switch (bytes[0] % 3) { + case 0: + roundtrip_method(bytes + 1, size - 1); + break; + case 1: + roundtrip_properties(bytes + 1, size - 1); + break; + default: + roundtrip_table(bytes + 1, size - 1); + break; + } + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/fuzz/fuzz_handle_input.c new/rabbitmq-c-0.17.0/fuzz/fuzz_handle_input.c --- old/rabbitmq-c-0.15.0/fuzz/fuzz_handle_input.c 1970-01-01 01:00:00.000000000 +0100 +++ new/rabbitmq-c-0.17.0/fuzz/fuzz_handle_input.c 2026-07-02 05:45:59.000000000 +0200 @@ -0,0 +1,55 @@ +// Copyright 2007 - 2026, Arthur Chan and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <rabbitmq-c/amqp.h> +#include <rabbitmq-c/framing.h> + +// Drives amqp_handle_input(), the byte->frame wire parser, feeding the input one +// frame at a time and advancing by the number of bytes it reports consuming. +extern int LLVMFuzzerTestOneInput(const char *data, size_t size) { + + amqp_connection_state_t conn; + amqp_bytes_t buffer; + size_t iterations = 0; + + if (size == 0) { + return 0; + } + + conn = amqp_new_connection(); + if (conn == NULL) { + return 0; + } + + buffer.bytes = (void *)data; + buffer.len = size; + + while (buffer.len > 0 && iterations < 4096) { + amqp_frame_t frame; + int res; + + memset(&frame, 0, sizeof(frame)); + res = amqp_handle_input(conn, buffer, &frame); + if (res <= 0) { + break; + } + + buffer.bytes = (void *)((const char *)buffer.bytes + (size_t)res); + buffer.len -= (size_t)res; + + if (frame.frame_type != 0) { + amqp_maybe_release_buffers_on_channel(conn, frame.channel); + } + + iterations++; + } + + amqp_destroy_connection(conn); + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/fuzz/fuzz_method_decode.c new/rabbitmq-c-0.17.0/fuzz/fuzz_method_decode.c --- old/rabbitmq-c-0.15.0/fuzz/fuzz_method_decode.c 1970-01-01 01:00:00.000000000 +0100 +++ new/rabbitmq-c-0.17.0/fuzz/fuzz_method_decode.c 2026-07-02 05:45:59.000000000 +0200 @@ -0,0 +1,43 @@ +// Copyright 2007 - 2022, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include <errno.h> +#include <inttypes.h> +#include <math.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <rabbitmq-c/amqp.h> +#include <rabbitmq-c/framing.h> + +// Drives amqp_decode_method(), the generated decoder for every AMQP method +// frame. The first 4 bytes are the method id (big-endian), the rest the body. +extern int LLVMFuzzerTestOneInput(const char *data, size_t size) { + + int unused_result; + amqp_pool_t pool; + amqp_method_number_t method_id; + + if (size < 4) { + return 0; + } + + method_id = ((amqp_method_number_t)(uint8_t)data[0] << 24) | + ((amqp_method_number_t)(uint8_t)data[1] << 16) | + ((amqp_method_number_t)(uint8_t)data[2] << 8) | + ((amqp_method_number_t)(uint8_t)data[3]); + + init_amqp_pool(&pool, 4096); + { + void *decoded = NULL; + amqp_bytes_t encoded; + encoded.len = size - 4; + encoded.bytes = (void *)(data + 4); + + unused_result = amqp_decode_method(method_id, &pool, encoded, &decoded); + } + empty_amqp_pool(&pool); + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/fuzz/fuzz_properties_decode.c new/rabbitmq-c-0.17.0/fuzz/fuzz_properties_decode.c --- old/rabbitmq-c-0.15.0/fuzz/fuzz_properties_decode.c 1970-01-01 01:00:00.000000000 +0100 +++ new/rabbitmq-c-0.17.0/fuzz/fuzz_properties_decode.c 2026-07-02 05:45:59.000000000 +0200 @@ -0,0 +1,42 @@ +// Copyright 2007 - 2026, Arthur Chan and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include <errno.h> +#include <inttypes.h> +#include <math.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <rabbitmq-c/amqp.h> +#include <rabbitmq-c/framing.h> + +// Drives amqp_decode_properties(), the generated decoder for every AMQP +// content-header (properties) frame. The first 2 bytes are the class id +// (big-endian), the rest the body. +extern int LLVMFuzzerTestOneInput(const char *data, size_t size) { + + int unused_result; + amqp_pool_t pool; + uint16_t class_id; + + if (size < 2) { + return 0; + } + + class_id = ((uint16_t)(uint8_t)data[0] << 8) | + ((uint16_t)(uint8_t)data[1]); + + init_amqp_pool(&pool, 4096); + { + void *decoded = NULL; + amqp_bytes_t encoded; + encoded.len = size - 2; + encoded.bytes = (void *)(data + 2); + + unused_result = amqp_decode_properties(class_id, &pool, encoded, &decoded); + } + empty_amqp_pool(&pool); + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/fuzz/fuzz_server.c new/rabbitmq-c-0.17.0/fuzz/fuzz_server.c --- old/rabbitmq-c-0.15.0/fuzz/fuzz_server.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/fuzz/fuzz_server.c 2026-07-02 05:45:59.000000000 +0200 @@ -1,152 +1,97 @@ // Copyright 2007 - 2022, Alan Antonuk and the rabbitmq-c contributors. // SPDX-License-Identifier: mit -#include <arpa/inet.h> -#include <errno.h> -#include <netinet/in.h> -#include <pthread.h> #include <stdint.h> -#include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/socket.h> -#include <unistd.h> #include <rabbitmq-c/amqp.h> -#include <rabbitmq-c/tcp_socket.h> +#include <rabbitmq-c/framing.h> -struct Fuzzer { - int socket; - uint16_t port; - pthread_t thread; +#include "amqp_private.h" +#include "amqp_socket.h" - uint64_t size; - uint8_t *buffer; +// In-memory socket: feeds the fuzz buffer to the client as the bytes a broker +// would send, with no real socket or thread, so amqp_login() drives the full +// client handshake (Connection.Start -> Tune -> Open -> OpenOk) from the input. + +struct fuzz_socket_t { + const struct amqp_socket_class_t *klass; + const uint8_t *data; + size_t size; + size_t offset; }; -typedef struct Fuzzer Fuzzer; -#define PORT 5672 -#define kMinInputLength 8 -#define kMaxInputLength 1024 - -void client(Fuzzer *fuzzer); - -void fuzzinit(Fuzzer *fuzzer) { - struct sockaddr_in server_addr; - int res; - fuzzer->socket = socket(AF_INET, SOCK_STREAM, 0); - if (fuzzer->socket == -1) { - fprintf(stderr, "socket failed %s\n", strerror(errno)); - exit(1); - } - memset(&server_addr, 0, sizeof(server_addr)); - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(fuzzer->port); - server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); - res = setsockopt(fuzzer->socket, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); - if (res) { - fprintf(stderr, "setsockopt failed: %s\n", strerror(errno)); - exit(1); - } - - res = bind(fuzzer->socket, (struct sockaddr *)&server_addr, sizeof(server_addr)); - if (res) { - fprintf(stderr, "bind failed: %s\n", strerror(errno)); - exit(1); - } - res = listen(fuzzer->socket, 1); - if (res) { - fprintf(stderr, "listen failed: %s\n", strerror(errno)); - exit(1); - } +static ssize_t fuzz_socket_send(AMQP_UNUSED void *base, + AMQP_UNUSED const void *buf, size_t len, + AMQP_UNUSED int flags) { + return (ssize_t)len; } -void *Server(void *args) { - Fuzzer *fuzzer = (Fuzzer *)args; - - int client; - int res; - char clientData[10240]; - - client = accept(fuzzer->socket, NULL, NULL); - if (client == -1) { - fprintf(stderr, "accept failed: %s\n", strerror(errno)); - exit(1); - } - - res = recv(client, clientData, sizeof(clientData), 0); - if (res == -1) { - fprintf(stderr, "recv failed: %s\n", strerror(errno)); - exit(1); - } - res = send(client, fuzzer->buffer, fuzzer->size, 0); - if (res == -1) { - fprintf(stderr, "send failed: %s\n", strerror(errno)); - exit(1); - } - - res = shutdown(client, SHUT_RDWR); - close(client); - return NULL; +static ssize_t fuzz_socket_recv(void *base, void *buf, size_t len, + AMQP_UNUSED int flags) { + struct fuzz_socket_t *self = (struct fuzz_socket_t *)base; + size_t remaining = self->size - self->offset; + size_t n; + + if (remaining == 0) { + return AMQP_STATUS_CONNECTION_CLOSED; + } + n = remaining < len ? remaining : len; + memcpy(buf, self->data + self->offset, n); + self->offset += n; + return (ssize_t)n; } -void clean(Fuzzer *fuzzer) { - shutdown(fuzzer->socket, SHUT_RDWR); - close(fuzzer->socket); - free(fuzzer->buffer); - free(fuzzer); +static int fuzz_socket_open(AMQP_UNUSED void *base, AMQP_UNUSED const char *host, + AMQP_UNUSED int port, + AMQP_UNUSED const struct timeval *timeout) { + return AMQP_STATUS_OK; } -extern int LLVMFuzzerTestOneInput(const char *data, size_t size) { - - if (size < kMinInputLength || size > kMaxInputLength) { - return 0; - } - - Fuzzer *fuzzer = (Fuzzer *)malloc(sizeof(Fuzzer)); - fuzzer->port = PORT; - - fuzzer->size = size; - fuzzer->buffer = malloc(fuzzer->size); - memcpy(fuzzer->buffer, data, size); - - fuzzinit(fuzzer); - - pthread_create(&fuzzer->thread, NULL, Server, fuzzer); - - client(fuzzer); +static int fuzz_socket_close(AMQP_UNUSED void *base, + AMQP_UNUSED amqp_socket_close_enum force) { + return AMQP_STATUS_OK; +} - pthread_join(fuzzer->thread, NULL); +static int fuzz_socket_get_sockfd(AMQP_UNUSED void *base) { + return 0; /* must be >= 0; -1 is treated as a closed connection */ +} - clean(fuzzer); +static void fuzz_socket_delete(void *base) { free(base); } - return 0; -} +static const struct amqp_socket_class_t fuzz_socket_class = { + fuzz_socket_send, fuzz_socket_recv, fuzz_socket_open, + fuzz_socket_close, fuzz_socket_get_sockfd, fuzz_socket_delete}; -void client(Fuzzer *fuzzer) { - char const *hostname; - int status; - amqp_socket_t *socket = NULL; +extern int LLVMFuzzerTestOneInput(const char *data, size_t size) { amqp_connection_state_t conn; + struct fuzz_socket_t *sock; - hostname = "127.0.0.1"; + if (size == 0) { + return 0; + } conn = amqp_new_connection(); - - socket = amqp_tcp_socket_new(conn); - if (!socket) { - exit(1); + if (conn == NULL) { + return 0; } - status = amqp_socket_open(socket, hostname, fuzzer->port); - if (status != AMQP_STATUS_OK) { - int sav_errno = errno; - fprintf(stderr, "amqp_socket_open failed: %s\n", amqp_error_string2(status)); - fprintf(stderr, "amqp_socket_open errno: %d: %s\n", sav_errno, strerror(sav_errno)); - exit(1); + sock = (struct fuzz_socket_t *)calloc(1, sizeof(*sock)); + if (sock == NULL) { + amqp_destroy_connection(conn); + return 0; } + sock->klass = &fuzz_socket_class; + sock->data = (const uint8_t *)data; + sock->size = size; - amqp_login(conn, "/", 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, "guest", "guest"); + // Takes ownership; amqp_destroy_connection frees it via fuzz_socket_delete. + amqp_set_socket(conn, (amqp_socket_t *)sock); + + amqp_login(conn, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 0, AMQP_SASL_METHOD_PLAIN, + "guest", "guest"); amqp_destroy_connection(conn); + return 0; } Binary files old/rabbitmq-c-0.15.0/fuzz/input/fuzz_codec.raw and new/rabbitmq-c-0.17.0/fuzz/input/fuzz_codec.raw differ Binary files old/rabbitmq-c-0.15.0/fuzz/input/fuzz_method_decode.raw and new/rabbitmq-c-0.17.0/fuzz/input/fuzz_method_decode.raw differ Binary files old/rabbitmq-c-0.15.0/fuzz/input/fuzz_properties_decode.raw and new/rabbitmq-c-0.17.0/fuzz/input/fuzz_properties_decode.raw differ Binary files old/rabbitmq-c-0.15.0/fuzz/input/fuzz_server.raw and new/rabbitmq-c-0.17.0/fuzz/input/fuzz_server.raw differ Binary files old/rabbitmq-c-0.15.0/fuzz/input/fuzz_server_ghsa-9mmv-r8g3-qp46.raw and new/rabbitmq-c-0.17.0/fuzz/input/fuzz_server_ghsa-9mmv-r8g3-qp46.raw differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/include/rabbitmq-c/amqp.h new/rabbitmq-c-0.17.0/include/rabbitmq-c/amqp.h --- old/rabbitmq-c-0.15.0/include/rabbitmq-c/amqp.h 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/include/rabbitmq-c/amqp.h 2026-07-02 05:45:59.000000000 +0200 @@ -118,7 +118,7 @@ */ #define AMQP_VERSION_MAJOR 0 -#define AMQP_VERSION_MINOR 15 +#define AMQP_VERSION_MINOR 17 #define AMQP_VERSION_PATCH 0 #define AMQP_VERSION_IS_RELEASE 1 @@ -864,7 +864,7 @@ * * \since v0.15 */ -#define amqp_literal_bytes(str) (amqp_bytes_t){sizeof(str) - 1, str} +#define amqp_literal_bytes(str) (amqp_bytes_t){sizeof(str) - 1, (void *)str} /** * Wraps a c string in an amqp_bytes_t @@ -888,6 +888,20 @@ amqp_bytes_t AMQP_CALL amqp_cstring_bytes(char const *cstr); /** + * Wraps a string of bytes in an amqp_bytes_t + * + * Takes a string of bytes and its length and creates an + * amqp_bytes_t that points to it. The input is not duplicated. + * + * \param [in] ptr the string of bytes to wrap + * \param [in] length the length of the string + * \return an amqp_bytes_t that describes the string + * + * \since v0.16 + */ +#define amqp_bytes_from_buffer(ptr, length) (amqp_bytes_t){length, (void *)ptr} + +/** * Duplicates an amqp_bytes_t buffer. * * The buffer is cloned and the contents copied. @@ -2002,7 +2016,15 @@ * This is an internal function and is not typically used by * client applications * - * \param [in] encoded the buffer containing the serialized data + * \warning The decoded table entries contain direct references (pointers) into + * the \p encoded buffer rather than independent copies. The \p encoded buffer + * **must** remain valid and unmodified for as long as the decoded table (or + * any data derived from it) is in use. Freeing or modifying \p encoded while + * the table is still live will result in use-after-free. Use + * amqp_table_clone() if you need a fully independent copy of the table. + * + * \param [in] encoded the buffer containing the serialized data. Must outlive + * the decoded \p output table. * \param [in] pool memory pool used to allocate the table entries from * \param [in] output the amqp_table_t structure to fill in. Any existing * entries will be erased diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/librabbitmq/CMakeLists.txt new/rabbitmq-c-0.17.0/librabbitmq/CMakeLists.txt --- old/rabbitmq-c-0.15.0/librabbitmq/CMakeLists.txt 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/librabbitmq/CMakeLists.txt 2026-07-02 05:45:59.000000000 +0200 @@ -29,7 +29,7 @@ set(PUBLIC_INCLUDE_DIRS $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/../include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include> - $<INSTALL_INTERFACE:include> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> ) set(PRIVATE_INCLUDE_DIRS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/librabbitmq/amqp_connection.c new/rabbitmq-c-0.17.0/librabbitmq/amqp_connection.c --- old/rabbitmq-c-0.15.0/librabbitmq/amqp_connection.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/librabbitmq/amqp_connection.c 2026-07-02 05:45:59.000000000 +0200 @@ -113,6 +113,10 @@ ENFORCE_STATE(state, CONNECTION_STATE_IDLE); + if (frame_max < AMQP_FRAME_MIN_SIZE) { + frame_max = AMQP_FRAME_MIN_SIZE; + } + state->channel_max = channel_max; state->frame_max = frame_max; @@ -320,6 +324,13 @@ switch (decoded_frame->frame_type) { case AMQP_FRAME_METHOD: + /* A METHOD frame body must contain at least the 4-byte method id. + * Reject undersized frames before subtracting from target_size to + * avoid an unsigned underflow that would yield a huge encoded.len + * and cause out-of-bounds reads in amqp_decode_method(). */ + if (state->target_size < HEADER_SIZE + 4 + FOOTER_SIZE) { + return AMQP_STATUS_BAD_AMQP_DATA; + } decoded_frame->payload.method.id = amqp_d32(amqp_offset(raw_frame, HEADER_SIZE)); encoded.bytes = amqp_offset(raw_frame, HEADER_SIZE + 4); @@ -335,6 +346,15 @@ break; case AMQP_FRAME_HEADER: + /* A HEADER frame body must contain at least 12 bytes (class_id, + * weight, body_size). Reject undersized frames before subtracting + * from target_size to avoid an unsigned underflow that would yield + * a huge encoded.len and cause out-of-bounds reads in + * amqp_decode_properties() / the table decoder + * (CVE: GHSA-9mmv-r8g3-qp46). */ + if (state->target_size < HEADER_SIZE + 12 + FOOTER_SIZE) { + return AMQP_STATUS_BAD_AMQP_DATA; + } decoded_frame->payload.properties.class_id = amqp_d16(amqp_offset(raw_frame, HEADER_SIZE)); /* unused 2-byte weight field goes here */ @@ -354,6 +374,9 @@ break; case AMQP_FRAME_BODY: + if (state->target_size < HEADER_SIZE + FOOTER_SIZE) { + return AMQP_STATUS_BAD_AMQP_DATA; + } decoded_frame->payload.body_fragment.len = state->target_size - HEADER_SIZE - FOOTER_SIZE; decoded_frame->payload.body_fragment.bytes = @@ -441,6 +464,14 @@ case AMQP_FRAME_BODY: { const amqp_bytes_t *body = &frame->payload.body_fragment; + /* Ensure the body fragment fits within the outbound buffer, leaving + * room for the frame header and footer. Without this check an + * oversized body fragment would overflow the heap-allocated buffer. */ + if (buffer.len < HEADER_SIZE + FOOTER_SIZE || + body->len > buffer.len - (HEADER_SIZE + FOOTER_SIZE)) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + memcpy(amqp_offset(out_frame, HEADER_SIZE), body->bytes, body->len); out_frame_len = body->len; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/librabbitmq/amqp_framing.c new/rabbitmq-c-0.17.0/librabbitmq/amqp_framing.c --- old/rabbitmq-c-0.15.0/librabbitmq/amqp_framing.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/librabbitmq/amqp_framing.c 2026-07-02 05:45:59.000000000 +0200 @@ -1413,15 +1413,16 @@ size_t offset = 0; amqp_flags_t flags = 0; - int flagword_index = 0; uint16_t partial_flags; - do { - if (!amqp_decode_16(encoded, &offset, &partial_flags)) - return AMQP_STATUS_BAD_AMQP_DATA; - flags |= (partial_flags << (flagword_index * 16)); - flagword_index++; - } while (partial_flags & 1); + if (!amqp_decode_16(encoded, &offset, &partial_flags)) + return AMQP_STATUS_BAD_AMQP_DATA; + /* No AMQP 0-9-1 class defines more than 14 properties, so all property + flags fit in a single 16-bit flag word. Bit 0 is the continuation bit; + if it is set the message declares further flag words, which we do not + support, so reject the frame rather than misinterpret it. */ + if (partial_flags & 1) return AMQP_STATUS_BAD_AMQP_DATA; + flags = partial_flags; switch (class_id) { case 10: { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/librabbitmq/amqp_private.h new/rabbitmq-c-0.17.0/librabbitmq/amqp_private.h --- old/rabbitmq-c-0.15.0/librabbitmq/amqp_private.h 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/librabbitmq/amqp_private.h 2026-07-02 05:45:59.000000000 +0200 @@ -299,7 +299,10 @@ if (input.len == 0) { return 1; } - if ((*offset = o + input.len) <= encoded.len) { + *offset = o + input.len; + /* Compare against remaining space rather than o + input.len to avoid size_t + * overflow; o <= encoded.len, so encoded.len - o cannot underflow. */ + if (o <= encoded.len && input.len <= encoded.len - o) { memcpy(amqp_offset(encoded.bytes, o), input.bytes, input.len); return 1; } else { @@ -310,7 +313,12 @@ static inline int amqp_decode_bytes(amqp_bytes_t encoded, size_t *offset, amqp_bytes_t *output, size_t len) { size_t o = *offset; - if ((*offset = o + len) <= encoded.len) { + *offset = o + len; + /* Compare against remaining space rather than o + len: with len read from the + * wire (uint32_t), o + len can overflow size_t on 32-bit platforms and wrap + * past the check, yielding an out-of-bounds amqp_bytes_t. o <= encoded.len, + * so encoded.len - o cannot underflow. */ + if (o <= encoded.len && len <= encoded.len - o) { output->bytes = amqp_offset(encoded.bytes, o); output->len = len; return 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/librabbitmq/amqp_socket.c new/rabbitmq-c-0.17.0/librabbitmq/amqp_socket.c --- old/rabbitmq-c-0.15.0/librabbitmq/amqp_socket.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/librabbitmq/amqp_socket.c 2026-07-02 05:45:59.000000000 +0200 @@ -174,7 +174,6 @@ return AMQP_STATUS_SOCKET_ERROR; } } - return AMQP_STATUS_OK; #elif defined(HAVE_SELECT) fd_set fds; fd_set exceptfds; @@ -292,6 +291,7 @@ #ifdef _WIN32 static int connect_socket(struct addrinfo *addr, amqp_time_t deadline) { int one = 1; + u_long nonblocking = 1; SOCKET sockfd; int last_error; @@ -306,7 +306,7 @@ } /* Set the socket to be non-blocking */ - if (SOCKET_ERROR == ioctlsocket(sockfd, FIONBIO, &one)) { + if (SOCKET_ERROR == ioctlsocket(sockfd, FIONBIO, &nonblocking)) { last_error = AMQP_STATUS_SOCKET_ERROR; goto err; } @@ -1387,6 +1387,7 @@ if (res < 0) { goto error_res; } + client_frame_max = (uint32_t)amqp_get_frame_max(state); { amqp_connection_tune_ok_t s; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/librabbitmq/codegen.py new/rabbitmq-c-0.17.0/librabbitmq/codegen.py --- old/rabbitmq-c-0.15.0/librabbitmq/codegen.py 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/librabbitmq/codegen.py 2026-07-02 05:45:59.000000000 +0200 @@ -313,7 +313,7 @@ print("""// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. // SPDX-License-Identifier: mit - + /* Generated code. Do not edit. Edit and re-run codegen.py instead. */ #ifdef HAVE_CONFIG_H @@ -330,7 +330,7 @@ print(""" char const *amqp_constant_name(int constantNumber) { switch (constantNumber) {""") - for (c,v,cls) in spec.constants: + for (c,_,_) in spec.constants: print(" case %s: return \"%s\";" % (cConstantName(c), cConstantName(c))) print(""" default: return "(unknown)"; } @@ -339,7 +339,7 @@ print(""" amqp_boolean_t amqp_constant_is_hard_error(int constantNumber) { switch (constantNumber) {""") - for (c,v,cls) in spec.constants: + for (c,_,cls) in spec.constants: if cls == 'hard-error': print(" case %s: return 1;" % (cConstantName(c),)) print(""" default: return 0; @@ -388,15 +388,16 @@ size_t offset = 0; amqp_flags_t flags = 0; - int flagword_index = 0; uint16_t partial_flags; - do { - if (!amqp_decode_16(encoded, &offset, &partial_flags)) - return AMQP_STATUS_BAD_AMQP_DATA; - flags |= (partial_flags << (flagword_index * 16)); - flagword_index++; - } while (partial_flags & 1); + if (!amqp_decode_16(encoded, &offset, &partial_flags)) + return AMQP_STATUS_BAD_AMQP_DATA; + /* No AMQP 0-9-1 class defines more than 14 properties, so all property + flags fit in a single 16-bit flag word. Bit 0 is the continuation bit; + if it is set the message declares further flag words, which we do not + support, so reject the frame rather than misinterpret it. */ + if (partial_flags & 1) return AMQP_STATUS_BAD_AMQP_DATA; + flags = partial_flags; switch (class_id) {""") for c in spec.allClasses(): genDecodeProperties(c) @@ -502,7 +503,7 @@ print("""// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. // SPDX-License-Identifier: mit - + /* Generated code. Do not edit. Edit and re-run codegen.py instead. */ /** @file rabbitmq-c/framing.h */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/tests/CMakeLists.txt new/rabbitmq-c-0.17.0/tests/CMakeLists.txt --- old/rabbitmq-c-0.15.0/tests/CMakeLists.txt 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/tests/CMakeLists.txt 2026-07-02 05:45:59.000000000 +0200 @@ -23,6 +23,10 @@ target_link_libraries(test_status_enum rabbitmq-static) add_test(status_enum test_status_enum) +add_executable(test_tune_connection test_tune_connection.c) +target_link_libraries(test_tune_connection rabbitmq-static) +add_test(tune_connection test_tune_connection) + add_executable(test_basic test_basic.c) target_link_libraries(test_basic rabbitmq-static) @@ -41,3 +45,10 @@ target_link_libraries(test_merge_capabilities rabbitmq-static) add_test(merge_capabilities test_merge_capabilities) +add_executable(test_send_frame test_send_frame.c) +target_link_libraries(test_send_frame rabbitmq-static) +add_test(send_frame test_send_frame) + +add_executable(test_decode_bytes test_decode_bytes.c) +target_link_libraries(test_decode_bytes rabbitmq-static) +add_test(decode_bytes test_decode_bytes) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/tests/test_basic.c new/rabbitmq-c-0.17.0/tests/test_basic.c --- old/rabbitmq-c-0.15.0/tests/test_basic.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/tests/test_basic.c 2026-07-02 05:45:59.000000000 +0200 @@ -110,6 +110,9 @@ assert(rpc_reply.reply_type == AMQP_RESPONSE_NORMAL); char *body = malloc(message.body.len); + if (body == NULL) { + return NULL; + } memcpy(body, message.body.bytes, message.body.len); *out_body_size_ = message.body.len; amqp_destroy_message(&message); @@ -126,6 +129,7 @@ uint64_t body_size; char *msg = basic_get(connection_state, test_queue_name, &body_size); + assert(msg != NULL && "Test errored: memory allocation failed!"); assert(body_size == strlen(msg_to_publish)); assert(strncmp(msg_to_publish, msg, body_size) == 0); free(msg); @@ -151,6 +155,9 @@ *out_body_size_ = envelope.message.body.len; char *body = malloc(*out_body_size_); + if (body == NULL) { + return NULL; + } if (*out_body_size_) { memcpy(body, envelope.message.body.bytes, *out_body_size_); } @@ -168,6 +175,7 @@ uint64_t body_size; char *msg = consume_message(connection_state, test_queue_name, &body_size); + assert(msg != NULL && "Test errored: memory allocation failed!"); assert(body_size == strlen(msg_to_publish)); assert(strncmp(msg_to_publish, msg, body_size) == 0); free(msg); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/tests/test_decode_bytes.c new/rabbitmq-c-0.17.0/tests/test_decode_bytes.c --- old/rabbitmq-c-0.15.0/tests/test_decode_bytes.c 1970-01-01 01:00:00.000000000 +0100 +++ new/rabbitmq-c-0.17.0/tests/test_decode_bytes.c 2026-07-02 05:45:59.000000000 +0200 @@ -0,0 +1,84 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "amqp_private.h" + +/* Regression test for GHSA-jgjf-7fwf-f3c7: a size_t integer overflow in + * amqp_decode_bytes bypassed the bounds check on 32-bit systems, producing an + * out-of-bounds amqp_bytes_t (information disclosure / crash). The check must + * reject any (offset, len) pair that would read past the end of the buffer, + * including ones where offset + len wraps around SIZE_MAX. */ + +static int failures = 0; + +static void expect_reject(const char *name, amqp_bytes_t encoded, size_t offset, + size_t len) { + amqp_bytes_t output; + size_t off = offset; + output.bytes = NULL; + output.len = 0; + if (amqp_decode_bytes(encoded, &off, &output, len)) { + fprintf(stderr, + "FAIL %s: amqp_decode_bytes accepted an out-of-bounds length " + "(offset=%zu len=%zu buffer=%zu) -> output.len=%zu\n", + name, offset, len, encoded.len, output.len); + failures++; + } +} + +static void expect_accept(const char *name, amqp_bytes_t encoded, size_t offset, + size_t len) { + amqp_bytes_t output; + size_t off = offset; + output.bytes = NULL; + output.len = 0; + if (!amqp_decode_bytes(encoded, &off, &output, len)) { + fprintf(stderr, + "FAIL %s: amqp_decode_bytes rejected a valid length " + "(offset=%zu len=%zu buffer=%zu)\n", + name, offset, len, encoded.len); + failures++; + return; + } + if (output.len != len || output.bytes != amqp_offset(encoded.bytes, offset)) { + fprintf(stderr, "FAIL %s: amqp_decode_bytes produced wrong output\n", name); + failures++; + } +} + +int main(void) { + char buffer[16]; + amqp_bytes_t encoded; + encoded.bytes = buffer; + encoded.len = sizeof(buffer); + + /* Normal, in-bounds decodes still work. */ + expect_accept("full buffer", encoded, 0, sizeof(buffer)); + expect_accept("partial at offset", encoded, 4, 8); + expect_accept("zero length", encoded, 8, 0); + + /* Plain out-of-bounds (no overflow) is rejected. */ + expect_reject("len past end", encoded, 0, sizeof(buffer) + 1); + expect_reject("offset past end", encoded, sizeof(buffer) + 1, 0); + + /* The core of the advisory: a wire length large enough that offset + len + * wraps around SIZE_MAX. On 32-bit platforms a uint32_t length of + * 0xFFFFFFF5 with a small offset wraps to a tiny value; on any platform we + * can force the wrap with a len near SIZE_MAX. Both must be rejected rather + * than producing a multi-gigabyte amqp_bytes_t into a small buffer. */ + expect_reject("overflow to zero", encoded, 11, (size_t)0 - 11); + expect_reject("overflow wraps small", encoded, 16, (size_t)0 - 8); + expect_reject("max len", encoded, 1, (size_t)-1); + expect_reject("32-bit style len", encoded, 11, (size_t)0xFFFFFFF5u); + + if (failures) { + fprintf(stderr, "%d test(s) failed\n", failures); + return 1; + } + printf("all amqp_decode_bytes bounds tests passed\n"); + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/tests/test_send_frame.c new/rabbitmq-c-0.17.0/tests/test_send_frame.c --- old/rabbitmq-c-0.15.0/tests/test_send_frame.c 1970-01-01 01:00:00.000000000 +0100 +++ new/rabbitmq-c-0.17.0/tests/test_send_frame.c 2026-07-02 05:45:59.000000000 +0200 @@ -0,0 +1,57 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "amqp_private.h" +#include <rabbitmq-c/amqp.h> +#include <rabbitmq-c/framing.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Regression test for GHSA-hfjv-vcp3-39wh: passing an oversized + * AMQP_FRAME_BODY to amqp_send_frame() must not overflow the outbound + * buffer. It should be rejected with AMQP_STATUS_BAD_AMQP_DATA. */ +static void test_oversized_body_frame_rejected(void) { + amqp_connection_state_t state = amqp_new_connection(); + amqp_frame_t frame; + size_t body_len; + char *body; + int res; + + if (state == NULL) { + fprintf(stderr, "amqp_new_connection failed\n"); + abort(); + } + + /* The default outbound buffer is AMQP_DEFAULT_FRAME_SIZE bytes; use a + * body fragment that is larger than that buffer can hold. */ + body_len = state->outbound_buffer.len + 1024; + body = malloc(body_len); + if (body == NULL) { + fprintf(stderr, "malloc failed\n"); + abort(); + } + memset(body, 'A', body_len); + + memset(&frame, 0, sizeof(frame)); + frame.frame_type = AMQP_FRAME_BODY; + frame.channel = 1; + frame.payload.body_fragment.bytes = body; + frame.payload.body_fragment.len = body_len; + + res = amqp_send_frame(state, &frame); + if (res != AMQP_STATUS_BAD_AMQP_DATA) { + fprintf(stderr, "expected AMQP_STATUS_BAD_AMQP_DATA (%d), got %d\n", + AMQP_STATUS_BAD_AMQP_DATA, res); + abort(); + } + + free(body); + amqp_destroy_connection(state); +} + +int main(void) { + test_oversized_body_frame_rejected(); + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/tests/test_tune_connection.c new/rabbitmq-c-0.17.0/tests/test_tune_connection.c --- old/rabbitmq-c-0.15.0/tests/test_tune_connection.c 1970-01-01 01:00:00.000000000 +0100 +++ new/rabbitmq-c-0.17.0/tests/test_tune_connection.c 2026-07-02 05:45:59.000000000 +0200 @@ -0,0 +1,51 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +#include "amqp_private.h" +#include <rabbitmq-c/amqp.h> +#include <rabbitmq-c/framing.h> + +#include <stdio.h> +#include <stdlib.h> + +static void expect_frame_max(int requested, int expected) { + int res; + amqp_connection_state_t state = amqp_new_connection(); + + if (state == NULL) { + fprintf(stderr, "amqp_new_connection failed\n"); + abort(); + } + + state->state = CONNECTION_STATE_IDLE; + + res = amqp_tune_connection(state, 0, requested, 0); + if (res != AMQP_STATUS_OK) { + fprintf(stderr, "amqp_tune_connection returned %d\n", res); + abort(); + } + + if (amqp_get_frame_max(state) != expected) { + fprintf(stderr, "expected frame_max %d, got %d\n", expected, + amqp_get_frame_max(state)); + abort(); + } + + if (state->outbound_buffer.len != (size_t)expected) { + fprintf(stderr, "expected outbound buffer length %d, got %zu\n", expected, + state->outbound_buffer.len); + abort(); + } + + amqp_destroy_connection(state); +} + +int main(void) { + expect_frame_max(0, AMQP_FRAME_MIN_SIZE); + expect_frame_max(1, AMQP_FRAME_MIN_SIZE); + expect_frame_max(AMQP_FRAME_MIN_SIZE - 1, AMQP_FRAME_MIN_SIZE); + expect_frame_max(AMQP_FRAME_MIN_SIZE, AMQP_FRAME_MIN_SIZE); + expect_frame_max(AMQP_DEFAULT_FRAME_SIZE, AMQP_DEFAULT_FRAME_SIZE); + + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/tools/common.c new/rabbitmq-c-0.17.0/tools/common.c --- old/rabbitmq-c-0.15.0/tools/common.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/tools/common.c 2026-07-02 05:45:59.000000000 +0200 @@ -136,12 +136,13 @@ static char *amqp_cacert = "/etc/ssl/certs/cacert.pem"; static char *amqp_key = NULL; static char *amqp_cert = NULL; +static int amqp_no_default_cert_paths = 0; #endif /* WITH_SSL */ const char *connect_options_title = "Connection options"; struct poptOption connect_options[] = { - {"url", 'u', POPT_ARG_STRING, &amqp_url, 0, "the AMQP URL to connect to", - "amqp://..."}, + {"url", 'u', POPT_ARG_STRING, &amqp_url, 0, + "the AMQP URL to connect to (overrides AMQP_URL env var)", "amqp://..."}, {"server", 's', POPT_ARG_STRING, &amqp_server, 0, "the AMQP server to connect to", "hostname"}, {"port", 0, POPT_ARG_INT, &amqp_port, 0, "the port to connect on", "port"}, @@ -163,10 +164,12 @@ "path to the client private key file", "key.pem"}, {"cert", 0, POPT_ARG_STRING, &amqp_cert, 0, "path to the client certificate file", "cert.pem"}, + {"no-default-cert-paths", 0, POPT_ARG_NONE, &amqp_no_default_cert_paths, 0, + "do not use default certificate paths", NULL}, #endif /* WITH_SSL */ {NULL, '\0', 0, NULL, 0, NULL, NULL}}; -void read_authfile(const char *path) { +static void read_authfile(const char *path) { size_t n; FILE *fp = NULL; char token[MAXAUTHTOKENLEN]; @@ -208,6 +211,7 @@ if (!feof(fp)) { die("Malformed auth file (trailing data)"); } + fclose(fp); } static void init_connection_info(struct amqp_connection_info *ci) { @@ -220,6 +224,17 @@ amqp_default_connection_info(ci); + /* If no connection options were given on the CLI, fall back to AMQP_URL env + * var. Any explicit CLI connection flag takes full precedence and bypasses + * the env var entirely. */ + if (!amqp_url && !amqp_server && amqp_port < 0 && !amqp_username && + !amqp_password && !amqp_vhost) { + const char *env_url = getenv("AMQP_URL"); + if (env_url) { + amqp_url = (char *)env_url; + } + } + if (amqp_url) die_amqp_error(amqp_parse_url(strdup(amqp_url), ci), "Parsing URL '%s'", amqp_url); @@ -349,6 +364,9 @@ if (amqp_key) { amqp_ssl_socket_set_key(socket, amqp_cert, amqp_key); } + if (!amqp_no_default_cert_paths) { + amqp_ssl_socket_enable_default_verify_paths(socket); + } #else die("librabbitmq was not built with SSL/TLS support"); #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/tools/consume.c new/rabbitmq-c-0.17.0/tools/consume.c --- old/rabbitmq-c-0.15.0/tools/consume.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/tools/consume.c 2026-07-02 05:45:59.000000000 +0200 @@ -16,6 +16,9 @@ static char *stringify_bytes(amqp_bytes_t bytes) { /* We will need up to 4 chars per byte, plus the terminating 0 */ char *res = malloc(bytes.len * 4 + 1); + if (res == NULL) { + return NULL; + } uint8_t *data = bytes.bytes; char *p = res; size_t i; @@ -67,6 +70,10 @@ char *sq; queue_bytes = amqp_bytes_malloc_dup(res->queue); sq = stringify_bytes(queue_bytes); + if (sq == NULL) { + fprintf(stderr, "Memory allocation failed.\n"); + exit(1); + } fprintf(stderr, "Server provided queue name: %s\n", sq); free(sq); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/tools/doc/librabbitmq-tools.xml new/rabbitmq-c-0.17.0/tools/doc/librabbitmq-tools.xml --- old/rabbitmq-c-0.15.0/tools/doc/librabbitmq-tools.xml 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/tools/doc/librabbitmq-tools.xml 2026-07-02 05:45:59.000000000 +0200 @@ -38,6 +38,18 @@ <title>Common Options</title> <variablelist> <varlistentry> + <term><option>-u</option></term> + <term><option>--url</option>=<replaceable class="parameter">amqp://...</replaceable></term> + <listitem> + <para> + The AMQP URL to connect to. Takes precedence + over the <envar>AMQP_URL</envar> environment + variable. If neither is given, the default + connection parameters are used. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>-s</option></term> <term><option>--server</option>=<replaceable class="parameter">hostname:port</replaceable></term> <listitem> @@ -74,6 +86,28 @@ </para> </listitem> </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Environment Variables</title> + <variablelist> + <varlistentry> + <term><envar>AMQP_URL</envar></term> + <listitem> + <para> + Specifies the default AMQP URL to connect to. + Ignored if any connection option + (<option>--url</option>, + <option>--server</option>, + <option>--port</option>, + <option>--username</option>, + <option>--password</option>, or + <option>--vhost</option>) is given on the + command line. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.15.0/tools/publish.c new/rabbitmq-c-0.17.0/tools/publish.c --- old/rabbitmq-c-0.15.0/tools/publish.c 2024-11-20 03:57:22.000000000 +0100 +++ new/rabbitmq-c-0.17.0/tools/publish.c 2026-07-02 05:45:59.000000000 +0200 @@ -91,6 +91,10 @@ amqp_table_t *table = &props.headers; table->num_entries = num; table->entries = calloc(num, sizeof(amqp_table_entry_t)); + if (table->entries == NULL) { + fprintf(stderr, "Memory allocation failed\n"); + return 1; + } int i = 0; for (pos = headers; *pos; pos++) { char *colon = strchr(*pos, ':'); @@ -118,6 +122,10 @@ } else { if (line_buffered) { body_bytes.bytes = (char *)malloc(MAX_LINE_LENGTH); + if (body_bytes.bytes == NULL) { + fprintf(stderr, "Memory allocation failed\n"); + return 1; + } while (fgets(body_bytes.bytes, MAX_LINE_LENGTH, stdin)) { body_bytes.len = strlen(body_bytes.bytes); do_publish(conn, exchange, routing_key, &props, body_bytes); ++++++ rabbitmq-c-default-cacert-location.patch ++++++ --- /var/tmp/diff_new_pack.OqH35T/_old 2026-08-28 19:51:04.730728897 +0200 +++ /var/tmp/diff_new_pack.OqH35T/_new 2026-08-28 19:51:04.746729456 +0200 @@ -1,8 +1,6 @@ -Index: rabbitmq-c-0.14.0/tools/common.c -=================================================================== ---- rabbitmq-c-0.14.0.orig/tools/common.c -+++ rabbitmq-c-0.14.0/tools/common.c -@@ -133,7 +133,7 @@ static int amqp_heartbeat = 0; +--- rabbitmq-c-0.17.0/tools/common.c.orig 2026-08-26 03:35:30.443591639 +0000 ++++ rabbitmq-c-0.17.0/tools/common.c 2026-08-26 03:35:37.713675206 +0000 +@@ -133,7 +133,7 @@ static char *amqp_authfile; #ifdef WITH_SSL static int amqp_ssl = 0; @@ -10,5 +8,5 @@ +static char *amqp_cacert = "/var/lib/ca-certificates/ca-bundle.pem"; static char *amqp_key = NULL; static char *amqp_cert = NULL; - #endif /* WITH_SSL */ + static int amqp_no_default_cert_paths = 0;
