Skip to content
Merged
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
667 changes: 667 additions & 0 deletions SPECS/erlang/CVE-2026-55951.patch

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions SPECS/erlang/CVE-2026-59696.nopatch
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Fixed by CVE-2026-55951.patch (same upstream commit aba0fe8c2d700bf4ac94607cf7f00e53bbe4042d)
91 changes: 91 additions & 0 deletions SPECS/erlang/CVE-2026-66357.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
From 43ff2d8f9eb8eb2881072225a43276c0758ac187 Mon Sep 17 00:00:00 2001
From: Konrad Pietrzak <konrad@erlang.org>
Date: Mon, 27 Jul 2026 14:50:42 +0200
Subject: [PATCH] inets: Reject obs-fold header continuation in httpd (RFC
9112)

A line starting with SP or HTAB after CRLF in HTTP headers (obs-fold)
was parsed as an independent header by httpd. The key_value/1 helper
strips leading whitespace, promoting the continuation content to a
standalone header. This enables CL.TE request smuggling when httpd
sits behind a folding-aware reverse proxy.

RFC 9112 Section 5.2 requires that a server which does not support
obs-fold MUST reject such requests with 400 Bad Request.

Add a clause in parse_headers/7 that detects obs-fold (SP/HTAB after
CRLF) and returns {error, {bad_request, 400, _}, Version}. Add a
matching error handler in httpd_request_handler to send the 400
response to the client.

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/erlang/otp/commit/220d618d2479a7180b4a06d0c5aacfaa4af4a85b.patch
---
lib/inets/src/http_server/httpd_request.erl | 5 ++++
lib/inets/test/httpd_SUITE.erl | 27 +++++++++++++++++++++
2 files changed, 32 insertions(+)

diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl
index 3b5d90b..f687354 100644
--- a/lib/inets/src/http_server/httpd_request.erl
+++ b/lib/inets/src/http_server/httpd_request.erl
@@ -253,6 +253,11 @@ parse_headers(<<?LF, Octet, Rest/binary>>, Header, Headers, Current, Max,
%% If ?CR is is missing RFC2616 section-19.3
parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, Current, Max,
Options, Result);
+parse_headers(<<?CR, ?LF, Obs:8, _/binary>>, _, _, _, _, _, Result) when
+ Obs =:= ?SP;
+ Obs =:= ?TAB ->
+ HttpVersion = lists:nth(3, lists:reverse(Result)),
+ {error, {bad_request, 400, "obs-fold not supported"}, HttpVersion};
parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, Current, Max,
Options, Result) ->
case http_request:key_value(lists:reverse(Header)) of
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index fa8837a..38a5fd1 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -175,6 +175,7 @@ http_get() ->
max_header,
max_content_length,
ignore_invalid_header,
+ reject_obs_fold,
ipv6,
same_file_name_dir_name
].
@@ -1638,6 +1639,32 @@ ignore_invalid_header(Config) when is_list(Config) ->
{ok,{{_,204,_}, _, _}}
= httpc:request(get, {Url, Header}, [{timeout, 45000} | Opts], [{headers_as_is, true}]).

+%%-------------------------------------------------------------------------
+reject_obs_fold() ->
+ [{doc, "RFC 9112 Section 5.2 - obs-fold MUST be rejected with 400"}].
+reject_obs_fold(Config) when is_list(Config) ->
+ Version = proplists:get_value(http_version, Config),
+ Host = proplists:get_value(host, Config),
+ Port = proplists:get_value(port, Config),
+ Type = proplists:get_value(type, Config),
+ Node = proplists:get_value(node, Config),
+ %% SP obs-fold: line starting with space after CRLF
+ ok = httpd_test_lib:verify_request(
+ Type, Host, Port, Node,
+ "GET /index.html " ++ Version ++ "\r\n" ++
+ "Host:" ++ Host ++ "\r\n" ++
+ "X-Foo: bar\r\n" ++
+ " Transfer-Encoding: chunked\r\n\r\n",
+ [{statuscode, 400}, {version, Version}]),
+ %% HTAB obs-fold: line starting with tab after CRLF
+ ok = httpd_test_lib:verify_request(
+ Type, Host, Port, Node,
+ "GET /index.html " ++ Version ++ "\r\n" ++
+ "Host:" ++ Host ++ "\r\n" ++
+ "X-Foo: bar\r\n" ++
+ "\tX-Injected: evil\r\n\r\n",
+ [{statuscode, 400}, {version, Version}]).
+
%%-------------------------------------------------------------------------
security_1_1(Config) when is_list(Config) ->
security([{http_version, "HTTP/1.1"} | Config]).
--
2.45.4

1 change: 1 addition & 0 deletions SPECS/erlang/CVE-2026-66835.nopatch
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Fixed by CVE-2026-73270.patch (same upstream commit bac19eb3dbd96cc49b6d8cabc1c04248bf8c79f6)
153 changes: 153 additions & 0 deletions SPECS/erlang/CVE-2026-69664.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
From 0b64046d57ce7376ac4814a807dfa56c833ef658 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lukas=20Backstr=C3=B6m?= <lukas@erlang.org>
Date: Mon, 1 Jun 2026 10:26:29 +0200
Subject: [PATCH] inets: Fix rejection of invalid chunk sizes

Before this fix the server would hang indefinitely without the need
to send any bytes to keep it running. This causes it to leak a process
which could be used to DOS the server.

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/erlang/otp/commit/a3adf63078438c86527d704e23282b7721d8ca12.patch
---
.../src/http_server/httpd_request_handler.erl | 22 +++++---
lib/inets/test/httpd_SUITE.erl | 50 ++++++++++++++++++-
2 files changed, 63 insertions(+), 9 deletions(-)

diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl
index 4f71c11..9d28714 100644
--- a/lib/inets/src/http_server/httpd_request_handler.erl
+++ b/lib/inets/src/http_server/httpd_request_handler.erl
@@ -226,7 +226,6 @@ handle_info({Proto, Socket, Data},
(Proto =:= ssl) orelse
(Proto =:= dummy)) andalso is_binary(Data)) ->

- PROCESSED = (catch Module:Function([Data | Args])),
NewDataSize = case State#state.byte_limit of
undefined ->
undefined;
@@ -234,7 +233,7 @@ handle_info({Proto, Socket, Data},
State#state.data + byte_size(Data)
end,

- case PROCESSED of
+ try Module:Function([Data | Args]) of
{ok, Result} ->
NewState = case NewDataSize of
undefined ->
@@ -243,13 +242,15 @@ handle_info({Proto, Socket, Data},
set_new_data_size(cancel_request_timeout(State), NewDataSize)
end,
handle_msg(Result, NewState);
+
{error, {size_error, MaxSize, ErrCode, ErrStr}, Version} ->
NewModData = ModData#mod{http_version = Version},
httpd_response:send_status(NewModData, ErrCode, ErrStr, {max_size, MaxSize}),
{stop, normal, State#state{response_sent = true,
mod = NewModData}};
+
{error, {version_error, ErrCode, ErrStr}, Version} ->
- NewModData = ModData#mod{http_version = Version},
+ NewModData = ModData#mod{http_version = Version},
httpd_response:send_status(NewModData, ErrCode, ErrStr),
{stop, normal, State#state{response_sent = true,
mod = NewModData}};
@@ -259,17 +260,22 @@ handle_info({Proto, Socket, Data},
{stop, normal, State#state{response_sent = true,
mod = NewModData}};

- {http_chunk = Module, Function, Args} when ChunkState =/= undefined ->
- NewState = handle_chunk(Module, Function, Args, State),
- {noreply, NewState};
- NewMFA ->
- setopts(Socket, SockType, [{active, once}]),
+ {http_chunk = Module, Function, Args} when ChunkState =/= undefined ->
+ NewState = handle_chunk(Module, Function, Args, State),
+ {noreply, NewState};
+
+ {_M, _F, _A} = NewMFA ->
+ setopts(Socket, SockType, [{active, once}]),
case NewDataSize of
undefined ->
{noreply, State#state{mfa = NewMFA}};
_ ->
{noreply, State#state{mfa = NewMFA, data = NewDataSize}}
end
+ catch throw:{error, Error} when Module =:= http_chunk ->
+ httpd_response:send_status(ModData, 400,
+ "Bad input", {chunk_decoding, bad_input, Error}),
+ {stop, normal, State#state{response_sent = true}}
end;

%% Error cases
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index 056e7fc..7862082 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -127,7 +127,11 @@ groups() ->
reload_config_file,
reload_invalid_config_survives
]},
- {post, [], [chunked_post, chunked_chunked_encoded_post, post_204, multiple_content_length_header]},
+ {post, [], [chunked_post,
+ chunked_chunked_encoded_post,
+ post_204,
+ chunked_invalid_chunk_size,
+ multiple_content_length_header]},
{basic_auth, [], [basic_auth_1_1, basic_auth_1_0, verify_href_1_1]},
{auth_api, [], [auth_api_1_1, auth_api_1_0]},
{auth_api_dets, [], [auth_api_1_1, auth_api_1_0]},
@@ -894,6 +898,50 @@ post_204(Config) ->
{args, [SockType, Host, Port, TranspOpts]}]})
end.

+%% This test used to make httpd hang
+chunked_invalid_chunk_size(Config) ->
+ Host = proplists:get_value(host, Config),
+ Port = proplists:get_value(port, Config),
+ SockType = proplists:get_value(type, Config),
+ TranspOpts = transport_opts(SockType, Config),
+ try inets_test_lib:connect_bin(SockType, Host, Port, TranspOpts) of
+ {ok, Socket} ->
+ RequestStr = "POST /cgi-bin/erl/httpd_example:post_chunked HTTP/1.1\r\n" ++
+ "Host: " ++ Host ++ "\r\n" ++
+ "Transfer-Encoding: chunked\r\n" ++
+ "\r\n",
+ io:format("Sending request with invalid chunked encoding: '~p'~n", [RequestStr]),
+ ok = inets_test_lib:send(SockType, Socket, RequestStr),
+ receive
+ {tcp, Socket, Data} ->
+ io:format("Received response: '~p'~n", [Data]),
+ ct:fail("Expected server to not send a response yet.")
+ after 1000 ->
+ ok
+ end,
+ io:format("Sending request with too large header: '~p'~n", ["zz\r\n"]),
+ ok = inets_test_lib:send(SockType, Socket, "zz\r\n"),
+ receive
+ {tcp, Socket, Data2} ->
+ io:format("Received response: '~p'~n", [Data2]),
+ case binary:match(Data2, <<"400">>,[]) of
+ nomatch ->
+ ct:fail("Expected 400 Bad Request response.");
+ {_, _} ->
+ ok
+ end
+ after 2000 ->
+ ct:fail(connection_timed_out)
+ end
+ catch
+ T:E:Stk ->
+ ct:fail({connect_failure,
+ [{type, T},
+ {error, E},
+ {stacktrace, Stk},
+ {args, [SockType, Host, Port, TranspOpts]}]})
+ end.
+
%%-------------------------------------------------------------------------
host() ->
[{doc, "Test host header"}].
--
2.45.4

113 changes: 113 additions & 0 deletions SPECS/erlang/CVE-2026-70399.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
From 3a3859b69008ed8567eb3ac0c3b633c41b0a8c50 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lukas=20Backstr=C3=B6m?= <lukas@erlang.org>
Date: Mon, 1 Jun 2026 16:29:47 +0200
Subject: [PATCH] inets: Fix default max_clients

The documentation specified the max_clients to be 150 by default,
but the implementation did not respect that default, so it allowed
an infinite amount of clients.

Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/erlang/otp/commit/d94a94c96eb4cccbd6f7d7da5975f0c035b69612.patch
---
lib/inets/src/http_lib/http_internal.hrl | 1 +
lib/inets/src/http_server/httpd_manager.erl | 47 +++++----------------
2 files changed, 11 insertions(+), 37 deletions(-)

diff --git a/lib/inets/src/http_lib/http_internal.hrl b/lib/inets/src/http_lib/http_internal.hrl
index fbf08f0..20d8c05 100644
--- a/lib/inets/src/http_lib/http_internal.hrl
+++ b/lib/inets/src/http_lib/http_internal.hrl
@@ -30,6 +30,7 @@
-define(HTTP_MAX_VERSION_STRING, 8).
-define(HTTP_MAX_METHOD_STRING, 20).
-define(HTTP_MAX_CONTENT_LENGTH, 100000000).
+-define(HTTP_MAX_CLIENTS, 150).

%%% Response headers
-record(http_response_h,{
diff --git a/lib/inets/src/http_server/httpd_manager.erl b/lib/inets/src/http_server/httpd_manager.erl
index ddaf5f9..e4b150c 100644
--- a/lib/inets/src/http_server/httpd_manager.erl
+++ b/lib/inets/src/http_server/httpd_manager.erl
@@ -22,6 +22,7 @@
-module(httpd_manager).

-include("httpd.hrl").
+-include("../http_lib/http_internal.hrl").

-behaviour(gen_server).

@@ -224,8 +225,8 @@ handle_call({unblock, Blocker}, _, #state{blocker_ref = {Blocker, Monitor},
handle_call({unblock, _}, _, State) ->
{reply, {error, only_blocker_may_unblock}, State};

-handle_call({new_connection, Pid}, _From, State) ->
- {Status, NewState} = handle_new_connection(State, Pid),
+handle_call({new_connection, _Pid}, _From, State) ->
+ {Status, NewState} = handle_new_connection(State),
{reply, Status, NewState};

handle_call(Request, From, State) ->
@@ -319,22 +320,16 @@ code_change(_FromVsn, State, _Extra) ->
%%%--------------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
-handle_new_connection(#state{admin_state = AdminState} = State, Handler) ->
- UsageState = get_ustate(State),
- handle_new_connection(UsageState, AdminState, State, Handler).
-
-handle_new_connection(_UsageState, unblocked,
- #state{config_db = Db, connection_sup = CSup} =
- State, _) ->
- Max = httpd_util:lookup(Db, max_clients),
- case count_children(CSup) of
- Count when Count =< Max ->
- {{ok, accept}, State};
+handle_new_connection(#state{admin_state = unblocked, config_db = Db} = State) ->
+ Children = count_children(State#state.connection_sup),
+ case httpd_util:lookup(Db, max_clients, ?HTTP_MAX_CLIENTS) of
+ MaxClients when MaxClients < Children ->
+ {{reject, busy}, State};
_ ->
- {{reject, busy}, State}
+ {{ok, accept}, State}
end;

-handle_new_connection(_UsageState, _AdminState, State, _Handler) ->
+handle_new_connection(State) ->
{{reject, blocked}, State}.

handle_block(disturbing, infinity,
@@ -424,28 +419,6 @@ check_constant_values(Db, Config) ->
end,
ok.

-
-%% get_ustate(State) -> idle | active | busy
-%%
-%% Retrieve the usage state of the HTTP server:
-%% 0 active connection -> idle
-%% max_clients active connections -> busy
-%% Otherwise -> active
-%%
-get_ustate(State) ->
- get_ustate(count_children(State#state.connection_sup),State).
-
-get_ustate(0,_State) ->
- idle;
-get_ustate(ConnectionCnt,State) ->
- ConfigDB = State#state.config_db,
- case httpd_util:lookup(ConfigDB, max_clients, 150) of
- ConnectionCnt ->
- busy;
- _ ->
- active
- end.
-
make_name(Addr, Port, Profile) ->
httpd_util:make_name("httpd", Addr, Port, Profile).

--
2.45.4

1 change: 1 addition & 0 deletions SPECS/erlang/CVE-2026-70405.nopatch
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Fixed by CVE-2026-55951.patch (same upstream commit aba0fe8c2d700bf4ac94607cf7f00e53bbe4042d)
1 change: 1 addition & 0 deletions SPECS/erlang/CVE-2026-70409.nopatch
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Fixed by CVE-2026-55951.patch (same upstream commit aba0fe8c2d700bf4ac94607cf7f00e53bbe4042d)
Loading
Loading