File 0657-ssl-Properly-handle-default-session-data-storage.patch of Package erlang
From 142694507b0e6148dc21ed63ecd734b3ba8c6c26 Mon Sep 17 00:00:00 2001
From: Ingela Anderton Andin <ingela@erlang.org>
Date: Mon, 13 Sep 2021 17:12:31 +0200
Subject: [PATCH] ssl: Properly handle default session data storage
When a client tries to reuse an expired session the default
server storage handling would crash loosing other session data.
This would cause a error report and possible loss of abbreviated handshakes.
Solves #5192
---
lib/ssl/src/ssl_server_session_cache.erl | 10 +-
lib/ssl/test/ssl_session_SUITE.erl | 126 ++++++++++++++++++++++-
2 files changed, 126 insertions(+), 10 deletions(-)
diff --git a/lib/ssl/src/ssl_server_session_cache.erl b/lib/ssl/src/ssl_server_session_cache.erl
index 4c77fa951b..a89959413c 100644
--- a/lib/ssl/src/ssl_server_session_cache.erl
+++ b/lib/ssl/src/ssl_server_session_cache.erl
@@ -143,8 +143,8 @@ handle_call({reuse_session, SessionId}, _From, #state{store_cb = Cb,
true ->
{reply, Session, State0};
false ->
- Order = invalidate_session(Cb, Store0, Order0, SessionId, InId),
- {reply, not_reusable, State0#state{session_order = Order}}
+ {Store, Order} = invalidate_session(Cb, Store0, Order0, SessionId, InId),
+ {reply, not_reusable, State0#state{db = Store, session_order = Order}}
end
end.
@@ -220,9 +220,9 @@ session_id(Key) ->
Bin2 = crypto:crypto_one_time(aes_128_ecb, Key, <<Unique2:128>>, true),
<<Bin1/binary, Bin2/binary>>.
-invalidate_session(Cb, Store, Order, SessionId, InternalId) ->
- Cb:delete(Store, SessionId),
- gb_trees:delete(InternalId, Order).
+invalidate_session(Cb, Store0, Order, SessionId, InternalId) ->
+ Store = delete(Cb, Store0, SessionId),
+ {Store, gb_trees:delete(InternalId, Order)}.
init(Cb, Options) ->
Cb:init(Options).
diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl
index 33a455bf77..8f2be84a84 100644
--- a/lib/ssl/test/ssl_session_SUITE.erl
+++ b/lib/ssl/test/ssl_session_SUITE.erl
@@ -49,6 +49,8 @@
server_does_not_want_to_reuse_session/1,
explicit_session_reuse/0,
explicit_session_reuse/1,
+ explicit_session_reuse_expired/0,
+ explicit_session_reuse_expired/1,
no_reuses_session_server_restart_new_cert/0,
no_reuses_session_server_restart_new_cert/1,
no_reuses_session_server_restart_new_cert_file/0,
@@ -62,7 +64,7 @@
]).
-define(SLEEP, 500).
--define(EXPIRE, 10).
+-define(EXPIRE, 2).
-define(CLIENT_CB, ssl_client_session_cache_db).
%%--------------------------------------------------------------------
@@ -91,6 +93,7 @@ session_tests() ->
reuse_session_expired,
server_does_not_want_to_reuse_session,
explicit_session_reuse,
+ explicit_session_reuse_expired,
no_reuses_session_server_restart_new_cert,
no_reuses_session_server_restart_new_cert_file,
client_max_session_table,
@@ -121,7 +124,8 @@ init_per_group(GroupName, Config) ->
end_per_group(GroupName, Config) ->
ssl_test_lib:end_per_group(GroupName, Config).
-init_per_testcase(reuse_session_expired, Config) ->
+init_per_testcase(TestCase, Config) when TestCase == reuse_session_expired;
+ TestCase == explicit_session_reuse_expired ->
Versions = ssl_test_lib:protocol_version(Config),
ssl:stop(),
application:load(ssl),
@@ -216,7 +220,7 @@ reuse_session_expired(Config) when is_list(Config) ->
Server0 ! listen,
%% Make sure session is unregistered due to expiration
- ct:sleep((?EXPIRE*2)),
+ ct:sleep({seconds, ?EXPIRE*2}),
make_sure_expired(Hostname, Port0, SID),
@@ -341,6 +345,118 @@ explicit_session_reuse(Config) when is_list(Config) ->
{ok, [{session_id, ID}]} = ssl:connection_information(Client1Sock, [session_id]).
+%%--------------------------------------------------------------------
+
+explicit_session_reuse_expired() ->
+ [{doc,"Test to reuse a session that has expired and make sure server session table is correctly handled"}].
+explicit_session_reuse_expired(Config) when is_list(Config) ->
+ ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ {Client0, Client0Sock} =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, [{reuse_sessions, false} | ClientOpts]},
+ return_socket
+ ]),
+ %% Retrieve session data
+ {ok, [{session_id, ID0}, {session_data, SessData}]} = ssl:connection_information(Client0Sock, [session_id, session_data]),
+
+ ssl_test_lib:close(Client0),
+
+ Server ! listen,
+
+ SupName = sup_name(ServerOpts),
+ Sup = whereis(SupName),
+ %% Will only be one process, that is one server, in our test scenario
+ [{_, SessionCachePid, worker,[ssl_server_session_cache]}] = supervisor:which_children(Sup),
+
+ %% Start a new connections so there are three sessions
+ {Client1, Client1Sock} = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]},
+ return_socket]),
+
+ Server ! listen,
+
+ {Client2, Client2Sock} = ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]},
+ return_socket]),
+
+ {ok, [{session_id, ID1}]} = ssl:connection_information(Client1Sock, [session_id]),
+
+ %% Assert three sessions in server table
+ {SessionCacheCb, SessionCacheDb} = session_cachce_info(SessionCachePid),
+ 3 = SessionCacheCb:size(SessionCacheDb),
+
+ Server ! listen,
+
+ {ok, [{session_id, ID2}]} = ssl:connection_information(Client2Sock, [session_id]),
+
+ ssl_test_lib:close(Client1),
+ ssl_test_lib:close(Client2),
+
+ %% Make sure session expired
+ ct:sleep({seconds, ?EXPIRE*2}),
+
+ %% Try to reuse session one after expiration
+ {_, Client3Sock} =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, [{reuse_session, {ID0, SessData}} | ClientOpts]},
+ return_socket]),
+
+ %% Verify that we got a new session
+ {ok, [{session_id, ID3}]} = ssl:connection_information(Client3Sock, [session_id]),
+
+ %% We tried reusing ID0. So ID1 and ID2 should not be possible but assert anyway
+ true = (ID3=/= ID0) andalso (ID3 =/= ID1) andalso (ID3 =/= ID2),
+
+ %% Server table should have removed the expired session that we tried to reuse.
+ %% and replaced the second one that expired. Leaving the third and the new
+ {SessionCacheCb1, SessionCacheDb1} = session_cachce_info(SessionCachePid),
+ 2 = SessionCacheCb1:size(SessionCacheDb1),
+
+
+ Server ! listen,
+
+ %% Make sure session expired
+ ct:sleep({seconds, ?EXPIRE*2}),
+
+
+ %% Nothing is removed yet
+ {SessionCacheCb2, SessionCacheDb2} = session_cachce_info(SessionCachePid),
+ 2 = SessionCacheCb2:size(SessionCacheDb2),
+
+
+ {_, Client4Sock} =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, ClientOpts},
+ return_socket]),
+
+ %% Expired session is not reused
+ {ok, [{session_id, ID4}]} = ssl:connection_information(Client4Sock, [session_id]),
+ true = (ID4 =/= ID3),
+
+ %% One expired session is replaced
+ {SessionCacheCb3, SessionCacheDb3} = session_cachce_info(SessionCachePid),
+ 2 = SessionCacheCb3:size(SessionCacheDb3).
+
+
%%--------------------------------------------------------------------
no_reuses_session_server_restart_new_cert() ->
[{doc,"Check that a session is not reused if the server is restarted with a new cert."}].
@@ -500,7 +616,7 @@ session_table_stable_size_on_tcp_close(Config) when is_list(Config)->
Sup = whereis(ssl_server_session_cache_sup),
- %% Will only be one process, that is one server, in our test senario
+ %% Will only be one process, that is one server, in our test scenario
[{_, SessionCachePid, worker,[ssl_server_session_cache]}] = supervisor:which_children(Sup),
@@ -538,7 +654,7 @@ faulty_client(Host, Port) ->
CH = client_hello(Random),
CHBin = encode_client_hello(CH, Random),
gen_tcp:send(Sock, CHBin),
- ct:sleep(100),
+ ct:sleep(?SLEEP),
gen_tcp:close(Sock).
--
2.31.1