From 3e189a5a29b70bff72add4cc54b300a7651bfcdc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20W=C4=85sowski?= <michal@erlang.org>
Date: Sat, 7 Mar 2026 18:33:39 +0100
Subject: [PATCH 1/6] Disable zlib by default and limit size of decompressed
 data

---
 lib/ssh/src/ssh_connection_handler.erl |  7 +++
 lib/ssh/src/ssh_transport.erl          | 64 ++++++++++++++++++++++----
 2 files changed, 63 insertions(+), 8 deletions(-)

Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_connection_handler.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_connection_handler.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_connection_handler.erl
@@ -1226,6 +1226,13 @@ handle_event(info, {Proto, Sock, NewData
                                  io_lib:format("Bad packet: Size (~p bytes) exceeds max size",
                                                [PacketLen]),
                                  StateName, D0),
+            {stop, Shutdown, D};
+
+    {error, exceeds_max_decompressed_size} ->
+            {Shutdown, D} =
+                ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
+                                 "Bad packet: Size after decompression exceeds max size",
+                                 StateName, D0),
             {stop, Shutdown, D}
     catch
 	Class:Reason0:Stacktrace ->
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_transport.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_transport.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_transport.erl
@@ -192,6 +192,9 @@ default_algorithms1(public_key) ->
                                       'ssh-dss'
                                      ]);
 
+default_algorithms1(compression) ->
+    supported_algorithms(compression, same(['zlib']));
+
 default_algorithms1(Alg) ->
     supported_algorithms(Alg, []).
 
@@ -1432,8 +1435,12 @@ handle_packet_part(DecryptedPfx, Encrypt
     case unpack(pkt_type(CryptoAlg), mac_type(MacAlg),
                 DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0) of
         {ok, Payload, NextPacketBytes, Ssh1} ->
-            {Ssh, DecompressedPayload} = decompress(Ssh1, Payload),
-            {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh};
+            case decompress(Ssh1, Payload) of
+                {ok, Ssh, DecompressedPayload} ->
+                    {packet_decrypted, DecompressedPayload, NextPacketBytes, Ssh};
+                Other ->
+                    Other
+            end;
         Other ->
             Other
     end.
@@ -1949,15 +1956,56 @@ decompress_final(#ssh{decompress = 'zlib
     {ok, Ssh#ssh{decompress = none, decompress_ctx = undefined}}.
 
 decompress(#ssh{decompress = none} = Ssh, Data) ->
-    {Ssh, Data};
+    {ok, Ssh, Data};
 decompress(#ssh{decompress = zlib, decompress_ctx = Context} = Ssh, Data) ->
-    Decompressed = zlib:inflate(Context, Data),
-    {Ssh, list_to_binary(Decompressed)};
+    case safe_zlib_inflate(Context, Data) of
+        {ok, Decompressed} ->
+            {ok, Ssh, Decompressed};
+        Other ->
+            Other
+    end;
 decompress(#ssh{decompress = 'zlib@openssh.com', authenticated = false} = Ssh, Data) ->
-    {Ssh, Data};
+    {ok, Ssh, Data};
 decompress(#ssh{decompress = 'zlib@openssh.com', decompress_ctx = Context, authenticated = true} = Ssh, Data) ->
-    Decompressed = zlib:inflate(Context, Data),
-    {Ssh, list_to_binary(Decompressed)}.
+    case safe_zlib_inflate(Context, Data) of
+        {ok, Decompressed} ->
+            {ok, Ssh, Decompressed};
+        Other ->
+            Other
+    end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Safe decompression loop
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+safe_zlib_inflate(Context, Data) ->
+    safe_zlib_inflate_loop(Context, {0, []}, zlib:safeInflate(Context, Data)).
+
+safe_zlib_inflate_loop(Context, {AccLen0, AccData}, {Status, Chunk})
+  when Status == continue; Status == finished ->
+    ChunkLen = iolist_size(Chunk),
+    AccLen = AccLen0 + ChunkLen,
+    %% RFC 4253 section 6
+    %% Align with packets that don't use compression, we can process payloads with length
+    %% that required minimum padding.
+    %% From ?SSH_MAX_PACKET_SIZE subtract:
+    %% 1 byte for length of padding_length field
+    %% 4 bytes for minimum allowed length of padding
+    %% We don't subtract:
+    %% 4 bytes for packet_length field - not included in packet_length
+    %% x bytes for mac (size depends on type of used mac) - not included in packet_length
+    case AccLen > (?SSH_MAX_PACKET_SIZE - 5) of
+        true ->
+            {error, exceeds_max_decompressed_size};
+        false when Status == continue ->
+            Next = zlib:safeInflate(Context, []),
+            safe_zlib_inflate_loop(Context, {AccLen, [Chunk | AccData]}, Next);
+        false when Status == finished ->
+            Reversed = lists:reverse([Chunk | AccData]),
+            {ok, iolist_to_binary(Reversed)}
+    end;
+safe_zlib_inflate_loop(_Context, {_AccLen, _AccData}, {need_dictionary, Adler, _Chunk}) ->
+    erlang:error({need_dictionary, Adler}).
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_basic_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_basic_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_basic_SUITE.erl
@@ -56,6 +56,7 @@
          double_close/1,
          exec/1,
          exec_compressed/1,
+         exec_compressed_post_auth_compression/1,
          exec_with_io_in/1,
          exec_with_io_out/1,
          host_equal/2,
@@ -153,7 +154,7 @@ groups() ->
                                            ]},
      
      {p_basic, [?PARALLEL], [send, peername_sockname,
-                             exec, exec_compressed, 
+                             exec, exec_compressed, exec_compressed_post_auth_compression,
                              exec_with_io_out, exec_with_io_in,
                              cli, cli_exit_normal, cli_exit_status,
                              idle_time_client, idle_time_server,
@@ -401,37 +402,42 @@ exec_with_io_in(Config) when is_list(Con
 %%--------------------------------------------------------------------
 %%% Test that compression option works
 exec_compressed(Config) when is_list(Config) ->
-    case ssh_test_lib:ssh_supports(zlib, compression) of
-	false ->
-	    {skip, "zlib compression is not supported"};
+    exec_compressed_helper(Config, 'zlib').
 
-	true ->
-	    process_flag(trap_exit, true),
-	    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
-	    UserDir = proplists:get_value(priv_dir, Config), 
-
-	    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
-						     {preferred_algorithms,[{compression, [zlib]}]},
-						     {failfun, fun ssh_test_lib:failfun/2}]),
-    
-	    ConnectionRef =
-		ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
-						  {user_dir, UserDir},
-						  {user_interaction, false}]),
-	    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
-	    success = ssh_connection:exec(ConnectionRef, ChannelId,
-					  "1+1.", infinity),
-	    Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"2">>}},
-	    case ssh_test_lib:receive_exec_result(Data) of
-		expected ->
-		    ok;
-		Other ->
-		    ct:fail(Other)
-	    end,
-	    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
-	    ssh:close(ConnectionRef),
-	    ssh:stop_daemon(Pid)
-    end.
+%%--------------------------------------------------------------------
+%%% Test that post authentication compression option works
+exec_compressed_post_auth_compression(Config) when is_list(Config) ->
+    exec_compressed_helper(Config, 'zlib@openssh.com').
+
+%%--------------------------------------------------------------------
+%%% Exec compressed helper
+exec_compressed_helper(Config, CompressAlgorithm) ->
+    process_flag(trap_exit, true),
+    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+    UserDir = proplists:get_value(priv_dir, Config),
+
+    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
+                                             {preferred_algorithms,[{compression, [CompressAlgorithm]}]},
+                                             {failfun, fun ssh_test_lib:failfun/2}]),
+
+    ConnectionRef =
+        ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+                                          {user_dir, UserDir},
+                                          {user_interaction, false},
+                                          {preferred_algorithms,[{compression, [CompressAlgorithm]}]}]),
+    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+    success = ssh_connection:exec(ConnectionRef, ChannelId,
+                                  "1+1.", infinity),
+    Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"2">>}},
+    case ssh_test_lib:receive_exec_result(Data) of
+        expected ->
+            ok;
+        Other ->
+            ct:fail(Other)
+    end,
+    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
+    ssh:close(ConnectionRef),
+    ssh:stop_daemon(Pid).
 
 %%--------------------------------------------------------------------
 %%% Idle timeout test
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_protocol_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_protocol_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -84,6 +84,10 @@
          no_ext_info_s2/1,
          packet_length_too_large/1,
          packet_length_too_short/1,
+         decompression_bomb_client/1,
+         decompression_bomb_client_after_auth/1,
+         decompression_bomb_server/1,
+         decompression_bomb_server_after_auth/1,
          preferred_algorithms/1,
          service_name_length_too_large/1,
          service_name_length_too_short/1,
@@ -147,7 +151,11 @@ groups() ->
 		       lib_no_match
 		      ]},
      {packet_size_error, [], [packet_length_too_large,
-			      packet_length_too_short]},
+			      packet_length_too_short,
+			      decompression_bomb_client,
+			      decompression_bomb_client_after_auth,
+			      decompression_bomb_server,
+			      decompression_bomb_server_after_auth]},
      {field_size_error, [], [service_name_length_too_large,
 			     service_name_length_too_short]},
      {kex, [], [custom_kexinit,
@@ -241,6 +249,8 @@ init_per_testcase(TC, Config) when TC ==
 		     [{preferred_algorithms,[{cipher,?DEFAULT_CIPHERS}
                                             ]}
 		      | Opts]);
+init_per_testcase(decompression_bomb_client, Config) ->
+    start_std_daemon(Config, [{preferred_algorithms, [{compression, ['zlib']}]}]);
 init_per_testcase(_TestCase, Config) ->
     check_std_daemon_works(Config, ?LINE).
 
@@ -256,6 +266,8 @@ end_per_testcase(TC, Config) when TC ==
 				  TC == gex_client_old_request_exact ;
 				  TC == gex_client_old_request_noexact ->
     stop_std_daemon(Config);
+end_per_testcase(decompression_bomb_client, Config) ->
+    stop_std_daemon(Config);
 end_per_testcase(_TestCase, Config) ->
     check_std_daemon_works(Config, ?LINE).
 
@@ -693,6 +705,138 @@ bad_packet_length(Config, LengthExcess)
 	  ], InitialState).
 
 %%%--------------------------------------------------------------------
+decompression_bomb_client(Config) ->
+    {ok, InitialState} = connect_and_kex(Config, ssh_trpt_test_lib:exec([]),
+                                         [{kex, [?DEFAULT_KEX]},
+                                          {cipher, ?DEFAULT_CIPHERS},
+                                          {compression, ['zlib']}]),
+    %% ?SSH_MAX_PACKET_SIZE - 9 is enough to trigger disconnect because Payload of ssh packet becomes:
+    %% 1 byte message identifier
+    %% 4 bytes length of data field
+    %% ?SSH_MAX_PACKET_SIZE - 9 bytes of data
+    %% This is longer than max decompressed Payload length which is ?SSH_MAX_PACKET_SIZE - 5
+    %% See more in ssh_transport:safe_zlib_inflate_loop
+    Data = binary:copy(<<0>>, ?SSH_MAX_PACKET_SIZE - 9),
+    {ok, _} =
+        ssh_trpt_test_lib:exec([
+                                {send, #ssh_msg_ignore{data = Data}},
+                                {match, disconnect(), receive_msg}
+                               ], InitialState).
+
+%%%--------------------------------------------------------------------
+decompression_bomb_client_after_auth(Config) ->
+    {ok, InitialState} = connect_and_kex(Config, ssh_trpt_test_lib:exec([]),
+                                         [{kex, [?DEFAULT_KEX]},
+                                          {cipher, ?DEFAULT_CIPHERS},
+                                          {compression, ['zlib@openssh.com']}]),
+    {User, Pwd} = server_user_password(Config),
+    {ok, AfterAuthState} =
+        ssh_trpt_test_lib:exec(
+          [{send, #ssh_msg_service_request{name = "ssh-userauth"}},
+           {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg},
+           {send, #ssh_msg_userauth_request{user = User,
+                                            service = "ssh-connection",
+                                            method = "password",
+                                            data = <<?BOOLEAN(?FALSE),
+                                                     ?STRING(unicode:characters_to_binary(Pwd))>>
+                                           }},
+           {match, #ssh_msg_userauth_success{_='_'}, receive_msg}
+          ], InitialState),
+    %% See explanation in decompression_bomb_client
+    Data = binary:copy(<<0>>, ?SSH_MAX_PACKET_SIZE - 9),
+    {ok, _} =
+        ssh_trpt_test_lib:exec([
+                                {send, #ssh_msg_ignore{data = Data}},
+                                {match, disconnect(), receive_msg}
+                               ], AfterAuthState).
+
+%%%--------------------------------------------------------------------
+decompression_bomb_server(Config) ->
+    {ok, InitialState} = ssh_trpt_test_lib:exec(listen),
+    HostPort = ssh_trpt_test_lib:server_host_port(InitialState),
+    %% See explanation in decompression_bomb_client
+    Data = binary:copy(<<0>>, ?SSH_MAX_PACKET_SIZE - 9),
+    ServerPid =
+        spawn_link(
+          fun() ->
+                  {ok, _} =
+                      ssh_trpt_test_lib:exec(
+                        [{set_options, [print_ops, print_messages]},
+                         {accept, [{system_dir, system_dir(Config)},
+                                   {user_dir, user_dir(Config)},
+                                   {preferred_algorithms,[{kex, [?DEFAULT_KEX]},
+                                                          {cipher, ?DEFAULT_CIPHERS},
+                                                          {compression, ['zlib']}]}]},
+                         receive_hello,
+                         {send, hello},
+                         {send, ssh_msg_kexinit},
+                         {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+                         {match, #ssh_msg_kexdh_init{_='_'}, receive_msg},
+                         {send, ssh_msg_kexdh_reply},
+                         {send, #ssh_msg_newkeys{}},
+                         {match, #ssh_msg_newkeys{_='_'}, receive_msg},
+                         {send, #ssh_msg_ignore{data = Data}},
+                         {match, disconnect(), receive_msg}
+                        ], InitialState)
+          end),
+    Ref = monitor(process, ServerPid),
+    {error, "Protocol error"} =
+        std_connect(HostPort, Config,
+                    [{silently_accept_hosts, true},
+                     {user_dir, user_dir(Config)},
+                     {user_interaction, false},
+                     {preferred_algorithms, [{compression,['zlib']}]}]),
+    receive
+        {'DOWN', Ref, process, ServerPid, normal} -> ok
+    end.
+
+%%%--------------------------------------------------------------------
+decompression_bomb_server_after_auth(Config) ->
+    {ok, InitialState} = ssh_trpt_test_lib:exec(listen),
+    HostPort = ssh_trpt_test_lib:server_host_port(InitialState),
+    %% See explanation in decompression_bomb_client
+    Data = binary:copy(<<0>>, ?SSH_MAX_PACKET_SIZE - 9),
+    ServerPid =
+        spawn_link(
+          fun() ->
+                  {ok ,_} =
+                      ssh_trpt_test_lib:exec(
+                        [{set_options, [print_ops, print_messages]},
+                         {accept, [{system_dir, system_dir(Config)},
+                                   {user_dir, user_dir(Config)},
+                                   {preferred_algorithms,[{kex, [?DEFAULT_KEX]},
+                                                          {cipher, ?DEFAULT_CIPHERS},
+                                                          {compression, ['zlib@openssh.com']}]}]},
+                         receive_hello,
+                         {send, hello},
+                         {send, ssh_msg_kexinit},
+                         {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+                         {match, #ssh_msg_kexdh_init{_='_'}, receive_msg},
+                         {send, ssh_msg_kexdh_reply},
+                         {send, #ssh_msg_newkeys{}},
+                         {match, #ssh_msg_newkeys{_='_'}, receive_msg},
+                         {match, #ssh_msg_service_request{name="ssh-userauth"}, receive_msg},
+                         {send, #ssh_msg_service_accept{name="ssh-userauth"}},
+                         {match, #ssh_msg_userauth_request{service="ssh-connection",
+                                                           method="none",
+                                                           _='_'}, receive_msg},
+                         {send, #ssh_msg_userauth_success{}},
+                         {send, #ssh_msg_ignore{data = Data}},
+                         {match, disconnect(), receive_msg}
+                        ], InitialState)
+          end),
+    Ref = monitor(process, ServerPid),
+    {ok, _} =
+        std_connect(HostPort, Config,
+                    [{silently_accept_hosts, true},
+                     {user_dir, user_dir(Config)},
+                     {user_interaction, false},
+                     {preferred_algorithms, [{compression, ['zlib@openssh.com']}]}]),
+    receive
+        {'DOWN', Ref, process, ServerPid, normal} -> ok
+    end.
+
+%%%--------------------------------------------------------------------
 service_name_length_too_large(Config) -> bad_service_name_length(Config, +4).
 
 service_name_length_too_short(Config) -> bad_service_name_length(Config, -4).
@@ -1565,24 +1709,26 @@ connect_and_kex(Config) ->
     connect_and_kex(Config, ssh_trpt_test_lib:exec([]) ).
 
 connect_and_kex(Config, InitialState) ->
+    ClientAlgs = [{kex,[?DEFAULT_KEX]}, {cipher,?DEFAULT_CIPHERS}],
+    connect_and_kex(Config, InitialState, ClientAlgs).
+
+connect_and_kex(Config, InitialState, ClientAlgs) ->
     ssh_trpt_test_lib:exec(
       [{connect,
-	server_host(Config),server_port(Config),
-	[{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
-                                {cipher,?DEFAULT_CIPHERS}
-                               ]},
+        server_host(Config),server_port(Config),
+        [{preferred_algorithms, ClientAlgs},
          {silently_accept_hosts, true},
          {recv_ext_info, false},
-	 {user_dir, user_dir(Config)},
-	 {user_interaction, false}
-         | proplists:get_value(extra_options,Config,[])
+         {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},
-       {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg},
+       {match, #ssh_msg_kexdh_reply{_='_'}, receive_msg},
        {send, #ssh_msg_newkeys{}},
        {match, #ssh_msg_newkeys{_='_'}, receive_msg}
       ],
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_trpt_test_lib.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_trpt_test_lib.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_trpt_test_lib.erl
@@ -447,7 +447,13 @@ send(S0, #ssh_msg_newkeys{} = Msg) ->
 	    fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
     {ok, Packet, C} = ssh_transport:new_keys_message(S#s.ssh),
     send_bytes(Packet, S#s{ssh = C});
-    
+
+send(S0, #ssh_msg_userauth_success{} = Msg) ->
+    S = opt(print_messages, S0,
+        fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
+    {Packet, C} = ssh_transport:ssh_packet(Msg, S#s.ssh),
+    send_bytes(Packet, S#s{ssh = C#ssh{authenticated = true}, return_value = Msg});
+
 send(S0, Msg) when is_tuple(Msg) ->
     S = opt(print_messages, S0,
 	    fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
@@ -513,6 +519,9 @@ recv(S0 = #s{}) ->
 		#ssh_msg_newkeys{} ->
 		    {ok, C} = ssh_transport:handle_new_keys(PeerMsg, S#s.ssh),
 		    S#s{ssh=C};
+		#ssh_msg_userauth_success{} -> % Always the client
+		    C = S#s.ssh,
+		    S#s{ssh = C#ssh{authenticated = true}};
 		_ ->
 		    S
 	    end
Index: otp-OTP-23.3.4.19/lib/ssh/doc/src/SSH_app.xml
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/doc/src/SSH_app.xml
+++ otp-OTP-23.3.4.19/lib/ssh/doc/src/SSH_app.xml
@@ -275,8 +275,17 @@
 	<list type="bulleted">
 	  <item>none</item>
 	  <item>zlib@openssh.com</item>
-	  <item>zlib</item>
 	</list>
+	<p>The following compression algorithm is disabled by default:</p>
+	<list>
+	  <item>(zlib)</item>
+	</list>
+	<p>It can be enabled with the
+	<seetype marker="ssh:ssh#preferred_algorithms_common_option">preferred_algorithms</seetype>
+	or
+	<seetype marker="ssh:ssh#modify_algorithms_common_option">modify_algorithms</seetype>
+	options.
+	</p>
       </item>
     </taglist>
   </section>
Index: otp-OTP-23.3.4.19/lib/ssh/doc/src/configurations.xml
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/doc/src/configurations.xml
+++ otp-OTP-23.3.4.19/lib/ssh/doc/src/configurations.xml
@@ -189,8 +189,8 @@ Eshell V10.6.4  (abort with ^G)
                        'hmac-sha1']},
        {server2client,['hmac-sha2-256','hmac-sha2-512',
                        'hmac-sha1']}]},
- {compression,[{client2server,[none,'zlib@openssh.com',zlib]},
-               {server2client,[none,'zlib@openssh.com',zlib]}]}]
+ {compression,[{client2server,[none,'zlib@openssh.com']},
+               {server2client,[none,'zlib@openssh.com']}]}]
 	</code>
 	<p>Note that the algorithms in the file <c>ex2.config</c> is not yet applied. They will be
 	when we start ssh:
@@ -205,8 +205,8 @@ ok
           {server2client,['aes192-ctr']}]},
  {mac,[{client2server,['hmac-sha1']},
        {server2client,['hmac-sha1']}]},
- {compression,[{client2server,[none,'zlib@openssh.com',zlib]},
-               {server2client,[none,'zlib@openssh.com',zlib]}]}]
+ {compression,[{client2server,[none,'zlib@openssh.com']},
+               {server2client,[none,'zlib@openssh.com']}]}]
 4> 
 
 	</code>
Index: otp-OTP-23.3.4.19/lib/ssh/doc/src/configure_algos.xml
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/doc/src/configure_algos.xml
+++ otp-OTP-23.3.4.19/lib/ssh/doc/src/configure_algos.xml
@@ -100,8 +100,9 @@
 
 	<tag><c>compression</c></tag>
 	<item>
-	  <p>If and how to compress the message. Examples are <c>none</c>, that is, no compression and
-	  <c>zlib</c>.</p>
+	  <p>If and how to compress the message. Examples are <c>none</c>, that is, no compression,
+	  <c>zlib</c> for pre-authentication compression (disabled by default), and <c>'zlib@openssh.com'</c>
+	  for post-authentication compression.</p>
 	  <p>This list is also divided into two for the both directions</p>
 	</item>
 	
@@ -145,8 +146,8 @@
                        'hmac-sha1']},
        {server2client,['hmac-sha2-256','hmac-sha2-512',
                        'hmac-sha1']}]},
- {compression,[{client2server,[none,'zlib@openssh.com',zlib]},
-               {server2client,[none,'zlib@openssh.com',zlib]}]}]
+ {compression,[{client2server,[none,'zlib@openssh.com']},
+               {server2client,[none,'zlib@openssh.com']}]}]
 
       </code>
       <p>To change the algorithm list, there are two options which can be used in 
@@ -196,8 +197,8 @@
                        'hmac-sha1']},
        {server2client,['hmac-sha2-256','hmac-sha2-512',
                        'hmac-sha1']}]},
- {compression,[{client2server,[none,'zlib@openssh.com',zlib]},
-               {server2client,[none,'zlib@openssh.com',zlib]}]}]
+ {compression,[{client2server,[none,'zlib@openssh.com']},
+               {server2client,[none,'zlib@openssh.com']}]}]
       </code>
       <p>Note that the unmentioned lists (<c>public_key</c>, <c>cipher</c>, <c>mac</c> and <c>compression</c>)
       are un-changed.</p>
@@ -230,8 +231,8 @@
                        'hmac-sha1']},
        {server2client,['hmac-sha2-256','hmac-sha2-512',
                        'hmac-sha1']}]},
- {compression,[{client2server,[none,'zlib@openssh.com',zlib]},
-               {server2client,[none,'zlib@openssh.com',zlib]}]}]
+ {compression,[{client2server,[none,'zlib@openssh.com']},
+               {server2client,[none,'zlib@openssh.com']}]}]
       </code>
       <p>Note that both lists in <c>cipher</c> has been changed to the provided value (<c>'aes128-ctr'</c>).</p>
     </section>
@@ -265,8 +266,8 @@
                        'hmac-sha1']},
        {server2client,['hmac-sha2-256','hmac-sha2-512',
                        'hmac-sha1']}]},
- {compression,[{client2server,[none,'zlib@openssh.com',zlib]},
-               {server2client,[none,'zlib@openssh.com',zlib]}]}]
+ {compression,[{client2server,[none,'zlib@openssh.com']},
+               {server2client,[none,'zlib@openssh.com']}]}]
       </code>
     </section>
 
@@ -353,8 +354,8 @@
                        'hmac-sha1']},
        {server2client,['hmac-sha2-256','hmac-sha2-512',
                        'hmac-sha1']}]},
- {compression,[{client2server,[none,'zlib@openssh.com',zlib]},
-               {server2client,[none,'zlib@openssh.com',zlib]}]}]
+ {compression,[{client2server,[none,'zlib@openssh.com']},
+               {server2client,[none,'zlib@openssh.com']}]}]
 
       </code>
       <p>And the result shows that the Diffie-Hellman Group1 is added at the head of the kex list</p>
Index: otp-OTP-23.3.4.19/lib/ssh/doc/src/ssh.xml
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/doc/src/ssh.xml
+++ otp-OTP-23.3.4.19/lib/ssh/doc/src/ssh.xml
@@ -1025,7 +1025,7 @@
 	  {cipher,[{client2server,['aes128-ctr']},
           {server2client,['aes128-cbc','3des-cbc']}]},
 	  {mac,['hmac-sha2-256','hmac-sha1']},
-	  {compression,[none,zlib]}
+	  {compression,[none,'zlib@openssh.com']}
 	  ]
 	  }
 	</code>
Index: otp-OTP-23.3.4.19/lib/ssh/doc/src/hardening.xml
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/doc/src/hardening.xml
+++ otp-OTP-23.3.4.19/lib/ssh/doc/src/hardening.xml
@@ -288,6 +288,28 @@ end.
       with OS-level isolation mechanisms such as chroot jails, containers, or
       mandatory access control (SELinux, AppArmor).</p>
     </section>
+    <section>
+      <title>Resilience to compression-based attacks</title>
+      <p>SSH supports compression of the data stream.
+      </p>
+      <p>Reasonable finite
+      <seeerl marker="ssh#hardening_daemon_options--max_sessions">max_sessions</seeerl>
+      option is highly recommended if compression is used to prevent excessive resource
+      usage by the compression library.
+      See <seeguide marker="#counters-and-parallelism">Counters and parallelism</seeguide>.
+      </p>
+      <p>The <c>'zlib@openssh.com'</c> algorithm is recommended because it only activates
+      after successful authentication.
+      </p>
+      <p>The <c>'zlib'</c> algorithm is not recommended because it activates before
+      authentication completes, allowing unauthenticated clients to expose potential
+      vulnerabilities in compression libraries, and increases attack surface of
+      compression-based side-channel and traffic-analysis attacks.
+      </p>
+      <p>In both algorithms decompression is protected by a size limit that prevents
+      excessive memory consumption.
+      </p>
+    </section>
   </section>
 
 </chapter>
