Skip to content
Open
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
111 changes: 111 additions & 0 deletions SPECS/rabbitmq-server/CVE-2026-43968.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
From df89c6e2e6924b0820467e61bea252486e9baacd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= <essen@ninenines.eu>
Date: Mon, 11 May 2026 12:15:58 +0200
Subject: [PATCH] Make building SSE events more closely match the spec

Also add many more tests.

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/ninenines/cowlib/commit/6165fc40efa159ba1cceee7e7981e790acba5d9c.patch
---
deps/cowlib/src/cow_sse.erl | 64 +++++++++++++++++++++++++++++++++++--
1 file changed, 61 insertions(+), 3 deletions(-)

diff --git a/deps/cowlib/src/cow_sse.erl b/deps/cowlib/src/cow_sse.erl
index 6e7081f..3503089 100644
--- a/deps/cowlib/src/cow_sse.erl
+++ b/deps/cowlib/src/cow_sse.erl
@@ -301,7 +301,8 @@ event_comment(_) ->
[].

event_id(#{id := ID}) ->
- nomatch = binary:match(iolist_to_binary(ID), <<"\n">>),
+ nomatch = binary:match(iolist_to_binary(ID),
+ [<<"\r\n">>, <<"\r">>, <<"\n">>]),
[<<"id: ">>, ID, $\n];
event_id(_) ->
[].
@@ -311,7 +312,8 @@ event_name(#{event := Name0}) ->
is_atom(Name0) -> atom_to_binary(Name0, utf8);
true -> iolist_to_binary(Name0)
end,
- nomatch = binary:match(Name, <<"\n">>),
+ nomatch = binary:match(Name,
+ [<<"\r\n">>, <<"\r">>, <<"\n">>]),
[<<"event: ">>, Name, $\n];
event_name(_) ->
[].
@@ -327,7 +329,8 @@ event_retry(_) ->
[].

prefix_lines(IoData, Prefix) ->
- Lines = binary:split(iolist_to_binary(IoData), <<"\n">>, [global]),
+ Lines = binary:split(iolist_to_binary(IoData),
+ [<<"\r\n">>, <<"\r">>, <<"\n">>], [global]),
[[Prefix, <<": ">>, Line, $\n] || Line <- Lines].

-ifdef(TEST).
@@ -345,5 +348,60 @@ event_test() ->
_ = event(#{retry => 5000}),
_ = event(#{event => "test", data => "test"}),
_ = event(#{id => "test", event => "test", data => "test"}),
+ _ = event(#{data => "test\r\ntest"}),
+ _ = event(#{data => "test\rtest\r"}),
+ _ = event(#{data => "test\ntest"}),
ok.
+
+event_error_test() ->
+ {'EXIT', _} = (catch event(#{id => "test\n"})),
+ {'EXIT', _} = (catch event(#{id => "test\r"})),
+ {'EXIT', _} = (catch event(#{id => "test\r\n"})),
+ {'EXIT', _} = (catch event(#{event => "test\n"})),
+ {'EXIT', _} = (catch event(#{event => "test\r"})),
+ {'EXIT', _} = (catch event(#{event => "test\r\n"})),
+ ok.
+
+identity_test_() ->
+ Tests = [
+ #{data => <<"hello">>},
+ #{event => <<"update">>, data => <<"hello">>},
+ #{id => <<"42">>, data => <<"hello">>},
+ #{data => <<"a\nb">>},
+ #{data => <<"multi\nline\ndata">>},
+ #{event => <<"update">>, data => <<"hello">>},
+ #{id => <<"abc">>, data => <<"x">>},
+ #{comment => <<"c1">>, data => <<"d1">>, event => <<"e1">>, id => <<"i1">>},
+ #{data => <<>>},
+ #{data => <<"data with trailing newline\n">>},
+ #{data => <<"\n">>},
+ #{data => <<"\n\n">>},
+ #{data => <<"">>, id => <<"1">>},
+ #{data => <<"z">>},
+ #{id => <<"17">>},
+ #{data => << <<$a>> || _ <- lists:seq(1,200) >>},
+ #{data => <<"こんにちは世界">>},
+ #{retry => 30000, data => <<"reconnect">>}
+ ],
+ [{lists:flatten(io_lib:format("~0p", [V])),
+ fun() -> true = do_identity_result(V) =:= do_identity_build_parse(V) end}
+ || V <- Tests].
+
+do_identity_build_parse(Event) ->
+ {event, Parsed, _} = parse(iolist_to_binary(event(Event)), init()),
+ case Parsed of
+ #{data := Data} -> Parsed#{data => iolist_to_binary(Data)};
+ _ -> Parsed
+ end.
+
+do_identity_result(E=#{id := ID}) when map_size(E) =:= 1 ->
+ #{
+ last_event_id => ID
+ };
+do_identity_result(Event) ->
+ #{
+ event_type => maps:get(event, Event, <<"message">>),
+ data => maps:get(data, Event, <<>>),
+ last_event_id => maps:get(id, Event, <<>>)
+ }.
-endif.
--
2.45.4

136 changes: 136 additions & 0 deletions SPECS/rabbitmq-server/CVE-2026-7790.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
From d83b148d75a76db9a42b6c0dc50526a8d5b0ba28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= <essen@ninenines.eu>
Date: Mon, 11 May 2026 10:57:28 +0200
Subject: [PATCH] Limit length of transfer-encoding: chunked chunks

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/ninenines/cowlib/commit/a4b8039ce8c93ab00867ef6b7e888822c09f4369.patch
---
deps/cowlib/src/cow_http_te.erl | 78 +++++++++++++++++----------------
1 file changed, 40 insertions(+), 38 deletions(-)

diff --git a/deps/cowlib/src/cow_http_te.erl b/deps/cowlib/src/cow_http_te.erl
index e3473cf..c78b5db 100644
--- a/deps/cowlib/src/cow_http_te.erl
+++ b/deps/cowlib/src/cow_http_te.erl
@@ -138,7 +138,7 @@ stream_chunked(Data, State) ->

%% New chunk.
stream_chunked(Data = << C, _/bits >>, {0, Streamed}, Acc) when C =/= $\r ->
- case chunked_len(Data, Streamed, Acc, 0) of
+ case chunked_len(Data, Streamed, Acc, 0, 0) of
{next, Rest, State, Acc2} ->
stream_chunked(Rest, State, Acc2);
{more, State, Acc2} ->
@@ -174,54 +174,54 @@ stream_chunked(Data, {Rem, Streamed}, Acc) when Rem > 2 ->
{more, << Acc/binary, Data/binary >>, Rem2, {Rem2, Streamed + DataSize}}
end.

-chunked_len(<< $0, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16);
-chunked_len(<< $1, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 1);
-chunked_len(<< $2, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 2);
-chunked_len(<< $3, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 3);
-chunked_len(<< $4, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 4);
-chunked_len(<< $5, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 5);
-chunked_len(<< $6, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 6);
-chunked_len(<< $7, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 7);
-chunked_len(<< $8, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 8);
-chunked_len(<< $9, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 9);
-chunked_len(<< $A, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 10);
-chunked_len(<< $B, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 11);
-chunked_len(<< $C, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 12);
-chunked_len(<< $D, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 13);
-chunked_len(<< $E, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 14);
-chunked_len(<< $F, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 15);
-chunked_len(<< $a, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 10);
-chunked_len(<< $b, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 11);
-chunked_len(<< $c, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 12);
-chunked_len(<< $d, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 13);
-chunked_len(<< $e, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 14);
-chunked_len(<< $f, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 15);
+chunked_len(<< $0, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16, D + 1);
+chunked_len(<< $1, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 1, D + 1);
+chunked_len(<< $2, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 2, D + 1);
+chunked_len(<< $3, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 3, D + 1);
+chunked_len(<< $4, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 4, D + 1);
+chunked_len(<< $5, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 5, D + 1);
+chunked_len(<< $6, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 6, D + 1);
+chunked_len(<< $7, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 7, D + 1);
+chunked_len(<< $8, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 8, D + 1);
+chunked_len(<< $9, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 9, D + 1);
+chunked_len(<< $A, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 10, D + 1);
+chunked_len(<< $B, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 11, D + 1);
+chunked_len(<< $C, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 12, D + 1);
+chunked_len(<< $D, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 13, D + 1);
+chunked_len(<< $E, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 14, D + 1);
+chunked_len(<< $F, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 15, D + 1);
+chunked_len(<< $a, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 10, D + 1);
+chunked_len(<< $b, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 11, D + 1);
+chunked_len(<< $c, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 12, D + 1);
+chunked_len(<< $d, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 13, D + 1);
+chunked_len(<< $e, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 14, D + 1);
+chunked_len(<< $f, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 15, D + 1);
%% Chunk extensions.
%%
%% Note that we currently skip the first character we encounter here,
%% and not in the skip_chunk_ext function. If we latter implement
%% chunk extensions (unlikely) we will need to change this clause too.
-chunked_len(<< C, R/bits >>, S, A, Len) when ?IS_WS(C); C =:= $; -> skip_chunk_ext(R, S, A, Len, 0);
+chunked_len(<< C, R/bits >>, S, A, Len, _) when ?IS_WS(C); C =:= $; -> skip_chunk_ext(R, S, A, Len, 0);
%% Final chunk.
%%
%% When trailers are following we simply return them as the Rest.
%% Then the user code can decide to call the stream_trailers function
%% to parse them. The user can therefore ignore trailers as necessary
%% if they do not wish to handle them.
-chunked_len(<< "\r\n\r\n", R/bits >>, _, <<>>, 0) -> {done, no_trailers, R};
-chunked_len(<< "\r\n\r\n", R/bits >>, _, A, 0) -> {done, A, no_trailers, R};
-chunked_len(<< "\r\n", R/bits >>, _, <<>>, 0) when byte_size(R) > 2 -> {done, trailers, R};
-chunked_len(<< "\r\n", R/bits >>, _, A, 0) when byte_size(R) > 2 -> {done, A, trailers, R};
-chunked_len(_, _, _, 0) -> more;
+chunked_len(<< "\r\n\r\n", R/bits >>, _, <<>>, 0, _) -> {done, no_trailers, R};
+chunked_len(<< "\r\n\r\n", R/bits >>, _, A, 0, _) -> {done, A, no_trailers, R};
+chunked_len(<< "\r\n", R/bits >>, _, <<>>, 0, _) when byte_size(R) > 2 -> {done, trailers, R};
+chunked_len(<< "\r\n", R/bits >>, _, A, 0, _) when byte_size(R) > 2 -> {done, A, trailers, R};
+chunked_len(_, _, _, 0, _) -> more;
%% Normal chunk. Add 2 to Len for the trailing \r\n.
-chunked_len(<< "\r\n", R/bits >>, S, A, Len) -> {next, R, {Len + 2, S}, A};
-chunked_len(<<"\r">>, _, <<>>, _) -> more;
-chunked_len(<<"\r">>, S, A, _) -> {more, {0, S}, A};
-chunked_len(<<>>, _, <<>>, _) -> more;
-chunked_len(<<>>, S, A, _) -> {more, {0, S}, A}.
-
-skip_chunk_ext(R = << "\r", _/bits >>, S, A, Len, _) -> chunked_len(R, S, A, Len);
-skip_chunk_ext(R = <<>>, S, A, Len, _) -> chunked_len(R, S, A, Len);
+chunked_len(<< "\r\n", R/bits >>, S, A, Len, _) -> {next, R, {Len + 2, S}, A};
+chunked_len(<<"\r">>, _, <<>>, _, _) -> more;
+chunked_len(<<"\r">>, S, A, _, _) -> {more, {0, S}, A};
+chunked_len(<<>>, _, <<>>, _, _) -> more;
+chunked_len(<<>>, S, A, _, _) -> {more, {0, S}, A}.
+
+skip_chunk_ext(R = << "\r", _/bits >>, S, A, Len, _) -> chunked_len(R, S, A, Len, 0);
+skip_chunk_ext(R = <<>>, S, A, Len, _) -> chunked_len(R, S, A, Len, 0);
%% We skip up to 128 characters of chunk extensions. The value
%% is hardcoded: chunk extensions are very rarely seen in the
%% wild and Cowboy doesn't do anything with them anyway.
@@ -305,6 +305,7 @@ stream_chunked_n_passes_test() ->
{more, <<"abc">>, 2, {2, 3}} = stream_chunked(<<"\n3\r\nabc">>, {1, 0}),
{more, <<"abc">>, {1, 3}} = stream_chunked(<<"3\r\nabc\r">>, {0, 0}),
{more, <<"abc">>, <<"123">>, {0, 3}} = stream_chunked(<<"3\r\nabc\r\n123">>, {0, 0}),
+ {more, <<>>, 18446744073709551617, _} = stream_chunked(<<"FFFFFFFFFFFFFFFF\r\n">>, {0, 0}),
ok.

stream_chunked_dripfeed_test() ->
@@ -339,7 +340,8 @@ stream_chunked_dripfeed2_test() ->
stream_chunked_error_test_() ->
Tests = [
{<<>>, undefined},
- {<<"\n\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>, {2, 0}}
+ {<<"\n\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>, {2, 0}},
+ {<<"10000000000000000\r\n">>, {0, 0}}
],
[{lists:flatten(io_lib:format("value ~p state ~p", [V, S])),
fun() -> {'EXIT', _} = (catch stream_chunked(V, S)) end}
--
2.45.4

7 changes: 6 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: 3%{?dist}
Release: 4%{?dist}
License: Apache-2.0 and MPL 2.0
Vendor: Microsoft Corporation
Distribution: Azure Linux
Expand All @@ -11,6 +11,8 @@ URL: https://rabbitmq.com
Source0: https://github.com/rabbitmq/%{name}/releases/download/v%{version}/%{name}-%{version}.tar.xz
Patch0: CVE-2025-30219.patch
Patch1: CVE-2025-50200.patch
Patch2: CVE-2026-43968.patch
Patch3: CVE-2026-7790.patch

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

%changelog
* Fri May 15 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 3.13.7-4
- Patch for CVE-2026-7790, CVE-2026-43968

* Wed Oct 29 2025 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 3.13.7-3
- Patch for CVE-2025-50200

Expand Down
Loading