From 4dfe5710eb9da42b1cfc5b7fb2ce8c3ca3e4da31 Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Wed, 15 Jul 2026 17:08:23 +0000 Subject: [PATCH 1/3] Patch rabbitmq-server for CVE-2026-57221, CVE-2026-57220, CVE-2026-57219, CVE-2026-57218, CVE-2026-57217, CVE-2026-57215, CVE-2026-57214, CVE-2026-57213, CVE-2026-57212, CVE-2026-57211 --- SPECS/rabbitmq-server/CVE-2026-57211.patch | 81 +++ SPECS/rabbitmq-server/CVE-2026-57212.patch | 81 +++ SPECS/rabbitmq-server/CVE-2026-57213.patch | 28 + SPECS/rabbitmq-server/CVE-2026-57214.patch | 27 + SPECS/rabbitmq-server/CVE-2026-57215.patch | 45 ++ SPECS/rabbitmq-server/CVE-2026-57217.patch | 64 +++ SPECS/rabbitmq-server/CVE-2026-57218.patch | 586 +++++++++++++++++++++ SPECS/rabbitmq-server/CVE-2026-57219.patch | 95 ++++ SPECS/rabbitmq-server/CVE-2026-57220.patch | 50 ++ SPECS/rabbitmq-server/CVE-2026-57221.patch | 43 ++ SPECS/rabbitmq-server/rabbitmq-server.spec | 15 +- 11 files changed, 1114 insertions(+), 1 deletion(-) create mode 100644 SPECS/rabbitmq-server/CVE-2026-57211.patch create mode 100644 SPECS/rabbitmq-server/CVE-2026-57212.patch create mode 100644 SPECS/rabbitmq-server/CVE-2026-57213.patch create mode 100644 SPECS/rabbitmq-server/CVE-2026-57214.patch create mode 100644 SPECS/rabbitmq-server/CVE-2026-57215.patch create mode 100644 SPECS/rabbitmq-server/CVE-2026-57217.patch create mode 100644 SPECS/rabbitmq-server/CVE-2026-57218.patch create mode 100644 SPECS/rabbitmq-server/CVE-2026-57219.patch create mode 100644 SPECS/rabbitmq-server/CVE-2026-57220.patch create mode 100644 SPECS/rabbitmq-server/CVE-2026-57221.patch diff --git a/SPECS/rabbitmq-server/CVE-2026-57211.patch b/SPECS/rabbitmq-server/CVE-2026-57211.patch new file mode 100644 index 00000000000..c8041c491e5 --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57211.patch @@ -0,0 +1,81 @@ +From 63e00787c762998eaa5d760923c435e49ce6af46 Mon Sep 17 00:00:00 2001 +From: Michael Klishin +Date: Mon, 23 Mar 2026 10:49:03 -0700 +Subject: [PATCH] Merge pull request #15804 from + rabbitmq/mergify/bp/v4.3.x/pr-15803 + +Management plugin: apply path filtering earlier (backport #15803) + +(cherry picked from commit d157b663fa4eff8b85bf7802b3b631a624a9a30e) +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/rabbitmq/rabbitmq-server/commit/6730797f6a34b4e8308cea60adf1243857e70204.patch +--- + .../src/rabbit_mgmt_wm_static.erl | 47 ++++++++++++++++--- + 1 file changed, 40 insertions(+), 7 deletions(-) + +diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl +index 12ebf4b..b1b7fd9 100644 +--- a/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl ++++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_static.erl +@@ -31,18 +31,51 @@ init(Req0, [{App, Path}]) -> + init(Req0, [{App, Path}|Tail]) -> + Req1 = rabbit_mgmt_headers:set_common_permission_headers(Req0, ?MODULE), + PathInfo = cowboy_req:path_info(Req1), +- Filepath = filename:join([code:priv_dir(App), Path|PathInfo]), +- %% We use erl_prim_loader because the file may be inside an .ez archive. +- FileInfo = erl_prim_loader:read_file_info(binary_to_list(Filepath)), +- case FileInfo of +- {ok, #file_info{type = regular}} -> do_init(Req1, App, Path); +- {ok, #file_info{type = symlink}} -> do_init(Req1, App, Path); +- _ -> init(Req0, Tail) ++ case validate_path_info(PathInfo) of ++ ok -> ++ Filepath = filename:join([code:priv_dir(App), Path|PathInfo]), ++ %% We use `erl_prim_loader` because the file may be inside ++ %% an .ez archive. ++ FileInfo = erl_prim_loader:read_file_info( ++ binary_to_list(Filepath)), ++ case FileInfo of ++ {ok, #file_info{type = regular}} -> ++ do_init(Req1, App, Path); ++ {ok, #file_info{type = symlink}} -> ++ do_init(Req1, App, Path); ++ _ -> ++ init(Req0, Tail) ++ end; ++ error -> ++ init(Req0, Tail) + end. + + do_init(Req, App, Path) -> + cowboy_static:init(Req, {priv_dir, App, Path}). + ++%% Must be done here before `erl_prim_loader:read_file_info/1` ++%% is used. ++%% ++%% This mirrors the validation that `cowboy_static` performs internally ++%% except that this validation must happen before the one in Cowboy. ++validate_path_info([]) -> ++ ok; ++validate_path_info([<<".">>|_]) -> ++ error; ++validate_path_info([<<"..">>|_]) -> ++ error; ++validate_path_info([Segment|Tail]) -> ++ case validate_segment(Segment) of ++ ok -> validate_path_info(Tail); ++ error -> error ++ end. ++ ++validate_segment(Segment) -> ++ case binary:match(Segment, [<<$/>>, <<$\\>>, <<0>>]) of ++ nomatch -> ok; ++ _ -> error ++ end. ++ + malformed_request(Req, State) -> + cowboy_static:malformed_request(Req, State). + +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/CVE-2026-57212.patch b/SPECS/rabbitmq-server/CVE-2026-57212.patch new file mode 100644 index 00000000000..e8b9c1a8158 --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57212.patch @@ -0,0 +1,81 @@ +From 38424a9a6ff0362f4c46620486f0847814b16ceb Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Wed, 15 Jul 2026 16:57:45 +0000 +Subject: [PATCH] HTTP API: refactor read_complete_body/1 + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/b8fc2ef7c50a2797d15e1ea7cf34f290032303bb.patch +--- + .../src/rabbit_mgmt_util.erl | 47 +++++++++++++------ + 1 file changed, 33 insertions(+), 14 deletions(-) + +diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_util.erl b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl +index 9c4c65d..395ef82 100644 +--- a/deps/rabbitmq_management/src/rabbit_mgmt_util.erl ++++ b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl +@@ -713,11 +713,26 @@ read_complete_body(Req, Acc) -> + read_complete_body(Req0, Acc, BodySizeLimit) -> + case bit_size(Acc) > BodySizeLimit of + true -> +- {error, "Exceeded HTTP request body size limit"}; ++ N = erlang:round(bit_size(Acc) / 8), ++ {error, http_body_limit_exceeded, BodySizeLimit, N}; + false -> + case cowboy_req:read_body(Req0) of +- {ok, Data, Req} -> {ok, <>, Req}; +- {more, Data, Req} -> read_complete_body(Req, <>) ++ {ok, Data, Req} -> ++ Total = <>, ++ case byte_size(Total) > BodySizeLimit of ++ true -> ++ {error, http_body_limit_exceeded, BodySizeLimit, byte_size(Total)}; ++ false -> ++ {ok, Total, Req} ++ end; ++ {more, Data, Req} -> ++ Total = <>, ++ case byte_size(Total) > BodySizeLimit of ++ true -> ++ {error, http_body_limit_exceeded, BodySizeLimit, byte_size(Total)}; ++ false -> ++ read_complete_body(Req, Total, BodySizeLimit) ++ end + end + end. + +@@ -834,17 +849,21 @@ with_vhost_and_props(Fun, ReqData, Context) -> + not_found(rabbit_data_coercion:to_binary("vhost_not_found"), + ReqData, Context); + VHost -> +- {ok, Body, ReqData1} = read_complete_body(ReqData), +- case decode(Body) of +- {ok, Props} -> +- try +- Fun(VHost, Props, ReqData1) +- catch {error, Error} -> +- bad_request(Error, ReqData1, Context) +- end; +- {error, Reason} -> +- bad_request(rabbit_mgmt_format:escape_html_tags(Reason), +- ReqData1, Context) ++ case read_complete_body(ReqData) of ++ {error, http_body_limit_exceeded, LimitApplied, BytesRead} -> ++ ?LOG_WARNING("HTTP API: request exceeded maximum allowed payload size (limit: ~tp bytes, payload size: ~tp bytes)", [LimitApplied, BytesRead]), ++ bad_request("Exceeded HTTP request body size limit", ReqData, Context); ++ {ok, Body, ReqData1} -> ++ case decode(Body) of ++ {ok, Props} -> ++ try ++ Fun(VHost, Props, ReqData1) ++ catch {error, Error} -> ++ bad_request(Error, ReqData1, Context) ++ end; ++ {error, Reason} -> ++ bad_request(Reason, ReqData1, Context) ++ end + end + end. + +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/CVE-2026-57213.patch b/SPECS/rabbitmq-server/CVE-2026-57213.patch new file mode 100644 index 00000000000..bfc6b223ec5 --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57213.patch @@ -0,0 +1,28 @@ +From 79a579397e4cc8c901f2680813d76eb77bb6074e Mon Sep 17 00:00:00 2001 +From: Michael Klishin +Date: Wed, 11 Mar 2026 14:29:26 -0700 +Subject: [PATCH] Federation management: Use fmt_string/1 in one more place + +(cherry picked from commit c2d0d69edf01efbd6e87dfb250c373a32da957f8) +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/rabbitmq/rabbitmq-server/commit/33dedfe4fd53ff009cc67ab36358d0624c6b2e53.patch +--- + .../priv/www/js/tmpl/federation.ejs | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/deps/rabbitmq_federation_management/priv/www/js/tmpl/federation.ejs b/deps/rabbitmq_federation_management/priv/www/js/tmpl/federation.ejs +index e47f697..06e3f62 100644 +--- a/deps/rabbitmq_federation_management/priv/www/js/tmpl/federation.ejs ++++ b/deps/rabbitmq_federation_management/priv/www/js/tmpl/federation.ejs +@@ -82,7 +82,7 @@ + + <%= link.timestamp %> + <%= link.id %> +- <%= link.consumer_tag %> ++ <%= fmt_string(link.consumer_tag) %> + +
+ +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/CVE-2026-57214.patch b/SPECS/rabbitmq-server/CVE-2026-57214.patch new file mode 100644 index 00000000000..7f50c7a8de9 --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57214.patch @@ -0,0 +1,27 @@ +From d4255edcca60bb42dfbd1c85bec1e25006bd9b10 Mon Sep 17 00:00:00 2001 +From: Michael Klishin +Date: Mon, 2 Mar 2026 09:32:07 -0800 +Subject: [PATCH] Escape x-internal-purpose used by the federation plugin + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/rabbitmq/rabbitmq-server/commit/b267a290dd89e42c6e0256f46fc273a8adb7f3ec.patch +--- + deps/rabbitmq_management/priv/www/js/formatters.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/deps/rabbitmq_management/priv/www/js/formatters.js b/deps/rabbitmq_management/priv/www/js/formatters.js +index 317ecd8..f49746f 100644 +--- a/deps/rabbitmq_management/priv/www/js/formatters.js ++++ b/deps/rabbitmq_management/priv/www/js/formatters.js +@@ -817,7 +817,7 @@ function _link_to(name, url, highlight, args) { + if (highlight == undefined) highlight = true; + var title = null; + if (args != undefined && args['x-internal-purpose'] != undefined) { +- var purpose = args['x-internal-purpose']; ++ var purpose = fmt_escape_html(args['x-internal-purpose']); + title = 'This is used internally by the ' + purpose + ' mechanism.'; + } + return ' +Date: Wed, 15 Jul 2026 16:57:13 +0000 +Subject: [PATCH] Reject bindings to Direct Reply-to virtual queues + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/9055500d10ca7629dd2b051c6dc7a4b0bb8f6734.patch +--- + deps/rabbit/src/rabbit_channel.erl | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/deps/rabbit/src/rabbit_channel.erl b/deps/rabbit/src/rabbit_channel.erl +index 310f098..89a8c25 100644 +--- a/deps/rabbit/src/rabbit_channel.erl ++++ b/deps/rabbit/src/rabbit_channel.erl +@@ -1119,6 +1119,18 @@ check_exchange_deletion(XName = #resource{name = <<"amq.", _/binary>>, + check_exchange_deletion(_) -> + ok. + ++reject_volatile_queue_as_binding_target(queue, DestinationNameBin) -> ++ case rabbit_volatile_queue:is(DestinationNameBin) of ++ true -> ++ rabbit_misc:protocol_error( ++ access_refused, ++ "cannot bind or unbind to a volatile (direct reply-to) queue", []); ++ false -> ++ ok ++ end; ++reject_volatile_queue_as_binding_target(_, _) -> ++ ok. ++ + %% check that an exchange/queue name does not contain the reserved + %% "amq." prefix. + %% +@@ -1881,6 +1893,7 @@ binding_action(Action, SourceNameBin0, DestinationType, DestinationNameBin0, + #user{username = Username} = User) -> + ExchangeNameBin = strip_cr_lf(SourceNameBin0), + DestinationNameBin = strip_cr_lf(DestinationNameBin0), ++ reject_volatile_queue_as_binding_target(DestinationType, DestinationNameBin), + DestinationName = name_to_resource(DestinationType, DestinationNameBin, VHostPath), + check_write_permitted(DestinationName, User, AuthzContext), + ExchangeName = rabbit_misc:r(VHostPath, exchange, ExchangeNameBin), +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/CVE-2026-57217.patch b/SPECS/rabbitmq-server/CVE-2026-57217.patch new file mode 100644 index 00000000000..f13b14177de --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57217.patch @@ -0,0 +1,64 @@ +From 7a1ce386a23a332cd531c830df2f3da41235f588 Mon Sep 17 00:00:00 2001 +From: Michael Klishin +Date: Mon, 6 Apr 2026 00:30:53 -0700 +Subject: [PATCH] Khepri: distinguish missing keys from errors in one place + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/rabbitmq/rabbitmq-server/commit/ce1f682aa6b398820c5e3ce1ff7435184027c82c.patch +--- + deps/rabbit/src/rabbit_auth_backend_internal.erl | 2 ++ + deps/rabbit/src/rabbit_db_user.erl | 14 ++++++++++---- + 2 files changed, 12 insertions(+), 4 deletions(-) + +diff --git a/deps/rabbit/src/rabbit_auth_backend_internal.erl b/deps/rabbit/src/rabbit_auth_backend_internal.erl +index 1c52ee9..444f1fd 100644 +--- a/deps/rabbit/src/rabbit_auth_backend_internal.erl ++++ b/deps/rabbit/src/rabbit_auth_backend_internal.erl +@@ -162,6 +162,8 @@ check_topic_access(#auth_user{username = Username}, + case rabbit_db_user:get_topic_permissions(Username, VHostPath, Name) of + undefined -> + true; ++ {error, _} = Err -> ++ Err; + #topic_permission{permission = P} -> + PermRegexp = case element(permission_index(Permission), P) of + %% <<"^$">> breaks Emacs' erlang mode +diff --git a/deps/rabbit/src/rabbit_db_user.erl b/deps/rabbit/src/rabbit_db_user.erl +index fb00b01..0b71b57 100644 +--- a/deps/rabbit/src/rabbit_db_user.erl ++++ b/deps/rabbit/src/rabbit_db_user.erl +@@ -621,12 +621,14 @@ clear_all_permissions_for_vhost_in_khepri(VHostName) -> + Username :: rabbit_types:username(), + VHostName :: vhost:name(), + ExchangeName :: binary(), +- Ret :: TopicPermission | undefined, ++ Ret :: TopicPermission | undefined | {error, term()}, + TopicPermission :: #topic_permission{}. + %% @doc Returns the topic permissions for the given user and exchange in the + %% given virtual host. + %% +-%% @returns the topic permissions record if any, or `undefined'. ++%% @returns the topic permissions record if any, `undefined' if no topic ++%% permissions are set, or `{error, Reason}' if the metadata store query ++%% fails. + %% + %% @private + +@@ -659,8 +661,12 @@ get_topic_permissions_in_mnesia(Username, VHostName, ExchangeName) -> + get_topic_permissions_in_khepri(Username, VHostName, ExchangeName) -> + Path = khepri_topic_permission_path(Username, VHostName, ExchangeName), + case rabbit_khepri:get(Path) of +- {ok, TopicPermission} -> TopicPermission; +- _ -> undefined ++ {ok, TopicPermission} -> ++ TopicPermission; ++ {error, {khepri, node_not_found, _}} -> ++ undefined; ++ {error, _} = Error -> ++ Error + end. + + %% ------------------------------------------------------------------- +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/CVE-2026-57218.patch b/SPECS/rabbitmq-server/CVE-2026-57218.patch new file mode 100644 index 00000000000..1143e63d3bd --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57218.patch @@ -0,0 +1,586 @@ +From 90fb993d28889e96c1e7de4409f9fefceac97384 Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Wed, 15 Jul 2026 16:57:20 +0000 +Subject: [PATCH] AMQP 0-9-1: clear permissions cache and re-check when secret + changes + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/501ad947cd6bbcc9486fe96e0d073992bfe52cc4.patch +--- + deps/rabbit/Makefile.rej | 10 + + deps/rabbit/src/rabbit_channel.erl | 69 ++++- + deps/rabbit/src/rabbit_channel.erl.rej | 84 +++++++ + deps/rabbit/src/rabbit_reader.erl | 14 ++ + deps/rabbit/src/rabbit_reader.erl.rej | 94 +++++++ + .../test/consumer_recheck_prop_SUITE.erl | 238 ++++++++++++++++++ + 6 files changed, 508 insertions(+), 1 deletion(-) + create mode 100644 deps/rabbit/Makefile.rej + create mode 100644 deps/rabbit/src/rabbit_channel.erl.rej + create mode 100644 deps/rabbit/src/rabbit_reader.erl.rej + create mode 100644 deps/rabbit/test/consumer_recheck_prop_SUITE.erl + +diff --git a/deps/rabbit/Makefile.rej b/deps/rabbit/Makefile.rej +new file mode 100644 +index 0000000..c280db9 +--- /dev/null ++++ b/deps/rabbit/Makefile.rej +@@ -0,0 +1,10 @@ ++diff a/deps/rabbit/Makefile b/deps/rabbit/Makefile (rejected hunks) ++@@ -268,7 +268,7 @@ PARALLEL_CT_SET_4_B = per_user_connection_tracking per_vhost_connection_limit ra ++ PARALLEL_CT_SET_4_C = msg_size_metrics unit_msg_size_metrics per_vhost_msg_store per_vhost_queue_limit priority_queue upgrade_preparation vhost ++ PARALLEL_CT_SET_4_D = per_user_connection_channel_tracking product_info queue_type rabbitmq_queues_cli_integration rabbitmq_streams_cli_integration rabbitmqctl_integration rabbitmqctl_shutdown routing rabbit_amqqueue ++ ++-PARALLEL_CT_SET_5_A = rabbit_direct_reply_to_prop rabbit_quorum_queue_prop direct_reply_to_amqpl direct_reply_to_amqp classic_queue +++PARALLEL_CT_SET_5_A = consumer_recheck_prop rabbit_direct_reply_to_prop rabbit_quorum_queue_prop direct_reply_to_amqpl direct_reply_to_amqp classic_queue ++ PARALLEL_CT_SET_5_B = feature_flags_v2 backing_queue transactions ++ PARALLEL_CT_SET_5_C = cluster_upgrade maintenance_mode ++ PARALLEL_CT_SET_5_D = rabbit_fifo_dlx_integration publisher_confirms_parallel rabbit_exchange_type_modulus_hash +diff --git a/deps/rabbit/src/rabbit_channel.erl b/deps/rabbit/src/rabbit_channel.erl +index 89a8c25..fade885 100644 +--- a/deps/rabbit/src/rabbit_channel.erl ++++ b/deps/rabbit/src/rabbit_channel.erl +@@ -804,7 +804,9 @@ handle_info(tick, State0 = #ch{queue_states = QueueStates0}) -> + Return + end; + handle_info({update_user_state, User}, State = #ch{cfg = Cfg}) -> +- noreply(State#ch{cfg = Cfg#conf{user = User}}). ++ ok = clear_permission_cache(), ++ State1 = State#ch{cfg = Cfg#conf{user = User}}, ++ noreply(recheck_consumers(State1)). + + + handle_pre_hibernate(State0) -> +@@ -1888,6 +1890,71 @@ queue_down_consumer_action(CTag, CMap) -> + _ -> {recover, ConsumeSpec} + end. + ++%% After a credential update, re-checks read access for all existing consumers. ++%% Consumers that fail the authorization check are cancelled. ++recheck_consumers(State = #ch{cfg = #conf{user = User, ++ authz_context = AuthzContext}, ++ consumer_mapping = CMap}) -> ++ maps:fold( ++ fun(CTag, {Q, _CParams}, StateAcc) when ?is_amqqueue(Q) -> ++ QName = amqqueue:get_name(Q), ++ try ++ check_resource_access(User, QName, read, AuthzContext), ++ StateAcc ++ catch ++ exit:#amqp_error{name = access_refused} -> ++ ?LOG_WARNING( ++ "Cancelling consumer ~tp on ~ts: " ++ "read access refused after credential update", ++ [CTag, rabbit_misc:rs(QName)]), ++ cancel_consumer_recheck(CTag, Q, StateAcc) ++ end ++ end, State, CMap). ++ ++cancel_consumer_recheck(CTag, Q, ++ #ch{cfg = #conf{user = #user{username = Username}}, ++ consumer_mapping = CMap, ++ queue_consumers = QCons, ++ queue_states = QStates0} = State) -> ++ QName = amqqueue:get_name(Q), ++ case server_consumer_cancel_supported(State) of ++ true -> ++ ok = send(#'basic.cancel'{consumer_tag = CTag, ++ nowait = true}, State); ++ false -> ++ ok ++ end, ++ %% Use delete_any because the consumer might not be in queue_consumers yet ++ %% (a `basic.consume-ok` wasn't yet received). ++ QCons1 = case maps:find(QName, QCons) of ++ error -> ++ QCons; ++ {ok, CTags} -> ++ CTags1 = gb_sets:delete_any(CTag, CTags), ++ case gb_sets:is_empty(CTags1) of ++ true -> maps:remove(QName, QCons); ++ false -> maps:put(QName, CTags1, QCons) ++ end ++ end, ++ Spec = #{consumer_tag => CTag, ++ ok_msg => undefined, ++ user => Username}, ++ QStates1 = case rabbit_misc:with_exit_handler( ++ fun() -> {error, not_found} end, ++ fun() -> ++ rabbit_queue_type:cancel(Q, Spec, QStates0) ++ end) of ++ {ok, QS} -> QS; ++ {error, not_found} -> QStates0 ++ end, ++ rabbit_global_counters:consumer_deleted(amqp091), ++ rabbit_event:notify(consumer_deleted, [{consumer_tag, CTag}, ++ {channel, self()}, ++ {queue, QName}]), ++ State#ch{consumer_mapping = maps:remove(CTag, CMap), ++ queue_consumers = QCons1, ++ queue_states = QStates1}. ++ + binding_action(Action, SourceNameBin0, DestinationType, DestinationNameBin0, + RoutingKey, Arguments, VHostPath, ConnPid, AuthzContext, + #user{username = Username} = User) -> +diff --git a/deps/rabbit/src/rabbit_channel.erl.rej b/deps/rabbit/src/rabbit_channel.erl.rej +new file mode 100644 +index 0000000..b8316ae +--- /dev/null ++++ b/deps/rabbit/src/rabbit_channel.erl.rej +@@ -0,0 +1,84 @@ ++diff a/deps/rabbit/src/rabbit_channel.erl b/deps/rabbit/src/rabbit_channel.erl (rejected hunks) ++@@ -706,7 +706,9 @@ handle_info(tick, #ch{} = State0) -> ++ end, ++ noreply(init_tick_timer(reset_tick_timer(State0))); ++ handle_info({update_user_state, User}, State = #ch{cfg = Cfg}) -> ++- noreply(State#ch{cfg = Cfg#conf{user = User}}). +++ ok = clear_permission_cache(), +++ State1 = State#ch{cfg = Cfg#conf{user = User}}, +++ noreply(recheck_consumers(State1)). ++ ++ ++ handle_pre_hibernate(State0) -> ++@@ -1720,6 +1722,71 @@ server_consumer_cancel_supported(#ch{cfg = #conf{capabilities = Capabilities}}) ++ {bool, true} == rabbit_misc:table_lookup(Capabilities, ++ <<"consumer_cancel_notify">>). ++ +++%% After a credential update, re-checks read access for all existing consumers. +++%% Consumers that fail the authorization check are cancelled. +++recheck_consumers(State = #ch{cfg = #conf{user = User, +++ authz_context = AuthzContext}, +++ consumer_mapping = CMap}) -> +++ maps:fold( +++ fun(CTag, {Q, _CParams}, StateAcc) when ?is_amqqueue(Q) -> +++ QName = amqqueue:get_name(Q), +++ try +++ check_resource_access(User, QName, read, AuthzContext), +++ StateAcc +++ catch +++ exit:#amqp_error{name = access_refused} -> +++ ?LOG_WARNING( +++ "Cancelling consumer ~tp on ~ts: " +++ "read access refused after credential update", +++ [CTag, rabbit_misc:rs(QName)]), +++ cancel_consumer_recheck(CTag, Q, StateAcc) +++ end +++ end, State, CMap). +++ +++cancel_consumer_recheck(CTag, Q, +++ #ch{cfg = #conf{user = #user{username = Username}}, +++ consumer_mapping = CMap, +++ queue_consumers = QCons, +++ queue_states = QStates0} = State) -> +++ QName = amqqueue:get_name(Q), +++ case server_consumer_cancel_supported(State) of +++ true -> +++ ok = send(#'basic.cancel'{consumer_tag = CTag, +++ nowait = true}, State); +++ false -> +++ ok +++ end, +++ %% Use delete_any because the consumer might not be in queue_consumers yet +++ %% (a `basic.consume-ok` wasn't yet received). +++ QCons1 = case maps:find(QName, QCons) of +++ error -> +++ QCons; +++ {ok, CTags} -> +++ CTags1 = gb_sets:delete_any(CTag, CTags), +++ case gb_sets:is_empty(CTags1) of +++ true -> maps:remove(QName, QCons); +++ false -> maps:put(QName, CTags1, QCons) +++ end +++ end, +++ Spec = #{consumer_tag => CTag, +++ ok_msg => undefined, +++ user => Username}, +++ QStates1 = case rabbit_misc:with_exit_handler( +++ fun() -> {error, not_found} end, +++ fun() -> +++ rabbit_queue_type:cancel(Q, Spec, QStates0) +++ end) of +++ {ok, QS} -> QS; +++ {error, not_found} -> QStates0 +++ end, +++ rabbit_global_counters:consumer_deleted(amqp091), +++ rabbit_event:notify(consumer_deleted, [{consumer_tag, CTag}, +++ {channel, self()}, +++ {queue, QName}]), +++ State#ch{consumer_mapping = maps:remove(CTag, CMap), +++ queue_consumers = QCons1, +++ queue_states = QStates1}. +++ ++ binding_action_with_checks( ++ Action, SourceNameBin0, DestinationType, DestinationNameBin0, ++ RoutingKey, Arguments, VHostPath, ConnPid, AuthzContext, +diff --git a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl +index fed666c..1f990db 100644 +--- a/deps/rabbit/src/rabbit_reader.erl ++++ b/deps/rabbit/src/rabbit_reader.erl +@@ -634,6 +634,20 @@ handle_other({bump_credit, Msg}, State) -> + %% Here we are receiving credit by some channel process. + credit_flow:handle_bump_msg(Msg), + control_throttle(State); ++handle_other(credential_expired, State) when ?IS_STOPPING(State) -> ++ State; ++handle_other(credential_expired, ++ State = #v1{connection = #connection{ ++ user = #user{username = Username}, ++ log_name = ConnName}}) -> ++ ?LOG_WARNING( ++ "closing AMQP connection ~ts of user '~ts': credential has expired", ++ [dynamic_connection_name(ConnName), Username]), ++ {ForceTermination, NewState} = terminate("credential expired", State), ++ case ForceTermination of ++ force -> stop; ++ normal -> NewState ++ end; + handle_other(Other, State) -> + %% internal error -> something worth dying for + maybe_emit_stats(State), +diff --git a/deps/rabbit/src/rabbit_reader.erl.rej b/deps/rabbit/src/rabbit_reader.erl.rej +new file mode 100644 +index 0000000..dd13f04 +--- /dev/null ++++ b/deps/rabbit/src/rabbit_reader.erl.rej +@@ -0,0 +1,94 @@ ++diff a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl (rejected hunks) ++@@ -104,7 +104,8 @@ ++ proxy_socket, ++ %% dynamic buffer ++ dynamic_buffer_size = 128, ++- dynamic_buffer_moving_average = 0.0 +++ dynamic_buffer_moving_average = 0.0, +++ credential_expiry_timer ++ }). ++ ++ -record(throttle, { ++@@ -681,6 +682,20 @@ handle_other({bump_credit, Msg}, State) -> ++ %% Here we are receiving credit by some channel process. ++ credit_flow:handle_bump_msg(Msg), ++ control_throttle(State); +++handle_other(credential_expired, State) when ?IS_STOPPING(State) -> +++ State; +++handle_other(credential_expired, +++ State = #v1{connection = #connection{ +++ user = #user{username = Username}, +++ log_name = ConnName}}) -> +++ ?LOG_WARNING( +++ "closing AMQP connection ~ts of user '~ts': credential has expired", +++ [dynamic_connection_name(ConnName), Username]), +++ {ForceTermination, NewState} = terminate("credential expired", State), +++ case ForceTermination of +++ force -> stop; +++ normal -> NewState +++ end; ++ handle_other(Other, State) -> ++ %% internal error -> something worth dying for ++ maybe_emit_stats(State), ++@@ -696,6 +711,23 @@ terminate(Explanation, State) when ?IS_RUNNING(State) -> ++ terminate(_Explanation, State) -> ++ {force, State}. ++ +++maybe_start_credential_expiry_timer(User, +++ State = #v1{credential_expiry_timer = OldTimer}) -> +++ cancel_credential_expiry_timer(OldTimer), +++ case rabbit_access_control:expiry_timestamp(User) of +++ never -> +++ State#v1{credential_expiry_timer = undefined}; +++ Ts when is_integer(Ts) -> +++ Time = max(0, (Ts - os:system_time(second)) * 1000), +++ Ref = erlang:send_after(Time, self(), credential_expired), +++ State#v1{credential_expiry_timer = Ref} +++ end. +++ +++cancel_credential_expiry_timer(undefined) -> ok; +++cancel_credential_expiry_timer(Ref) -> +++ _ = erlang:cancel_timer(Ref), +++ ok. +++ ++ send_blocked(#v1{connection = #connection{capabilities = Capabilities}, ++ sock = Sock}, Reason) -> ++ case rabbit_misc:table_lookup(Capabilities, <<"connection.blocked">>) of ++@@ -1313,7 +1345,7 @@ handle_method0(#'connection.open'{virtual_host = VHost}, ++ ?LOG_INFO( ++ "connection ~ts: user '~ts' authenticated and granted access to vhost '~ts'", ++ [dynamic_connection_name(ConnName), Username, VHost]), ++- State1; +++ maybe_start_credential_expiry_timer(User, State1); ++ handle_method0(#'connection.close'{}, State) when ?IS_RUNNING(State) -> ++ lists:foreach(fun rabbit_channel:shutdown/1, all_channels()), ++ maybe_close(State#v1{connection_state = closing}); ++@@ -1339,12 +1371,12 @@ handle_method0(#'connection.update_secret'{new_secret = NewSecret, reason = Reas ++ [dynamic_connection_name(ConnName), Username, Reason]), ++ case rabbit_access_control:update_state(User, NewSecret) of ++ {ok, User1} -> ++- %% User/auth backend state has been updated. Now we can propagate it to channels ++- %% asynchronously and return. All the channels have to do is to update their ++- %% own state. ++- %% ++- %% Any secret update errors coming from the authz backend will be handled in the other branch. ++- %% Therefore we optimistically do no error handling here. MK. +++ %% User/auth backend state has been updated. Re-check vhost access +++ %% before proceeding, then propagate to channels. Each channel will +++ %% update its user state and re-check authorization for existing +++ %% consumers, cancelling any that are no longer authorized. +++ VHost = Conn#connection.vhost, +++ ok = rabbit_access_control:check_vhost_access(User1, VHost, {socket, Sock}, #{}), ++ lists:foreach(fun(Ch) -> ++ ?LOG_DEBUG("Updating user/auth backend state for channel ~tp", [Ch]), ++ _ = rabbit_channel:update_user_state(Ch, User1) ++@@ -1353,7 +1385,8 @@ handle_method0(#'connection.update_secret'{new_secret = NewSecret, reason = Reas ++ ?LOG_INFO( ++ "connection ~ts: user '~ts' updated secret, reason: ~ts", ++ [dynamic_connection_name(ConnName), Username, Reason]), ++- State#v1{connection = Conn#connection{user = User1}}; +++ State1 = State#v1{connection = Conn#connection{user = User1}}, +++ maybe_start_credential_expiry_timer(User1, State1); ++ {refused, Message} -> ++ ?LOG_ERROR("Secret update was refused for user '~ts': ~tp", ++ [Username, Message]), +diff --git a/deps/rabbit/test/consumer_recheck_prop_SUITE.erl b/deps/rabbit/test/consumer_recheck_prop_SUITE.erl +new file mode 100644 +index 0000000..fdb1144 +--- /dev/null ++++ b/deps/rabbit/test/consumer_recheck_prop_SUITE.erl +@@ -0,0 +1,238 @@ ++%% This Source Code Form is subject to the terms of the Mozilla Public ++%% License, v. 2.0. If a copy of the MPL was not distributed with this ++%% file, You can obtain one at https://mozilla.org/MPL/2.0/. ++%% ++%% Copyright (c) 2007-2026 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. ++%% ++ ++%% Property-based tests for the consumer recheck algorithm used in ++%% rabbit_channel:recheck_consumers/1 after a credential update. ++%% ++%% The core invariant: after cancelling any subset of consumers, the ++%% consumer_mapping and queue_consumers structures remain consistent ++%% (bijection between the two, no empty gb_sets in queue_consumers). ++ ++-module(consumer_recheck_prop_SUITE). ++ ++-compile(nowarn_export_all). ++-compile(export_all). ++ ++-include_lib("proper/include/proper.hrl"). ++-include_lib("eunit/include/eunit.hrl"). ++ ++all() -> ++ [prop_cancel_preserves_consistency, ++ prop_cancel_correct_membership, ++ prop_cancel_with_pending_consumers]. ++ ++suite() -> ++ [{timetrap, {minutes, 1}}]. ++ ++init_per_suite(Config) -> ++ rabbit_ct_helpers:log_environment(), ++ rabbit_ct_helpers:run_setup_steps(Config). ++ ++end_per_suite(Config) -> ++ rabbit_ct_helpers:run_teardown_steps(Config). ++ ++init_per_testcase(Testcase, Config) -> ++ rabbit_ct_helpers:testcase_started(Config, Testcase). ++ ++end_per_testcase(Testcase, Config) -> ++ rabbit_ct_helpers:testcase_finished(Config, Testcase). ++ ++%% ------------------------------------------------------------------- ++%% Generators ++%% ------------------------------------------------------------------- ++ ++queue_name_gen() -> ++ ?LET(N, range(1, 8), ++ list_to_binary("q" ++ integer_to_list(N))). ++ ++consumer_tag_gen() -> ++ ?LET(N, range(1, 60), ++ list_to_binary("ctag-" ++ integer_to_list(N))). ++ ++%% Generates a deduplicated list of {CTag, QName, Allowed} triples. ++consumer_setup_gen() -> ++ ?LET(RawPairs, ++ list({consumer_tag_gen(), queue_name_gen(), bool()}), ++ begin ++ {_, Deduped} = lists:foldl( ++ fun({CTag, QName, Allowed}, {Seen, Acc}) -> ++ case sets:is_element(CTag, Seen) of ++ true -> {Seen, Acc}; ++ false -> {sets:add_element(CTag, Seen), ++ [{CTag, QName, Allowed} | Acc]} ++ end ++ end, {sets:new(), []}, RawPairs), ++ lists:reverse(Deduped) ++ end). ++ ++%% Like consumer_setup_gen but also marks some consumers as "pending" -- ++%% present in consumer_mapping but NOT yet in queue_consumers. ++%% This models the window before a `basic.consume-ok` is received. ++consumer_setup_with_pending_gen() -> ++ ?LET(RawPairs, ++ list({consumer_tag_gen(), queue_name_gen(), bool(), bool()}), ++ begin ++ {_, Deduped} = lists:foldl( ++ fun({CTag, QName, Allowed, Pending}, {Seen, Acc}) -> ++ case sets:is_element(CTag, Seen) of ++ true -> {Seen, Acc}; ++ false -> {sets:add_element(CTag, Seen), ++ [{CTag, QName, Allowed, Pending} | Acc]} ++ end ++ end, {sets:new(), []}, RawPairs), ++ lists:reverse(Deduped) ++ end). ++ ++%% ------------------------------------------------------------------- ++%% Build state from generated setup ++%% ------------------------------------------------------------------- ++ ++build_state(Setup) -> ++ CMap = maps:from_list([{CTag, QName} || {CTag, QName, _} <- Setup]), ++ QCons = lists:foldl( ++ fun({CTag, QName, _}, Acc) -> ++ CTags0 = maps:get(QName, Acc, gb_sets:new()), ++ maps:put(QName, gb_sets:add_element(CTag, CTags0), Acc) ++ end, #{}, Setup), ++ DeniedCTags = sets:from_list([CTag || {CTag, _, false} <- Setup]), ++ {CMap, QCons, DeniedCTags}. ++ ++build_state_with_pending(Setup) -> ++ CMap = maps:from_list([{CTag, QName} || {CTag, QName, _, _} <- Setup]), ++ QCons = lists:foldl( ++ fun({_CTag, _QName, _, true}, Acc) -> ++ Acc; ++ ({CTag, QName, _, false}, Acc) -> ++ CTags0 = maps:get(QName, Acc, gb_sets:new()), ++ maps:put(QName, gb_sets:add_element(CTag, CTags0), Acc) ++ end, #{}, Setup), ++ DeniedCTags = sets:from_list([CTag || {CTag, _, false, _} <- Setup]), ++ {CMap, QCons, DeniedCTags}. ++ ++%% ------------------------------------------------------------------- ++%% Cancellation logic (same algorithm as rabbit_channel) ++%% ------------------------------------------------------------------- ++ ++apply_recheck(DeniedSet, OrigCMap, CMap0, QCons0) -> ++ maps:fold( ++ fun(CTag, QName, {CMap, QCons}) -> ++ case sets:is_element(CTag, DeniedSet) of ++ false -> ++ {CMap, QCons}; ++ true -> ++ CMap1 = maps:remove(CTag, CMap), ++ QCons1 = ++ case maps:find(QName, QCons) of ++ error -> ++ QCons; ++ {ok, CTags} -> ++ CTags1 = gb_sets:delete_any(CTag, CTags), ++ case gb_sets:is_empty(CTags1) of ++ true -> maps:remove(QName, QCons); ++ false -> maps:put(QName, CTags1, QCons) ++ end ++ end, ++ {CMap1, QCons1} ++ end ++ end, {CMap0, QCons0}, OrigCMap). ++ ++%% ------------------------------------------------------------------- ++%% Consistency checks ++%% ------------------------------------------------------------------- ++ ++is_consistent(CMap, QCons) -> ++ no_empty_sets(QCons) ++ andalso cmap_subset_of_qcons(CMap, QCons) ++ andalso qcons_subset_of_cmap(QCons, CMap). ++ ++%% Weaker invariant for states with pending consumers: every entry in ++%% queue_consumers must appear in consumer_mapping, but not necessarily ++%% the other way around (pending consumers are in CMap only). ++is_weakly_consistent(CMap, QCons) -> ++ no_empty_sets(QCons) ++ andalso qcons_subset_of_cmap(QCons, CMap). ++ ++no_empty_sets(QCons) -> ++ maps:fold(fun(_, CTags, Acc) -> ++ Acc andalso (gb_sets:size(CTags) > 0) ++ end, true, QCons). ++ ++cmap_subset_of_qcons(CMap, QCons) -> ++ maps:fold( ++ fun(CTag, QName, Acc) -> ++ Acc andalso ++ case maps:find(QName, QCons) of ++ {ok, CTags} -> gb_sets:is_member(CTag, CTags); ++ error -> false ++ end ++ end, true, CMap). ++ ++qcons_subset_of_cmap(QCons, CMap) -> ++ maps:fold( ++ fun(QName, CTags, Acc) -> ++ gb_sets:fold( ++ fun(CTag, InnerAcc) -> ++ InnerAcc andalso (maps:get(CTag, CMap, undefined) =:= QName) ++ end, Acc, CTags) ++ end, true, QCons). ++ ++%% ------------------------------------------------------------------- ++%% Properties ++%% ------------------------------------------------------------------- ++ ++%% After cancelling any subset of consumers from a consistent state, ++%% the result is still consistent. ++prop_cancel_preserves_consistency(_Config) -> ++ rabbit_ct_proper_helpers:run_proper( ++ fun() -> ++ ?FORALL(Setup, consumer_setup_gen(), ++ begin ++ {CMap, QCons, Denied} = build_state(Setup), ++ %% Verify the builder produces a consistent initial state ++ ?assert(is_consistent(CMap, QCons)), ++ {CMap1, QCons1} = apply_recheck(Denied, CMap, CMap, QCons), ++ is_consistent(CMap1, QCons1) ++ end) ++ end, [], 5000). ++ ++%% Exactly the denied consumers are removed and all allowed consumers ++%% are retained. ++prop_cancel_correct_membership(_Config) -> ++ rabbit_ct_proper_helpers:run_proper( ++ fun() -> ++ ?FORALL(Setup, consumer_setup_gen(), ++ begin ++ {CMap, QCons, Denied} = build_state(Setup), ++ ?assert(is_consistent(CMap, QCons)), ++ {CMap1, _QCons1} = apply_recheck(Denied, CMap, CMap, QCons), ++ AllowedCTags = [CTag || {CTag, _, true} <- Setup], ++ DeniedCTags = [CTag || {CTag, _, false} <- Setup], ++ lists:all(fun(C) -> not maps:is_key(C, CMap1) end, ++ DeniedCTags) ++ andalso lists:all(fun(C) -> maps:is_key(C, CMap1) end, ++ AllowedCTags) ++ andalso (maps:size(CMap1) =:= length(AllowedCTags)) ++ end) ++ end, [], 5000). ++ ++%% When some consumers are "pending" (in consumer_mapping but not yet in ++%% queue_consumers), the recheck must not crash and the result must ++%% still satisfy the weak invariant. ++prop_cancel_with_pending_consumers(_Config) -> ++ rabbit_ct_proper_helpers:run_proper( ++ fun() -> ++ ?FORALL(Setup, consumer_setup_with_pending_gen(), ++ begin ++ {CMap, QCons, Denied} = build_state_with_pending(Setup), ++ ?assert(is_weakly_consistent(CMap, QCons)), ++ {CMap1, QCons1} = apply_recheck(Denied, CMap, CMap, QCons), ++ is_weakly_consistent(CMap1, QCons1) ++ andalso sets:fold( ++ fun(C, Acc) -> Acc andalso not maps:is_key(C, CMap1) end, ++ true, Denied) ++ end) ++ end, [], 5000). +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/CVE-2026-57219.patch b/SPECS/rabbitmq-server/CVE-2026-57219.patch new file mode 100644 index 00000000000..6ee22168056 --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57219.patch @@ -0,0 +1,95 @@ +From 16f77eda4b1ce018f7079f1890f6bbd87ce2145c Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Wed, 15 Jul 2026 16:52:11 +0000 +Subject: [PATCH] Remove deprecated endpoint + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/aa387c4451e7b674df3e3ba89df86a99d697cc7f.patch +--- + .../rabbitmq_management/priv/www/api/index.html | 11 ----------- + .../src/rabbit_mgmt_dispatcher.erl | 4 +--- + .../src/rabbit_mgmt_wm_auth.erl | 17 ----------------- + 3 files changed, 1 insertion(+), 31 deletions(-) + +diff --git a/deps/rabbitmq_management/priv/www/api/index.html b/deps/rabbitmq_management/priv/www/api/index.html +index 01a429a..830962b 100644 +--- a/deps/rabbitmq_management/priv/www/api/index.html ++++ b/deps/rabbitmq_management/priv/www/api/index.html +@@ -1131,17 +1131,6 @@ or: +
curl -4u 'guest:guest' -H 'content-type:application/json' -X PUT localhost:15672/api/vhost-limits/my-vhost/max-connections -d '{"value": 50}'
+ + +- +- X +- +- +- +- /api/auth +- +- Details about the OAuth2 configuration. It will return HTTP +- status 200 with body:
{"oauth_enabled":"boolean", "oauth_client_id":"string", "oauth_provider_url":"string"}
+- +- + + + +diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl +index 0b963e4..16abd33 100644 +--- a/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl ++++ b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl +@@ -193,9 +193,7 @@ dispatcher() -> + {"/health/checks/node-is-quorum-critical", rabbit_mgmt_wm_health_check_node_is_quorum_critical, []}, + {"/reset", rabbit_mgmt_wm_reset, []}, + {"/reset/:node", rabbit_mgmt_wm_reset, []}, +- {"/rebalance/queues", rabbit_mgmt_wm_rebalance_queues, [{queues, all}]}, +- {"/auth", rabbit_mgmt_wm_auth, []}, +- {"/auth/attempts/:node", rabbit_mgmt_wm_auth_attempts, [all]}, ++ {"/rebalance/queues", rabbit_mgmt_wm_rebalance_queues, [{queues, all}]}, {"/auth/attempts/:node", rabbit_mgmt_wm_auth_attempts, [all]}, + {"/auth/attempts/:node/source", rabbit_mgmt_wm_auth_attempts, [by_source]}, + {"/login", rabbit_mgmt_wm_login, []}, + {"/config/effective", rabbit_mgmt_wm_environment, []}, +diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl +index 1e7c974..b3212f6 100644 +--- a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl ++++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl +@@ -7,8 +7,6 @@ + + -module(rabbit_mgmt_wm_auth). + +--export([init/2, to_json/2, content_types_provided/2, is_authorized/2]). +--export([variances/2]). + -export([authSettings/0]). %% for testing only + + -include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl"). +@@ -17,15 +15,6 @@ + + %%-------------------------------------------------------------------- + +-init(Req, _State) -> +- {cowboy_rest, rabbit_mgmt_headers:set_common_permission_headers(Req, ?MODULE), #context{}}. +- +-variances(Req, Context) -> +- {[<<"accept-encoding">>, <<"origin">>], Req, Context}. +- +-content_types_provided(ReqData, Context) -> +- {rabbit_mgmt_util:responder_map(to_json), ReqData, Context}. +- + merge_oauth_provider_info(OAuthResourceServer, MgtResourceServer, ManagementProps) -> + OAuthProviderResult = case proplists:get_value(oauth_provider_id, OAuthResourceServer) of + undefined -> oauth2_client:get_oauth_provider([issuer]); +@@ -144,12 +133,6 @@ filter_empty_properties(ListOfProperties) -> + + to_binary(Value) -> rabbit_data_coercion:to_binary(Value). + +-to_json(ReqData, Context) -> +- rabbit_mgmt_util:reply(authSettings(), ReqData, Context). +- +-is_authorized(ReqData, Context) -> +- {true, ReqData, Context}. +- + is_invalid(List) -> + lists:any(fun(V) -> case V of + "" -> true; +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/CVE-2026-57220.patch b/SPECS/rabbitmq-server/CVE-2026-57220.patch new file mode 100644 index 00000000000..8591be27630 --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57220.patch @@ -0,0 +1,50 @@ +From 9157e249d26ca25be999f86aa91f1f81f716293c Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Wed, 15 Jul 2026 16:52:36 +0000 +Subject: [PATCH] Limit heap size for unauthenticated connections + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/595ec28fa1621b1f2c28124e4e0466a8ad963547.patch +--- + deps/rabbit/src/rabbit_access_control.erl | 21 +++++++++++++++++++++ + 1 file changed, 21 insertions(+) + +diff --git a/deps/rabbit/src/rabbit_access_control.erl b/deps/rabbit/src/rabbit_access_control.erl +index c76610d..ae9c7c7 100644 +--- a/deps/rabbit/src/rabbit_access_control.erl ++++ b/deps/rabbit/src/rabbit_access_control.erl +@@ -13,6 +13,8 @@ + check_vhost_access/4, check_resource_access/4, check_topic_access/4]). + + -export([permission_cache_can_expire/1, update_state/2, expiry_timestamp/1]). ++-export([set_max_heap_size_unauthenticated/1, ++ clear_max_heap_size/0]). + + %%---------------------------------------------------------------------------- + +@@ -271,3 +273,22 @@ expiry_timestamp(User = #user{authz_backends = Modules}) -> + Ts0 + end + end, never, Modules). ++ ++%% Limit heap size for unauthenticated connections as a defense-in-depth ++%% mechanism to protect the RabbitMQ node against pre-authentication memory ++%% exhaustion attacks. ++-spec set_max_heap_size_unauthenticated(atom()) -> ok. ++set_max_heap_size_unauthenticated(App) -> ++ %% 16 MiB by default ++ Default = 16 * 1024 * 1024 div erlang:system_info(wordsize), ++ Size = application:get_env(App, max_heap_size_unauthenticated, Default), ++ _ = erlang:process_flag(max_heap_size, #{size => Size, ++ kill => true, ++ error_logger => true}), ++ ok. ++ ++-spec clear_max_heap_size() -> ok. ++clear_max_heap_size() -> ++ %% "If set to zero, the heap size limit is disabled." ++ _ = erlang:process_flag(max_heap_size, 0), ++ ok. +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/CVE-2026-57221.patch b/SPECS/rabbitmq-server/CVE-2026-57221.patch new file mode 100644 index 00000000000..645f4e21b23 --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57221.patch @@ -0,0 +1,43 @@ +From ad6ad1b4ba0a272c1166925d33c39fed313e5503 Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Wed, 15 Jul 2026 16:52:14 +0000 +Subject: [PATCH] AMQP 0-9-1: apply configure checks to passive queue, exchange + declaration + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/709a14e49e06c138a8cd672c9809ca34a2767962.patch +--- + deps/rabbit/src/rabbit_channel.erl | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/deps/rabbit/src/rabbit_channel.erl b/deps/rabbit/src/rabbit_channel.erl +index f6b34ef..310f098 100644 +--- a/deps/rabbit/src/rabbit_channel.erl ++++ b/deps/rabbit/src/rabbit_channel.erl +@@ -2580,9 +2580,10 @@ handle_method(#'queue.declare'{queue = QueueNameBin, + handle_method(#'queue.declare'{queue = QueueNameBin, + nowait = NoWait, + passive = true}, +- ConnPid, _AuthzContext, _CollectorPid, VHostPath, _User) -> ++ ConnPid, AuthzContext, _CollectorPid, VHostPath, User) -> + StrippedQueueNameBin = strip_cr_lf(QueueNameBin), + QueueName = rabbit_misc:r(VHostPath, queue, StrippedQueueNameBin), ++ check_configure_permitted(QueueName, User, AuthzContext), + Action = fun (Q0) -> + QStat = maybe_stat(NoWait, Q0), + {QStat, Q0} +@@ -2675,9 +2676,10 @@ handle_method(#'exchange.declare'{exchange = ExchangeNameBin, + AutoDelete, Internal, Args); + handle_method(#'exchange.declare'{exchange = ExchangeNameBin, + passive = true}, +- _ConnPid, _AuthzContext, _CollectorPid, VHostPath, _User) -> ++ _ConnPid, AuthzContext, _CollectorPid, VHostPath, User) -> + ExchangeName = rabbit_misc:r(VHostPath, exchange, strip_cr_lf(ExchangeNameBin)), + check_not_default_exchange(ExchangeName), ++ check_configure_permitted(ExchangeName, User, AuthzContext), + _ = rabbit_exchange:lookup_or_die(ExchangeName). + + handle_deliver(CTag, Ack, Msgs, State) when is_list(Msgs) -> +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/rabbitmq-server.spec b/SPECS/rabbitmq-server/rabbitmq-server.spec index a684828ce07..456a938603f 100644 --- a/SPECS/rabbitmq-server/rabbitmq-server.spec +++ b/SPECS/rabbitmq-server/rabbitmq-server.spec @@ -2,7 +2,7 @@ Summary: rabbitmq-server Name: rabbitmq-server Version: 3.13.7 -Release: 6%{?dist} +Release: 7%{?dist} License: Apache-2.0 and MPL 2.0 Vendor: Microsoft Corporation Distribution: Azure Linux @@ -15,6 +15,16 @@ Patch2: CVE-2026-8466.patch Patch3: CVE-2026-43968.patch Patch4: CVE-2026-7790.patch Patch5: CVE-2026-43973.patch +Patch6: CVE-2026-57211.patch +Patch7: CVE-2026-57212.patch +Patch8: CVE-2026-57213.patch +Patch9: CVE-2026-57214.patch +Patch10: CVE-2026-57215.patch +Patch11: CVE-2026-57217.patch +Patch12: CVE-2026-57218.patch +Patch13: CVE-2026-57219.patch +Patch14: CVE-2026-57220.patch +Patch15: CVE-2026-57221.patch BuildRequires: elixir BuildRequires: erlang @@ -71,6 +81,9 @@ done %{_libdir}/rabbitmq/lib/rabbitmq_server-%{version}/* %changelog +* Wed Jul 15 2026 Azure Linux Security Servicing Account - 3.13.7-7 +- Patch for CVE-2026-57221, CVE-2026-57220, CVE-2026-57219, CVE-2026-57218, CVE-2026-57217, CVE-2026-57215, CVE-2026-57214, CVE-2026-57213, CVE-2026-57212, CVE-2026-57211 + * Wed Jun 17 2026 Azure Linux Security Servicing Account - 3.13.7-6 - Patch for CVE-2026-43973 From 6f85987729573ad5a2add063cfe23adb09c5f6c6 Mon Sep 17 00:00:00 2001 From: Sushil Sati Date: Fri, 17 Jul 2026 05:03:04 +0000 Subject: [PATCH 2/3] Updated for CVE-2026-57212, CVE-2026-57215, CVE-2026-57218, CVE-2026-57219, CVE-2026-57220, rabbitmq-server.spec --- SPECS/rabbitmq-server/CVE-2026-57212.patch | 23 +- SPECS/rabbitmq-server/CVE-2026-57215.patch | 20 +- SPECS/rabbitmq-server/CVE-2026-57216.patch | 73 +++ SPECS/rabbitmq-server/CVE-2026-57218.patch | 558 ++++----------------- SPECS/rabbitmq-server/CVE-2026-57219.patch | 15 +- SPECS/rabbitmq-server/CVE-2026-57220.patch | 124 ++++- SPECS/rabbitmq-server/rabbitmq-server.spec | 15 +- 7 files changed, 318 insertions(+), 510 deletions(-) create mode 100644 SPECS/rabbitmq-server/CVE-2026-57216.patch diff --git a/SPECS/rabbitmq-server/CVE-2026-57212.patch b/SPECS/rabbitmq-server/CVE-2026-57212.patch index e8b9c1a8158..27d5f04ee36 100644 --- a/SPECS/rabbitmq-server/CVE-2026-57212.patch +++ b/SPECS/rabbitmq-server/CVE-2026-57212.patch @@ -6,20 +6,15 @@ Subject: [PATCH] HTTP API: refactor read_complete_body/1 Signed-off-by: Azure Linux Security Servicing Account Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/b8fc2ef7c50a2797d15e1ea7cf34f290032303bb.patch --- - .../src/rabbit_mgmt_util.erl | 47 +++++++++++++------ - 1 file changed, 33 insertions(+), 14 deletions(-) + .../src/rabbit_mgmt_util.erl | 46 +++++++++++++------ + 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_util.erl b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl -index 9c4c65d..395ef82 100644 +index 9c4c65d..24694af 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_util.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl -@@ -713,11 +713,26 @@ read_complete_body(Req, Acc) -> - read_complete_body(Req0, Acc, BodySizeLimit) -> - case bit_size(Acc) > BodySizeLimit of - true -> -- {error, "Exceeded HTTP request body size limit"}; -+ N = erlang:round(bit_size(Acc) / 8), -+ {error, http_body_limit_exceeded, BodySizeLimit, N}; +@@ -716,8 +716,22 @@ read_complete_body(Req0, Acc, BodySizeLimit) -> + {error, "Exceeded HTTP request body size limit"}; false -> case cowboy_req:read_body(Req0) of - {ok, Data, Req} -> {ok, <>, Req}; @@ -43,7 +38,7 @@ index 9c4c65d..395ef82 100644 end end. -@@ -834,17 +849,21 @@ with_vhost_and_props(Fun, ReqData, Context) -> +@@ -834,17 +848,23 @@ with_vhost_and_props(Fun, ReqData, Context) -> not_found(rabbit_data_coercion:to_binary("vhost_not_found"), ReqData, Context); VHost -> @@ -60,7 +55,9 @@ index 9c4c65d..395ef82 100644 - ReqData1, Context) + case read_complete_body(ReqData) of + {error, http_body_limit_exceeded, LimitApplied, BytesRead} -> -+ ?LOG_WARNING("HTTP API: request exceeded maximum allowed payload size (limit: ~tp bytes, payload size: ~tp bytes)", [LimitApplied, BytesRead]), ++ rabbit_log:warning( ++ "HTTP API: request exceeded maximum allowed payload size (limit: ~tp bytes, payload size: ~tp bytes)", ++ [LimitApplied, BytesRead]), + bad_request("Exceeded HTTP request body size limit", ReqData, Context); + {ok, Body, ReqData1} -> + case decode(Body) of @@ -71,7 +68,7 @@ index 9c4c65d..395ef82 100644 + bad_request(Error, ReqData1, Context) + end; + {error, Reason} -> -+ bad_request(Reason, ReqData1, Context) ++ bad_request(Reason, ReqData1, Context) + end end end. diff --git a/SPECS/rabbitmq-server/CVE-2026-57215.patch b/SPECS/rabbitmq-server/CVE-2026-57215.patch index 4ae96ed6b90..f9daba7680b 100644 --- a/SPECS/rabbitmq-server/CVE-2026-57215.patch +++ b/SPECS/rabbitmq-server/CVE-2026-57215.patch @@ -6,24 +6,28 @@ Subject: [PATCH] Reject bindings to Direct Reply-to virtual queues Signed-off-by: Azure Linux Security Servicing Account Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/9055500d10ca7629dd2b051c6dc7a4b0bb8f6734.patch --- - deps/rabbit/src/rabbit_channel.erl | 13 +++++++++++++ - 1 file changed, 13 insertions(+) + deps/rabbit/src/rabbit_channel.erl | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) diff --git a/deps/rabbit/src/rabbit_channel.erl b/deps/rabbit/src/rabbit_channel.erl -index 310f098..89a8c25 100644 +index f6b34ef..8297694 100644 --- a/deps/rabbit/src/rabbit_channel.erl +++ b/deps/rabbit/src/rabbit_channel.erl -@@ -1119,6 +1119,18 @@ check_exchange_deletion(XName = #resource{name = <<"amq.", _/binary>>, +@@ -1119,6 +1119,22 @@ check_exchange_deletion(XName = #resource{name = <<"amq.", _/binary>>, check_exchange_deletion(_) -> ok. +reject_volatile_queue_as_binding_target(queue, DestinationNameBin) -> -+ case rabbit_volatile_queue:is(DestinationNameBin) of -+ true -> ++ case DestinationNameBin of ++ <<"amq.rabbitmq.reply-to">> -> + rabbit_misc:protocol_error( + access_refused, + "cannot bind or unbind to a volatile (direct reply-to) queue", []); -+ false -> ++ <<"amq.rabbitmq.reply-to.", _/binary>> -> ++ rabbit_misc:protocol_error( ++ access_refused, ++ "cannot bind or unbind to a volatile (direct reply-to) queue", []); ++ _ -> + ok + end; +reject_volatile_queue_as_binding_target(_, _) -> @@ -32,7 +36,7 @@ index 310f098..89a8c25 100644 %% check that an exchange/queue name does not contain the reserved %% "amq." prefix. %% -@@ -1881,6 +1893,7 @@ binding_action(Action, SourceNameBin0, DestinationType, DestinationNameBin0, +@@ -1881,6 +1897,7 @@ binding_action(Action, SourceNameBin0, DestinationType, DestinationNameBin0, #user{username = Username} = User) -> ExchangeNameBin = strip_cr_lf(SourceNameBin0), DestinationNameBin = strip_cr_lf(DestinationNameBin0), diff --git a/SPECS/rabbitmq-server/CVE-2026-57216.patch b/SPECS/rabbitmq-server/CVE-2026-57216.patch new file mode 100644 index 00000000000..539146974a4 --- /dev/null +++ b/SPECS/rabbitmq-server/CVE-2026-57216.patch @@ -0,0 +1,73 @@ +From 9f8c39fcf0acbc43080ee7017a62a02832114112 Mon Sep 17 00:00:00 2001 +From: Michael Klishin +Date: Sun, 5 Apr 2026 15:59:06 -0700 +Subject: [PATCH 1/2] Use `peername/1` over `sockname/1` in a few places + +Upstream-reference: https://patch-diff.githubusercontent.com/raw/rabbitmq/rabbitmq-server/pull/15936.patch + +--- + deps/rabbit/src/rabbit_reader.erl | 3 ++- + deps/rabbit_common/src/rabbit_net.erl | 2 +- + deps/rabbitmq_stream/src/rabbit_stream_reader.erl | 5 +++-- + 3 files changed, 6 insertions(+), 4 deletions(-) + +diff --git a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl +index b003ac4..df29bf7 100644 +--- a/deps/rabbit/src/rabbit_reader.erl ++++ b/deps/rabbit/src/rabbit_reader.erl +@@ -1496,6 +1496,7 @@ auth_phase(Response, + #connection{protocol = Protocol, + auth_mechanism = {Name, AuthMechanism}, + auth_state = AuthState, ++ peer_host = PeerHost, + host = RemoteAddress}, + sock = Sock}) -> + rabbit_log:debug("Client address during authN phase: ~tp", [RemoteAddress]), +@@ -1517,7 +1518,7 @@ auth_phase(Response, + auth_state = AuthState1}}; + {ok, User = #user{username = Username}} -> + rabbit_access_control:clear_max_heap_size(), +- case rabbit_access_control:check_user_loopback(Username, Sock) of ++ case rabbit_access_control:check_user_loopback(Username, PeerHost) of + ok -> + rabbit_core_metrics:auth_attempt_succeeded(RemoteAddress, Username, amqp091), + notify_auth_result(Username, user_authentication_success, +diff --git a/deps/rabbit_common/src/rabbit_net.erl b/deps/rabbit_common/src/rabbit_net.erl +index 88ff58b..75a0cc4 100644 +--- a/deps/rabbit_common/src/rabbit_net.erl ++++ b/deps/rabbit_common/src/rabbit_net.erl +@@ -305,7 +305,7 @@ sock_funs(inbound) -> {fun peername/1, fun sockname/1}; + sock_funs(outbound) -> {fun sockname/1, fun peername/1}. + + is_loopback(Sock) when is_port(Sock) ; ?IS_SSL(Sock) -> +- case sockname(Sock) of ++ case peername(Sock) of + {ok, {Addr, _Port}} -> is_loopback(Addr); + {error, _} -> false + end; +diff --git a/deps/rabbitmq_stream/src/rabbit_stream_reader.erl b/deps/rabbitmq_stream/src/rabbit_stream_reader.erl +index 785bb0a..aae5eda 100644 +--- a/deps/rabbitmq_stream/src/rabbit_stream_reader.erl ++++ b/deps/rabbitmq_stream/src/rabbit_stream_reader.erl +@@ -1314,7 +1314,8 @@ handle_frame_pre_auth(Transport, + handle_frame_pre_auth(Transport, + #stream_connection{socket = S, + authentication_state = AuthState0, +- host = Host} = ++ host = Host, ++ peer_host = PeerHost} = + Connection0, + State, + {request, CorrelationId, +@@ -1366,7 +1367,7 @@ handle_frame_pre_auth(Transport, + rabbit_access_control:clear_max_heap_size(), + case + rabbit_access_control:check_user_loopback(Username, +- S) ++ PeerHost) + of + ok -> + rabbit_core_metrics:auth_attempt_succeeded(Host, +-- +2.45.4 + diff --git a/SPECS/rabbitmq-server/CVE-2026-57218.patch b/SPECS/rabbitmq-server/CVE-2026-57218.patch index 1143e63d3bd..0b91711ff5a 100644 --- a/SPECS/rabbitmq-server/CVE-2026-57218.patch +++ b/SPECS/rabbitmq-server/CVE-2026-57218.patch @@ -7,36 +7,12 @@ Subject: [PATCH] AMQP 0-9-1: clear permissions cache and re-check when secret Signed-off-by: Azure Linux Security Servicing Account Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/501ad947cd6bbcc9486fe96e0d073992bfe52cc4.patch --- - deps/rabbit/Makefile.rej | 10 + - deps/rabbit/src/rabbit_channel.erl | 69 ++++- - deps/rabbit/src/rabbit_channel.erl.rej | 84 +++++++ - deps/rabbit/src/rabbit_reader.erl | 14 ++ - deps/rabbit/src/rabbit_reader.erl.rej | 94 +++++++ - .../test/consumer_recheck_prop_SUITE.erl | 238 ++++++++++++++++++ - 6 files changed, 508 insertions(+), 1 deletion(-) - create mode 100644 deps/rabbit/Makefile.rej - create mode 100644 deps/rabbit/src/rabbit_channel.erl.rej - create mode 100644 deps/rabbit/src/rabbit_reader.erl.rej - create mode 100644 deps/rabbit/test/consumer_recheck_prop_SUITE.erl + deps/rabbit/src/rabbit_channel.erl | 71 +++++++++++++++++++++++++++++- + deps/rabbit/src/rabbit_reader.erl | 51 +++++++++++++++++---- + 2 files changed, 112 insertions(+), 10 deletions(-) -diff --git a/deps/rabbit/Makefile.rej b/deps/rabbit/Makefile.rej -new file mode 100644 -index 0000000..c280db9 ---- /dev/null -+++ b/deps/rabbit/Makefile.rej -@@ -0,0 +1,10 @@ -+diff a/deps/rabbit/Makefile b/deps/rabbit/Makefile (rejected hunks) -+@@ -268,7 +268,7 @@ PARALLEL_CT_SET_4_B = per_user_connection_tracking per_vhost_connection_limit ra -+ PARALLEL_CT_SET_4_C = msg_size_metrics unit_msg_size_metrics per_vhost_msg_store per_vhost_queue_limit priority_queue upgrade_preparation vhost -+ PARALLEL_CT_SET_4_D = per_user_connection_channel_tracking product_info queue_type rabbitmq_queues_cli_integration rabbitmq_streams_cli_integration rabbitmqctl_integration rabbitmqctl_shutdown routing rabbit_amqqueue -+ -+-PARALLEL_CT_SET_5_A = rabbit_direct_reply_to_prop rabbit_quorum_queue_prop direct_reply_to_amqpl direct_reply_to_amqp classic_queue -++PARALLEL_CT_SET_5_A = consumer_recheck_prop rabbit_direct_reply_to_prop rabbit_quorum_queue_prop direct_reply_to_amqpl direct_reply_to_amqp classic_queue -+ PARALLEL_CT_SET_5_B = feature_flags_v2 backing_queue transactions -+ PARALLEL_CT_SET_5_C = cluster_upgrade maintenance_mode -+ PARALLEL_CT_SET_5_D = rabbit_fifo_dlx_integration publisher_confirms_parallel rabbit_exchange_type_modulus_hash diff --git a/deps/rabbit/src/rabbit_channel.erl b/deps/rabbit/src/rabbit_channel.erl -index 89a8c25..fade885 100644 +index 8297694..6a153e7 100644 --- a/deps/rabbit/src/rabbit_channel.erl +++ b/deps/rabbit/src/rabbit_channel.erl @@ -804,7 +804,9 @@ handle_info(tick, State0 = #ch{queue_states = QueueStates0}) -> @@ -50,10 +26,14 @@ index 89a8c25..fade885 100644 handle_pre_hibernate(State0) -> -@@ -1888,6 +1890,71 @@ queue_down_consumer_action(CTag, CMap) -> +@@ -1892,6 +1894,73 @@ queue_down_consumer_action(CTag, CMap) -> _ -> {recover, ConsumeSpec} end. ++server_consumer_cancel_supported(#ch{cfg = #conf{capabilities = Capabilities}}) -> ++ {bool, true} == rabbit_misc:table_lookup(Capabilities, ++ <<"consumer_cancel_notify">>). ++ +%% After a credential update, re-checks read access for all existing consumers. +%% Consumers that fail the authorization check are cancelled. +recheck_consumers(State = #ch{cfg = #conf{user = User, @@ -67,7 +47,7 @@ index 89a8c25..fade885 100644 + StateAcc + catch + exit:#amqp_error{name = access_refused} -> -+ ?LOG_WARNING( ++ rabbit_log:warning( + "Cancelling consumer ~tp on ~ts: " + "read access refused after credential update", + [CTag, rabbit_misc:rs(QName)]), @@ -100,13 +80,11 @@ index 89a8c25..fade885 100644 + false -> maps:put(QName, CTags1, QCons) + end + end, -+ Spec = #{consumer_tag => CTag, -+ ok_msg => undefined, -+ user => Username}, + QStates1 = case rabbit_misc:with_exit_handler( + fun() -> {error, not_found} end, + fun() -> -+ rabbit_queue_type:cancel(Q, Spec, QStates0) ++ rabbit_queue_type:cancel(Q, CTag, undefined, ++ Username, QStates0) + end) of + {ok, QS} -> QS; + {error, not_found} -> QStates0 @@ -122,101 +100,21 @@ index 89a8c25..fade885 100644 binding_action(Action, SourceNameBin0, DestinationType, DestinationNameBin0, RoutingKey, Arguments, VHostPath, ConnPid, AuthzContext, #user{username = Username} = User) -> -diff --git a/deps/rabbit/src/rabbit_channel.erl.rej b/deps/rabbit/src/rabbit_channel.erl.rej -new file mode 100644 -index 0000000..b8316ae ---- /dev/null -+++ b/deps/rabbit/src/rabbit_channel.erl.rej -@@ -0,0 +1,84 @@ -+diff a/deps/rabbit/src/rabbit_channel.erl b/deps/rabbit/src/rabbit_channel.erl (rejected hunks) -+@@ -706,7 +706,9 @@ handle_info(tick, #ch{} = State0) -> -+ end, -+ noreply(init_tick_timer(reset_tick_timer(State0))); -+ handle_info({update_user_state, User}, State = #ch{cfg = Cfg}) -> -+- noreply(State#ch{cfg = Cfg#conf{user = User}}). -++ ok = clear_permission_cache(), -++ State1 = State#ch{cfg = Cfg#conf{user = User}}, -++ noreply(recheck_consumers(State1)). -+ -+ -+ handle_pre_hibernate(State0) -> -+@@ -1720,6 +1722,71 @@ server_consumer_cancel_supported(#ch{cfg = #conf{capabilities = Capabilities}}) -+ {bool, true} == rabbit_misc:table_lookup(Capabilities, -+ <<"consumer_cancel_notify">>). -+ -++%% After a credential update, re-checks read access for all existing consumers. -++%% Consumers that fail the authorization check are cancelled. -++recheck_consumers(State = #ch{cfg = #conf{user = User, -++ authz_context = AuthzContext}, -++ consumer_mapping = CMap}) -> -++ maps:fold( -++ fun(CTag, {Q, _CParams}, StateAcc) when ?is_amqqueue(Q) -> -++ QName = amqqueue:get_name(Q), -++ try -++ check_resource_access(User, QName, read, AuthzContext), -++ StateAcc -++ catch -++ exit:#amqp_error{name = access_refused} -> -++ ?LOG_WARNING( -++ "Cancelling consumer ~tp on ~ts: " -++ "read access refused after credential update", -++ [CTag, rabbit_misc:rs(QName)]), -++ cancel_consumer_recheck(CTag, Q, StateAcc) -++ end -++ end, State, CMap). -++ -++cancel_consumer_recheck(CTag, Q, -++ #ch{cfg = #conf{user = #user{username = Username}}, -++ consumer_mapping = CMap, -++ queue_consumers = QCons, -++ queue_states = QStates0} = State) -> -++ QName = amqqueue:get_name(Q), -++ case server_consumer_cancel_supported(State) of -++ true -> -++ ok = send(#'basic.cancel'{consumer_tag = CTag, -++ nowait = true}, State); -++ false -> -++ ok -++ end, -++ %% Use delete_any because the consumer might not be in queue_consumers yet -++ %% (a `basic.consume-ok` wasn't yet received). -++ QCons1 = case maps:find(QName, QCons) of -++ error -> -++ QCons; -++ {ok, CTags} -> -++ CTags1 = gb_sets:delete_any(CTag, CTags), -++ case gb_sets:is_empty(CTags1) of -++ true -> maps:remove(QName, QCons); -++ false -> maps:put(QName, CTags1, QCons) -++ end -++ end, -++ Spec = #{consumer_tag => CTag, -++ ok_msg => undefined, -++ user => Username}, -++ QStates1 = case rabbit_misc:with_exit_handler( -++ fun() -> {error, not_found} end, -++ fun() -> -++ rabbit_queue_type:cancel(Q, Spec, QStates0) -++ end) of -++ {ok, QS} -> QS; -++ {error, not_found} -> QStates0 -++ end, -++ rabbit_global_counters:consumer_deleted(amqp091), -++ rabbit_event:notify(consumer_deleted, [{consumer_tag, CTag}, -++ {channel, self()}, -++ {queue, QName}]), -++ State#ch{consumer_mapping = maps:remove(CTag, CMap), -++ queue_consumers = QCons1, -++ queue_states = QStates1}. -++ -+ binding_action_with_checks( -+ Action, SourceNameBin0, DestinationType, DestinationNameBin0, -+ RoutingKey, Arguments, VHostPath, ConnPid, AuthzContext, diff --git a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl -index fed666c..1f990db 100644 +index fed666c..df4bb2b 100644 --- a/deps/rabbit/src/rabbit_reader.erl +++ b/deps/rabbit/src/rabbit_reader.erl -@@ -634,6 +634,20 @@ handle_other({bump_credit, Msg}, State) -> +@@ -94,7 +94,8 @@ + %% throttling state, for both + %% credit- and resource-driven flow control + throttle, +- proxy_socket}). ++ proxy_socket, ++ credential_expiry_timer}). + + -record(throttle, { + %% never | timestamp() +@@ -634,6 +635,20 @@ handle_other({bump_credit, Msg}, State) -> %% Here we are receiving credit by some channel process. credit_flow:handle_bump_msg(Msg), control_throttle(State); @@ -226,7 +124,7 @@ index fed666c..1f990db 100644 + State = #v1{connection = #connection{ + user = #user{username = Username}, + log_name = ConnName}}) -> -+ ?LOG_WARNING( ++ rabbit_log:warning( + "closing AMQP connection ~ts of user '~ts': credential has expired", + [dynamic_connection_name(ConnName), Username]), + {ForceTermination, NewState} = terminate("credential expired", State), @@ -237,350 +135,68 @@ index fed666c..1f990db 100644 handle_other(Other, State) -> %% internal error -> something worth dying for maybe_emit_stats(State), -diff --git a/deps/rabbit/src/rabbit_reader.erl.rej b/deps/rabbit/src/rabbit_reader.erl.rej -new file mode 100644 -index 0000000..dd13f04 ---- /dev/null -+++ b/deps/rabbit/src/rabbit_reader.erl.rej -@@ -0,0 +1,94 @@ -+diff a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl (rejected hunks) -+@@ -104,7 +104,8 @@ -+ proxy_socket, -+ %% dynamic buffer -+ dynamic_buffer_size = 128, -+- dynamic_buffer_moving_average = 0.0 -++ dynamic_buffer_moving_average = 0.0, -++ credential_expiry_timer -+ }). -+ -+ -record(throttle, { -+@@ -681,6 +682,20 @@ handle_other({bump_credit, Msg}, State) -> -+ %% Here we are receiving credit by some channel process. -+ credit_flow:handle_bump_msg(Msg), -+ control_throttle(State); -++handle_other(credential_expired, State) when ?IS_STOPPING(State) -> -++ State; -++handle_other(credential_expired, -++ State = #v1{connection = #connection{ -++ user = #user{username = Username}, -++ log_name = ConnName}}) -> -++ ?LOG_WARNING( -++ "closing AMQP connection ~ts of user '~ts': credential has expired", -++ [dynamic_connection_name(ConnName), Username]), -++ {ForceTermination, NewState} = terminate("credential expired", State), -++ case ForceTermination of -++ force -> stop; -++ normal -> NewState -++ end; -+ handle_other(Other, State) -> -+ %% internal error -> something worth dying for -+ maybe_emit_stats(State), -+@@ -696,6 +711,23 @@ terminate(Explanation, State) when ?IS_RUNNING(State) -> -+ terminate(_Explanation, State) -> -+ {force, State}. -+ -++maybe_start_credential_expiry_timer(User, -++ State = #v1{credential_expiry_timer = OldTimer}) -> -++ cancel_credential_expiry_timer(OldTimer), -++ case rabbit_access_control:expiry_timestamp(User) of -++ never -> -++ State#v1{credential_expiry_timer = undefined}; -++ Ts when is_integer(Ts) -> -++ Time = max(0, (Ts - os:system_time(second)) * 1000), -++ Ref = erlang:send_after(Time, self(), credential_expired), -++ State#v1{credential_expiry_timer = Ref} -++ end. -++ -++cancel_credential_expiry_timer(undefined) -> ok; -++cancel_credential_expiry_timer(Ref) -> -++ _ = erlang:cancel_timer(Ref), -++ ok. -++ -+ send_blocked(#v1{connection = #connection{capabilities = Capabilities}, -+ sock = Sock}, Reason) -> -+ case rabbit_misc:table_lookup(Capabilities, <<"connection.blocked">>) of -+@@ -1313,7 +1345,7 @@ handle_method0(#'connection.open'{virtual_host = VHost}, -+ ?LOG_INFO( -+ "connection ~ts: user '~ts' authenticated and granted access to vhost '~ts'", -+ [dynamic_connection_name(ConnName), Username, VHost]), -+- State1; -++ maybe_start_credential_expiry_timer(User, State1); -+ handle_method0(#'connection.close'{}, State) when ?IS_RUNNING(State) -> -+ lists:foreach(fun rabbit_channel:shutdown/1, all_channels()), -+ maybe_close(State#v1{connection_state = closing}); -+@@ -1339,12 +1371,12 @@ handle_method0(#'connection.update_secret'{new_secret = NewSecret, reason = Reas -+ [dynamic_connection_name(ConnName), Username, Reason]), -+ case rabbit_access_control:update_state(User, NewSecret) of -+ {ok, User1} -> -+- %% User/auth backend state has been updated. Now we can propagate it to channels -+- %% asynchronously and return. All the channels have to do is to update their -+- %% own state. -+- %% -+- %% Any secret update errors coming from the authz backend will be handled in the other branch. -+- %% Therefore we optimistically do no error handling here. MK. -++ %% User/auth backend state has been updated. Re-check vhost access -++ %% before proceeding, then propagate to channels. Each channel will -++ %% update its user state and re-check authorization for existing -++ %% consumers, cancelling any that are no longer authorized. -++ VHost = Conn#connection.vhost, -++ ok = rabbit_access_control:check_vhost_access(User1, VHost, {socket, Sock}, #{}), -+ lists:foreach(fun(Ch) -> -+ ?LOG_DEBUG("Updating user/auth backend state for channel ~tp", [Ch]), -+ _ = rabbit_channel:update_user_state(Ch, User1) -+@@ -1353,7 +1385,8 @@ handle_method0(#'connection.update_secret'{new_secret = NewSecret, reason = Reas -+ ?LOG_INFO( -+ "connection ~ts: user '~ts' updated secret, reason: ~ts", -+ [dynamic_connection_name(ConnName), Username, Reason]), -+- State#v1{connection = Conn#connection{user = User1}}; -++ State1 = State#v1{connection = Conn#connection{user = User1}}, -++ maybe_start_credential_expiry_timer(User1, State1); -+ {refused, Message} -> -+ ?LOG_ERROR("Secret update was refused for user '~ts': ~tp", -+ [Username, Message]), -diff --git a/deps/rabbit/test/consumer_recheck_prop_SUITE.erl b/deps/rabbit/test/consumer_recheck_prop_SUITE.erl -new file mode 100644 -index 0000000..fdb1144 ---- /dev/null -+++ b/deps/rabbit/test/consumer_recheck_prop_SUITE.erl -@@ -0,0 +1,238 @@ -+%% This Source Code Form is subject to the terms of the Mozilla Public -+%% License, v. 2.0. If a copy of the MPL was not distributed with this -+%% file, You can obtain one at https://mozilla.org/MPL/2.0/. -+%% -+%% Copyright (c) 2007-2026 Broadcom. All Rights Reserved. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. -+%% -+ -+%% Property-based tests for the consumer recheck algorithm used in -+%% rabbit_channel:recheck_consumers/1 after a credential update. -+%% -+%% The core invariant: after cancelling any subset of consumers, the -+%% consumer_mapping and queue_consumers structures remain consistent -+%% (bijection between the two, no empty gb_sets in queue_consumers). -+ -+-module(consumer_recheck_prop_SUITE). -+ -+-compile(nowarn_export_all). -+-compile(export_all). -+ -+-include_lib("proper/include/proper.hrl"). -+-include_lib("eunit/include/eunit.hrl"). -+ -+all() -> -+ [prop_cancel_preserves_consistency, -+ prop_cancel_correct_membership, -+ prop_cancel_with_pending_consumers]. -+ -+suite() -> -+ [{timetrap, {minutes, 1}}]. -+ -+init_per_suite(Config) -> -+ rabbit_ct_helpers:log_environment(), -+ rabbit_ct_helpers:run_setup_steps(Config). -+ -+end_per_suite(Config) -> -+ rabbit_ct_helpers:run_teardown_steps(Config). -+ -+init_per_testcase(Testcase, Config) -> -+ rabbit_ct_helpers:testcase_started(Config, Testcase). -+ -+end_per_testcase(Testcase, Config) -> -+ rabbit_ct_helpers:testcase_finished(Config, Testcase). -+ -+%% ------------------------------------------------------------------- -+%% Generators -+%% ------------------------------------------------------------------- -+ -+queue_name_gen() -> -+ ?LET(N, range(1, 8), -+ list_to_binary("q" ++ integer_to_list(N))). -+ -+consumer_tag_gen() -> -+ ?LET(N, range(1, 60), -+ list_to_binary("ctag-" ++ integer_to_list(N))). -+ -+%% Generates a deduplicated list of {CTag, QName, Allowed} triples. -+consumer_setup_gen() -> -+ ?LET(RawPairs, -+ list({consumer_tag_gen(), queue_name_gen(), bool()}), -+ begin -+ {_, Deduped} = lists:foldl( -+ fun({CTag, QName, Allowed}, {Seen, Acc}) -> -+ case sets:is_element(CTag, Seen) of -+ true -> {Seen, Acc}; -+ false -> {sets:add_element(CTag, Seen), -+ [{CTag, QName, Allowed} | Acc]} -+ end -+ end, {sets:new(), []}, RawPairs), -+ lists:reverse(Deduped) -+ end). -+ -+%% Like consumer_setup_gen but also marks some consumers as "pending" -- -+%% present in consumer_mapping but NOT yet in queue_consumers. -+%% This models the window before a `basic.consume-ok` is received. -+consumer_setup_with_pending_gen() -> -+ ?LET(RawPairs, -+ list({consumer_tag_gen(), queue_name_gen(), bool(), bool()}), -+ begin -+ {_, Deduped} = lists:foldl( -+ fun({CTag, QName, Allowed, Pending}, {Seen, Acc}) -> -+ case sets:is_element(CTag, Seen) of -+ true -> {Seen, Acc}; -+ false -> {sets:add_element(CTag, Seen), -+ [{CTag, QName, Allowed, Pending} | Acc]} -+ end -+ end, {sets:new(), []}, RawPairs), -+ lists:reverse(Deduped) -+ end). -+ -+%% ------------------------------------------------------------------- -+%% Build state from generated setup -+%% ------------------------------------------------------------------- -+ -+build_state(Setup) -> -+ CMap = maps:from_list([{CTag, QName} || {CTag, QName, _} <- Setup]), -+ QCons = lists:foldl( -+ fun({CTag, QName, _}, Acc) -> -+ CTags0 = maps:get(QName, Acc, gb_sets:new()), -+ maps:put(QName, gb_sets:add_element(CTag, CTags0), Acc) -+ end, #{}, Setup), -+ DeniedCTags = sets:from_list([CTag || {CTag, _, false} <- Setup]), -+ {CMap, QCons, DeniedCTags}. -+ -+build_state_with_pending(Setup) -> -+ CMap = maps:from_list([{CTag, QName} || {CTag, QName, _, _} <- Setup]), -+ QCons = lists:foldl( -+ fun({_CTag, _QName, _, true}, Acc) -> -+ Acc; -+ ({CTag, QName, _, false}, Acc) -> -+ CTags0 = maps:get(QName, Acc, gb_sets:new()), -+ maps:put(QName, gb_sets:add_element(CTag, CTags0), Acc) -+ end, #{}, Setup), -+ DeniedCTags = sets:from_list([CTag || {CTag, _, false, _} <- Setup]), -+ {CMap, QCons, DeniedCTags}. -+ -+%% ------------------------------------------------------------------- -+%% Cancellation logic (same algorithm as rabbit_channel) -+%% ------------------------------------------------------------------- -+ -+apply_recheck(DeniedSet, OrigCMap, CMap0, QCons0) -> -+ maps:fold( -+ fun(CTag, QName, {CMap, QCons}) -> -+ case sets:is_element(CTag, DeniedSet) of -+ false -> -+ {CMap, QCons}; -+ true -> -+ CMap1 = maps:remove(CTag, CMap), -+ QCons1 = -+ case maps:find(QName, QCons) of -+ error -> -+ QCons; -+ {ok, CTags} -> -+ CTags1 = gb_sets:delete_any(CTag, CTags), -+ case gb_sets:is_empty(CTags1) of -+ true -> maps:remove(QName, QCons); -+ false -> maps:put(QName, CTags1, QCons) -+ end -+ end, -+ {CMap1, QCons1} -+ end -+ end, {CMap0, QCons0}, OrigCMap). -+ -+%% ------------------------------------------------------------------- -+%% Consistency checks -+%% ------------------------------------------------------------------- -+ -+is_consistent(CMap, QCons) -> -+ no_empty_sets(QCons) -+ andalso cmap_subset_of_qcons(CMap, QCons) -+ andalso qcons_subset_of_cmap(QCons, CMap). -+ -+%% Weaker invariant for states with pending consumers: every entry in -+%% queue_consumers must appear in consumer_mapping, but not necessarily -+%% the other way around (pending consumers are in CMap only). -+is_weakly_consistent(CMap, QCons) -> -+ no_empty_sets(QCons) -+ andalso qcons_subset_of_cmap(QCons, CMap). -+ -+no_empty_sets(QCons) -> -+ maps:fold(fun(_, CTags, Acc) -> -+ Acc andalso (gb_sets:size(CTags) > 0) -+ end, true, QCons). -+ -+cmap_subset_of_qcons(CMap, QCons) -> -+ maps:fold( -+ fun(CTag, QName, Acc) -> -+ Acc andalso -+ case maps:find(QName, QCons) of -+ {ok, CTags} -> gb_sets:is_member(CTag, CTags); -+ error -> false -+ end -+ end, true, CMap). -+ -+qcons_subset_of_cmap(QCons, CMap) -> -+ maps:fold( -+ fun(QName, CTags, Acc) -> -+ gb_sets:fold( -+ fun(CTag, InnerAcc) -> -+ InnerAcc andalso (maps:get(CTag, CMap, undefined) =:= QName) -+ end, Acc, CTags) -+ end, true, QCons). -+ -+%% ------------------------------------------------------------------- -+%% Properties -+%% ------------------------------------------------------------------- -+ -+%% After cancelling any subset of consumers from a consistent state, -+%% the result is still consistent. -+prop_cancel_preserves_consistency(_Config) -> -+ rabbit_ct_proper_helpers:run_proper( -+ fun() -> -+ ?FORALL(Setup, consumer_setup_gen(), -+ begin -+ {CMap, QCons, Denied} = build_state(Setup), -+ %% Verify the builder produces a consistent initial state -+ ?assert(is_consistent(CMap, QCons)), -+ {CMap1, QCons1} = apply_recheck(Denied, CMap, CMap, QCons), -+ is_consistent(CMap1, QCons1) -+ end) -+ end, [], 5000). -+ -+%% Exactly the denied consumers are removed and all allowed consumers -+%% are retained. -+prop_cancel_correct_membership(_Config) -> -+ rabbit_ct_proper_helpers:run_proper( -+ fun() -> -+ ?FORALL(Setup, consumer_setup_gen(), -+ begin -+ {CMap, QCons, Denied} = build_state(Setup), -+ ?assert(is_consistent(CMap, QCons)), -+ {CMap1, _QCons1} = apply_recheck(Denied, CMap, CMap, QCons), -+ AllowedCTags = [CTag || {CTag, _, true} <- Setup], -+ DeniedCTags = [CTag || {CTag, _, false} <- Setup], -+ lists:all(fun(C) -> not maps:is_key(C, CMap1) end, -+ DeniedCTags) -+ andalso lists:all(fun(C) -> maps:is_key(C, CMap1) end, -+ AllowedCTags) -+ andalso (maps:size(CMap1) =:= length(AllowedCTags)) -+ end) -+ end, [], 5000). -+ -+%% When some consumers are "pending" (in consumer_mapping but not yet in -+%% queue_consumers), the recheck must not crash and the result must -+%% still satisfy the weak invariant. -+prop_cancel_with_pending_consumers(_Config) -> -+ rabbit_ct_proper_helpers:run_proper( -+ fun() -> -+ ?FORALL(Setup, consumer_setup_with_pending_gen(), -+ begin -+ {CMap, QCons, Denied} = build_state_with_pending(Setup), -+ ?assert(is_weakly_consistent(CMap, QCons)), -+ {CMap1, QCons1} = apply_recheck(Denied, CMap, CMap, QCons), -+ is_weakly_consistent(CMap1, QCons1) -+ andalso sets:fold( -+ fun(C, Acc) -> Acc andalso not maps:is_key(C, CMap1) end, -+ true, Denied) -+ end) -+ end, [], 5000). +@@ -649,6 +664,23 @@ terminate(Explanation, State) when ?IS_RUNNING(State) -> + terminate(_Explanation, State) -> + {force, State}. + ++maybe_start_credential_expiry_timer(User, ++ State = #v1{credential_expiry_timer = OldTimer}) -> ++ cancel_credential_expiry_timer(OldTimer), ++ case rabbit_access_control:expiry_timestamp(User) of ++ never -> ++ State#v1{credential_expiry_timer = undefined}; ++ Ts when is_integer(Ts) -> ++ Time = max(0, (Ts - os:system_time(second)) * 1000), ++ Ref = erlang:send_after(Time, self(), credential_expired), ++ State#v1{credential_expiry_timer = Ref} ++ end. ++ ++cancel_credential_expiry_timer(undefined) -> ok; ++cancel_credential_expiry_timer(Ref) -> ++ _ = erlang:cancel_timer(Ref), ++ ok. ++ + send_blocked(#v1{connection = #connection{protocol = Protocol, + capabilities = Capabilities}, + sock = Sock}, Reason) -> +@@ -1286,7 +1318,7 @@ handle_method0(#'connection.open'{virtual_host = VHost}, + "connection ~tp (~ts): " + "user '~ts' authenticated and granted access to vhost '~ts'", + [self(), dynamic_connection_name(ConnName), Username, VHost]), +- State1; ++ maybe_start_credential_expiry_timer(User, State1); + handle_method0(#'connection.close'{}, State) when ?IS_RUNNING(State) -> + lists:foreach(fun rabbit_channel:shutdown/1, all_channels()), + maybe_close(State#v1{connection_state = closing}); +@@ -1314,12 +1346,12 @@ handle_method0(#'connection.update_secret'{new_secret = NewSecret, reason = Reas + [self(), dynamic_connection_name(ConnName), Username, Reason]), + case rabbit_access_control:update_state(User, NewSecret) of + {ok, User1} -> +- %% User/auth backend state has been updated. Now we can propagate it to channels +- %% asynchronously and return. All the channels have to do is to update their +- %% own state. +- %% +- %% Any secret update errors coming from the authz backend will be handled in the other branch. +- %% Therefore we optimistically do no error handling here. MK. ++ %% User/auth backend state has been updated. Re-check vhost access ++ %% before proceeding, then propagate to channels. Each channel will ++ %% update its user state and re-check authorization for existing ++ %% consumers, cancelling any that are no longer authorized. ++ VHost = Conn#connection.vhost, ++ ok = rabbit_access_control:check_vhost_access(User1, VHost, {socket, Sock}, #{}), + lists:foreach(fun(Ch) -> + rabbit_log:debug("Updating user/auth backend state for channel ~tp", [Ch]), + _ = rabbit_channel:update_user_state(Ch, User1) +@@ -1329,7 +1361,8 @@ handle_method0(#'connection.update_secret'{new_secret = NewSecret, reason = Reas + "connection ~tp (~ts): " + "user '~ts' updated secret, reason: ~ts", + [self(), dynamic_connection_name(ConnName), Username, Reason]), +- State#v1{connection = Conn#connection{user = User1}}; ++ State1 = State#v1{connection = Conn#connection{user = User1}}, ++ maybe_start_credential_expiry_timer(User1, State1); + {refused, Message} -> + rabbit_log_connection:error("Secret update was refused for user '~ts': ~tp", + [Username, Message]), -- 2.45.4 diff --git a/SPECS/rabbitmq-server/CVE-2026-57219.patch b/SPECS/rabbitmq-server/CVE-2026-57219.patch index 6ee22168056..976644584af 100644 --- a/SPECS/rabbitmq-server/CVE-2026-57219.patch +++ b/SPECS/rabbitmq-server/CVE-2026-57219.patch @@ -7,9 +7,9 @@ Signed-off-by: Azure Linux Security Servicing Account diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl -index 0b963e4..16abd33 100644 +index 0b963e4..3b8f7ae 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl -@@ -193,9 +193,7 @@ dispatcher() -> - {"/health/checks/node-is-quorum-critical", rabbit_mgmt_wm_health_check_node_is_quorum_critical, []}, +@@ -194,7 +194,6 @@ dispatcher() -> {"/reset", rabbit_mgmt_wm_reset, []}, {"/reset/:node", rabbit_mgmt_wm_reset, []}, -- {"/rebalance/queues", rabbit_mgmt_wm_rebalance_queues, [{queues, all}]}, + {"/rebalance/queues", rabbit_mgmt_wm_rebalance_queues, [{queues, all}]}, - {"/auth", rabbit_mgmt_wm_auth, []}, -- {"/auth/attempts/:node", rabbit_mgmt_wm_auth_attempts, [all]}, -+ {"/rebalance/queues", rabbit_mgmt_wm_rebalance_queues, [{queues, all}]}, {"/auth/attempts/:node", rabbit_mgmt_wm_auth_attempts, [all]}, + {"/auth/attempts/:node", rabbit_mgmt_wm_auth_attempts, [all]}, {"/auth/attempts/:node/source", rabbit_mgmt_wm_auth_attempts, [by_source]}, {"/login", rabbit_mgmt_wm_login, []}, - {"/config/effective", rabbit_mgmt_wm_environment, []}, diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl index 1e7c974..b3212f6 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_wm_auth.erl diff --git a/SPECS/rabbitmq-server/CVE-2026-57220.patch b/SPECS/rabbitmq-server/CVE-2026-57220.patch index 8591be27630..48bedaa4ffc 100644 --- a/SPECS/rabbitmq-server/CVE-2026-57220.patch +++ b/SPECS/rabbitmq-server/CVE-2026-57220.patch @@ -6,8 +6,16 @@ Subject: [PATCH] Limit heap size for unauthenticated connections Signed-off-by: Azure Linux Security Servicing Account Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/commit/595ec28fa1621b1f2c28124e4e0466a8ad963547.patch --- - deps/rabbit/src/rabbit_access_control.erl | 21 +++++++++++++++++++++ - 1 file changed, 21 insertions(+) + deps/rabbit/src/rabbit_access_control.erl | 21 +++++++++++++++++++ + deps/rabbit/src/rabbit_reader.erl | 2 ++ + .../src/rabbit_mqtt_processor.erl | 1 + + deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl | 1 + + .../src/rabbit_stomp_processor.erl | 1 + + .../src/rabbit_stomp_reader.erl | 1 + + .../src/rabbit_stream_reader.erl | 2 ++ + .../src/rabbit_web_mqtt_handler.erl | 1 + + .../src/rabbit_web_stomp_handler.erl | 1 + + 9 files changed, 31 insertions(+) diff --git a/deps/rabbit/src/rabbit_access_control.erl b/deps/rabbit/src/rabbit_access_control.erl index c76610d..ae9c7c7 100644 @@ -45,6 +53,118 @@ index c76610d..ae9c7c7 100644 + %% "If set to zero, the heap size limit is disabled." + _ = erlang:process_flag(max_heap_size, 0), + ok. +diff --git a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl +index df4bb2b..b003ac4 100644 +--- a/deps/rabbit/src/rabbit_reader.erl ++++ b/deps/rabbit/src/rabbit_reader.erl +@@ -162,6 +162,7 @@ shutdown(Pid, Explanation) -> + + init(Parent, HelperSup, Ref) -> + ?LG_PROCESS_TYPE(reader), ++ rabbit_access_control:set_max_heap_size_unauthenticated(rabbit), + {ok, Sock} = rabbit_networking:handshake(Ref, + application:get_env(rabbit, proxy_protocol, false)), + Deb = sys:debug_options([]), +@@ -1515,6 +1516,7 @@ auth_phase(Response, + State#v1{connection = Connection#connection{ + auth_state = AuthState1}}; + {ok, User = #user{username = Username}} -> ++ rabbit_access_control:clear_max_heap_size(), + case rabbit_access_control:check_user_loopback(Username, Sock) of + ok -> + rabbit_core_metrics:auth_attempt_succeeded(RemoteAddress, Username, amqp091), +diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl +index 883d2f2..324d1a7 100644 +--- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl ++++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl +@@ -199,6 +199,7 @@ process_connect( + ok ?= check_vhost_connection_limit(VHost), + {ok, User = #user{username = Username}} ?= check_user_login(VHost, Username2, Password, + ClientId, PeerIp, ConnName0), ++ rabbit_access_control:clear_max_heap_size(), + ok ?= check_user_connection_limit(Username), + {ok, AuthzCtx} ?= check_vhost_access(VHost, User, ClientId, PeerIp), + ok ?= check_user_loopback(Username, PeerIp), +diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl +index 82862b8..cc67ec3 100644 +--- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl ++++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_reader.erl +@@ -71,6 +71,7 @@ close_connection(Pid, Reason) -> + init(Ref) -> + process_flag(trap_exit, true), + logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [mqtt]}), ++ rabbit_access_control:set_max_heap_size_unauthenticated(?APP_NAME), + {ok, Sock} = rabbit_networking:handshake(Ref, + application:get_env(?APP_NAME, proxy_protocol, false)), + RealSocket = rabbit_net:unwrap_socket(Sock), +diff --git a/deps/rabbitmq_stomp/src/rabbit_stomp_processor.erl b/deps/rabbitmq_stomp/src/rabbit_stomp_processor.erl +index 2822897..f2dbabf 100644 +--- a/deps/rabbitmq_stomp/src/rabbit_stomp_processor.erl ++++ b/deps/rabbitmq_stomp/src/rabbit_stomp_processor.erl +@@ -589,6 +589,7 @@ do_login(Username, Passwd, VirtualHost, Heartbeat, AdapterInfo, Version, + virtual_host = VirtualHost, + adapter_info = AdapterInfo}, Username, Addr) of + {ok, Connection} -> ++ rabbit_access_control:clear_max_heap_size(), + link(Connection), + {ok, Channel} = amqp_connection:open_channel(Connection), + link(Channel), +diff --git a/deps/rabbitmq_stomp/src/rabbit_stomp_reader.erl b/deps/rabbitmq_stomp/src/rabbit_stomp_reader.erl +index 7bb9b89..029e9bb 100644 +--- a/deps/rabbitmq_stomp/src/rabbit_stomp_reader.erl ++++ b/deps/rabbitmq_stomp/src/rabbit_stomp_reader.erl +@@ -63,6 +63,7 @@ close_connection(Pid, Reason) -> + + init([SupHelperPid, Ref, Configuration]) -> + process_flag(trap_exit, true), ++ rabbit_access_control:set_max_heap_size_unauthenticated(rabbitmq_stomp), + {ok, Sock} = rabbit_networking:handshake(Ref, + application:get_env(rabbitmq_stomp, proxy_protocol, false)), + RealSocket = rabbit_net:unwrap_socket(Sock), +diff --git a/deps/rabbitmq_stream/src/rabbit_stream_reader.erl b/deps/rabbitmq_stream/src/rabbit_stream_reader.erl +index 9754138..785bb0a 100644 +--- a/deps/rabbitmq_stream/src/rabbit_stream_reader.erl ++++ b/deps/rabbitmq_stream/src/rabbit_stream_reader.erl +@@ -135,6 +135,7 @@ init([KeepaliveSup, + heartbeat := Heartbeat, + transport := ConnTransport}]) -> + process_flag(trap_exit, true), ++ rabbit_access_control:set_max_heap_size_unauthenticated(rabbitmq_stream), + {ok, Sock} = + rabbit_networking:handshake(Ref, + application:get_env(rabbitmq_stream, +@@ -1362,6 +1363,7 @@ handle_frame_pre_auth(Transport, + {sasl_authenticate, ?RESPONSE_SASL_CHALLENGE, + Challenge}}; + {ok, User = #user{username = Username}} -> ++ rabbit_access_control:clear_max_heap_size(), + case + rabbit_access_control:check_user_loopback(Username, + S) +diff --git a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl +index 84f4a8d..b85093c 100644 +--- a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl ++++ b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl +@@ -113,6 +113,7 @@ info(Pid, Items) -> + {cowboy_websocket:commands(), state(), hibernate}. + websocket_init(State0 = #state{socket = Sock, should_use_fhc = ShouldUseFHC}) -> + logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [web_mqtt]}), ++ rabbit_access_control:set_max_heap_size_unauthenticated(?APP_NAME), + case ShouldUseFHC of + true -> + ok = file_handle_cache:obtain(); +diff --git a/deps/rabbitmq_web_stomp/src/rabbit_web_stomp_handler.erl b/deps/rabbitmq_web_stomp/src/rabbit_web_stomp_handler.erl +index 1919396..06cbbce 100644 +--- a/deps/rabbitmq_web_stomp/src/rabbit_web_stomp_handler.erl ++++ b/deps/rabbitmq_web_stomp/src/rabbit_web_stomp_handler.erl +@@ -112,6 +112,7 @@ websocket_init(State = #state{should_use_fhc = ShouldUseFHC}) -> + ok = file_handle_cache:obtain() + end, + process_flag(trap_exit, true), ++ rabbit_access_control:set_max_heap_size_unauthenticated(rabbitmq_web_stomp), + {ok, ProcessorState} = init_processor_state(State), + {ok, rabbit_event:init_stats_timer( + State#state{proc_state = ProcessorState, -- 2.45.4 diff --git a/SPECS/rabbitmq-server/rabbitmq-server.spec b/SPECS/rabbitmq-server/rabbitmq-server.spec index 456a938603f..6296022457d 100644 --- a/SPECS/rabbitmq-server/rabbitmq-server.spec +++ b/SPECS/rabbitmq-server/rabbitmq-server.spec @@ -19,12 +19,13 @@ Patch6: CVE-2026-57211.patch Patch7: CVE-2026-57212.patch Patch8: CVE-2026-57213.patch Patch9: CVE-2026-57214.patch -Patch10: CVE-2026-57215.patch -Patch11: CVE-2026-57217.patch -Patch12: CVE-2026-57218.patch -Patch13: CVE-2026-57219.patch -Patch14: CVE-2026-57220.patch -Patch15: CVE-2026-57221.patch +Patch10: CVE-2026-57215.patch +Patch11: CVE-2026-57217.patch +Patch12: CVE-2026-57218.patch +Patch13: CVE-2026-57219.patch +Patch14: CVE-2026-57220.patch +Patch15: CVE-2026-57221.patch +Patch16: CVE-2026-57216.patch BuildRequires: elixir BuildRequires: erlang @@ -82,7 +83,7 @@ done %changelog * Wed Jul 15 2026 Azure Linux Security Servicing Account - 3.13.7-7 -- Patch for CVE-2026-57221, CVE-2026-57220, CVE-2026-57219, CVE-2026-57218, CVE-2026-57217, CVE-2026-57215, CVE-2026-57214, CVE-2026-57213, CVE-2026-57212, CVE-2026-57211 +- Patch for CVE-2026-57221, CVE-2026-57220, CVE-2026-57219, CVE-2026-57218, CVE-2026-57217, CVE-2026-57215, CVE-2026-57214, CVE-2026-57213, CVE-2026-57212, CVE-2026-57211, CVE-2026-57216 * Wed Jun 17 2026 Azure Linux Security Servicing Account - 3.13.7-6 - Patch for CVE-2026-43973 From 0cd4a7339c8ea979506b375546c252909ca97139 Mon Sep 17 00:00:00 2001 From: Sushil Sati Date: Sat, 18 Jul 2026 06:13:09 +0000 Subject: [PATCH 3/3] Updated for CVE-2026-57218.patch, CVE-2026-57220.patch, CVE-2026-57216.patch, rabbitmq-server.spec --- SPECS/rabbitmq-server/CVE-2026-57216.patch | 4 ++-- SPECS/rabbitmq-server/CVE-2026-57218.patch | 24 +++++++++++----------- SPECS/rabbitmq-server/CVE-2026-57220.patch | 10 ++++----- SPECS/rabbitmq-server/rabbitmq-server.spec | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/SPECS/rabbitmq-server/CVE-2026-57216.patch b/SPECS/rabbitmq-server/CVE-2026-57216.patch index 539146974a4..6308dd5bb3e 100644 --- a/SPECS/rabbitmq-server/CVE-2026-57216.patch +++ b/SPECS/rabbitmq-server/CVE-2026-57216.patch @@ -12,14 +12,14 @@ Upstream-reference: https://patch-diff.githubusercontent.com/raw/rabbitmq/rabbit 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl -index b003ac4..df29bf7 100644 +index f1603f1..65d9d4a 100644 --- a/deps/rabbit/src/rabbit_reader.erl +++ b/deps/rabbit/src/rabbit_reader.erl @@ -1496,6 +1496,7 @@ auth_phase(Response, #connection{protocol = Protocol, auth_mechanism = {Name, AuthMechanism}, auth_state = AuthState, -+ peer_host = PeerHost, ++ peer_host = PeerHost, host = RemoteAddress}, sock = Sock}) -> rabbit_log:debug("Client address during authN phase: ~tp", [RemoteAddress]), diff --git a/SPECS/rabbitmq-server/CVE-2026-57218.patch b/SPECS/rabbitmq-server/CVE-2026-57218.patch index 0b91711ff5a..1c7941d8994 100644 --- a/SPECS/rabbitmq-server/CVE-2026-57218.patch +++ b/SPECS/rabbitmq-server/CVE-2026-57218.patch @@ -12,7 +12,7 @@ Upstream-reference: AI Backport of https://github.com/rabbitmq/rabbitmq-server/c 2 files changed, 112 insertions(+), 10 deletions(-) diff --git a/deps/rabbit/src/rabbit_channel.erl b/deps/rabbit/src/rabbit_channel.erl -index 8297694..6a153e7 100644 +index 8297694..379ba36 100644 --- a/deps/rabbit/src/rabbit_channel.erl +++ b/deps/rabbit/src/rabbit_channel.erl @@ -804,7 +804,9 @@ handle_info(tick, State0 = #ch{queue_states = QueueStates0}) -> @@ -47,7 +47,7 @@ index 8297694..6a153e7 100644 + StateAcc + catch + exit:#amqp_error{name = access_refused} -> -+ rabbit_log:warning( ++ rabbit_log:warning( + "Cancelling consumer ~tp on ~ts: " + "read access refused after credential update", + [CTag, rabbit_misc:rs(QName)]), @@ -83,7 +83,7 @@ index 8297694..6a153e7 100644 + QStates1 = case rabbit_misc:with_exit_handler( + fun() -> {error, not_found} end, + fun() -> -+ rabbit_queue_type:cancel(Q, CTag, undefined, ++ rabbit_queue_type:cancel(Q, CTag, undefined, + Username, QStates0) + end) of + {ok, QS} -> QS; @@ -101,7 +101,7 @@ index 8297694..6a153e7 100644 RoutingKey, Arguments, VHostPath, ConnPid, AuthzContext, #user{username = Username} = User) -> diff --git a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl -index fed666c..df4bb2b 100644 +index fed666c..b551dcb 100644 --- a/deps/rabbit/src/rabbit_reader.erl +++ b/deps/rabbit/src/rabbit_reader.erl @@ -94,7 +94,8 @@ @@ -178,12 +178,12 @@ index fed666c..df4bb2b 100644 - %% - %% Any secret update errors coming from the authz backend will be handled in the other branch. - %% Therefore we optimistically do no error handling here. MK. -+ %% User/auth backend state has been updated. Re-check vhost access -+ %% before proceeding, then propagate to channels. Each channel will -+ %% update its user state and re-check authorization for existing -+ %% consumers, cancelling any that are no longer authorized. -+ VHost = Conn#connection.vhost, -+ ok = rabbit_access_control:check_vhost_access(User1, VHost, {socket, Sock}, #{}), ++ %% User/auth backend state has been updated. Re-check vhost access ++ %% before proceeding, then propagate to channels. Each channel will ++ %% update its user state and re-check authorization for existing ++ %% consumers, cancelling any that are no longer authorized. ++ VHost = Conn#connection.vhost, ++ ok = rabbit_access_control:check_vhost_access(User1, VHost, {socket, Sock}, #{}), lists:foreach(fun(Ch) -> rabbit_log:debug("Updating user/auth backend state for channel ~tp", [Ch]), _ = rabbit_channel:update_user_state(Ch, User1) @@ -192,8 +192,8 @@ index fed666c..df4bb2b 100644 "user '~ts' updated secret, reason: ~ts", [self(), dynamic_connection_name(ConnName), Username, Reason]), - State#v1{connection = Conn#connection{user = User1}}; -+ State1 = State#v1{connection = Conn#connection{user = User1}}, -+ maybe_start_credential_expiry_timer(User1, State1); ++ State1 = State#v1{connection = Conn#connection{user = User1}}, ++ maybe_start_credential_expiry_timer(User1, State1); {refused, Message} -> rabbit_log_connection:error("Secret update was refused for user '~ts': ~tp", [Username, Message]), diff --git a/SPECS/rabbitmq-server/CVE-2026-57220.patch b/SPECS/rabbitmq-server/CVE-2026-57220.patch index 48bedaa4ffc..a66f9c50fe2 100644 --- a/SPECS/rabbitmq-server/CVE-2026-57220.patch +++ b/SPECS/rabbitmq-server/CVE-2026-57220.patch @@ -54,7 +54,7 @@ index c76610d..ae9c7c7 100644 + _ = erlang:process_flag(max_heap_size, 0), + ok. diff --git a/deps/rabbit/src/rabbit_reader.erl b/deps/rabbit/src/rabbit_reader.erl -index df4bb2b..b003ac4 100644 +index b551dcb..f1603f1 100644 --- a/deps/rabbit/src/rabbit_reader.erl +++ b/deps/rabbit/src/rabbit_reader.erl @@ -162,6 +162,7 @@ shutdown(Pid, Explanation) -> @@ -142,26 +142,26 @@ index 9754138..785bb0a 100644 rabbit_access_control:check_user_loopback(Username, S) diff --git a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl -index 84f4a8d..b85093c 100644 +index 84f4a8d..32cf37f 100644 --- a/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl +++ b/deps/rabbitmq_web_mqtt/src/rabbit_web_mqtt_handler.erl @@ -113,6 +113,7 @@ info(Pid, Items) -> {cowboy_websocket:commands(), state(), hibernate}. websocket_init(State0 = #state{socket = Sock, should_use_fhc = ShouldUseFHC}) -> logger:set_process_metadata(#{domain => ?RMQLOG_DOMAIN_CONN ++ [web_mqtt]}), -+ rabbit_access_control:set_max_heap_size_unauthenticated(?APP_NAME), ++ rabbit_access_control:set_max_heap_size_unauthenticated(?APP_NAME), case ShouldUseFHC of true -> ok = file_handle_cache:obtain(); diff --git a/deps/rabbitmq_web_stomp/src/rabbit_web_stomp_handler.erl b/deps/rabbitmq_web_stomp/src/rabbit_web_stomp_handler.erl -index 1919396..06cbbce 100644 +index 1919396..bab18cb 100644 --- a/deps/rabbitmq_web_stomp/src/rabbit_web_stomp_handler.erl +++ b/deps/rabbitmq_web_stomp/src/rabbit_web_stomp_handler.erl @@ -112,6 +112,7 @@ websocket_init(State = #state{should_use_fhc = ShouldUseFHC}) -> ok = file_handle_cache:obtain() end, process_flag(trap_exit, true), -+ rabbit_access_control:set_max_heap_size_unauthenticated(rabbitmq_web_stomp), ++ rabbit_access_control:set_max_heap_size_unauthenticated(rabbitmq_web_stomp), {ok, ProcessorState} = init_processor_state(State), {ok, rabbit_event:init_stats_timer( State#state{proc_state = ProcessorState, diff --git a/SPECS/rabbitmq-server/rabbitmq-server.spec b/SPECS/rabbitmq-server/rabbitmq-server.spec index 6296022457d..872675dd599 100644 --- a/SPECS/rabbitmq-server/rabbitmq-server.spec +++ b/SPECS/rabbitmq-server/rabbitmq-server.spec @@ -25,7 +25,7 @@ Patch12: CVE-2026-57218.patch Patch13: CVE-2026-57219.patch Patch14: CVE-2026-57220.patch Patch15: CVE-2026-57221.patch -Patch16: CVE-2026-57216.patch +Patch16: CVE-2026-57216.patch BuildRequires: elixir BuildRequires: erlang