Skip to content

Commit 778a9fb

Browse files
committed
Implement openssh mitigation for CVE-2008-5161
1 parent 656d231 commit 778a9fb

3 files changed

Lines changed: 259 additions & 181 deletions

File tree

lib/ssh/src/ssh_connection_handler.erl

Lines changed: 140 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,122 +1157,44 @@ 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{transport_protocol = Proto,
1162+
socket = Sock,
1163+
discard_bytes_left = DiscardBytesLeft,
1164+
discard_mac_already = DiscardMacAlready,
1165+
discard_reason = Reason,
1166+
ssh_params = Ssh}) when DiscardBytesLeft > 0 ->
1167+
D1 = reset_alive(D0), %% TODO: Should this be here?
1168+
NewDataSize = byte_size(NewData),
1169+
case NewDataSize >= DiscardBytesLeft of
1170+
true ->
1171+
%% Enough bytes discarded
1172+
ssh_transport:finish_packet_discard(DiscardMacAlready, Ssh),
1173+
handle_packet_part_result(Reason, StateName, D1);
1174+
false ->
1175+
%% We don't have enough bytes to finish packet discard,
1176+
%% we must get more from the socket
1177+
inet:setopts(Sock, [{active, once}]),
1178+
D = D1#data{discard_bytes_left = DiscardBytesLeft - NewDataSize},
1179+
{keep_state, D}
1180+
end;
11601181

11611182
handle_event(info, {Proto, Sock, NewData}, StateName,
11621183
D0 = #data{socket = Sock,
11631184
transport_protocol = Proto,
11641185
ssh_params = SshParams}) ->
11651186
D1 = reset_alive(D0),
11661187
try ssh_transport:handle_packet_part(
1167-
D1#data.decrypted_data_buffer,
1168-
<<(D1#data.encrypted_data_buffer)/binary, NewData/binary>>,
1188+
D1#data.decrypted_data_buffer,
1189+
<<(D1#data.encrypted_data_buffer)/binary, NewData/binary>>,
11691190
D1#data.aead_data,
11701191
D1#data.undecrypted_packet_length,
1171-
D1#data.ssh_params)
1192+
D1#data.ssh_params)
11721193
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-
Msg ->
1210-
{keep_state, D2, [{next_event, internal, prepare_next_packet},
1211-
{next_event, internal, Msg}
1212-
]}
1213-
catch
1214-
Class:Reason0:Stacktrace ->
1215-
Reason = ssh_lib:trim_reason(Reason0),
1216-
MsgFun =
1217-
fun(debug) ->
1218-
io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p",
1219-
[Class,Reason,Stacktrace],
1220-
[{chars_limit, ssh_lib:max_log_len(SshParams)}]);
1221-
(_) ->
1222-
io_lib:format("Bad packet: Decrypted, but can't decode ~p:~p",
1223-
[Class, Reason],
1224-
[{chars_limit, ssh_lib:max_log_len(SshParams)}])
1225-
end,
1226-
{Shutdown, D} =
1227-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1228-
?SELECT_MSG(MsgFun),
1229-
StateName, D2),
1230-
{stop, Shutdown, D}
1231-
end;
1232-
1233-
{get_more, DecryptedBytes, EncryptedDataRest, AeadData, RemainingSshPacketLen, Ssh1} ->
1234-
%% Here we know that there are not enough bytes in
1235-
%% EncryptedDataRest to use. We must wait for more.
1236-
inet:setopts(Sock, [{active, once}]),
1237-
{keep_state, D1#data{encrypted_data_buffer = EncryptedDataRest,
1238-
decrypted_data_buffer = DecryptedBytes,
1239-
undecrypted_packet_length = RemainingSshPacketLen,
1240-
aead_data = AeadData,
1241-
ssh_params = Ssh1}};
1242-
1243-
{bad_mac, Ssh1} ->
1244-
{Shutdown, D} =
1245-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1246-
"Bad packet: bad mac",
1247-
StateName, D1#data{ssh_params=Ssh1}),
1248-
{stop, Shutdown, D};
1249-
1250-
{error, {exceeds_max_size,PacketLen}} ->
1251-
{Shutdown, D} =
1252-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1253-
io_lib:format("Bad packet: Size (~p bytes) exceeds max size",
1254-
[PacketLen]),
1255-
StateName, D1),
1256-
{stop, Shutdown, D};
1257-
1258-
{error, exceeds_max_decompressed_size} ->
1259-
{Shutdown, D} =
1260-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1261-
"Bad packet: Size after decompression exceeds max size",
1262-
StateName, D1),
1263-
{stop, Shutdown, D};
1264-
1265-
{error, {packet_not_aligned, PacketLen}} ->
1266-
BlockSize0 = SshParams#ssh.decrypt_block_size,
1267-
BlockSize = max(8, BlockSize0),
1268-
{Shutdown, D} =
1269-
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
1270-
io_lib:format("Bad packet: Size (~p bytes) not aligned to block size (~p)",
1271-
[PacketLen, BlockSize]),
1272-
StateName, D1),
1273-
{stop, Shutdown, D}
1194+
Result ->
1195+
handle_packet_part_result(Result, StateName, D1)
12741196
catch
1275-
Class:Reason0:Stacktrace ->
1197+
Class:Reason0:Stacktrace ->
12761198
MsgFun =
12771199
fun(debug) ->
12781200
io_lib:format("Bad packet: Couldn't decrypt~n~p:~p~n~p",
@@ -1488,6 +1410,119 @@ handle_event(Type, Ev, StateName, D0) ->
14881410
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D0),
14891411
{stop, Shutdown, D}.
14901412

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

14921527
%%--------------------------------------------------------------------
14931528
-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)