Skip to content

Commit 756ba35

Browse files
committed
Merge branch 'maint'
2 parents 6e00d8c + 09c8d57 commit 756ba35

4 files changed

Lines changed: 475 additions & 216 deletions

File tree

lib/ssh/src/ssh_connection_handler.erl

Lines changed: 159 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,131 +1157,43 @@ handle_event(info, {Proto, Sock, Info}, {hello,_}, #data{socket = Sock,
11571157
{keep_state_and_data, [{next_event, internal, {info_line,Info}}]}
11581158
end;
11591159

1160+
handle_event(info, {_Proto, Sock, NewData}, StateName,
1161+
D0 = #data{discard_bytes_left = DiscardBytesLeft,
1162+
discard_mac_already = DiscardMacAlready,
1163+
discard_reason = DiscardReason}) when DiscardBytesLeft > 0 ->
1164+
%% Receiving data during discard proves peer is alive;
1165+
%% prevents keepalive timeout from interrupting camouflage
1166+
D1 = reset_alive(D0),
1167+
NewDataSize = byte_size(NewData),
1168+
case NewDataSize >= DiscardBytesLeft of
1169+
true ->
1170+
%% Enough bytes discarded
1171+
ssh_transport:finish_packet_discard(DiscardMacAlready, D1#data.ssh_params),
1172+
handle_packet_part_result(DiscardReason, StateName, D1);
1173+
false ->
1174+
%% We don't have enough bytes to finish packet discard,
1175+
%% we must get more from the socket
1176+
inet:setopts(Sock, [{active, once}]),
1177+
D = D1#data{discard_bytes_left = DiscardBytesLeft - NewDataSize},
1178+
{keep_state, D}
1179+
end;
11601180

11611181
handle_event(info, {Proto, Sock, NewData}, StateName,
11621182
D0 = #data{socket = Sock,
11631183
transport_protocol = Proto,
11641184
ssh_params = SshParams}) ->
11651185
D1 = reset_alive(D0),
11661186
try ssh_transport:handle_packet_part(
1167-
D1#data.decrypted_data_buffer,
1168-
<<(D1#data.encrypted_data_buffer)/binary, NewData/binary>>,
1187+
D1#data.decrypted_data_buffer,
1188+
<<(D1#data.encrypted_data_buffer)/binary, NewData/binary>>,
11691189
D1#data.aead_data,
11701190
D1#data.undecrypted_packet_length,
1171-
D1#data.ssh_params)
1191+
D1#data.ssh_params)
11721192
of
1173-
{packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} ->
1174-
D2 = D1#data{ssh_params =
1175-
Ssh1#ssh{recv_sequence =
1176-
ssh_transport:next_seqnum(StateName,
1177-
Ssh1#ssh.recv_sequence,
1178-
SshParams)},
1179-
decrypted_data_buffer = <<>>,
1180-
undecrypted_packet_length = undefined,
1181-
aead_data = <<>>,
1182-
encrypted_data_buffer = EncryptedDataRest},
1183-
try
1184-
ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D2))
1185-
of
1186-
#ssh_msg_kexinit{} = Msg ->
1187-
{keep_state, D2, [{next_event, internal, prepare_next_packet},
1188-
{next_event, internal, {Msg,DecryptedBytes}}
1189-
]};
1190-
1191-
#ssh_msg_global_request{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1192-
#ssh_msg_request_success{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1193-
#ssh_msg_request_failure{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1194-
#ssh_msg_channel_open{} = Msg -> {keep_state, D2,
1195-
[{{timeout, max_initial_idle_time}, cancel} |
1196-
?CONNECTION_MSG(Msg)
1197-
]};
1198-
#ssh_msg_channel_open_confirmation{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1199-
#ssh_msg_channel_open_failure{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1200-
#ssh_msg_channel_window_adjust{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1201-
#ssh_msg_channel_data{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1202-
#ssh_msg_channel_extended_data{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1203-
#ssh_msg_channel_eof{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1204-
#ssh_msg_channel_close{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1205-
#ssh_msg_channel_request{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1206-
#ssh_msg_channel_failure{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1207-
#ssh_msg_channel_success{} = Msg -> {keep_state, D2, ?CONNECTION_MSG(Msg)};
1208-
1209-
#ssh_msg_userauth_request{} = Msg ->
1210-
DecryptedSize = byte_size(DecryptedBytes),
1211-
case ?GET_OPT(max_auth_request_size, (D2#data.ssh_params)#ssh.opts) of
1212-
MaxAuthRequestSize when DecryptedSize > MaxAuthRequestSize ->
1213-
DetailedMsg = io_lib:format("Auth length exceeded, message has ~B bytes"
1214-
" and the maximum is ~B bytes.",
1215-
[DecryptedSize, MaxAuthRequestSize]),
1216-
{Shutdown, D} =
1217-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1218-
"Auth length exceeded.",
1219-
DetailedMsg,
1220-
StateName, D2),
1221-
{stop, Shutdown, D};
1222-
_ ->
1223-
{keep_state, D2, [{next_event, internal, prepare_next_packet},
1224-
{next_event, internal, Msg}
1225-
]}
1226-
end;
1227-
1228-
Msg ->
1229-
{keep_state, D2, [{next_event, internal, prepare_next_packet},
1230-
{next_event, internal, Msg}
1231-
]}
1232-
catch
1233-
Class:Reason0:Stacktrace ->
1234-
Reason = ssh_lib:trim_reason(Reason0),
1235-
MsgFun =
1236-
fun(debug) ->
1237-
io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p",
1238-
[Class,Reason,Stacktrace],
1239-
[{chars_limit, ssh_lib:max_log_len(SshParams)}]);
1240-
(_) ->
1241-
io_lib:format("Bad packet: Decrypted, but can't decode ~p:~p",
1242-
[Class, Reason],
1243-
[{chars_limit, ssh_lib:max_log_len(SshParams)}])
1244-
end,
1245-
{Shutdown, D} =
1246-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1247-
?SELECT_MSG(MsgFun),
1248-
StateName, D2),
1249-
{stop, Shutdown, D}
1250-
end;
1251-
1252-
{get_more, DecryptedBytes, EncryptedDataRest, AeadData, RemainingSshPacketLen, Ssh1} ->
1253-
%% Here we know that there are not enough bytes in
1254-
%% EncryptedDataRest to use. We must wait for more.
1255-
inet:setopts(Sock, [{active, once}]),
1256-
{keep_state, D1#data{encrypted_data_buffer = EncryptedDataRest,
1257-
decrypted_data_buffer = DecryptedBytes,
1258-
undecrypted_packet_length = RemainingSshPacketLen,
1259-
aead_data = AeadData,
1260-
ssh_params = Ssh1}};
1261-
1262-
{bad_mac, Ssh1} ->
1263-
{Shutdown, D} =
1264-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1265-
"Bad packet: bad mac",
1266-
StateName, D1#data{ssh_params=Ssh1}),
1267-
{stop, Shutdown, D};
1268-
1269-
{error, {exceeds_max_size,PacketLen}} ->
1270-
{Shutdown, D} =
1271-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1272-
io_lib:format("Bad packet: Size (~p bytes) exceeds max size",
1273-
[PacketLen]),
1274-
StateName, D1),
1275-
{stop, Shutdown, D};
1276-
1277-
{error, exceeds_max_decompressed_size} ->
1278-
{Shutdown, D} =
1279-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1280-
"Bad packet: Size after decompression exceeds max size",
1281-
StateName, D1),
1282-
{stop, Shutdown, D}
1193+
Result ->
1194+
handle_packet_part_result(Result, StateName, D1)
12831195
catch
1284-
Class:Reason0:Stacktrace ->
1196+
Class:Reason0:Stacktrace ->
12851197
MsgFun =
12861198
fun(debug) ->
12871199
io_lib:format("Bad packet: Couldn't decrypt~n~p:~p~n~p",
@@ -1497,6 +1409,139 @@ handle_event(Type, Ev, StateName, D0) ->
14971409
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D0),
14981410
{stop, Shutdown, D}.
14991411

1412+
%% Handle reason returned from handle_packet_part, or {start_packet_discard, ...}
1413+
handle_packet_part_result({packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh},
1414+
StateName,
1415+
D0 = #data{ssh_params = Ssh0}) ->
1416+
D1 = D0#data{ssh_params =
1417+
Ssh#ssh{recv_sequence =
1418+
ssh_transport:next_seqnum(StateName,
1419+
Ssh#ssh.recv_sequence,
1420+
Ssh0)},
1421+
decrypted_data_buffer = <<>>,
1422+
undecrypted_packet_length = undefined,
1423+
aead_data = <<>>,
1424+
encrypted_data_buffer = EncryptedDataRest},
1425+
try
1426+
ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D1))
1427+
of
1428+
#ssh_msg_kexinit{} = Msg ->
1429+
{keep_state, D1, [{next_event, internal, prepare_next_packet},
1430+
{next_event, internal, {Msg,DecryptedBytes}}
1431+
]};
1432+
1433+
#ssh_msg_global_request{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1434+
#ssh_msg_request_success{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1435+
#ssh_msg_request_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1436+
#ssh_msg_channel_open{} = Msg -> {keep_state, D1,
1437+
[{{timeout, max_initial_idle_time}, cancel} |
1438+
?CONNECTION_MSG(Msg)
1439+
]};
1440+
#ssh_msg_channel_open_confirmation{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1441+
#ssh_msg_channel_open_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1442+
#ssh_msg_channel_window_adjust{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1443+
#ssh_msg_channel_data{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1444+
#ssh_msg_channel_extended_data{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1445+
#ssh_msg_channel_eof{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1446+
#ssh_msg_channel_close{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1447+
#ssh_msg_channel_request{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1448+
#ssh_msg_channel_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1449+
#ssh_msg_channel_success{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
1450+
1451+
1452+
#ssh_msg_userauth_request{} = Msg ->
1453+
DecryptedSize = byte_size(DecryptedBytes),
1454+
case ?GET_OPT(max_auth_request_size, (D1#data.ssh_params)#ssh.opts) of
1455+
MaxAuthRequestSize when DecryptedSize > MaxAuthRequestSize ->
1456+
DetailedMsg = io_lib:format("Auth length exceeded, message has ~B bytes"
1457+
" and the maximum is ~B bytes.",
1458+
[DecryptedSize, MaxAuthRequestSize]),
1459+
{Shutdown, D} =
1460+
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1461+
"Auth length exceeded.",
1462+
DetailedMsg,
1463+
StateName, D1),
1464+
{stop, Shutdown, D};
1465+
_ ->
1466+
{keep_state, D1, [{next_event, internal, prepare_next_packet},
1467+
{next_event, internal, Msg}
1468+
]}
1469+
end;
1470+
1471+
Msg ->
1472+
{keep_state, D1, [{next_event, internal, prepare_next_packet},
1473+
{next_event, internal, Msg}
1474+
]}
1475+
catch
1476+
Class:Reason0:Stacktrace ->
1477+
Reason = ssh_lib:trim_reason(Reason0),
1478+
MsgFun =
1479+
fun(debug) ->
1480+
io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p",
1481+
[Class,Reason,Stacktrace],
1482+
[{chars_limit, ssh_lib:max_log_len(Ssh0)}]);
1483+
(_) ->
1484+
io_lib:format("Bad packet: Decrypted, but can't decode ~p:~p",
1485+
[Class, Reason],
1486+
[{chars_limit, ssh_lib:max_log_len(Ssh0)}])
1487+
end,
1488+
{Shutdown, D} =
1489+
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1490+
?SELECT_MSG(MsgFun),
1491+
StateName, D1),
1492+
{stop, Shutdown, D}
1493+
end;
1494+
handle_packet_part_result({get_more, DecryptedBytes, EncryptedDataRest, AeadData, RemainingSshPacketLen, Ssh},
1495+
_StateName, D0 = #data{socket = Sock}) ->
1496+
%% Here we know that there are not enough bytes in
1497+
%% EncryptedDataRest to use. We must wait for more.
1498+
inet:setopts(Sock, [{active, once}]),
1499+
{keep_state, D0#data{encrypted_data_buffer = EncryptedDataRest,
1500+
decrypted_data_buffer = DecryptedBytes,
1501+
undecrypted_packet_length = RemainingSshPacketLen,
1502+
aead_data = AeadData,
1503+
ssh_params = Ssh}};
1504+
handle_packet_part_result({bad_mac, Ssh}, StateName, D0) ->
1505+
{Shutdown, D} =
1506+
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1507+
"Bad packet: bad mac",
1508+
StateName, D0#data{ssh_params=Ssh}),
1509+
{stop, Shutdown, D};
1510+
handle_packet_part_result({error, {exceeds_max_size, PacketLen}}, StateName, D0) ->
1511+
{Shutdown, D} =
1512+
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1513+
io_lib:format("Bad packet: Size (~p bytes) exceeds max size",
1514+
[PacketLen]),
1515+
StateName, D0),
1516+
{stop, Shutdown, D};
1517+
handle_packet_part_result({error, exceeds_max_decompressed_size}, StateName, D0) ->
1518+
{Shutdown, D} =
1519+
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1520+
"Bad packet: Size after decompression exceeds max size",
1521+
StateName, D0),
1522+
{stop, Shutdown, D};
1523+
handle_packet_part_result({error, {packet_not_aligned, PacketLen, BlockSize}}, StateName, D0) ->
1524+
{Shutdown, D} =
1525+
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1526+
io_lib:format("Bad packet: Size (~p bytes) not aligned to block size (~p)",
1527+
[PacketLen, BlockSize]),
1528+
StateName, D0),
1529+
{stop, Shutdown, D};
1530+
handle_packet_part_result({start_packet_discard, DiscardBytesLeft, DiscardMacAlready, DiscardReason, Ssh},
1531+
StateName, D) when DiscardBytesLeft =< 0 ->
1532+
%% Immediately upon start of packet discard we have enough bytes from the socket,
1533+
%% we can finish packet discard
1534+
ssh_transport:finish_packet_discard(DiscardMacAlready, Ssh),
1535+
handle_packet_part_result(DiscardReason, StateName, D#data{ssh_params = Ssh});
1536+
handle_packet_part_result({start_packet_discard, DiscardBytesLeft, DiscardMacAlready, DiscardReason, Ssh},
1537+
_StateName, D0 = #data{socket = Sock}) ->
1538+
%% We don't have enough bytes to finish packet discard, we must get more from the socket
1539+
inet:setopts(Sock, [{active, once}]),
1540+
D = D0#data{discard_bytes_left = DiscardBytesLeft,
1541+
discard_mac_already = DiscardMacAlready,
1542+
discard_reason = DiscardReason,
1543+
ssh_params = Ssh},
1544+
{keep_state, D}.
15001545

15011546
%%--------------------------------------------------------------------
15021547
-spec terminate(any(),

lib/ssh/src/ssh_fsm.hrl

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,27 @@
3030
%% Internal process state
3131
%%====================================================================
3232
-record(data, {
33-
starter :: pid()
34-
| undefined,
35-
auth_user :: string()
36-
| undefined,
37-
connection_state :: #connection{}
38-
| undefined,
39-
latest_channel_id = 0 :: non_neg_integer()
40-
| undefined,
41-
transport_protocol :: atom()
42-
| undefined, % ex: tcp
43-
transport_cb :: atom()
44-
| undefined, % ex: gen_tcp
45-
transport_close_tag :: atom()
46-
| undefined, % ex: tcp_closed
47-
ssh_params :: #ssh{}
48-
| undefined,
49-
socket :: gen_tcp:socket()
50-
| undefined,
51-
decrypted_data_buffer = <<>> :: binary()
52-
| undefined,
53-
encrypted_data_buffer = <<>> :: binary()
54-
| undefined,
55-
aead_data = <<>> :: binary()
56-
| undefined,
57-
undecrypted_packet_length :: undefined | non_neg_integer(),
58-
key_exchange_init_msg :: #ssh_msg_kexinit{}
59-
| undefined,
60-
last_size_rekey = 0 :: non_neg_integer(),
61-
event_queue = [] :: list(),
62-
inet_initial_buffer_size :: pos_integer()
63-
| undefined
64-
}).
33+
starter :: pid() | undefined,
34+
auth_user :: string() | undefined,
35+
connection_state :: #connection{} | undefined,
36+
latest_channel_id = 0 :: non_neg_integer() | undefined,
37+
transport_protocol :: atom() | undefined, % ex: tcp
38+
transport_cb :: atom() | undefined, % ex: gen_tcp
39+
transport_close_tag :: atom() | undefined, % ex: tcp_closed
40+
ssh_params :: #ssh{} | undefined,
41+
socket :: gen_tcp:socket() | undefined,
42+
decrypted_data_buffer = <<>> :: binary() | undefined,
43+
encrypted_data_buffer = <<>> :: binary() | undefined,
44+
aead_data = <<>> :: binary() | undefined,
45+
undecrypted_packet_length :: undefined | non_neg_integer(),
46+
key_exchange_init_msg :: #ssh_msg_kexinit{} | undefined,
47+
last_size_rekey = 0 :: non_neg_integer(),
48+
event_queue = [] :: list(),
49+
inet_initial_buffer_size :: pos_integer() | undefined,
50+
discard_bytes_left = 0 :: integer(),
51+
discard_mac_already = 0 :: non_neg_integer(),
52+
discard_reason :: term()
53+
}).
6554

6655

6756
%%====================================================================

0 commit comments

Comments
 (0)