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-09-28 10:43:24 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rabbitmq-c (Old) and /work/SRC/openSUSE:Factory/.rabbitmq-c.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rabbitmq-c" Mon Sep 28 10:43:24 2026 rev:15 rq:1380684 version:0.18.0 Changes: -------- --- /work/SRC/openSUSE:Factory/rabbitmq-c/rabbitmq-c.changes 2026-08-28 19:51:02.148638642 +0200 +++ /work/SRC/openSUSE:Factory/.rabbitmq-c.new.383539/rabbitmq-c.changes 2026-09-28 10:43:28.216455991 +0200 @@ -1,0 +2,14 @@ +Fri Sep 25 12:42:53 UTC 2026 - Petr Gajdos <[email protected]> + gemini + +- Update to 0.18.0: + * Security: + - Fix client-side memory-exhaustion DoS in amqp_handle_input + (GHSA-5fp7-wg2f-hhgp, #899) + * Fixed: + - amqp_login/amqp_login_with_properties return an error instead + of crashing on NULL SASL credentials for + AMQP_SASL_METHOD_PLAIN; NULL and "" are now accepted for the + AMQP_SASL_METHOD_EXTERNAL identity argument (#898, #900) + - Fix pkgconfig bindings on Windows (#897) + +------------------------------------------------------------------- Old: ---- rabbitmq-c-0.17.0.tar.gz New: ---- rabbitmq-c-0.18.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rabbitmq-c.spec ++++++ --- /var/tmp/diff_new_pack.YJXiYu/_old 2026-09-28 10:43:29.062491420 +0200 +++ /var/tmp/diff_new_pack.YJXiYu/_new 2026-09-28 10:43:29.064491504 +0200 @@ -1,7 +1,7 @@ # # spec file for package rabbitmq-c # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # Copyright (c) 2012-2015 Remi Collet # # All modifications and additions to the file contributed by third parties @@ -20,7 +20,7 @@ %global libname librabbitmq %global majsonum 4 Name: rabbitmq-c -Version: 0.17.0 +Version: 0.18.0 Release: 0 Summary: Client library for AMQP License: MIT @@ -28,7 +28,7 @@ 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 Patch0: rabbitmq-c-default-cacert-location.patch -BuildRequires: cmake > 2.8.12 +BuildRequires: cmake >= 3.22 BuildRequires: gcc BuildRequires: openssl-devel BuildRequires: pkgconfig ++++++ rabbitmq-c-0.17.0.tar.gz -> rabbitmq-c-0.18.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.17.0/CMakeLists.txt new/rabbitmq-c-0.18.0/CMakeLists.txt --- old/rabbitmq-c-0.17.0/CMakeLists.txt 2026-07-02 05:45:59.000000000 +0200 +++ new/rabbitmq-c-0.18.0/CMakeLists.txt 2026-09-15 02:07:49.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 2) +set(RMQ_SOVERSION_REVISION 3) set(RMQ_SOVERSION_AGE 7) include(VersionFunctions) @@ -207,7 +207,9 @@ foreach (lib ${SOCKET_LIBRARIES}) set(libs_private "${libs_private} -l${lib}") endforeach(lib) -set(libs_private "${libs_private} -l${LIBRT}") +if(LIBRT) + set(libs_private "${libs_private} -l${LIBRT}") +endif() if (ENABLE_SSL_SUPPORT) set(libs_private "${libs_private} -lssl -lcrypto ${CMAKE_THREAD_LIBS_INIT}") endif() @@ -218,6 +220,15 @@ cmake_path(APPEND includedir "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") configure_file(cmake/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/librabbitmq/config.h) + +set(RMQ_LIBRARY_NAME rabbitmq) +if(WIN32) + if(BUILD_SHARED_LIBS) + set(RMQ_LIBRARY_NAME "rabbitmq.${RMQ_SOVERSION}") + else() + set(RMQ_LIBRARY_NAME "librabbitmq.${RMQ_SOVERSION}") + endif() +endif() configure_file(librabbitmq.pc.in ${CMAKE_CURRENT_BINARY_DIR}/librabbitmq.pc @ONLY) include(CMakePackageConfigHelpers) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.17.0/ChangeLog.md new/rabbitmq-c-0.18.0/ChangeLog.md --- old/rabbitmq-c-0.17.0/ChangeLog.md 2026-07-02 05:45:59.000000000 +0200 +++ new/rabbitmq-c-0.18.0/ChangeLog.md 2026-09-15 02:07:49.000000000 +0200 @@ -1,4 +1,13 @@ # Change Log +## v0.18.0 - 2026-09-14 + +### Security +- Fix client-side memory-exhaustion DoS in `amqp_handle_input` (GHSA-5fp7-wg2f-hhgp, #899) + +### Fixed +- `amqp_login`/`amqp_login_with_properties` return an error instead of crashing on `NULL` SASL credentials for `AMQP_SASL_METHOD_PLAIN`; `NULL` and `""` are now accepted for the `AMQP_SASL_METHOD_EXTERNAL` identity argument (#898, #900) +- Fix pkgconfig bindings on Windows (#897) + ## v0.17.0 - 2026-07-01 ### Security diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.17.0/include/rabbitmq-c/amqp.h new/rabbitmq-c-0.18.0/include/rabbitmq-c/amqp.h --- old/rabbitmq-c-0.17.0/include/rabbitmq-c/amqp.h 2026-07-02 05:45:59.000000000 +0200 +++ new/rabbitmq-c-0.18.0/include/rabbitmq-c/amqp.h 2026-09-15 02:07:49.000000000 +0200 @@ -118,7 +118,7 @@ */ #define AMQP_VERSION_MAJOR 0 -#define AMQP_VERSION_MINOR 17 +#define AMQP_VERSION_MINOR 18 #define AMQP_VERSION_PATCH 0 #define AMQP_VERSION_IS_RELEASE 1 @@ -1693,8 +1693,13 @@ * should be followed by two arguments in this order: * const char* username, and const char* password. * - AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL - * argument should be followed one argument: - * const char* identity. + * argument must be followed by one argument: + * const char* identity. NULL and an empty string ("") are + * both accepted and are equivalent: they indicate the + * caller has no authorization identity to send, and the + * broker should rely on the identity established by the + * underlying transport (e.g. the TLS client certificate). + * See RFC 4422 Appendix A. * \return amqp_rpc_reply_t indicating success or failure. * - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors @@ -1759,8 +1764,13 @@ * should be followed by two arguments in this order: * const char* username, and const char* password. * - AMQP_SASL_METHOD_EXTERNAL, the AMQP_SASL_METHOD_EXTERNAL - * argument should be followed one argument: - * const char* identity. + * argument must be followed by one argument: + * const char* identity. NULL and an empty string ("") are + * both accepted and are equivalent: they indicate the + * caller has no authorization identity to send, and the + * broker should rely on the identity established by the + * underlying transport (e.g. the TLS client certificate). + * See RFC 4422 Appendix A. * \return amqp_rpc_reply_t indicating success or failure. * - r.reply_type == AMQP_RESPONSE_NORMAL. Login completed successfully * - r.reply_type == AMQP_RESPONSE_LIBRARY_EXCEPTION. In most cases errors diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.17.0/librabbitmq/amqp_connection.c new/rabbitmq-c-0.18.0/librabbitmq/amqp_connection.c --- old/rabbitmq-c-0.17.0/librabbitmq/amqp_connection.c 2026-07-02 05:45:59.000000000 +0200 +++ new/rabbitmq-c-0.18.0/librabbitmq/amqp_connection.c 2026-09-15 02:07:49.000000000 +0200 @@ -263,6 +263,16 @@ channel = amqp_d16(amqp_offset(raw_frame, 1)); + /* channel_max == 0 means no explicit limit, AMQP sets this as 65535. */ + { + amqp_channel_t max_channel = (0 != state->channel_max) + ? (amqp_channel_t)state->channel_max + : (amqp_channel_t)UINT16_MAX; + if (channel > max_channel) { + return AMQP_STATUS_BAD_AMQP_DATA; + } + } + /* frame length is 3 bytes in */ frame_size = amqp_d32(amqp_offset(raw_frame, 3)); /* To prevent the target_size calculation below from overflowing, check @@ -384,12 +394,15 @@ break; case AMQP_FRAME_HEARTBEAT: + /* Heartbeat frames must be sent on the connection channel. */ + if (0 != decoded_frame->channel) { + return AMQP_STATUS_BAD_AMQP_DATA; + } break; default: - /* Ignore the frame */ - decoded_frame->frame_type = 0; - break; + /* Unrecognized frame type: this is a framing error per AMQP spec. */ + return AMQP_STATUS_BAD_AMQP_DATA; } return_to_idle(state); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.17.0/librabbitmq/amqp_socket.c new/rabbitmq-c-0.18.0/librabbitmq/amqp_socket.c --- old/rabbitmq-c-0.17.0/librabbitmq/amqp_socket.c 2026-07-02 05:45:59.000000000 +0200 +++ new/rabbitmq-c-0.18.0/librabbitmq/amqp_socket.c 2026-09-15 02:07:49.000000000 +0200 @@ -569,23 +569,34 @@ } static amqp_bytes_t sasl_response(amqp_pool_t *pool, - amqp_sasl_method_enum method, va_list args) { - amqp_bytes_t response; + amqp_sasl_method_enum method, va_list args, + int *status) { + amqp_bytes_t response = amqp_empty_bytes; + + *status = AMQP_STATUS_OK; switch (method) { case AMQP_SASL_METHOD_PLAIN: { char *username = va_arg(args, char *); - size_t username_len = strlen(username); char *password = va_arg(args, char *); - size_t password_len = strlen(password); + size_t username_len; + size_t password_len; char *response_buf; - amqp_pool_alloc_bytes(pool, strlen(username) + strlen(password) + 2, - &response); + if (username == NULL || password == NULL) { + *status = AMQP_STATUS_INVALID_PARAMETER; + return response; + } + + username_len = strlen(username); + password_len = strlen(password); + + amqp_pool_alloc_bytes(pool, username_len + password_len + 2, &response); if (response.bytes == NULL) /* We never request a zero-length block, because of the +2 above, so a NULL here really is ENOMEM. */ { + *status = AMQP_STATUS_NO_MEMORY; return response; } @@ -598,10 +609,27 @@ } case AMQP_SASL_METHOD_EXTERNAL: { char *identity = va_arg(args, char *); - size_t identity_len = strlen(identity); + size_t identity_len; + + /* NULL is treated the same as an empty identity: the caller has no + authorization identity to send and the broker should rely on the + identity established by the underlying transport (e.g. the TLS + client certificate). See RFC 4422 Appendix A. */ + if (identity == NULL) { + identity = ""; + } + + identity_len = strlen(identity); + if (identity_len == 0) { + /* response is already amqp_empty_bytes; amqp_pool_alloc_bytes() + would return NULL for a zero-length request, which is + indistinguishable from ENOMEM, so skip the allocation entirely. */ + break; + } amqp_pool_alloc_bytes(pool, identity_len, &response); if (response.bytes == NULL) { + *status = AMQP_STATUS_NO_MEMORY; return response; } @@ -1291,9 +1319,12 @@ goto error_res; } - response_bytes = sasl_response(channel_pool, sasl_method, vl); - if (response_bytes.bytes == NULL) { - res = AMQP_STATUS_NO_MEMORY; + response_bytes = sasl_response(channel_pool, sasl_method, vl, &res); + if (AMQP_STATUS_OK != res) { + /* Note: response_bytes.bytes == NULL is not itself an error here: a + zero-length response (e.g. AMQP_SASL_METHOD_EXTERNAL with an empty + identity) is a valid, successful result. sasl_response() reports + real allocation failures via res. */ goto error_res; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.17.0/librabbitmq.pc.in new/rabbitmq-c-0.18.0/librabbitmq.pc.in --- old/rabbitmq-c-0.17.0/librabbitmq.pc.in 2026-07-02 05:45:59.000000000 +0200 +++ new/rabbitmq-c-0.18.0/librabbitmq.pc.in 2026-09-15 02:07:49.000000000 +0200 @@ -8,6 +8,6 @@ Version: @RMQ_VERSION@ URL: https://github.com/alanxz/rabbitmq-c Requires.private: @requires_private@ -Libs: -L${libdir} -lrabbitmq +Libs: -L${libdir} -l@RMQ_LIBRARY_NAME@ Libs.private: @libs_private@ Cflags: -I${includedir} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.17.0/tests/CMakeLists.txt new/rabbitmq-c-0.18.0/tests/CMakeLists.txt --- old/rabbitmq-c-0.17.0/tests/CMakeLists.txt 2026-07-02 05:45:59.000000000 +0200 +++ new/rabbitmq-c-0.18.0/tests/CMakeLists.txt 2026-09-15 02:07:49.000000000 +0200 @@ -52,3 +52,7 @@ add_executable(test_decode_bytes test_decode_bytes.c) target_link_libraries(test_decode_bytes rabbitmq-static) add_test(decode_bytes test_decode_bytes) + +add_executable(test_handle_input_frame_validation test_handle_input_frame_validation.c) +target_link_libraries(test_handle_input_frame_validation rabbitmq-static) +add_test(handle_input_frame_validation test_handle_input_frame_validation) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rabbitmq-c-0.17.0/tests/test_handle_input_frame_validation.c new/rabbitmq-c-0.18.0/tests/test_handle_input_frame_validation.c --- old/rabbitmq-c-0.17.0/tests/test_handle_input_frame_validation.c 1970-01-01 01:00:00.000000000 +0100 +++ new/rabbitmq-c-0.18.0/tests/test_handle_input_frame_validation.c 2026-09-15 02:07:49.000000000 +0200 @@ -0,0 +1,185 @@ +// Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. +// SPDX-License-Identifier: mit + +/* Regression test for a client-side memory-exhaustion DoS: frames on + * channels beyond channel_max, or with an unrecognized frame type, were + * accepted instead of rejected, letting a peer grow per-channel pool + * memory without bound. */ + +#include "amqp_private.h" +#include <rabbitmq-c/amqp.h> +#include <rabbitmq-c/framing.h> + +#include <stdio.h> +#include <stdlib.h> + +static amqp_connection_state_t new_ready_state(int channel_max, int frame_max) { + amqp_connection_state_t state = amqp_new_connection(); + if (state == NULL) { + fprintf(stderr, "amqp_new_connection failed\n"); + abort(); + } + + /* Mirrors the private return_to_idle() helper, so amqp_handle_input() + * parses an ordinary frame instead of expecting a protocol header. */ + state->inbound_buffer.bytes = state->header_buffer; + state->inbound_buffer.len = sizeof(state->header_buffer); + state->inbound_offset = 0; + state->target_size = HEADER_SIZE; + state->state = CONNECTION_STATE_IDLE; + state->channel_max = channel_max; + state->frame_max = frame_max; + + return state; +} + +/* Builds a minimal frame: header (type, channel, size) + empty payload + + * frame-end footer. */ +static size_t build_empty_frame(uint8_t *buf, uint8_t frame_type, + uint16_t channel) { + buf[0] = frame_type; + amqp_e16(channel, buf + 1); + amqp_e32(0, buf + 3); + buf[7] = AMQP_FRAME_END; + return 8; +} + +static void test_channel_exceeding_channel_max_rejected(void) { + amqp_connection_state_t state = new_ready_state(1, AMQP_FRAME_MIN_SIZE); + amqp_frame_t frame; + amqp_bytes_t data; + uint8_t buf[8]; + int res; + + data.len = build_empty_frame(buf, 0xff, 2); + data.bytes = buf; + + res = amqp_handle_input(state, data, &frame); + if (res != AMQP_STATUS_BAD_AMQP_DATA) { + fprintf(stderr, + "expected AMQP_STATUS_BAD_AMQP_DATA (%d) for channel > " + "channel_max, got %d\n", + AMQP_STATUS_BAD_AMQP_DATA, res); + abort(); + } + + if (amqp_get_channel_pool(state, 2) != NULL) { + fprintf(stderr, + "channel pool was created for a channel beyond channel_max\n"); + abort(); + } + + amqp_destroy_connection(state); +} + +/* channel_max == 0 means no explicit limit, which per spec caps channels + * at 65535 rather than leaving them unbounded. */ +static void test_channel_max_zero_means_protocol_max(void) { + amqp_connection_state_t state = new_ready_state(0, AMQP_FRAME_MIN_SIZE); + amqp_frame_t frame; + amqp_bytes_t data; + uint8_t buf[8]; + int res; + + data.len = build_empty_frame(buf, AMQP_FRAME_BODY, 65535); + data.bytes = buf; + + res = amqp_handle_input(state, data, &frame); + if (res != (int)data.len) { + fprintf(stderr, "expected frame to be consumed (%d), got %d\n", + (int)data.len, res); + abort(); + } + if (frame.frame_type != AMQP_FRAME_BODY || frame.channel != 65535) { + fprintf(stderr, "expected a body frame on channel 65535 to be accepted\n"); + abort(); + } + + amqp_destroy_connection(state); +} + +static void test_heartbeat_on_nonzero_channel_rejected(void) { + amqp_connection_state_t state = new_ready_state(0, AMQP_FRAME_MIN_SIZE); + amqp_frame_t frame; + amqp_bytes_t data; + uint8_t buf[8]; + int res; + + data.len = build_empty_frame(buf, AMQP_FRAME_HEARTBEAT, 1); + data.bytes = buf; + + res = amqp_handle_input(state, data, &frame); + if (res != AMQP_STATUS_BAD_AMQP_DATA) { + fprintf(stderr, + "expected AMQP_STATUS_BAD_AMQP_DATA (%d) for heartbeat on " + "non-zero channel, got %d\n", + AMQP_STATUS_BAD_AMQP_DATA, res); + abort(); + } + + amqp_destroy_connection(state); +} + +static void test_unknown_frame_type_rejected(void) { + amqp_connection_state_t state = new_ready_state(1, AMQP_FRAME_MIN_SIZE); + amqp_frame_t frame; + amqp_bytes_t data; + uint8_t buf[8]; + int res; + + data.len = build_empty_frame(buf, 0xff, 1); + data.bytes = buf; + + res = amqp_handle_input(state, data, &frame); + if (res != AMQP_STATUS_BAD_AMQP_DATA) { + fprintf(stderr, + "expected AMQP_STATUS_BAD_AMQP_DATA (%d) for an unrecognized " + "frame type, got %d\n", + AMQP_STATUS_BAD_AMQP_DATA, res); + abort(); + } + + amqp_destroy_connection(state); +} + +/* Unlike test_unknown_frame_type_rejected, also checks that no pool memory + * accumulates before the rejection. */ +static void test_unknown_frame_type_stream_rejected_immediately(void) { + amqp_connection_state_t state = new_ready_state(1, AMQP_FRAME_MIN_SIZE); + amqp_pool_t *pool; + amqp_frame_t frame; + amqp_bytes_t data; + uint8_t buf[8]; + int res; + + data.len = build_empty_frame(buf, 0xff, 1); + data.bytes = buf; + + res = amqp_handle_input(state, data, &frame); + if (res != AMQP_STATUS_BAD_AMQP_DATA) { + fprintf(stderr, + "expected the first unrecognized frame to be rejected " + "immediately, got %d\n", + res); + abort(); + } + + pool = amqp_get_channel_pool(state, 1); + if (pool != NULL && pool->pages.num_blocks > 1) { + fprintf(stderr, + "channel pool grew to %d pages after a single rejected frame\n", + pool->pages.num_blocks); + abort(); + } + + amqp_destroy_connection(state); +} + +int main(void) { + test_channel_exceeding_channel_max_rejected(); + test_channel_max_zero_means_protocol_max(); + test_heartbeat_on_nonzero_channel_rejected(); + test_unknown_frame_type_rejected(); + test_unknown_frame_type_stream_rejected_immediately(); + return 0; +}
