Skip to content

Commit

Permalink
ssl: Handle really big handshake packages
Browse files Browse the repository at this point in the history
If a handshake message is really big it could happen that the ssl
process would hang due to failing of requesting more data from the
socket. This has been fixed.

Also added option to limit max handshake size. It has a default
value that should be big enough to handle normal usage and small
enough to mitigate DoS attacks.
  • Loading branch information
IngelaAndin committed Jan 17, 2017
1 parent 605a462 commit 1364c73
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 15 deletions.
8 changes: 8 additions & 0 deletions lib/ssl/doc/src/ssl.xml
Expand Up @@ -424,6 +424,14 @@ marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_valid
</taglist>

</item>

<tag><c>max_handshake_size</c></tag>
<item>
<p>Integer (24 bits unsigned). Used to limit the size of
valid TLS handshake packets to avoid DoS attacks.
Defaults to 256*1024.</p>
</item>

</taglist>

</item>
Expand Down
8 changes: 6 additions & 2 deletions lib/ssl/src/ssl.erl
Expand Up @@ -765,7 +765,8 @@ handle_options(Opts0, Role) ->
client, Role),
crl_check = handle_option(crl_check, Opts, false),
crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}}),
v2_hello_compatible = handle_option(v2_hello_compatible, Opts, false)
v2_hello_compatible = handle_option(v2_hello_compatible, Opts, false),
max_handshake_size = handle_option(max_handshake_size, Opts, ?DEFAULT_MAX_HANDSHAKE_SIZE)
},

CbInfo = proplists:get_value(cb_info, Opts, default_cb_info(Protocol)),
Expand All @@ -780,7 +781,8 @@ handle_options(Opts0, Role) ->
alpn_preferred_protocols, next_protocols_advertised,
client_preferred_next_protocols, log_alert,
server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache,
fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation, v2_hello_compatible],
fallback, signature_algs, eccs, honor_ecc_order, beast_mitigation, v2_hello_compatible,
max_handshake_size],

SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
Expand Down Expand Up @@ -1028,6 +1030,8 @@ validate_option(beast_mitigation, Value) when Value == one_n_minus_one orelse
Value;
validate_option(v2_hello_compatible, Value) when is_boolean(Value) ->
Value;
validate_option(max_handshake_size, Value) when is_integer(Value) andalso Value =< ?MAX_UNIT24 ->
Value;
validate_option(Opt, Value) ->
throw({error, {options, {Opt, Value}}}).

Expand Down
3 changes: 3 additions & 0 deletions lib/ssl/src/ssl_handshake.hrl
Expand Up @@ -80,6 +80,9 @@
-define(CLIENT_KEY_EXCHANGE, 16).
-define(FINISHED, 20).

-define(MAX_UNIT24, 8388607).
-define(DEFAULT_MAX_HANDSHAKE_SIZE, (256*1024)).

-record(random, {
gmt_unix_time, % uint32
random_bytes % opaque random_bytes[28]
Expand Down
3 changes: 2 additions & 1 deletion lib/ssl/src/ssl_internal.hrl
Expand Up @@ -142,7 +142,8 @@
signature_algs,
eccs,
honor_ecc_order :: boolean(),
v2_hello_compatible :: boolean()
v2_hello_compatible :: boolean(),
max_handshake_size :: integer()
}).

-record(socket_options,
Expand Down
50 changes: 39 additions & 11 deletions lib/ssl/src/tls_connection.erl
Expand Up @@ -424,18 +424,26 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
ssl_options = Options} = State0) ->
try
{Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options),
State =
State1 =
State0#state{protocol_buffers =
Buffers#protocol_buffers{tls_handshake_buffer = Buf}},
Events = tls_handshake_events(Packets),
case StateName of
connection ->
ssl_connection:hibernate_after(StateName, State, Events);
_ ->
{next_state, StateName, State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
end
case Packets of
[] ->
assert_buffer_sanity(Buf, Options),
{Record, State} = next_record(State1),
next_event(StateName, Record, State);
_ ->
Events = tls_handshake_events(Packets),
case StateName of
connection ->
ssl_connection:hibernate_after(StateName, State1, Events);
_ ->
{next_state, StateName,
State1#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events}
end
end
catch throw:#alert{} = Alert ->
ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
end;
%%% TLS record protocol level application data messages
handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) ->
Expand Down Expand Up @@ -615,8 +623,6 @@ next_event(StateName, Record, State, Actions) ->
{next_state, StateName, State, [{next_event, internal, Alert} | Actions]}
end.

tls_handshake_events([]) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake));
tls_handshake_events(Packets) ->
lists:map(fun(Packet) ->
{next_event, internal, {handshake, Packet}}
Expand Down Expand Up @@ -735,3 +741,25 @@ unprocessed_events(Events) ->
%% handshake events left to process before we should
%% process more TLS-records received on the socket.
erlang:length(Events)-1.


assert_buffer_sanity(<<?BYTE(_Type), ?UINT24(Length), Rest/binary>>, #ssl_options{max_handshake_size = Max}) when
Length =< Max ->
case size(Rest) of
N when N < Length ->
true;
N when N > Length ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
too_big_handshake_data));
_ ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data))
end;
assert_buffer_sanity(Bin, _) ->
case size(Bin) of
N when N < 3 ->
true;
_ ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE,
malformed_handshake_data))
end.
26 changes: 25 additions & 1 deletion lib/ssl/test/ssl_basic_SUITE.erl
Expand Up @@ -136,7 +136,8 @@ options_tests() ->
honor_server_cipher_order,
honor_client_cipher_order,
unordered_protocol_versions_server,
unordered_protocol_versions_client
unordered_protocol_versions_client,
max_handshake_size
].

options_tests_tls() ->
Expand Down Expand Up @@ -3859,6 +3860,29 @@ unordered_protocol_versions_client(Config) when is_list(Config) ->
ServerMsg = ClientMsg = {ok, {'tlsv1.2', CipherSuite}},
ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg).

%%--------------------------------------------------------------------
max_handshake_size() ->
[{doc,"Test that we can set max_handshake_size to max value."}].

max_handshake_size(Config) when is_list(Config) ->
ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
ServerOpts = ssl_test_lib:ssl_options(server_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, send_recv_result_active, []}},
{options, [{max_handshake_size, 8388607} |ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),

Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
{mfa, {ssl_test_lib, send_recv_result_active, []}},
{options, [{max_handshake_size, 8388607} | ClientOpts]}]),

ssl_test_lib:check_result(Server, ok, Client, ok).

%%--------------------------------------------------------------------

server_name_indication_option() ->
Expand Down

0 comments on commit 1364c73

Please sign in to comment.