[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1105009: marked as done (bookworm-pu: package erlang/1:25.2.3+dfsg-1+deb12u2)



Your message dated Sat, 06 Sep 2025 12:14:50 +0100
with message-id <ee4c0876608d99eb3f8b333b556fbd92e7a652eb.camel@adam-barratt.org.uk>
and subject line Closing p-u requests for fixes included in 12.12
has caused the Debian Bug report #1105009,
regarding bookworm-pu: package erlang/1:25.2.3+dfsg-1+deb12u2
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1105009: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1105009
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: erlang@packages.debian.org
Control: affects -1 + src:erlang
User: release.debian.org@packages.debian.org
Usertags: pu

[ Reason ]
Recently, another vulnerability was published for the ssh
application in Erlang (CVE-2025-46712, see [1], [2] for detail).

Debian security team contacted me (see [3]) and suggested
not to do DSA but to upload the fixed package into
proposed-updates.

[ Impact ]
Without the fix, in theory, everyone that runs SSH daemon
from the Debian Erlang distribution have a weakness in
its key exchange mechanism. I'm not aware if an exploit
of this weakness exists as for now.

[ Tests ]
Upstream added a few tests for the new code. I don't run them
automatically on build (or by autopkgtests), but I've successfully
run them manually on amd64 architecture machine. There wasn't
any breakage of old tests as well.

[ Risks ]
I don't expect any breakage by introducing the new code.

[ Checklist ]
  [+] *all* changes are documented in the d/changelog
  [+] I reviewed all changes and I approve them
  [+] attach debdiff against the package in (old)stable
  [+] the issue is verified as fixed in unstable

[ Changes ]
The main patch is to lib/ssh/src/ssh_fsm_kexinit.erl, it implements
the strict KEX algorithm and adds checks for strict KEX essentially
to all steps of key exchange process.

The important additional patch is to
lib/ssh/test/ssh_protocol_SUITE.erl, where three new tests have
been added for this new code. Also, the auxiliary test library
lib/ssh/test/ssh_trpt_test_lib.erl is also augmented.

[1] https://security-tracker.debian.org/tracker/CVE-2025-46712
[2] https://www.cve.org/CVERecord?id=CVE-2025-46712
[3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1104963

-- 
Sergei Golovan
diff -Nru erlang-25.2.3+dfsg/debian/changelog erlang-25.2.3+dfsg/debian/changelog
--- erlang-25.2.3+dfsg/debian/changelog	2025-04-20 08:09:59.000000000 +0300
+++ erlang-25.2.3+dfsg/debian/changelog	2025-05-09 09:29:41.000000000 +0300
@@ -1,3 +1,9 @@
+erlang (1:25.2.3+dfsg-1+deb12u2) bookworm-proposed-updates; urgency=medium
+
+  * ssh: fix strict KEX hardening (CVE-2025-46712) (closes: #1104963).
+
+ -- Sergei Golovan <sgolovan@debian.org>  Fri, 09 May 2025 09:29:41 +0300
+
 erlang (1:25.2.3+dfsg-1+deb12u1) bookworm-security; urgency=high
 
   [ Salvatore Bonaccorso ]
diff -Nru erlang-25.2.3+dfsg/debian/patches/series erlang-25.2.3+dfsg/debian/patches/series
--- erlang-25.2.3+dfsg/debian/patches/series	2025-04-20 08:09:59.000000000 +0300
+++ erlang-25.2.3+dfsg/debian/patches/series	2025-05-09 09:29:41.000000000 +0300
@@ -13,3 +13,4 @@
 ssh-use-chars_limit-for-bad-packets-error-messages.patch
 ssh-custom_kexinit-test-added.patch
 ssh-early-RCE-fix.patch
+ssh-strict-KEX-exchange-hardening.patch
diff -Nru erlang-25.2.3+dfsg/debian/patches/ssh-strict-KEX-exchange-hardening.patch erlang-25.2.3+dfsg/debian/patches/ssh-strict-KEX-exchange-hardening.patch
--- erlang-25.2.3+dfsg/debian/patches/ssh-strict-KEX-exchange-hardening.patch	1970-01-01 03:00:00.000000000 +0300
+++ erlang-25.2.3+dfsg/debian/patches/ssh-strict-KEX-exchange-hardening.patch	2025-05-09 09:29:41.000000000 +0300
@@ -0,0 +1,607 @@
+From: Jakub Witczak <kuba@erlang.org>
+Date: Tue, 6 May 2025 17:01:29 +0200
+Subject: ssh: KEX strict implementation fixes
+ - fixed KEX strict implementation
+ - draft-miller-sshm-strict-kex-01.txt
+ - ssh_dbg added to ssh_fsm_kexinit module
+ - CVE-2025-46712
+Origin: https://github.com/erlang/otp/commit/e4b56a9f4a511aa9990dd86c16c61439c828df83
+Bug-Debian: https://bugs.debian.org/1104963
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2025-46712
+
+--- a/lib/ssh/src/ssh_connection_handler.erl
++++ b/lib/ssh/src/ssh_connection_handler.erl
+@@ -34,7 +34,6 @@
+ -include("ssh_transport.hrl").
+ -include("ssh_auth.hrl").
+ -include("ssh_connect.hrl").
+-
+ -include("ssh_fsm.hrl").
+ 
+ %%====================================================================
+@@ -705,16 +704,6 @@
+     disconnect_fun("Received disconnect: "++Desc, D),
+     {stop_and_reply, {shutdown,Desc}, Actions, D};
+ 
+-handle_event(internal, #ssh_msg_ignore{}, {_StateName, _Role, init},
+-             #data{ssh_params = #ssh{kex_strict_negotiated = true,
+-                                     send_sequence = SendSeq,
+-                                     recv_sequence = RecvSeq}}) ->
+-    ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+-                io_lib:format("strict KEX violation: unexpected SSH_MSG_IGNORE "
+-                              "send_sequence = ~p  recv_sequence = ~p",
+-                              [SendSeq, RecvSeq])
+-               );
+-
+ handle_event(internal, #ssh_msg_ignore{}, _StateName, _) ->
+     keep_state_and_data;
+ 
+@@ -1118,11 +1107,14 @@
+     of
+ 	{packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} ->
+ 	    D1 = D0#data{ssh_params =
+-			    Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)},
+-			decrypted_data_buffer = <<>>,
+-                        undecrypted_packet_length = undefined,
+-                        aead_data = <<>>,
+-			encrypted_data_buffer = EncryptedDataRest},
++                             Ssh1#ssh{recv_sequence =
++                                          ssh_transport:next_seqnum(StateName,
++                                                                    Ssh1#ssh.recv_sequence,
++                                                                    SshParams)},
++                         decrypted_data_buffer = <<>>,
++                         undecrypted_packet_length = undefined,
++                         aead_data = <<>>,
++                         encrypted_data_buffer = EncryptedDataRest},
+ 	    try
+ 		ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D1))
+ 	    of
+--- a/lib/ssh/src/ssh_fsm_kexinit.erl
++++ b/lib/ssh/src/ssh_fsm_kexinit.erl
+@@ -43,6 +43,11 @@
+ -export([callback_mode/0, handle_event/4, terminate/3,
+ 	 format_status/2, code_change/4]).
+ 
++-behaviour(ssh_dbg).
++-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1,
++         ssh_dbg_on/1, ssh_dbg_off/1,
++         ssh_dbg_format/2]).
++
+ %%====================================================================
+ %% gen_statem callbacks
+ %%====================================================================
+@@ -53,8 +58,13 @@
+ 
+ %%--------------------------------------------------------------------
+ 
+-%%% ######## {kexinit, client|server, init|renegotiate} ####
+ 
++handle_event(Type, Event = prepare_next_packet, StateName, D) ->
++    ssh_connection_handler:handle_event(Type, Event, StateName, D);
++handle_event(Type, Event = {send_disconnect, _, _, _, _}, StateName, D) ->
++    ssh_connection_handler:handle_event(Type, Event, StateName, D);
++
++%%% ######## {kexinit, client|server, init|renegotiate} ####
+ handle_event(internal, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg},
+ 	     D = #data{key_exchange_init_msg = OwnKex}) ->
+     Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload),
+@@ -67,11 +77,10 @@
+ 	  end,
+     {next_state, {key_exchange,Role,ReNeg}, D#data{ssh_params=Ssh}};
+ 
+-
+ %%% ######## {key_exchange, client|server, init|renegotiate} ####
+-
+ %%%---- diffie-hellman
+ handle_event(internal, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
++    ok = check_kex_strict(Msg, D),
+     {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params),
+     ssh_connection_handler:send_bytes(KexdhReply, D),
+     {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
+@@ -81,6 +90,7 @@
+     {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
+ 
+ handle_event(internal, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
++    ok = check_kex_strict(Msg, D),
+     {ok, NewKeys, Ssh1} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params),
+     ssh_connection_handler:send_bytes(NewKeys, D),
+     {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+@@ -89,24 +99,28 @@
+ 
+ %%%---- diffie-hellman group exchange
+ handle_event(internal, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) ->
++    ok = check_kex_strict(Msg, D),
+     {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
+     ssh_connection_handler:send_bytes(GexGroup, D),
+     Ssh = ssh_transport:parallell_gen_key(Ssh1),
+     {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}};
+ 
+ handle_event(internal, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) ->
++    ok = check_kex_strict(Msg, D),
+     {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
+     ssh_connection_handler:send_bytes(GexGroup, D),
+     Ssh = ssh_transport:parallell_gen_key(Ssh1),
+     {next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}};
+ 
+ handle_event(internal, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) ->
++    ok = check_kex_strict(Msg, D),
+     {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params),
+     ssh_connection_handler:send_bytes(KexGexInit, D),
+     {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}};
+ 
+ %%%---- elliptic curve diffie-hellman
+ handle_event(internal, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
++    ok = check_kex_strict(Msg, D),
+     {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params),
+     ssh_connection_handler:send_bytes(KexEcdhReply, D),
+     {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
+@@ -116,16 +130,25 @@
+     {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
+ 
+ handle_event(internal, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
++    ok = check_kex_strict(Msg, D),
+     {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params),
+     ssh_connection_handler:send_bytes(NewKeys, D),
+     {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+     ssh_connection_handler:send_bytes(ExtInfo, D),
+     {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
+ 
++%%% ######## handle KEX strict
++handle_event(internal, _Event, {key_exchange,_Role,init},
++             #data{ssh_params = #ssh{algorithms = #alg{kex_strict_negotiated = true},
++                                     send_sequence = SendSeq,
++                                     recv_sequence = RecvSeq}}) ->
++    ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
++                io_lib:format("KEX strict violation: send_sequence = ~p  recv_sequence = ~p",
++                              [SendSeq, RecvSeq]));
+ 
+ %%% ######## {key_exchange_dh_gex_init, server, init|renegotiate} ####
+-
+ handle_event(internal, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) ->
++    ok = check_kex_strict(Msg, D),
+     {ok, KexGexReply, Ssh1} =  ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params),
+     ssh_connection_handler:send_bytes(KexGexReply, D),
+     {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
+@@ -133,20 +156,33 @@
+     {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+     ssh_connection_handler:send_bytes(ExtInfo, D),
+     {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
+-
++%%% ######## handle KEX strict
++handle_event(internal, _Event, {key_exchange_dh_gex_init,_Role,init},
++             #data{ssh_params = #ssh{algorithms = #alg{kex_strict_negotiated = true},
++                                     send_sequence = SendSeq,
++                                     recv_sequence = RecvSeq}}) ->
++    ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
++                io_lib:format("KEX strict violation: send_sequence = ~p  recv_sequence = ~p",
++                              [SendSeq, RecvSeq]));
+ 
+ %%% ######## {key_exchange_dh_gex_reply, client, init|renegotiate} ####
+-
+ handle_event(internal, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) ->
++    ok = check_kex_strict(Msg, D),
+     {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params),
+     ssh_connection_handler:send_bytes(NewKeys, D),
+     {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+     ssh_connection_handler:send_bytes(ExtInfo, D),
+     {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
+-
++%%% ######## handle KEX strict
++handle_event(internal, _Event, {key_exchange_dh_gex_reply,_Role,init},
++             #data{ssh_params = #ssh{algorithms = #alg{kex_strict_negotiated = true},
++                                     send_sequence = SendSeq,
++                                     recv_sequence = RecvSeq}}) ->
++    ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
++                io_lib:format("KEX strict violation: send_sequence = ~p  recv_sequence = ~p",
++                              [SendSeq, RecvSeq]));
+ 
+ %%% ######## {new_keys, client|server} ####
+-
+ %% First key exchange round:
+ handle_event(internal, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D0) ->
+     {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D0#data.ssh_params),
+@@ -162,6 +198,15 @@
+     %% ssh_connection_handler:send_bytes(ExtInfo, D),
+     {next_state, {ext_info,server,init}, D#data{ssh_params=Ssh}};
+ 
++%%% ######## handle KEX strict
++handle_event(internal, _Event, {new_keys,_Role,init},
++             #data{ssh_params = #ssh{algorithms = #alg{kex_strict_negotiated = true},
++                                     send_sequence = SendSeq,
++                                     recv_sequence = RecvSeq}}) ->
++    ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
++                io_lib:format("KEX strict violation (send_sequence = ~p recv_sequence = ~p)",
++                              [SendSeq, RecvSeq]));
++
+ %% Subsequent key exchange rounds (renegotiation):
+ handle_event(internal, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, D) ->
+     {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+@@ -183,7 +228,6 @@
+ handle_event(internal, #ssh_msg_newkeys{}=Msg, {ext_info,_Role,renegotiate}, D) ->
+     {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+     {keep_state, D#data{ssh_params = Ssh}};
+-    
+ 
+ handle_event(internal, Msg, {ext_info,Role,init}, D) when is_tuple(Msg) ->
+     %% If something else arrives, goto next state and handle the event in that one
+@@ -217,3 +261,70 @@
+ peer_role(client) -> server;
+ peer_role(server) -> client.
+ 
++check_kex_strict(Msg,
++                 #data{ssh_params =
++                           #ssh{algorithms =
++                                    #alg{
++                                       kex = Kex,
++                                       kex_strict_negotiated = KexStrictNegotiated},
++                                send_sequence = SendSeq,
++                                recv_sequence = RecvSeq}}) ->
++    case check_msg_group(Msg, get_alg_group(Kex), KexStrictNegotiated) of
++        ok ->
++            ok;
++        error ->
++            ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
++                        io_lib:format("KEX strict violation: send_sequence = ~p  recv_sequence = ~p",
++                                      [SendSeq, RecvSeq]))
++    end.
++
++get_alg_group(Kex) when Kex == 'diffie-hellman-group16-sha512';
++                        Kex == 'diffie-hellman-group18-sha512';
++                        Kex == 'diffie-hellman-group14-sha256';
++                        Kex == 'diffie-hellman-group14-sha1';
++                        Kex == 'diffie-hellman-group1-sha1' ->
++    dh_alg;
++get_alg_group(Kex) when Kex == 'diffie-hellman-group-exchange-sha256';
++                        Kex == 'diffie-hellman-group-exchange-sha1' ->
++    dh_gex_alg;
++get_alg_group(Kex) when Kex == 'curve25519-sha256';
++                        Kex == 'curve25519-sha256@libssh.org';
++                        Kex == 'curve448-sha512';
++                        Kex == 'ecdh-sha2-nistp521';
++                        Kex == 'ecdh-sha2-nistp384';
++                        Kex == 'ecdh-sha2-nistp256' ->
++    ecdh_alg.
++
++check_msg_group(_Msg, _AlgGroup, false) -> ok;
++check_msg_group(#ssh_msg_kexdh_init{},  dh_alg, true) -> ok;
++check_msg_group(#ssh_msg_kexdh_reply{}, dh_alg, true) -> ok;
++check_msg_group(#ssh_msg_kex_dh_gex_request_old{}, dh_gex_alg, true) -> ok;
++check_msg_group(#ssh_msg_kex_dh_gex_request{},     dh_gex_alg, true) -> ok;
++check_msg_group(#ssh_msg_kex_dh_gex_group{},       dh_gex_alg, true) -> ok;
++check_msg_group(#ssh_msg_kex_dh_gex_init{},        dh_gex_alg, true) -> ok;
++check_msg_group(#ssh_msg_kex_dh_gex_reply{},       dh_gex_alg, true) -> ok;
++check_msg_group(#ssh_msg_kex_ecdh_init{},  ecdh_alg, true) -> ok;
++check_msg_group(#ssh_msg_kex_ecdh_reply{}, ecdh_alg, true) -> ok;
++check_msg_group(_Msg, _AlgGroup, _) -> error.
++
++%%%################################################################
++%%%#
++%%%# Tracing
++%%%#
++
++ssh_dbg_trace_points() -> [connection_events].
++
++ssh_dbg_flags(connection_events) -> [c].
++
++ssh_dbg_on(connection_events) -> dbg:tp(?MODULE,   handle_event, 4, x).
++
++ssh_dbg_off(connection_events) -> dbg:ctpg(?MODULE, handle_event, 4).
++
++ssh_dbg_format(connection_events, {call, {?MODULE,handle_event, [EventType, EventContent, State, _Data]}}) ->
++    ["Connection event\n",
++     io_lib:format("[~w] EventType: ~p~nEventContent: ~p~nState: ~p~n", [?MODULE, EventType, EventContent, State])
++    ];
++ssh_dbg_format(connection_events, {return_from, {?MODULE,handle_event,4}, Ret}) ->
++    ["Connection event result\n",
++     io_lib:format("[~w] ~p~n", [?MODULE, ssh_dbg:reduce_state(Ret, #data{})])
++    ].
+--- a/lib/ssh/src/ssh_transport.erl
++++ b/lib/ssh/src/ssh_transport.erl
+@@ -26,12 +26,11 @@
+ 
+ -include_lib("public_key/include/public_key.hrl").
+ -include_lib("kernel/include/inet.hrl").
+-
+ -include("ssh_transport.hrl").
+ -include("ssh.hrl").
+ 
+ -export([versions/2, hello_version_msg/1]).
+--export([next_seqnum/1, 
++-export([next_seqnum/3,
+ 	 supported_algorithms/0, supported_algorithms/1,
+ 	 default_algorithms/0, default_algorithms/1,
+          clear_default_algorithms_env/0,
+@@ -297,7 +296,12 @@
+ hello_version_msg(Data) ->
+     [Data,"\r\n"].
+ 
+-next_seqnum(SeqNum) ->
++next_seqnum({State, _Role, init}, 16#ffffffff,
++            #ssh{algorithms = #alg{kex_strict_negotiated = true}})
++  when State == kexinit; State == key_exchange; State == new_keys ->
++    ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
++                io_lib:format("KEX strict violation: recv_sequence = 16#ffffffff", []));
++next_seqnum(_State, SeqNum, _) ->
+     (SeqNum + 1) band 16#ffffffff.
+ 
+ is_valid_mac(_, _ , #ssh{recv_mac_size = 0}) ->
+@@ -1082,7 +1086,7 @@
+ %%   algorithm.  Each string MUST contain at least one algorithm name.
+ select_algorithm(Role, Client, Server,
+                  #ssh{opts = Opts,
+-                         kex_strict_negotiated = KexStrictNegotiated0},
++                      kex_strict_negotiated = KexStrictNegotiated0},
+                  ReNeg) ->
+     KexStrictNegotiated =
+         case ReNeg of
+@@ -1108,7 +1112,6 @@
+             _ ->
+                 KexStrictNegotiated0
+         end,
+-
+     {Encrypt0, Decrypt0} = select_encrypt_decrypt(Role, Client, Server),
+     {SendMac0, RecvMac0} = select_send_recv_mac(Role, Client, Server),
+ 
+--- a/lib/ssh/test/ssh_protocol_SUITE.erl
++++ b/lib/ssh/test/ssh_protocol_SUITE.erl
+@@ -54,7 +54,9 @@
+          ext_info_c/1,
+          ext_info_s/1,
+          kex_strict_negotiated/1,
+-         kex_strict_msg_ignore/1,
++         kex_strict_violation_key_exchange/1,
++         kex_strict_violation_new_keys/1,
++         kex_strict_violation/1,
+          kex_strict_msg_unknown/1,
+          gex_client_init_option_groups/1,
+          gex_client_init_option_groups_file/1,
+@@ -143,7 +145,9 @@
+ 		gex_client_old_request_exact,
+ 		gex_client_old_request_noexact,
+                 kex_strict_negotiated,
+-                kex_strict_msg_ignore,
++                kex_strict_violation_key_exchange,
++                kex_strict_violation_new_keys,
++                kex_strict_violation,
+                 kex_strict_msg_unknown]},
+      {service_requests, [], [bad_service_name,
+ 			     bad_long_service_name,
+@@ -930,22 +934,145 @@
+     logger:set_primary_config(Level),
+     ok.
+ 
+-%% Connect to an erlang server and inject unexpected SSH ignore
+-kex_strict_msg_ignore(Config) ->
+-    ct:log("START: ~p~n=================================", [?FUNCTION_NAME]),
+-    ExpectedReason = "strict KEX violation: unexpected SSH_MSG_IGNORE",
+-    TestMessages =
+-        [{send, ssh_msg_ignore},
+-         {match, #ssh_msg_kexdh_reply{_='_'}, receive_msg},
+-         {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}],
+-    kex_strict_helper(Config, TestMessages, ExpectedReason).
++%% Connect to an erlang server and inject unexpected SSH message
++%% ssh_fsm_kexinit in key_exchange state
++kex_strict_violation_key_exchange(Config) ->
++    ExpectedReason = "KEX strict violation",
++    Injections = [ssh_msg_ignore, ssh_msg_debug, ssh_msg_unimplemented],
++    TestProcedure =
++        fun(M) ->
++                ct:log(
++                  "=================== START: ~p Message: ~p Expected Fail =================================",
++                  [?FUNCTION_NAME, M]),
++                [receive_hello,
++                 {send, hello},
++                 {send, ssh_msg_kexinit},
++                 {match, #ssh_msg_kexinit{_='_'}, receive_msg},
++                 {send, M},
++                 {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]
++        end,
++    [kex_strict_helper(Config, TestProcedure(Msg), ExpectedReason) ||
++        Msg <- Injections],
++    ct:log("========== END ========"),
++    ok.
++
++%% Connect to an erlang server and inject unexpected SSH message
++%% ssh_fsm_kexinit in new_keys state
++kex_strict_violation_new_keys(Config) ->
++    ExpectedReason = "KEX strict violation",
++    Injections = [ssh_msg_ignore, ssh_msg_debug, ssh_msg_unimplemented],
++    TestProcedure =
++        fun(M) ->
++                ct:log(
++                  "=================== START: ~p Message: ~p Expected Fail =================================",
++                  [?FUNCTION_NAME, M]),
++                [receive_hello,
++                 {send, hello},
++                 {send, ssh_msg_kexinit},
++                 {match, #ssh_msg_kexinit{_='_'}, receive_msg},
++                 {send, ssh_msg_kexdh_init},
++                 {send, M},
++                 {match, #ssh_msg_kexdh_reply{_='_'}, receive_msg},
++                 {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]
++        end,
++    [kex_strict_helper(Config, TestProcedure(Msg), ExpectedReason) ||
++        Msg <- Injections],
++    ct:log("========== END ========"),
++    ok.
++
++%% Connect to an erlang server and inject unexpected SSH message
++%% duplicated KEXINIT
++kex_strict_violation(Config) ->
++    KexDhReply =
++        #ssh_msg_kexdh_reply{
++           public_host_key = {{{'ECPoint',<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
++                               {namedCurve,{1,3,101,112}}},
++                              'ssh-ed25519'},
++           f = 18504393053016436370762156176197081926381112956345797067569792020930728564439992620494295053804030674742529174859108487694089045521619258420515443400605141150065440678508889060925968846155921972385560196703381004650914261218463420313738628465563288022895912907728767735629532940627575655703806353550720122093175255090704443612257683903495753071530605378193139909567971489952258218767352348904221407081210633467414579377014704081235998044497191940270966762124544755076128392259615566530695493013708460088312025006678879288856957348606386230195080105197251789635675011844976120745546472873505352732719507783227210178188,
++           h_sig = <<90,247,44,240,136,196,82,215,56,165,53,33,230,101,253,
++                     34,112,201,21,131,162,169,10,129,174,14,69,25,39,174,
++                     92,210,130,249,103,2,215,245,7,213,110,235,136,134,11,
++                     124,248,139,79,17,225,77,125,182,204,84,137,167,99,186,
++                     167,42,192,10>>},
++    TestFlows =
++        [
++         {kexinit, "KEX strict violation",
++          [receive_hello,
++           {send, hello},
++           {send, ssh_msg_kexinit},
++           {match, #ssh_msg_kexinit{_='_'}, receive_msg},
++           {send, ssh_msg_kexinit},
++           {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]},
++         {ssh_msg_kexdh_init, "KEX strict violation",
++          [receive_hello,
++           {send, hello},
++           {send, ssh_msg_kexinit},
++           {match, #ssh_msg_kexinit{_='_'}, receive_msg},
++           {send, ssh_msg_kexdh_init_dup},
++           {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg},
++           {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]},
++         {new_keys, "Message ssh_msg_newkeys in wrong state",
++          [receive_hello,
++           {send, hello},
++           {send, ssh_msg_kexinit},
++           {match, #ssh_msg_kexinit{_='_'}, receive_msg},
++           {send, ssh_msg_kexdh_init},
++           {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg},
++           {send, #ssh_msg_newkeys{}},
++           {match, #ssh_msg_newkeys{_='_'}, receive_msg},
++           {send, #ssh_msg_newkeys{}},
++           {match, disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR), receive_msg}]},
++         {ssh_msg_unexpected_dh_gex, "KEX strict violation",
++          [receive_hello,
++           {send, hello},
++           {send, ssh_msg_kexinit},
++           {match, #ssh_msg_kexinit{_='_'}, receive_msg},
++           %% dh_alg is expected but dh_gex_alg is provided
++	   {send, #ssh_msg_kex_dh_gex_request{min = 1000, n = 3000, max = 4000}},
++           {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]},
++         {wrong_role, "KEX strict violation",
++          [receive_hello,
++           {send, hello},
++           {send, ssh_msg_kexinit},
++           {match, #ssh_msg_kexinit{_='_'}, receive_msg},
++           %% client should not send message below
++           {send, KexDhReply},
++           {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]},
++         {wrong_role2, "KEX strict violation",
++          [receive_hello,
++           {send, hello},
++           {send, ssh_msg_kexinit},
++           {match, #ssh_msg_kexinit{_='_'}, receive_msg},
++           {send, ssh_msg_kexdh_init},
++           {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg},
++           %% client should not send message below
++           {send, KexDhReply},
++           {match, #ssh_msg_newkeys{_='_'}, receive_msg},
++           {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]}
++        ],
++    TestProcedure =
++        fun({Msg, _, P}) ->
++                ct:log(
++                  "==== START: ~p (duplicated ~p) Expected Fail ====~n~p",
++                  [?FUNCTION_NAME, Msg, P]),
++                P
++        end,
++    [kex_strict_helper(Config, TestProcedure(Procedure), Reason) ||
++        Procedure = {_, Reason, _} <- TestFlows],
++    ct:log("==== END ====="),
++    ok.
+ 
+ %% Connect to an erlang server and inject unexpected non-SSH binary
+ kex_strict_msg_unknown(Config) ->
+     ct:log("START: ~p~n=================================", [?FUNCTION_NAME]),
+     ExpectedReason = "Bad packet: Size",
+     TestMessages =
+-        [{send, ssh_msg_unknown},
++        [receive_hello,
++         {send, hello},
++         {send, ssh_msg_kexinit},
++         {match, #ssh_msg_kexinit{_='_'}, receive_msg},
++         {send, ssh_msg_kexdh_init},
++         {send, ssh_msg_unknown},
+          {match, #ssh_msg_kexdh_reply{_='_'}, receive_msg},
+          {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}],
+     kex_strict_helper(Config, TestMessages, ExpectedReason).
+@@ -970,12 +1097,7 @@
+              {user_dir, user_dir(Config)},
+              {user_interaction, false}
+             | proplists:get_value(extra_options,Config,[])
+-            ]},
+-           receive_hello,
+-           {send, hello},
+-           {send, ssh_msg_kexinit},
+-           {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+-           {send, ssh_msg_kexdh_init}] ++
++            ]}] ++
+               TestMessages,
+           InitialState),
+     ct:sleep(100),
+--- a/lib/ssh/test/ssh_trpt_test_lib.erl
++++ b/lib/ssh/test/ssh_trpt_test_lib.erl
+@@ -90,7 +90,8 @@
+ 	    report_trace(throw, Term, S1),
+ 	    throw({Term,Op});
+ 
+-	error:Error ->
++	error:Error:St ->
++            ct:log("Stacktrace=~n~p", [St]),
+ 	    report_trace(error, Error, S1),
+ 	    error({Error,Op});
+ 
+@@ -335,6 +336,17 @@
+     Msg = #ssh_msg_ignore{data = "unexpected_ignore_message"},
+     send(S0, Msg);
+ 
++send(S0, ssh_msg_debug) ->
++    Msg = #ssh_msg_debug{
++             always_display = true,
++             message = "some debug message",
++             language = "en"},
++    send(S0, Msg);
++
++send(S0, ssh_msg_unimplemented) ->
++    Msg = #ssh_msg_unimplemented{sequence = 123},
++    send(S0, Msg);
++
+ send(S0, ssh_msg_unknown) ->
+     Msg = binary:encode_hex(<<"0000000C060900000000000000000000">>),
+     send(S0, Msg);
+@@ -382,6 +394,26 @@
+ 	    end),
+     send_bytes(NextKexMsgBin, S#s{ssh = C});
+ 
++send(S0, ssh_msg_kexdh_init_dup) when ?role(S0) == client ->
++    {OwnMsg, PeerMsg} = S0#s.alg_neg,
++    {ok, NextKexMsgBin, C} =
++	try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S0#s.ssh, init)
++	catch
++	    Class:Exc ->
++		fail("Algorithm negotiation failed!",
++		     {"Algorithm negotiation failed at line ~p:~p~n~p:~s~nPeer: ~s~n Own: ~s",
++		      [?MODULE,?LINE,Class,format_msg(Exc),format_msg(PeerMsg),format_msg(OwnMsg)]},
++		     S0)
++	end,
++    S = opt(print_messages, S0,
++	    fun(X) when X==true;X==detail ->
++		    #ssh{keyex_key = {{_Private, Public}, {_G, _P}}} = C,
++		    Msg = #ssh_msg_kexdh_init{e = Public},
++		    {"Send (reconstructed)~n~s~n",[format_msg(Msg)]}
++	    end),
++    send_bytes(NextKexMsgBin, S#s{ssh = C}),
++    send_bytes(NextKexMsgBin, S#s{ssh = C});
++
+ send(S0, ssh_msg_kexdh_reply) ->
+     Bytes = proplists:get_value(ssh_msg_kexdh_reply, S0#s.reply),
+     S = opt(print_messages, S0,
+@@ -531,7 +563,10 @@
+            S0#s.ssh)
+      of
+          {packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} ->
+-             S1 = S0#s{ssh = Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)},
++             S1 = S0#s{ssh = Ssh1#ssh{recv_sequence =
++                                          ssh_transport:next_seqnum(undefined,
++                                                                    Ssh1#ssh.recv_sequence,
++                                                                    false)},
+                        decrypted_data_buffer = <<>>,
+                        undecrypted_packet_length = undefined,
+                        aead_data = <<>>,

--- End Message ---
--- Begin Message ---
Package: release.debian.org
Version: 12.12

Hi,

Each of the updates referenced by these requests was included in
today's 12.12 point release for bookworm.

Regards,

Adam

--- End Message ---

Reply to: