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