File 0240-ssl-Correct-max_frag_len-for-resumption-renegotiatio.patch of Package erlang

From cff9955408afbaf8e63b1b04a4de6570bd985726 Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Tue, 27 Jan 2026 10:08:01 +0100
Subject: [PATCH] ssl: Correct max_frag_len for resumption/renegotiation

According to RFC 6066 TLS-1.2 and previous TLS versions
should not change the max_fragment_length negotiated
in the first handshake during session resumption or renegotiation.

TLS-1.3 does not have the same mechanism.
---
 lib/ssl/src/dtls_client_connection.erl    |  2 +-
 lib/ssl/src/ssl_handshake.erl             | 60 ++++++++++++++---------
 lib/ssl/src/ssl_handshake.hrl             |  1 +
 lib/ssl/src/ssl_record.erl                | 11 +++--
 lib/ssl/src/tls_client_connection.erl     |  2 +-
 lib/ssl/src/tls_handshake_1_3.erl         |  5 +-
 lib/ssl/src/tls_server_connection_1_3.erl |  7 +--
 7 files changed, 52 insertions(+), 36 deletions(-)

diff --git a/lib/ssl/src/dtls_client_connection.erl b/lib/ssl/src/dtls_client_connection.erl
index 0d2ac57b0a..627ab22cbd 100644
--- a/lib/ssl/src/dtls_client_connection.erl
+++ b/lib/ssl/src/dtls_client_connection.erl
@@ -204,7 +204,7 @@ initial_hello({call, From}, {start, Timeout},
 					Session#session.session_id, Renegotiation),
 
     MaxFragEnum = maps:get(max_frag_enum, Hello#client_hello.extensions, undefined),
-    ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+    ConnectionStates1 = ssl_record:maybe_set_max_fragment_length(MaxFragEnum, ConnectionStates0),
     Version = Hello#client_hello.client_version,
     HelloVersion = dtls_record:hello_version(Version, Versions),
     State1 = dtls_gen_connection:prepare_flight(
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 9cb75753ec..bf0d0a3e9c 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -1504,29 +1504,27 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
                                  alpn_preferred_protocols := ALPNPreferredProtocols} = Opts,
 			       #session{cipher_suite = NegotiatedCipherSuite} = Session0,
 			       ConnectionStates0, Renegotiation, IsResumed) ->
-    Session = handle_srp_extension(maps:get(srp, Exts, undefined), Session0),
-    MaxFragEnum = handle_mfl_extension(maps:get(max_frag_enum, Exts, undefined)),
-    ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
-    ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined),
-						      Random, NegotiatedCipherSuite, 
-						      ClientCipherSuites,
-						      ConnectionStates1, Renegotiation, SecureRenegotation),
+    Session1 = handle_srp_extension(maps:get(srp, Exts, undefined), Session0),
+    ConnectionStates1 = handle_renegotiation_extension(server, RecordCB, Version,
+                                                       maps:get(renegotiation_info, Exts, undefined),
+                                                       Random, NegotiatedCipherSuite,
+                                                       ClientCipherSuites,
+                                                       ConnectionStates0,
+                                                       Renegotiation, SecureRenegotation),
 
     Empty = empty_extensions(Version, server_hello),
-    %% RFC 6066 - server doesn't include max_fragment_length for resumed sessions
-    ServerMaxFragEnum = if IsResumed ->
-                                undefined;
-                           true ->
-                                MaxFragEnum
-                        end,
-    ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server,
-                                                                            ConnectionStates, Renegotiation),
-                                   ec_point_formats => server_ecc_extension(Version, 
-                                                                            maps:get(ec_point_formats, Exts, undefined)),
-                                   use_srtp => use_srtp_ext(Opts),
-                                   max_frag_enum => ServerMaxFragEnum
-                                  },
-    
+    {ServerMaxFragEnum, ConnectionStates, Session} =
+        handle_max_fragment_length(Exts, Renegotiation, IsResumed,
+                                   Session1, ConnectionStates1),
+    ServerHelloExtensions =
+        Empty#{renegotiation_info => renegotiation_info(RecordCB, server,
+                                                        ConnectionStates, Renegotiation),
+               ec_point_formats => server_ecc_extension(Version,
+                                                        maps:get(ec_point_formats, Exts, undefined)),
+               use_srtp => use_srtp_ext(Opts),
+               max_frag_enum => ServerMaxFragEnum
+              },
+
     %% If we receive an ALPN extension and have ALPN configured for this connection,
     %% we handle it. Otherwise we check for the NPN extension.
     ALPN = maps:get(alpn, Exts, undefined),
@@ -1537,7 +1535,8 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites,
              ServerHelloExtensions#{alpn => encode_alpn([Protocol], Renegotiation)}};
         true ->
             NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined),
-            ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts),
+            ProtocolsToAdvertise =
+                handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts),
             {Session, ConnectionStates, undefined,
              ServerHelloExtensions#{next_protocol_negotiation =>
                                         encode_protocols_advertised_on_server(ProtocolsToAdvertise)}}
@@ -1597,6 +1596,23 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite,
             throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello))
     end.
 
+handle_max_fragment_length(Exts, Renegotiation, IsResumed,
+                           #session{max_frag_enum = MaxFragEnum0} = Session, ConnectionStates0) ->
+    %% RFC 6066 - TLS-1.2 (or previous version) server doesn't include
+    %% max_fragment_length in server hello extensions for resumed or renegotiated
+    %% sessions ...
+    if IsResumed orelse Renegotiation ->
+            %% ... and continues using previously negotiate value if it exists
+            {undefined,
+             ssl_record:maybe_set_max_fragment_length(MaxFragEnum0, ConnectionStates0),
+             Session};
+       true ->
+            MaxFragEnum = handle_mfl_extension(maps:get(max_frag_enum, Exts, undefined)),
+            {MaxFragEnum,
+             ssl_record:maybe_set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+             Session#session{max_frag_enum = MaxFragEnum}}
+    end.
+
 select_curve(Client, Server) ->
     select_curve(Client, Server, false).
 
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index 522a8bfd62..f31e52df0a 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -46,6 +46,7 @@
                   cipher_suite,
                   master_secret,
                   srp_username,
+                  max_frag_enum,
                   is_resumable,
                   time_stamp,
                   ecc,                   %% TLS 1.3 Group
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index b105ecbfd0..d163f88267 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -41,7 +41,7 @@
 	 set_renegotiation_flag/2,
 	 set_client_verify_data/3,
 	 set_server_verify_data/3,
-         set_max_fragment_length/2,
+         maybe_set_max_fragment_length/2,
 	 empty_connection_state/1,
 	 empty_connection_state/3,
          record_protocol_role/1,
@@ -212,11 +212,12 @@ set_renegotiation_flag(Flag, #{current_read := CurrentRead,
 		      pending_write => Update(PendingWrite)}.
 
 %%--------------------------------------------------------------------
--spec set_max_fragment_length(term(), connection_states()) -> connection_states().
+-spec maybe_set_max_fragment_length(term(), connection_states()) -> connection_states().
 %%
-%% Description: Set maximum fragment length in all connection states
+%% Description: Set maximum fragment length in all connection states when
+%% it has been negotiated.
 %%--------------------------------------------------------------------
-set_max_fragment_length(#max_frag_enum{enum = MaxFragEnum}, ConnectionStates)
+maybe_set_max_fragment_length(#max_frag_enum{enum = MaxFragEnum}, ConnectionStates)
   when is_integer(MaxFragEnum), 1 =< MaxFragEnum, MaxFragEnum =< 4 ->
     MaxFragmentLength = if MaxFragEnum == 1 -> ?MAX_FRAGMENT_LENGTH_BYTES_1;
                            MaxFragEnum == 2 -> ?MAX_FRAGMENT_LENGTH_BYTES_2;
@@ -224,7 +225,7 @@ set_max_fragment_length(#max_frag_enum{enum = MaxFragEnum}, ConnectionStates)
                            MaxFragEnum == 4 -> ?MAX_FRAGMENT_LENGTH_BYTES_4
                         end,
     ConnectionStates#{max_fragment_length => MaxFragmentLength};
-set_max_fragment_length(_,ConnectionStates) ->
+maybe_set_max_fragment_length(_,ConnectionStates) ->
     ConnectionStates.
 
 %%--------------------------------------------------------------------
diff --git a/lib/ssl/src/tls_client_connection.erl b/lib/ssl/src/tls_client_connection.erl
index c99226f78e..d5c730078e 100644
--- a/lib/ssl/src/tls_client_connection.erl
+++ b/lib/ssl/src/tls_client_connection.erl
@@ -214,7 +214,7 @@ initial_hello({call, From}, {start, Timeout},
     Hello2 = tls_handshake_1_3:maybe_add_binders(Hello1, TicketData, HelloVersion),
 
     MaxFragEnum = maps:get(max_frag_enum, Hello1#client_hello.extensions, undefined),
-    ConnectionStates1 = ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+    ConnectionStates1 = ssl_record:maybe_set_max_fragment_length(MaxFragEnum, ConnectionStates0),
     State2 = State1#state{connection_states = ConnectionStates1,
                           connection_env = CEnv#connection_env{negotiated_version = HelloVersion}},
 
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 5ef4f91a53..ff1c13f89d 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -183,7 +183,8 @@ maybe_add_cookie_extension(Cookie,
     Extensions = Extensions0#{cookie => Cookie},
     ClientHello#client_hello{extensions = Extensions}.
 
-encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
+encrypted_extensions(#state{session = #session{max_frag_enum = MaxFragEnum},
+                            handshake_env = HandshakeEnv}) ->
     E0 = #{},
     E1 = case HandshakeEnv#handshake_env.alpn of
              undefined ->
@@ -191,7 +192,7 @@ encrypted_extensions(#state{handshake_env = HandshakeEnv}) ->
              ALPNProtocol ->
                  ssl_handshake:add_alpn(#{}, ALPNProtocol)
          end,
-    E2 = case HandshakeEnv#handshake_env.max_frag_enum of
+    E2 = case MaxFragEnum of
              undefined ->
                  E1;
              MaxFragEnum ->
diff --git a/lib/ssl/src/tls_server_connection_1_3.erl b/lib/ssl/src/tls_server_connection_1_3.erl
index 8f0e0864c0..7dc693d848 100644
--- a/lib/ssl/src/tls_server_connection_1_3.erl
+++ b/lib/ssl/src/tls_server_connection_1_3.erl
@@ -460,11 +460,8 @@ do_handle_client_hello(#client_hello{cipher_suites = ClientCiphers,
         State2 = case maps:get(max_frag_enum, Extensions, undefined) of
                       MaxFragEnum when is_record(MaxFragEnum, max_frag_enum) ->
                          ConnectionStates1 =
-                             ssl_record:set_max_fragment_length(MaxFragEnum, ConnectionStates0),
-                         HsEnv1 = (State1#state.handshake_env)#handshake_env{max_frag_enum =
-                                                                                 MaxFragEnum},
-                         State1#state{handshake_env = HsEnv1,
-                                      session = Session,
+                             ssl_record:maybe_set_max_fragment_length(MaxFragEnum, ConnectionStates0),
+                         State1#state{session = Session#session{max_frag_enum = MaxFragEnum},
                                       connection_states = ConnectionStates1};
                      _ ->
                          State1#state{session = Session}
-- 
2.51.0

openSUSE Build Service is sponsored by