summaryrefslogtreecommitdiff
path: root/CVE-2025-32433.patch
diff options
context:
space:
mode:
Diffstat (limited to 'CVE-2025-32433.patch')
-rw-r--r--CVE-2025-32433.patch221
1 files changed, 221 insertions, 0 deletions
diff --git a/CVE-2025-32433.patch b/CVE-2025-32433.patch
new file mode 100644
index 0000000..0436a72
--- /dev/null
+++ b/CVE-2025-32433.patch
@@ -0,0 +1,221 @@
+From: Jakub Witczak <kuba@erlang.org>
+Date: Mon, 14 Apr 2025 16:33:21 +0200
+Subject: [PATCH] ssh: early RCE fix
+
+- disconnect when connection protocol message arrives
+- when user is not authenticated for connection
+- see RFC4252 sec.6
+
+origin: https://github.com/erlang/otp/commit/0fcd9c56524b28615e8ece65fc0c3f66ef6e4c12
+bug: https://github.com/erlang/otp/security/advisories/GHSA-37cp-fgq5-7wc2
+bug-debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1103442
+---
+ lib/ssh/src/ssh_connection.erl | 28 +++++++++---
+ lib/ssh/test/ssh_protocol_SUITE.erl | 86 +++++++++++++++++++------------------
+ 2 files changed, 67 insertions(+), 47 deletions(-)
+
+diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
+index a966f7b..3a607a5 100644
+--- a/lib/ssh/src/ssh_connection.erl
++++ b/lib/ssh/src/ssh_connection.erl
+@@ -26,6 +26,8 @@
+
+ -module(ssh_connection).
+
++-include_lib("kernel/include/logger.hrl").
++
+ -include("ssh.hrl").
+ -include("ssh_connect.hrl").
+ -include("ssh_transport.hrl").
+@@ -468,6 +470,25 @@ channel_data(ChannelId, DataType, Data0,
+ %%% Replies {Reply, UpdatedConnection}
+ %%%
+
++handle_msg(#ssh_msg_disconnect{code = Code, description = Description}, Connection, _, _SSH) ->
++ {disconnect, {Code, Description}, handle_stop(Connection)};
++
++handle_msg(Msg, Connection, server, Ssh = #ssh{authenticated = false}) ->
++ %% See RFC4252 6.
++ %% Message numbers of 80 and higher are reserved for protocols running
++ %% after this authentication protocol, so receiving one of them before
++ %% authentication is complete is an error, to which the server MUST
++ %% respond by disconnecting, preferably with a proper disconnect message
++ %% sent to ease troubleshooting.
++ MsgFun = fun(M) ->
++ MaxLogItemLen = ?GET_OPT(max_log_item_len, Ssh#ssh.opts),
++ io_lib:format("Connection terminated. Unexpected message for unauthenticated user."
++ " Message: ~w", [M],
++ [{chars_limit, MaxLogItemLen}])
++ end,
++ ?LOG_DEBUG(MsgFun, [Msg]),
++ {disconnect, {?SSH_DISCONNECT_PROTOCOL_ERROR, "Connection refused"}, handle_stop(Connection)};
++
+ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId,
+ sender_channel = RemoteId,
+ initial_window_size = WindowSz,
+@@ -973,12 +994,7 @@ handle_msg(#ssh_msg_request_success{data = Data},
+ #connection{requests = [{_, From, Fun} | Rest]} = Connection0, _, _SSH) ->
+ Connection = Fun({success,Data}, Connection0),
+ {[{channel_request_reply, From, {success, Data}}],
+- Connection#connection{requests = Rest}};
+-
+-handle_msg(#ssh_msg_disconnect{code = Code,
+- description = Description},
+- Connection, _, _SSH) ->
+- {disconnect, {Code, Description}, handle_stop(Connection)}.
++ Connection#connection{requests = Rest}}.
+
+
+ %%%----------------------------------------------------------------
+diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
+index 76fdbad..18f5441 100644
+--- a/lib/ssh/test/ssh_protocol_SUITE.erl
++++ b/lib/ssh/test/ssh_protocol_SUITE.erl
+@@ -70,6 +70,7 @@
+ no_common_alg_client_disconnects/1,
+ no_common_alg_server_disconnects/1,
+ custom_kexinit/1,
++ early_rce/1,
+ no_ext_info_s1/1,
+ no_ext_info_s2/1,
+ packet_length_too_large/1,
+@@ -110,6 +111,7 @@ suite() ->
+ all() ->
+ [{group,tool_tests},
+ client_info_line,
++ early_rce,
+ {group,kex},
+ {group,service_requests},
+ {group,authentication},
+@@ -127,10 +129,8 @@ groups() ->
+ ]},
+ {packet_size_error, [], [packet_length_too_large,
+ packet_length_too_short]},
+-
+ {field_size_error, [], [service_name_length_too_large,
+ service_name_length_too_short]},
+-
+ {kex, [], [custom_kexinit,
+ no_common_alg_server_disconnects,
+ no_common_alg_client_disconnects,
+@@ -171,7 +171,8 @@ init_per_suite(Config) ->
+ end_per_suite(Config) ->
+ stop_apps(Config).
+
+-init_per_testcase(Tc, Config) when Tc == no_common_alg_server_disconnects; Tc == custom_kexinit ->
++init_per_testcase(Tc, Config) when Tc == no_common_alg_server_disconnects;
++ Tc == custom_kexinit ->
+ start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}]);
+@@ -217,7 +218,8 @@ init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
+ init_per_testcase(_TestCase, Config) ->
+ check_std_daemon_works(Config, ?LINE).
+
+-end_per_testcase(Tc, Config) when Tc == no_common_alg_server_disconnects; Tc == custom_kexinit ->
++end_per_testcase(Tc, Config) when Tc == no_common_alg_server_disconnects;
++ Tc == custom_kexinit ->
+ stop_std_daemon(Config);
+ end_per_testcase(kex_strict_negotiated, Config) ->
+ Config;
+@@ -378,6 +380,44 @@ no_common_alg_server_disconnects(Config) ->
+ ]
+ ).
+
++early_rce(Config) ->
++ {ok,InitialState} =
++ ssh_trpt_test_lib:exec([{set_options, [print_ops, print_seqnums, print_messages]}]),
++ TypeOpen = "session",
++ ChannelId = 0,
++ WinSz = 425984,
++ PktSz = 65536,
++ DataOpen = <<>>,
++ SshMsgChannelOpen = ssh_connection:channel_open_msg(TypeOpen, ChannelId, WinSz, PktSz, DataOpen),
++
++ Id = 0,
++ TypeReq = "exec",
++ WantReply = true,
++ DataReq = <<?STRING(<<"lists:seq(1,10).">>)>>,
++ SshMsgChannelRequest =
++ ssh_connection:channel_request_msg(Id, TypeReq, WantReply, DataReq),
++ {ok,AfterKexState} =
++ ssh_trpt_test_lib:exec(
++ [{connect,
++ server_host(Config),server_port(Config),
++ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
++ {cipher,?DEFAULT_CIPHERS}
++ ]},
++ {silently_accept_hosts, true},
++ {recv_ext_info, false},
++ {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, SshMsgChannelOpen},
++ {send, SshMsgChannelRequest},
++ {match, disconnect(), receive_msg}
++ ], InitialState),
++ ok.
++
+ custom_kexinit(Config) ->
+ %% 16#C0 value causes unicode:characters_to_list to return a big error value
+ Trash = lists:duplicate(260_000, 16#C0),
+@@ -404,11 +444,6 @@ custom_kexinit(Config) ->
+ first_kex_packet_follows = false,
+ reserved = 0
+ },
+- PacketFun =
+- fun(Msg, Ssh) ->
+- BinMsg = custom_encode(Msg),
+- ssh_transport:pack(BinMsg, Ssh, 0)
+- end,
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, {print_messages,detail}]},
+@@ -424,42 +459,11 @@ custom_kexinit(Config) ->
+ receive_hello,
+ {send, hello},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+- {send, {special, KexInit, PacketFun}}, % with server unsupported 'ssh-dss' !
++ {send, KexInit}, % with server unsupported 'ssh-dss' !
+ {match, disconnect(), receive_msg}
+ ]
+ ).
+
+-custom_encode(#ssh_msg_kexinit{
+- cookie = Cookie,
+- kex_algorithms = KeyAlgs,
+- server_host_key_algorithms = HostKeyAlgs,
+- encryption_algorithms_client_to_server = EncAlgC2S,
+- encryption_algorithms_server_to_client = EncAlgS2C,
+- mac_algorithms_client_to_server = MacAlgC2S,
+- mac_algorithms_server_to_client = MacAlgS2C,
+- compression_algorithms_client_to_server = CompAlgS2C,
+- compression_algorithms_server_to_client = CompAlgC2S,
+- languages_client_to_server = LangC2S,
+- languages_server_to_client = LangS2C,
+- first_kex_packet_follows = Bool,
+- reserved = Reserved
+- }) ->
+- KeyAlgsBin0 = <<?Ename_list(KeyAlgs)>>,
+- <<?UINT32(Len0), Data:Len0/binary>> = KeyAlgsBin0,
+- KeyAlgsBin = <<?UINT32(Len0), Data/binary>>,
+- <<?Ebyte(?SSH_MSG_KEXINIT), Cookie/binary,
+- KeyAlgsBin/binary,
+- ?Ename_list(HostKeyAlgs),
+- ?Ename_list(EncAlgC2S),
+- ?Ename_list(EncAlgS2C),
+- ?Ename_list(MacAlgC2S),
+- ?Ename_list(MacAlgS2C),
+- ?Ename_list(CompAlgS2C),
+- ?Ename_list(CompAlgC2S),
+- ?Ename_list(LangC2S),
+- ?Ename_list(LangS2C),
+- ?Eboolean(Bool), ?Euint32(Reserved)>>.
+-
+ %%--------------------------------------------------------------------
+ %%% Algo negotiation fail. This should result in a ssh_msg_disconnect
+ %%% being sent from the client.