Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions SPECS/rabbitmq-server/CVE-2026-43966.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
From 3d0d824a2f42c9eca5fa057932a8c02b64f33d7f Mon Sep 17 00:00:00 2001
From: AllSpark <allspark@microsoft.com>
Date: Mon, 6 Jul 2026 17:17:58 +0000
Subject: [PATCH] Add invalid_response_headers HTTP/1 option

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: AI Backport of https://github.com/ninenines/cowboy/commit/f77cb9b5e730e300fffb551db1ba5d1c4ed878ef.patch https://github.com/ninenines/gun/commit/4f35609eb37109b106a863fc9ba83d7ee64e3e42.patch
---
deps/cowboy/src/cowboy_http.erl | 66 +++++++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)

diff --git a/deps/cowboy/src/cowboy_http.erl b/deps/cowboy/src/cowboy_http.erl
index ee1e725..a3bfd5b 100644
--- a/deps/cowboy/src/cowboy_http.erl
+++ b/deps/cowboy/src/cowboy_http.erl
@@ -31,6 +31,7 @@
idle_timeout => timeout(),
inactivity_timeout => timeout(),
initial_stream_flow_size => non_neg_integer(),
+ invalid_response_headers => error_terminate | ignore,
linger_timeout => timeout(),
logger => module(),
max_authority_length => non_neg_integer(),
@@ -1043,6 +1044,14 @@ commands(State, StreamID, [{error_response, _, _, _}|Tail]) ->
%% Send an informational response.
commands(State0=#state{socket=Socket, transport=Transport, out_state=wait, streams=Streams},
StreamID, [{inform, StatusCode, Headers}|Tail]) ->
+ case maybe_invalid_response_headers(Headers, State0) of
+ error_terminate ->
+ Reason = {internal_error, invalid_response_header,
+ 'An invalid response header was detected in an informational response.'},
+ terminate(stream_terminate(State0, StreamID, Reason), Reason);
+ ok ->
+ ok
+ end,
%% @todo I'm pretty sure the last stream in the list is the one we want
%% considering all others are queued.
#stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams),
@@ -1063,6 +1072,14 @@ commands(State0=#state{socket=Socket, transport=Transport, out_state=wait, strea
%% @todo Same two things above apply to DATA, possibly promise too.
commands(State0=#state{socket=Socket, transport=Transport, out_state=wait, streams=Streams}, StreamID,
[{response, StatusCode, Headers0, Body}|Tail]) ->
+ case maybe_invalid_response_headers(Headers0, State0) of
+ error_terminate ->
+ Reason = {internal_error, invalid_response_header,
+ 'An invalid response header was detected.'},
+ terminate(stream_terminate(State0, StreamID, Reason), Reason);
+ ok ->
+ ok
+ end,
%% @todo I'm pretty sure the last stream in the list is the one we want
%% considering all others are queued.
#stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams),
@@ -1084,6 +1101,14 @@ commands(State0=#state{socket=Socket, transport=Transport, out_state=wait, strea
commands(State0=#state{socket=Socket, transport=Transport,
opts=Opts, overriden_opts=Override, streams=Streams0, out_state=OutState},
StreamID, [{headers, StatusCode, Headers0}|Tail]) ->
+ case maybe_invalid_response_headers(Headers0, State0) of
+ error_terminate ->
+ Reason = {internal_error, invalid_response_header,
+ 'An invalid response header was detected.'},
+ terminate(stream_terminate(State0, StreamID, Reason), Reason);
+ ok ->
+ ok
+ end,
%% @todo Same as above (about the last stream in the list).
Stream = #stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams0),
Status = cow_http:status_to_integer(StatusCode),
@@ -1188,6 +1213,16 @@ commands(State0=#state{socket=Socket, transport=Transport, streams=Streams0, out
commands(State#state{streams=Streams}, StreamID, Tail);
commands(State0=#state{socket=Socket, transport=Transport, streams=Streams, out_state=OutState},
StreamID, [{trailers, Trailers}|Tail]) ->
+ case maybe_invalid_response_headers(Trailers, State0) of
+ error_terminate ->
+ %% When there are invalid trailer headers the only thing
+ %% we can do is drop the connection.
+ Reason = {internal_error, invalid_response_header,
+ 'An invalid response header was detected in trailers.'},
+ terminate(State0, Reason);
+ ok ->
+ ok
+ end,
case stream_te(OutState, lists:keyfind(StreamID, #stream.id, Streams)) of
trailers ->
ok = maybe_socket_error(State0,
@@ -1209,6 +1244,14 @@ commands(State0=#state{socket=Socket, transport=Transport, streams=Streams, out_
commands(State0=#state{ref=Ref, parent=Parent, socket=Socket, transport=Transport,
out_state=OutState, opts=Opts, buffer=Buffer, children=Children}, StreamID,
[{switch_protocol, Headers, Protocol, InitialState}|_Tail]) ->
+ case maybe_invalid_response_headers(Headers, State0) of
+ error_terminate ->
+ Reason = {internal_error, invalid_response_header,
+ 'An invalid response header was detected when switching protocol.'},
+ terminate(stream_terminate(State0, StreamID, Reason), Reason);
+ ok ->
+ ok
+ end,
%% @todo If there's streams opened after this one, fail instead of 101.
State1 = cancel_timeout(State0),
%% Before we send the 101 response we need to stop receiving data
@@ -1267,6 +1310,29 @@ commands(State=#state{opts=Opts}, StreamID, [Log={log, _, _, _}|Tail]) ->
commands(State, StreamID, [{push, _, _, _, _, _, _, _}|Tail]) ->
commands(State, StreamID, Tail).

+maybe_invalid_response_headers(Headers, #state{opts=Opts}) ->
+ case maps:get(invalid_response_headers, Opts, error_terminate) of
+ error_terminate ->
+ It = maps:iterator(Headers),
+ maybe_invalid_response_headers(maps:next(It));
+ ignore ->
+ ok
+ end.
+
+maybe_invalid_response_headers({_, V, It}) when is_binary(V) ->
+ case binary:match(V, [<<$\r>>, <<$\n>>]) of
+ nomatch ->
+ maybe_invalid_response_headers(maps:next(It));
+ _ ->
+ error_terminate
+ end;
+maybe_invalid_response_headers({K, V, It}) ->
+ %% We build a temporary binary for simplicity's sake
+ %% when we have a list/iolist.
+ maybe_invalid_response_headers({K, iolist_to_binary(V), It});
+maybe_invalid_response_headers(none) ->
+ ok.
+
%% The set-cookie header is special; we can only send one cookie per header.
headers_to_list(Headers0=#{<<"set-cookie">> := SetCookies}) ->
Headers1 = maps:to_list(maps:remove(<<"set-cookie">>, Headers0)),
--
2.45.4

6 changes: 5 additions & 1 deletion SPECS/rabbitmq-server/rabbitmq-server.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,6 +15,7 @@ Patch2: CVE-2026-8466.patch
Patch3: CVE-2026-43968.patch
Patch4: CVE-2026-7790.patch
Patch5: CVE-2026-43973.patch
Patch6: CVE-2026-43966.patch

BuildRequires: elixir
BuildRequires: erlang
Expand Down Expand Up @@ -71,6 +72,9 @@ done
%{_libdir}/rabbitmq/lib/rabbitmq_server-%{version}/*

%changelog
* Mon Jul 06 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 3.13.7-7
- Patch for CVE-2026-43966

* Wed Jun 17 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 3.13.7-6
- Patch for CVE-2026-43973

Expand Down
Loading