public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Peter Lemenkov <lemenkov@gmail.com>
To: git-commits@fedoraproject.org
Subject: [rpms/erlang] f44: CVEs backported
Date: Fri, 10 Jul 2026 16:26:05 GMT [thread overview]
Message-ID: <178370076588.1.16671500192109066630.rpms-erlang-8f8d34b6606f@fedoraproject.org> (raw)
A new commit has been pushed.
Repo : rpms/erlang
Branch : f44
Commit : 8f8d34b6606f31ff805813ba4e83ecf7f24919dd
Author : Peter Lemenkov <lemenkov@gmail.com>
Date : 2026-07-10T17:45:57+02:00
Stats : +532/-2 in 7 file(s)
URL : https://src.fedoraproject.org/rpms/erlang/c/8f8d34b6606f31ff805813ba4e83ecf7f24919dd?branch=f44
Log:
CVEs backported
Signed-off-by: Peter Lemenkov <lemenkov@gmail.com>
---
diff --git a/erlang.spec b/erlang.spec
index 5fff741..a3cba5c 100644
--- a/erlang.spec
+++ b/erlang.spec
@@ -70,7 +70,7 @@
Name: erlang
Version: 26.2.5.21
-Release: 3%{?dist}
+Release: 4%{?dist}
Summary: General-purpose programming language and runtime environment
License: Apache-2.0
@@ -109,6 +109,12 @@ Patch7: otp-0007-Avoid-forking-sed-to-get-basename.patch
Patch8: otp-0008-Load-man-pages-from-system-wide-directory.patch
Patch9: otp-0009-Add-GDB-tools.patch
Patch10: otp-0010-Fix-absolute-path-leak-from-SSH_FXP_READLINK.patch
+Patch11: otp-0011-ftp-validate-PASV-response-IP-against-control-connec.patch
+Patch12: otp-0012-ssl-Correct-function-used-to-check-peer-IP.patch
+Patch13: otp-0013-Protect-the-output-term-buffer-from-overflow.patch
+Patch14: otp-0014-Fix-extended-data-infinite-loop-in-sftpd.patch
+Patch15: otp-0015-ssl-TLS-Client-hardening.patch
+Patch16: otp-0016-ssl-Add-PSK-parameter-check.patch
# end of autogenerated patch tag list
BuildRequires: gcc
@@ -245,7 +251,7 @@ A byte code compiler for Erlang which produces highly compact code.
%package crypto
Summary: Cryptographical support
BuildRequires: pkgconfig(openssl)
-%if 0%{?fedora} > 40
+%if 0%{?fedora} > 40 && 0%{?fedora} < 45
BuildRequires: openssl-devel-engine
%endif
Requires: %{name}-erts%{?_isa} = %{version}-%{release}
@@ -1957,6 +1963,10 @@ ERL_TOP=${ERL_TOP} make TARGET=${TARGET} release_tests
%changelog
+* Fri Jul 10 2026 Peter Lemenkov <lemenkov@gmail.com> - 26.2.5.21-4
+- Backport fix for CVE-2026-48858, CVE-2026-48860, CVE-2026-49759,
+ CVE-2026-54886, CVE-2026-54891, CVE-2026-55952
+
* Sat Jun 13 2026 Peter Lemenkov <lemenkov@gmail.com> - 26.2.5.21-3
- Backport fix for CVE-2026-48855
diff --git a/otp-0011-ftp-validate-PASV-response-IP-against-control-connec.patch b/otp-0011-ftp-validate-PASV-response-IP-against-control-connec.patch
new file mode 100644
index 0000000..c88cc9b
--- /dev/null
+++ b/otp-0011-ftp-validate-PASV-response-IP-against-control-connec.patch
@@ -0,0 +1,199 @@
+From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= <jonatan@maennchen.ch>
+Date: Wed, 3 Jun 2026 09:57:54 +0200
+Subject: [PATCH] ftp: validate PASV response IP against control connection
+ peer
+
+The PASV handler (mode=passive, ipfamily=inet, ftp_extension=false)
+was connecting the data channel to the IP address advertised in the
+server's 227 response without validating it against the control
+connection peer address. A malicious or compromised FTP server could
+redirect the data connection to an arbitrary host, enabling SSRF and
+FTP bounce attacks.
+
+Fix by deriving the data connection IP from peername/1 on the control
+socket, ignoring the server-supplied value. This matches the behaviour
+of the adjacent EPSV handlers (inet6 and ftp_extension=true paths).
+
+Fixes GHSA-24cv-hwgr-37fq
+Fixes CVE-2026-48858
+
+diff --git a/lib/ftp/src/ftp_internal.erl b/lib/ftp/src/ftp_internal.erl
+index 77b82f0932..ad78556508 100644
+--- a/lib/ftp/src/ftp_internal.erl
++++ b/lib/ftp/src/ftp_internal.erl
+@@ -1504,6 +1504,7 @@ handle_ctrl_result({pos_compl, Lines},
+ ipfamily = inet,
+ client = From,
+ caller = {setup_data_connection, Caller},
++ csock = CSock,
+ timeout = Timeout,
+ sockopts_data_passive = SockOpts,
+ ftp_extension = false} = State) when is_list(Lines) ->
+@@ -1512,10 +1513,10 @@ handle_ctrl_result({pos_compl, Lines},
+ lists:splitwith(fun(?LEFT_PAREN) -> false; (_) -> true end, Lines),
+ {NewPortAddr, _} =
+ lists:splitwith(fun(?RIGHT_PAREN) -> false; (_) -> true end, Rest),
+- [A1, A2, A3, A4, P1, P2] =
++ [_, _, _, _, P1, P2] =
+ lists:map(fun(X) -> list_to_integer(X) end,
+ string:tokens(NewPortAddr, [$,])),
+- IP = {A1, A2, A3, A4},
++ {ok, {IP, _}} = peername(CSock),
+ Port = (P1 * 256) + P2,
+
+ ?DBG('<--data tcp connect to ~p:~p, Caller=~p~n',[IP,Port,Caller]),
+diff --git a/lib/ftp/test/ftp_SUITE.erl b/lib/ftp/test/ftp_SUITE.erl
+index c1284f6ff6..3a9a145af6 100644
+--- a/lib/ftp/test/ftp_SUITE.erl
++++ b/lib/ftp/test/ftp_SUITE.erl
+@@ -63,7 +63,8 @@ all() ->
+ appup,
+ error_ehost,
+ error_datafail,
+- clean_shutdown
++ clean_shutdown,
++ pasv_ip_not_validated
+ ].
+
+ groups() ->
+@@ -317,6 +318,8 @@ init_per_testcase(Case, Config0) ->
+ clean_shutdown ->
+ Config = start_ftpd(Config0),
+ init_per_testcase2(Case, Config);
++ pasv_ip_not_validated ->
++ Config0;
+ _ ->
+ init_per_testcase2(Case, Config0)
+ end.
+@@ -376,6 +379,7 @@ end_per_testcase(user, _Config) -> ok;
+ end_per_testcase(bad_user, _Config) -> ok;
+ end_per_testcase(error_elogin, _Config) -> ok;
+ end_per_testcase(error_ehost, _Config) -> ok;
++end_per_testcase(pasv_ip_not_validated, _Config) -> ok;
+ end_per_testcase(T, Config) when T =:= error_datafail; T =:= clean_shutdown ->
+ T == error_datafail andalso ftp__close(Config),
+ stop_ftpd(Config),
+@@ -1099,6 +1103,69 @@ error_datafail(Config) ->
+ Result = Recv(Recv),
+ Result.
+
++pasv_ip_not_validated() ->
++ [{doc, "PASV response IP must be validated against the control connection "
++ "peer address (CVE-2026-48858 / GHSA-24cv-hwgr-37fq). A malicious server "
++ "must not be able to redirect the data connection to an arbitrary host."}].
++
++pasv_ip_not_validated(_Config) ->
++ %% The victim service listens on 127.0.0.2 (a different loopback address).
++ %% The malicious FTP server listens on 127.0.0.1.
++ %% The PASV response will advertise 127.0.0.2:VictimPort.
++ %% Without the fix the client connects to 127.0.0.2 (victim).
++ %% With the fix the client ignores the IP in PASV and uses the control
++ %% peer address (127.0.0.1) instead, so the victim never gets a connection.
++ VictimIP = {127,0,0,2},
++ {ok, VictimLSock} = gen_tcp:listen(0,
++ [binary, {reuseaddr, true}, {active, false}, inet, {ip, VictimIP}]),
++ {ok, VictimPort} = inet:port(VictimLSock),
++
++ Self = self(),
++ spawn(fun() ->
++ case gen_tcp:accept(VictimLSock, 3000) of
++ {ok, Sock} ->
++ {ok, Peer} = inet:peername(Sock),
++ gen_tcp:close(Sock),
++ Self ! {victim_connected, Peer};
++ {error, _} ->
++ Self ! victim_not_connected
++ end
++ end),
++
++ %% Malicious FTP server on 127.0.0.1.
++ {ok, FtpLSock} = gen_tcp:listen(0,
++ [binary, {reuseaddr, true}, {active, false}, inet, {ip, {127,0,0,1}}]),
++ {ok, FtpPort} = inet:port(FtpLSock),
++
++ spawn_link(fun() -> malicious_ftp_server(FtpLSock, {VictimIP, VictimPort}) end),
++
++ application:ensure_started(ftp),
++ {ok, Pid} = ftp:open("127.0.0.1", [{port, FtpPort}]),
++ ok = ftp:user(Pid, "user", "pass"),
++ %% The ls call will trigger PASV. With the vulnerability present the
++ %% client connects to VictimPort; with the fix it should refuse to do so
++ %% and return an error instead.
++ _Ignored = ftp:ls(Pid),
++ catch ftp:close(Pid),
++
++ Result = receive
++ {victim_connected, Peer} ->
++ {fail, Peer};
++ victim_not_connected ->
++ ok
++ end,
++
++ gen_tcp:close(FtpLSock),
++ gen_tcp:close(VictimLSock),
++
++ case Result of
++ {fail, FailPeer} ->
++ ct:fail("ftp client connected data channel to redirected victim "
++ "address ~p instead of the FTP server (CVE-2026-48858)",
++ [FailPeer]);
++ ok ->
++ ok
++ end.
+ %%--------------------------------------------------------------------
+ %% Internal functions -----------------------------------------------
+ %%--------------------------------------------------------------------
+@@ -1410,3 +1477,53 @@ unwanted_error_report(LogFile) ->
+ ct:fail({no_logfile, LogFile})
+ end.
+
++%% Minimal FTP server that injects a malicious PASV redirect.
++malicious_ftp_server(LSock, VictimAddr) ->
++ {ok, Ctrl} = gen_tcp:accept(LSock),
++ gen_tcp:send(Ctrl, "220 PoC FTP Server\r\n"),
++ malicious_ftp_loop(Ctrl, VictimAddr).
++
++malicious_ftp_loop(Ctrl, VictimAddr) ->
++ case malicious_ftp_recv_line(Ctrl) of
++ {ok, Line} ->
++ [Cmd | _] = string:tokens(string:trim(Line), " "),
++ malicious_ftp_handle(string:uppercase(Cmd), Ctrl, VictimAddr),
++ malicious_ftp_loop(Ctrl, VictimAddr);
++ {error, _} ->
++ gen_tcp:close(Ctrl)
++ end.
++
++malicious_ftp_handle("USER", Ctrl, _) ->
++ gen_tcp:send(Ctrl, "331 Password required\r\n");
++malicious_ftp_handle("PASS", Ctrl, _) ->
++ gen_tcp:send(Ctrl, "230 Logged in\r\n");
++malicious_ftp_handle("SYST", Ctrl, _) ->
++ gen_tcp:send(Ctrl, "215 UNIX Type: L8\r\n");
++malicious_ftp_handle("TYPE", Ctrl, _) ->
++ gen_tcp:send(Ctrl, "200 Type set\r\n");
++malicious_ftp_handle("PASV", Ctrl, {{A1,A2,A3,A4}, VictimPort}) ->
++ %% Advertise the victim IP:port — a different host than the FTP server.
++ P1 = VictimPort bsr 8,
++ P2 = VictimPort band 16#FF,
++ Resp = io_lib:format(
++ "227 Entering Passive Mode (~b,~b,~b,~b,~b,~b)\r\n",
++ [A1, A2, A3, A4, P1, P2]),
++ gen_tcp:send(Ctrl, Resp);
++malicious_ftp_handle(Cmd, Ctrl, _) when Cmd =:= "LIST"; Cmd =:= "NLST" ->
++ gen_tcp:send(Ctrl, "150 Opening data connection\r\n"),
++ timer:sleep(200),
++ gen_tcp:send(Ctrl, "226 Transfer complete\r\n");
++malicious_ftp_handle("QUIT", Ctrl, _) ->
++ gen_tcp:send(Ctrl, "221 Goodbye\r\n"),
++ gen_tcp:close(Ctrl);
++malicious_ftp_handle(_, Ctrl, _) ->
++ gen_tcp:send(Ctrl, "500 Unknown command\r\n").
++
++malicious_ftp_recv_line(Sock) ->
++ malicious_ftp_recv_line(Sock, <<>>).
++malicious_ftp_recv_line(Sock, Acc) ->
++ case gen_tcp:recv(Sock, 1, 5000) of
++ {ok, <<"\n">>} -> {ok, binary_to_list(<<Acc/binary, "\n">>)};
++ {ok, Byte} -> malicious_ftp_recv_line(Sock, <<Acc/binary, Byte/binary>>);
++ {error, _} = E -> E
++ end.
diff --git a/otp-0012-ssl-Correct-function-used-to-check-peer-IP.patch b/otp-0012-ssl-Correct-function-used-to-check-peer-IP.patch
new file mode 100644
index 0000000..54fb51c
--- /dev/null
+++ b/otp-0012-ssl-Correct-function-used-to-check-peer-IP.patch
@@ -0,0 +1,18 @@
+From: Ingela Anderton Andin <ingela@erlang.org>
+Date: Fri, 29 May 2026 15:01:58 +0200
+Subject: [PATCH] ssl: Correct function used to check peer IP
+
+
+diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl
+index 776886e6fe..03fb4d6573 100644
+--- a/lib/ssl/src/inet_tls_dist.erl
++++ b/lib/ssl/src/inet_tls_dist.erl
+@@ -708,7 +708,7 @@ check_ip(Socket) ->
+ end,
+ {ok, Ifaddrs} ?= inet:getifaddrs(),
+ {ok, Netmask} ?= find_netmask(IP, Ifaddrs),
+- {ok, {PeerIP, _}} ?= inet:sockname(Socket),
++ {ok, {PeerIP, _}} ?= inet:peername(Socket),
+ ok ?= if is_tuple(PeerIP) -> ok;
+ true -> {error, {no_ip_address, PeerIP}}
+ end,
diff --git a/otp-0013-Protect-the-output-term-buffer-from-overflow.patch b/otp-0013-Protect-the-output-term-buffer-from-overflow.patch
new file mode 100644
index 0000000..b6b167a
--- /dev/null
+++ b/otp-0013-Protect-the-output-term-buffer-from-overflow.patch
@@ -0,0 +1,163 @@
+From: Raimo Niskanen <raimo@erlang.org>
+Date: Tue, 2 Jun 2026 18:16:04 +0200
+Subject: [PATCH] Protect the output term buffer from overflow
+
+Introduce an overflow check in the loop that can overrun
+the output buffer, if a malicious ERROR chunk or ABORT chunk
+is parsed. There has to be more error causes with larger
+cause specific information than possibly can happen.
+
+There are no out of bounds checks for any other message parsing,
+since all other parsing uses fixed and limited lengths.
+
+diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c
+index 6ea48c85bc..c7c3b73ff8 100644
+--- a/erts/emulator/drivers/common/inet_drv.c
++++ b/erts/emulator/drivers/common/inet_drv.c
+@@ -3390,7 +3390,7 @@ static int sctp_parse_ancillary_data
+ ** concerns the protocol implementation), so we omit it:
+ */
+ static int sctp_parse_error_chunk
+- (ErlDrvTermData * spec, int i, char * chunk, int chlen)
++ (ErlDrvTermData * spec, int i, int spec_size, char * chunk, int chlen)
+ {
+ /* The "chunk" itself contains its length, which must not be greater than
+ the "chlen" derived from the over-all msg size:
+@@ -3413,16 +3413,26 @@ static int sctp_parse_error_chunk
+ {
+ ccode = sock_ntohs (*((uint16_t*)(cause)));
+ clen = sock_ntohs (*((uint16_t*)(cause + 2)));
+- if (clen <= 0)
+- /* Strange, but must guard against that! */
+- break;
+-
+- /* Install the corresp atom for this "ccode": */
++ /* Install the corresp atom for this "ccode": */
+ i = LOAD_INT (spec, i, ccode);
++ s ++;
++ if (clen <= 0 || /* Overflow - truncate here */
++ i + 2*LOAD_INT_CNT+LOAD_NIL_CNT+LOAD_LIST_CNT > spec_size)
++ /* We do not have room for two INT:s which is
++ * the worst case for the next iteration, so truncate now.
++ *
++ * We should have room for the truncation marker since
++ * we have added at most one INT after the previous check.
++ */ {
++ i = LOAD_INT (spec, i, 0);
++ /* Truncation marker - there is no error 0 */
++ s ++;
++ break;
++ }
+ cause += clen;
+ coff += clen;
+- s ++;
+ }
++ /* Finalize the list */
+ i = LOAD_NIL (spec, i);
+ i = LOAD_LIST(spec, i, s+1);
+ return i;
+@@ -3433,7 +3443,7 @@ static int sctp_parse_error_chunk
+ ** are sent IN PLACE OF, not in conjunction with, the normal data:
+ */
+ static int sctp_parse_async_event
+- (ErlDrvTermData * spec, int i, int ok_pos,
++ (ErlDrvTermData * spec, int i, int spec_size, int ok_pos,
+ ErlDrvTermData error_atom, inet_descriptor* desc,
+ ErlDrvBinary * bin, int offs, int sz)
+ {
+@@ -3567,7 +3577,7 @@ static int sctp_parse_async_event
+ + sizeof(sptr->sre_assoc_id);
+ # endif
+ chlen = sptr->sre_length - (chunk - (char *)sptr);
+- i = sctp_parse_error_chunk(spec, i, chunk, chlen);
++ i = sctp_parse_error_chunk(spec, i, spec_size, chunk, chlen);
+
+ i = LOAD_TUPLE (spec, i, 4);
+ /* The {error, {...}} will be closed by the caller */
+@@ -3807,6 +3817,7 @@ inet_async_binary_data
+ ErlDrvBinary * bin, int offs, int len, void *mp)
+ {
+ unsigned int hsz = desc->hsz + phsz;
++ const int spec_size = PACKET_ERL_DRV_TERM_DATA_LEN;
+ ErlDrvTermData spec [PACKET_ERL_DRV_TERM_DATA_LEN];
+ ErlDrvTermData caller;
+ int aid;
+@@ -3857,7 +3868,8 @@ inet_async_binary_data
+ condition; in the latter case, the 'ok' above is overridden by
+ an 'error', and the Event we receive contains the error term: */
+ i = sctp_parse_async_event
+- (spec, i, ok_pos, am_error, desc, bin, offs+hsz, sz);
++ (spec, i, spec_size - 3*LOAD_TUPLE_CNT,
++ ok_pos, am_error, desc, bin, offs+hsz, sz);
+ else
+ /* This is SCTP data, not a notification event. The data can be
+ returned as a List or as a Binary, similar to the generic case:
+@@ -4041,6 +4053,7 @@ static int packet_binary_message(inet_descriptor* desc,
+ void *mp)
+ {
+ unsigned int hsz = desc->hsz;
++ const int spec_size = PACKET_ERL_DRV_TERM_DATA_LEN;
+ ErlDrvTermData spec [PACKET_ERL_DRV_TERM_DATA_LEN];
+ int i = 0;
+ int alen;
+@@ -4103,14 +4116,16 @@ static int packet_binary_message(inet_descriptor* desc,
+ i = sctp_parse_ancillary_data (spec, i, mptr);
+
+ /* Then: Data or Event (Notification)? */
+- if (mptr->msg_flags & MSG_NOTIFICATION)
++ if (mptr->msg_flags & MSG_NOTIFICATION) {
+ /* This is an Event, parse it. It may indicate a normal or an error
+ condition; in the latter case, the initial 'sctp' atom is over-
+ ridden by 'sctp_error', and the Event we receive contains the
+ error term: */
+ i = sctp_parse_async_event
+- (spec, i, 0, am_sctp_error, desc, bin, offs, len);
+- else
++ (spec, i, spec_size - 2*LOAD_TUPLE_CNT,
++ 0, am_sctp_error, desc, bin, offs, len);
++ }
++ else {
+ /* This is SCTP data, not a notification event. The data can be
+ returned as a List or as a Binary, similar to the generic case:
+ */
+@@ -4122,6 +4137,7 @@ static int packet_binary_message(inet_descriptor* desc,
+ else
+ /* INET_MODE_BINARY => Binary */
+ i = LOAD_BINARY(spec, i, bin, offs, len);
++ }
+
+ /* Close up the {[AncilData], Event_OR_Data} tuple: */
+ i = LOAD_TUPLE (spec, i, 2);
+@@ -9594,8 +9610,8 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
+ int i = 0;
+ int length = 0; /* Number of result list entries */
+
+- int spec_allocated = PACKET_ERL_DRV_TERM_DATA_LEN;
+- spec = ALLOC(sizeof(* spec) * spec_allocated);
++ int spec_size = PACKET_ERL_DRV_TERM_DATA_LEN;
++ spec = ALLOC(sizeof(* spec) * spec_size);
+
+ # define RETURN_ERROR(Spec, Errno) \
+ do { \
+@@ -9607,7 +9623,7 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
+ # define PLACE_FOR(Spec, Index, N) \
+ do { \
+ int need; \
+- if ((Index) > spec_allocated) { \
++ if ((Index) > spec_size) { \
+ erts_exit(ERTS_ERROR_EXIT,"Internal error in inet_drv, " \
+ "miscalculated buffer size"); \
+ } \
+@@ -9615,10 +9631,10 @@ static ErlDrvSSizeT sctp_fill_opts(inet_descriptor* desc,
+ if (need > INET_MAX_OPT_BUFFER/sizeof(ErlDrvTermData)) {\
+ RETURN_ERROR((Spec), -ENOMEM); \
+ } \
+- if (need > spec_allocated) { \
++ if (need > spec_size) { \
+ (Spec) = REALLOC((Spec), \
+ sizeof(* (Spec)) \
+- * (spec_allocated = need + 20)); \
++ * (spec_size = need + 20)); \
+ } \
+ } while (0)
+
diff --git a/otp-0014-Fix-extended-data-infinite-loop-in-sftpd.patch b/otp-0014-Fix-extended-data-infinite-loop-in-sftpd.patch
new file mode 100644
index 0000000..eb92721
--- /dev/null
+++ b/otp-0014-Fix-extended-data-infinite-loop-in-sftpd.patch
@@ -0,0 +1,74 @@
+From: =?UTF-8?q?Micha=C5=82=20W=C4=85sowski?= <michal@erlang.org>
+Date: Thu, 25 Jun 2026 11:33:33 +0200
+Subject: [PATCH] Fix extended data infinite loop in sftpd
+
+
+diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
+index ea29fb7c02..8de37bd712 100644
+--- a/lib/ssh/src/ssh_sftpd.erl
++++ b/lib/ssh/src/ssh_sftpd.erl
+@@ -208,7 +208,7 @@ handle_data(0, ChannelId, <<?UINT32(Len), Msg:Len/binary, Rest/binary>>,
+ end;
+ handle_data(0, _ChannelId, Data, State = #state{pending = <<>>}) ->
+ {ok, State#state{pending = Data}};
+-handle_data(Type, ChannelId, Data0, State = #state{pending = Pending}) ->
++handle_data(0, ChannelId, Data0, State = #state{pending = Pending}) ->
+ Data = <<Pending/binary, Data0/binary>>,
+ Size = byte_size(Data),
+ case Size > ?SSH_MAX_PACKET_SIZE of
+@@ -229,8 +229,11 @@ handle_data(Type, ChannelId, Data0, State = #state{pending = Pending}) ->
+ ?LOG_ERROR(ReportFun, [Size]),
+ {stop, ChannelId, State};
+ _ ->
+- handle_data(Type, ChannelId, Data, State#state{pending = <<>>})
+- end.
++ handle_data(0, ChannelId, Data, State#state{pending = <<>>})
++ end;
++handle_data(_Type, _ChannelId, _Data, State) ->
++ %% Same as openssh sftpd, we ignore extended data
++ {ok, State}.
+
+ %% From draft-ietf-secsh-filexfer-02 "The file handle strings MUST NOT be longer than 256 bytes."
+ handle_op(Request, ReqId, <<?UINT32(HLen), _/binary>>, State = #state{xf = XF})
+diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl
+index cb2eee14e7..8d73269499 100644
+--- a/lib/ssh/test/ssh_sftpd_SUITE.erl
++++ b/lib/ssh/test/ssh_sftpd_SUITE.erl
+@@ -56,7 +56,8 @@
+ ver3_rename/1,
+ ver6_basic/1,
+ write_file/1,
+- access_attributes_outside_root/1
++ access_attributes_outside_root/1,
++ extended_data_no_infinite_loop/1
+ ]).
+
+ -include_lib("common_test/include/ct.hrl").
+@@ -108,7 +109,8 @@ all() ->
+ relative_path,
+ open_file_dir_v5,
+ open_file_dir_v6,
+- access_attributes_outside_root].
++ access_attributes_outside_root,
++ extended_data_no_infinite_loop].
+
+ groups() ->
+ [].
+@@ -991,6 +993,17 @@ access_attributes_outside_root(Config) when is_list(Config) ->
+ file:delete(OutsideRootFile)
+ end.
+
++%%--------------------------------------------------------------------
++extended_data_no_infinite_loop(Config) when is_list(Config) ->
++ %% Regression test for CVE-2026-54886, sending extended data to ssh_sftpd.erl
++ %% caused an infinite loop
++ {Cm, Channel} = proplists:get_value(sftp, Config),
++ ok = ssh_connection:send(Cm, Channel, 1, <<"trigger">>),
++
++ Data = <<?UINT32(5), ?SSH_FXP_INIT, ?UINT32(6)>>,
++ ok = ssh_connection:send(Cm, Channel, Data),
++ {ok, <<?SSH_FXP_VERSION, ?UINT32(_Version), _/binary>>, _} = reply(Cm, Channel).
++
+ %%--------------------------------------------------------------------
+ %% Internal functions ------------------------------------------------
+ %%--------------------------------------------------------------------
diff --git a/otp-0015-ssl-TLS-Client-hardening.patch b/otp-0015-ssl-TLS-Client-hardening.patch
new file mode 100644
index 0000000..b9eac39
--- /dev/null
+++ b/otp-0015-ssl-TLS-Client-hardening.patch
@@ -0,0 +1,39 @@
+From: Ingela Anderton Andin <ingela@erlang.org>
+Date: Fri, 19 Jun 2026 11:27:05 +0200
+Subject: [PATCH] ssl: TLS Client hardening
+
+Guard TLS client for MITM injection of application data during
+"plain-text-window" during handshake.
+
+diff --git a/lib/ssl/src/tls_gen_connection.erl b/lib/ssl/src/tls_gen_connection.erl
+index eb40114a77..cfdc1a81e2 100644
+--- a/lib/ssl/src/tls_gen_connection.erl
++++ b/lib/ssl/src/tls_gen_connection.erl
+@@ -451,6 +451,27 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, StateName,
+ StateName == wait_finished->
+ Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, application_data_before_handshake_or_intervened_in_post_handshake_auth),
+ ssl_gen_statem:handle_own_alert(Alert, StateName, State);
++handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, StateName,
++ #state{static_env = #static_env{role = client},
++ handshake_env = #handshake_env{renegotiation = {false, first}}
++ } = State) when StateName == hello;
++ StateName == certify;
++ StateName == wait_stapling;
++ StateName == abbreviated;
++ StateName == cipher ->
++ %% Pre TLS-1.3 client guard "plain text window" for MITM injection
++ Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE,
++ application_data_before_handshake_completion),
++ ssl_gen_statem:handle_own_alert(Alert, StateName, State);
++handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA}, StateName,
++ #state{static_env = #static_env{role = client}
++ } = State) when
++ StateName == start;
++ StateName == wait_sh ->
++ %% TLS-1.3 client guard "plain text window" for MITM injection
++ Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE,
++ application_data_before_handshake_completion),
++ ssl_gen_statem:handle_own_alert(Alert, StateName, State);
+ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName,
+ #state{start_or_recv_from = From,
+ socket_options = #socket_options{active = false}} = State0) when From =/= undefined ->
diff --git a/otp-0016-ssl-Add-PSK-parameter-check.patch b/otp-0016-ssl-Add-PSK-parameter-check.patch
new file mode 100644
index 0000000..2084206
--- /dev/null
+++ b/otp-0016-ssl-Add-PSK-parameter-check.patch
@@ -0,0 +1,27 @@
+From: Ingela Anderton Andin <ingela@erlang.org>
+Date: Tue, 23 Jun 2026 17:58:22 +0200
+Subject: [PATCH] ssl: Add PSK parameter check
+
+
+diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
+index a52c0a62ee..0a0c34d8fb 100644
+--- a/lib/ssl/src/tls_handshake_1_3.erl
++++ b/lib/ssl/src/tls_handshake_1_3.erl
+@@ -1696,7 +1696,16 @@ handle_pre_shared_key(#state{ssl_options = #{session_tickets := Tickets},
+ OfferedPreSharedKeys}, Cipher) when Tickets =/= disabled ->
+ Tracker = proplists:get_value(session_tickets_tracker, Trackers),
+ #{prf := CipherHash} = ssl_cipher_format:suite_bin_to_map(Cipher),
+- tls_server_session_ticket:use(Tracker, OfferedPreSharedKeys, CipherHash, HHistory).
++ #offered_psks{
++ identities = Identities,
++ binders = Binders
++ } = OfferedPreSharedKeys,
++ case length(Identities) == length(Binders) of
++ true ->
++ tls_server_session_ticket:use(Tracker, OfferedPreSharedKeys, CipherHash, HHistory);
++ false ->
++ {error, ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, illegal_pre_shared_key)}
++ end.
+
+ %% If the handshake includes a HelloRetryRequest, the initial
+ %% ClientHello and HelloRetryRequest are included in the transcript
reply other threads:[~2026-07-10 16:26 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178370076588.1.16671500192109066630.rpms-erlang-8f8d34b6606f@fedoraproject.org \
--to=lemenkov@gmail.com \
--cc=git-commits@fedoraproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox