diff --git a/SOURCES/libssh-CVE-2018-10933.patch b/SOURCES/libssh-CVE-2018-10933.patch new file mode 100644 index 0000000..0424e5d --- /dev/null +++ b/SOURCES/libssh-CVE-2018-10933.patch @@ -0,0 +1,1756 @@ +From e5f0e711b05c2ec4c2d016a6abaedae2959ddba2 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Wed, 19 Sep 2018 14:08:28 +0200 +Subject: [PATCH 1/8] CVE-2018-10933: Introduced new auth states + +Introduced the states SSH_AUTH_STATE_PUBKEY_OFFER_SENT and +SSH_AUTH_STATE_PUBKEY_AUTH_SENT to know when SSH2_MSG_USERAUTH_PK_OK and +SSH2_MSG_USERAUTH_SUCCESS should be expected. + +Fixes T101 + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + include/libssh/auth.h | 4 ++++ + src/auth.c | 32 +++++++++++++++++++++----------- + 2 files changed, 25 insertions(+), 11 deletions(-) + +diff --git a/include/libssh/auth.h b/include/libssh/auth.h +index 2c0012b0..05754460 100644 +--- a/include/libssh/auth.h ++++ b/include/libssh/auth.h +@@ -90,6 +90,10 @@ enum ssh_auth_state_e { + SSH_AUTH_STATE_GSSAPI_TOKEN, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_MIC_SENT, ++ /** We have offered a pubkey to check if it is supported */ ++ SSH_AUTH_STATE_PUBKEY_OFFER_SENT, ++ /** We have sent pubkey and signature expecting to be authenticated */ ++ SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + }; + + /** @internal +diff --git a/src/auth.c b/src/auth.c +index b411f226..964e82a6 100755 +--- a/src/auth.c ++++ b/src/auth.c +@@ -85,6 +85,8 @@ static int ssh_auth_response_termination(void *user){ + case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: + case SSH_AUTH_STATE_GSSAPI_TOKEN: + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: ++ case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: ++ case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + return 0; + default: + return 1; +@@ -137,6 +139,8 @@ static int ssh_userauth_get_response(ssh_session session) { + case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: + case SSH_AUTH_STATE_GSSAPI_TOKEN: + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: ++ case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: ++ case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_NONE: + /* not reached */ + rc = SSH_AUTH_ERROR; +@@ -275,21 +279,27 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_success){ + SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok){ + int rc; + +- SSH_LOG(SSH_LOG_TRACE, "Received SSH_USERAUTH_PK_OK/INFO_REQUEST/GSSAPI_RESPONSE"); ++ SSH_LOG(SSH_LOG_TRACE, ++ "Received SSH_USERAUTH_PK_OK/INFO_REQUEST/GSSAPI_RESPONSE"); + +- if(session->auth_state==SSH_AUTH_STATE_KBDINT_SENT){ ++ if (session->auth_state == SSH_AUTH_STATE_KBDINT_SENT) { + /* Assuming we are in keyboard-interactive context */ + SSH_LOG(SSH_LOG_TRACE, +- "keyboard-interactive context, assuming SSH_USERAUTH_INFO_REQUEST"); +- rc=ssh_packet_userauth_info_request(session,type,packet,user); ++ "keyboard-interactive context, " ++ "assuming SSH_USERAUTH_INFO_REQUEST"); ++ rc = ssh_packet_userauth_info_request(session, type, packet, user); + #ifdef WITH_GSSAPI +- } else if (session->auth_state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT){ ++ } else if (session->auth_state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT) { + rc = ssh_packet_userauth_gssapi_response(session, type, packet, user); + #endif ++ } else if (session->auth_state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT) { ++ session->auth_state = SSH_AUTH_STATE_PK_OK; ++ SSH_LOG(SSH_LOG_TRACE, "Assuming SSH_USERAUTH_PK_OK"); ++ rc = SSH_PACKET_USED; + } else { +- session->auth_state=SSH_AUTH_STATE_PK_OK; +- SSH_LOG(SSH_LOG_TRACE, "Assuming SSH_USERAUTH_PK_OK"); +- rc=SSH_PACKET_USED; ++ session->auth_state = SSH_AUTH_STATE_ERROR; ++ SSH_LOG(SSH_LOG_TRACE, "SSH_USERAUTH_PK_OK received in wrong state"); ++ rc = SSH_PACKET_USED; + } + + return rc; +@@ -501,7 +511,7 @@ int ssh_userauth_try_publickey(ssh_session session, + + ssh_string_free(pubkey_s); + +- session->auth_state = SSH_AUTH_STATE_NONE; ++ session->auth_state = SSH_AUTH_STATE_PUBKEY_OFFER_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_OFFER_PUBKEY; + rc = packet_send(session); + if (rc == SSH_ERROR) { +@@ -622,7 +632,7 @@ int ssh_userauth_publickey(ssh_session session, + goto fail; + } + +- session->auth_state = SSH_AUTH_STATE_NONE; ++ session->auth_state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_PUBKEY; + rc = packet_send(session); + if (rc == SSH_ERROR) { +@@ -706,7 +716,7 @@ static int ssh_userauth_agent_publickey(ssh_session session, + goto fail; + } + +- session->auth_state = SSH_AUTH_STATE_NONE; ++ session->auth_state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_AGENT; + rc = packet_send(session); + if (rc == SSH_ERROR) { +-- +2.19.0 + + +From ddea46f890bd5d87669f23b26d676adedaa5f610 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Wed, 19 Sep 2018 14:12:56 +0200 +Subject: [PATCH 2/8] CVE-2018-10933: Introduce + SSH_AUTH_STATE_PASSWORD_AUTH_SENT + +The introduced auth state allows to identify when authentication using +password was tried. + +Fixes T101 + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + include/libssh/auth.h | 2 ++ + src/auth.c | 4 +++- + 2 files changed, 5 insertions(+), 1 deletion(-) + +diff --git a/include/libssh/auth.h b/include/libssh/auth.h +index 05754460..1fc00e20 100644 +--- a/include/libssh/auth.h ++++ b/include/libssh/auth.h +@@ -94,6 +94,8 @@ enum ssh_auth_state_e { + SSH_AUTH_STATE_PUBKEY_OFFER_SENT, + /** We have sent pubkey and signature expecting to be authenticated */ + SSH_AUTH_STATE_PUBKEY_AUTH_SENT, ++ /** We have sent a password expecting to be authenticated */ ++ SSH_AUTH_STATE_PASSWORD_AUTH_SENT, + }; + + /** @internal +diff --git a/src/auth.c b/src/auth.c +index 964e82a6..3719b18a 100755 +--- a/src/auth.c ++++ b/src/auth.c +@@ -87,6 +87,7 @@ static int ssh_auth_response_termination(void *user){ + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: ++ case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: + return 0; + default: + return 1; +@@ -141,6 +142,7 @@ static int ssh_userauth_get_response(ssh_session session) { + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: ++ case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: + case SSH_AUTH_STATE_NONE: + /* not reached */ + rc = SSH_AUTH_ERROR; +@@ -1173,7 +1175,7 @@ int ssh_userauth_password(ssh_session session, + goto fail; + } + +- session->auth_state = SSH_AUTH_STATE_NONE; ++ session->auth_state = SSH_AUTH_STATE_PASSWORD_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_OFFER_PUBKEY; + rc = packet_send(session); + if (rc == SSH_ERROR) { +-- +2.19.0 + + +From acd6a1ca8a332a204b616ab6bef413bee5d3409d Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Wed, 19 Sep 2018 15:55:43 +0200 +Subject: [PATCH 3/8] CVE-2018-10933: Introduce SSH_AUTH_STATE_AUTH_NONE_SENT + +The introduced auth state allows to identify when a request without +authentication information was sent. + +Fixes T101 + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + include/libssh/auth.h | 2 ++ + src/auth.c | 4 +++- + 2 files changed, 5 insertions(+), 1 deletion(-) + +diff --git a/include/libssh/auth.h b/include/libssh/auth.h +index 1fc00e20..75bc7546 100644 +--- a/include/libssh/auth.h ++++ b/include/libssh/auth.h +@@ -96,6 +96,8 @@ enum ssh_auth_state_e { + SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + /** We have sent a password expecting to be authenticated */ + SSH_AUTH_STATE_PASSWORD_AUTH_SENT, ++ /** We have sent a request without auth information (method 'none') */ ++ SSH_AUTH_STATE_AUTH_NONE_SENT, + }; + + /** @internal +diff --git a/src/auth.c b/src/auth.c +index 3719b18a..2bc73760 100755 +--- a/src/auth.c ++++ b/src/auth.c +@@ -88,6 +88,7 @@ static int ssh_auth_response_termination(void *user){ + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: ++ case SSH_AUTH_STATE_AUTH_NONE_SENT: + return 0; + default: + return 1; +@@ -143,6 +144,7 @@ static int ssh_userauth_get_response(ssh_session session) { + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: ++ case SSH_AUTH_STATE_AUTH_NONE_SENT: + case SSH_AUTH_STATE_NONE: + /* not reached */ + rc = SSH_AUTH_ERROR; +@@ -401,7 +403,7 @@ int ssh_userauth_none(ssh_session session, const char *username) { + goto fail; + } + +- session->auth_state = SSH_AUTH_STATE_NONE; ++ session->auth_state = SSH_AUTH_STATE_AUTH_NONE_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_NONE; + rc = packet_send(session); + if (rc == SSH_ERROR) { +-- +2.19.0 + + +From 7985acb76842ebf27e32e4afddfef55555209e8e Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Wed, 19 Sep 2018 14:23:35 +0200 +Subject: [PATCH 4/8] CVE-2018-10933: Set correct state after sending MIC + +After sending the client token, the auth state is set as +SSH_AUTH_STATE_GSSAPI_MIC_SENT. Then this can be expected to be the +state when a USERAUTH_FAILURE or USERAUTH_SUCCESS arrives. + +Fixes T101 + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + src/gssapi.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/gssapi.c b/src/gssapi.c +index c2b30f6d..9306672a 100644 +--- a/src/gssapi.c ++++ b/src/gssapi.c +@@ -943,8 +943,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ + packet_send(session); + } + if(maj_stat == GSS_S_COMPLETE){ +- session->auth_state = SSH_AUTH_STATE_NONE; + ssh_gssapi_send_mic(session); ++ session->auth_state = SSH_AUTH_STATE_GSSAPI_MIC_SENT; + } + return SSH_PACKET_USED; + } +-- +2.19.0 + + +From 3837a0547f08b160749fed7496316a62d6c11dea Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Wed, 19 Sep 2018 14:30:33 +0200 +Subject: [PATCH 5/8] CVE-2018-10933: Check channel state when + OPEN_CONFIRMATION arrives + +When a SSH2_MSG_OPEN_CONFIRMATION arrives, the channel state is checked +to be in SSH_CHANNEL_STATE_OPENING. + +Fixes T101 + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + src/channels.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/src/channels.c b/src/channels.c +index 30c31468..d5d36af5 100644 +--- a/src/channels.c ++++ b/src/channels.c +@@ -170,6 +170,15 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ + "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d", + channel->local_channel, + channel->remote_channel); ++ ++ if (channel->state != SSH_CHANNEL_STATE_OPENING) { ++ SSH_LOG(SSH_LOG_RARE, ++ "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION received in incorrect " ++ "channel state %d", ++ channel->state); ++ goto error; ++ } ++ + SSH_LOG(SSH_LOG_PROTOCOL, + "Remote window : %lu, maxpacket : %lu", + (long unsigned int) channel->remote_window, +-- +2.19.0 + + +From e5ff7aa410c23954a2963b52e7b721a2d41536f3 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Wed, 19 Sep 2018 14:37:40 +0200 +Subject: [PATCH 6/8] CVE-2018-10933: Check channel state when OPEN_FAILURE + arrives + +When a SSH2_MSG_OPEN_FAILURE arrives, the channel state is checked +to be in SSH_CHANNEL_STATE_OPENING. + +Fixes T101 + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + src/channels.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/src/channels.c b/src/channels.c +index d5d36af5..538956dd 100644 +--- a/src/channels.c ++++ b/src/channels.c +@@ -219,6 +219,14 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ + return SSH_PACKET_USED; + } + ++ if (channel->state != SSH_CHANNEL_STATE_OPENING) { ++ SSH_LOG(SSH_LOG_RARE, ++ "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel " ++ "state %d", ++ channel->state); ++ goto error; ++ } ++ + ssh_set_error(session, SSH_REQUEST_DENIED, + "Channel opening failure: channel %u error (%lu) %s", + channel->local_channel, +@@ -226,6 +234,9 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail){ + error); + SAFE_FREE(error); + channel->state=SSH_CHANNEL_STATE_OPEN_DENIED; ++ ++error: ++ ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; + } + +-- +2.19.0 + + +From b9033ad56a498d0642f3258a54a32251278a319e Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Wed, 19 Sep 2018 15:04:31 +0200 +Subject: [PATCH 7/8] CVE-2018-10933: Introduced packet filtering + +The packet filter checks required states for the incoming packets and +reject them if they arrived in the wrong state. + +Fixes T101 + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + include/libssh/packet.h | 6 + + src/packet.c | 787 +++++++++++++++++++++++++++++++++++++++- + 2 files changed, 791 insertions(+), 2 deletions(-) + +diff --git a/include/libssh/packet.h b/include/libssh/packet.h +index d8ef35bb..206c0b21 100644 +--- a/include/libssh/packet.h ++++ b/include/libssh/packet.h +@@ -43,6 +43,12 @@ enum ssh_packet_state_e { + PACKET_STATE_PROCESSING + }; + ++enum ssh_packet_filter_result_e { ++ SSH_PACKET_UNKNOWN, ++ SSH_PACKET_ALLOWED, ++ SSH_PACKET_DENIED ++}; ++ + int packet_send(ssh_session session); + + #ifdef WITH_SSH1 +diff --git a/src/packet.c b/src/packet.c +index d16cd165..5a263328 100644 +--- a/src/packet.c ++++ b/src/packet.c +@@ -127,6 +127,775 @@ static ssh_packet_callback default_packet_handlers[]= { + ssh_packet_channel_failure, // SSH2_MSG_CHANNEL_FAILURE 100 + }; + ++/** @internal ++ * @brief check if the received packet is allowed for the current session state ++ * @param session current ssh_session ++ * @returns SSH_PACKET_ALLOWED if the packet is allowed; SSH_PACKET_DENIED ++ * if the packet arrived in wrong state; SSH_PACKET_UNKNOWN if the packet type ++ * is unknown ++ */ ++static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session session) ++{ ++ enum ssh_packet_filter_result_e rc; ++ ++#ifdef DEBUG_PACKET ++ SSH_LOG(SSH_LOG_PACKET, "Filtering packet type %d", ++ session->in_packet.type); ++#endif ++ ++ switch(session->in_packet.type) { ++ case SSH2_MSG_DISCONNECT: // 1 ++ /* ++ * States required: ++ * - None ++ * ++ * Transitions: ++ * - session->socket->state = SSH_SOCKET_CLOSED ++ * - session->session_state = SSH_SESSION_STATE_ERROR ++ * */ ++ ++ /* Always allowed */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_IGNORE: // 2 ++ /* ++ * States required: ++ * - None ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ /* Always allowed */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_UNIMPLEMENTED: // 3 ++ /* ++ * States required: ++ * - None ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ /* Always allowed */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_DEBUG: // 4 ++ /* ++ * States required: ++ * - None ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ /* Always allowed */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_SERVICE_REQUEST: // 5 ++ /* Server only */ ++ ++ /* ++ * States required: ++ * - session->session_state == SSH_SESSION_STATE_AUTHENTICATING ++ * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * - session->dh_handshake_state == DH_STATE_FINISHED ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ /* If this is a client, reject the message */ ++ if (session->client) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && ++ (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) ++ { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->dh_handshake_state != DH_STATE_FINISHED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_SERVICE_ACCEPT: // 6 ++ /* ++ * States required: ++ * - session->session_state == SSH_SESSION_STATE_AUTHENTICATING ++ * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * - session->dh_handshake_state == DH_STATE_FINISHED ++ * - session->auth_service_state == SSH_AUTH_SERVICE_SENT ++ * ++ * Transitions: ++ * - auth_service_state = SSH_AUTH_SERVICE_ACCEPTED ++ * */ ++ ++ if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && ++ (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) ++ { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->dh_handshake_state != DH_STATE_FINISHED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ /* TODO check if only auth service can be requested */ ++ if (session->auth_service_state != SSH_AUTH_SERVICE_SENT) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_KEXINIT: // 20 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * or session_state == SSH_SESSION_STATE_INITIAL_KEX ++ * - dh_handshake_state == DH_STATE_INIT ++ * or dh_handshake_state == DH_STATE_FINISHED (re-exchange) ++ * ++ * Transitions: ++ * - session->dh_handshake_state = DH_STATE_INIT ++ * - session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED ++ * ++ * On server: ++ * - session->session_state = SSH_SESSION_STATE_DH ++ * */ ++ ++ if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATED) && ++ (session->session_state != SSH_SESSION_STATE_INITIAL_KEX)) ++ { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if ((session->dh_handshake_state != DH_STATE_INIT) && ++ (session->dh_handshake_state != DH_STATE_FINISHED)) ++ { ++ rc = SSH_PACKET_DENIED; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_NEWKEYS: // 21 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_DH ++ * - dh_handshake_state == DH_STATE_NEWKEYS_SENT ++ * ++ * Transitions: ++ * - session->dh_handshake_state = DH_STATE_FINISHED ++ * - session->session_state = SSH_SESSION_STATE_AUTHENTICATING ++ * if session->flags & SSH_SESSION_FLAG_AUTHENTICATED ++ * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED ++ * */ ++ ++ /* If DH has not been started, reject message */ ++ if (session->session_state != SSH_SESSION_STATE_DH) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ /* Only allowed if dh_handshake_state is in NEWKEYS_SENT state */ ++ if (session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_KEXDH_INIT: // 30 ++ // SSH2_MSG_KEX_ECDH_INIT: // 30 ++ // SSH2_MSG_ECMQV_INIT: // 30 ++ // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD: // 30 ++ ++ /* Server only */ ++ ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_DH ++ * - dh_handshake_state == DH_STATE_INIT ++ * ++ * Transitions: ++ * - session->dh_handshake_state = DH_STATE_INIT_SENT ++ * then calls dh_handshake_server which triggers: ++ * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_DH) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ /* Only allowed if dh_handshake_state is in initial state */ ++ if (session->dh_handshake_state != DH_STATE_INIT) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_KEXDH_REPLY: // 31 ++ // SSH2_MSG_KEX_ECDH_REPLY: // 31 ++ // SSH2_MSG_ECMQV_REPLY: // 31 ++ // SSH2_MSG_KEX_DH_GEX_GROUP: // 31 ++ ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_DH ++ * - dh_handshake_state == DH_STATE_INIT_SENT ++ * ++ * Transitions: ++ * - session->dh_handhsake_state = DH_STATE_NEWKEYS_SENT ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_DH) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->dh_handshake_state != DH_STATE_INIT_SENT) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_KEX_DH_GEX_INIT: // 32 ++ /* TODO Not filtered */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_KEX_DH_GEX_REPLY: // 33 ++ /* TODO Not filtered */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_KEX_DH_GEX_REQUEST: // 34 ++ /* TODO Not filtered */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_REQUEST: // 50 ++ /* Server only */ ++ ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATING ++ * - dh_hanshake_state == DH_STATE_FINISHED ++ * ++ * Transitions: ++ * - if authentication was successful: ++ * - session_state = SSH_SESSION_STATE_AUTHENTICATED ++ * */ ++ ++ /* If this is a client, reject the message */ ++ if (session->client) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->dh_handshake_state != DH_STATE_FINISHED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_FAILURE: // 51 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATING ++ * - dh_hanshake_state == DH_STATE_FINISHED ++ * - session->auth_state == SSH_AUTH_STATE_KBDINT_SENT ++ * or session->auth_state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT ++ * or session->auth_state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT ++ * or session->auth_state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT ++ * or session->auth_state == SSH_AUTH_STATE_GSSAPI_MIC_SENT ++ * ++ * Transitions: ++ * - if unpacking failed: ++ * - session->auth_state = SSH_AUTH_ERROR ++ * - if failure was partial: ++ * - session->auth_state = SSH_AUTH_PARTIAL ++ * - else: ++ * - session->auth_state = SSH_AUTH_STATE_FAILED ++ * */ ++ ++ /* If this is a server, reject the message */ ++ if (session->server) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->dh_handshake_state != DH_STATE_FINISHED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_SUCCESS: // 52 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATING ++ * - dh_hanshake_state == DH_STATE_FINISHED ++ * - session->auth_state == SSH_AUTH_STATE_KBDINT_SENT ++ * or session->auth_state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT ++ * or session->auth_state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT ++ * or session->auth_state == SSH_AUTH_STATE_GSSAPI_MIC_SENT ++ * or session->auth_state == SSH_AUTH_STATE_AUTH_NONE_SENT ++ * ++ * Transitions: ++ * - session->auth_state = SSH_AUTH_STATE_SUCCESS ++ * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED ++ * - session->flags |= SSH_SESSION_FLAG_AUTHENTICATED ++ * - sessions->auth.current_method = SSH_AUTH_METHOD_UNKNOWN ++ * */ ++ ++ /* If this is a server, reject the message */ ++ if (session->server) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->dh_handshake_state != DH_STATE_FINISHED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if ((session->auth_state != SSH_AUTH_STATE_KBDINT_SENT) && ++ (session->auth_state != SSH_AUTH_STATE_PUBKEY_AUTH_SENT) && ++ (session->auth_state != SSH_AUTH_STATE_PASSWORD_AUTH_SENT) && ++ (session->auth_state != SSH_AUTH_STATE_GSSAPI_MIC_SENT) && ++ (session->auth_state != SSH_AUTH_STATE_AUTH_NONE_SENT)) ++ { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_BANNER: // 53 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATING ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_PK_OK: // 60 ++ // SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // 60 ++ // SSH2_MSG_USERAUTH_INFO_REQUEST: // 60 ++ // SSH2_MSG_USERAUTH_GSSAPI_RESPONSE: // 60 ++ ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATING ++ * - session->auth_state == SSH_AUTH_STATE_KBDINT_SENT ++ * or ++ * session->auth_state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT ++ * or ++ * session->auth_state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT ++ * ++ * Transitions: ++ * Depending on the current state, the message is treated ++ * differently: ++ * - session->auth_state == SSH_AUTH_STATE_KBDINT_SENT ++ * - session->auth_state = SSH_AUTH_STATE_INFO ++ * - session->auth_state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT ++ * - session->auth_state = SSH_AUTH_STATE_GSSAPI_TOKEN ++ * - session->auth_state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT ++ * - session->auth_state = SSH_AUTH_STATE_PK_OK ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if ((session->auth_state != SSH_AUTH_STATE_KBDINT_SENT) && ++ (session->auth_state != SSH_AUTH_STATE_PUBKEY_OFFER_SENT) && ++ (session->auth_state != SSH_AUTH_STATE_GSSAPI_REQUEST_SENT)) ++ { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_INFO_RESPONSE: // 61 ++ // SSH2_MSG_USERAUTH_GSSAPI_TOKEN: // 61 ++ ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATING ++ * - session_state->auth_state == SSH_SESSION_STATE_GSSAPI_TOKEN ++ * or ++ * session_state->auth_state == SSH_SESSION_STATE_INFO ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if ((session->auth_state != SSH_AUTH_STATE_INFO) && ++ (session->auth_state != SSH_AUTH_STATE_GSSAPI_TOKEN)) ++ { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE: // 63 ++ /* TODO Not filtered */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_GSSAPI_ERROR: // 64 ++ /* TODO Not filtered */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_GSSAPI_ERRTOK: // 65 ++ /* TODO Not filtered */ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_USERAUTH_GSSAPI_MIC: // 66 ++ /* Server only */ ++ ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATING ++ * - session->gssapi->state == SSH_GSSAPI_STATE_RCV_MIC ++ * ++ * Transitions: ++ * Depending on the result of the verification, the states are ++ * changed: ++ * - SSH_AUTH_SUCCESS: ++ * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED ++ * - session->flags != SSH_SESSION_FLAG_AUTHENTICATED ++ * - SSH_AUTH_PARTIAL: ++ * - None ++ * - any other case: ++ * - None ++ * */ ++ ++ /* If this is a client, reject the message */ ++ if (session->client) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->dh_handshake_state != DH_STATE_FINISHED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_GLOBAL_REQUEST: // 80 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_REQUEST_SUCCESS: // 81 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * - session->global_req_state == SSH_CHANNEL_REQ_STATE_PENDING ++ * ++ * Transitions: ++ * - session->global_req_state == SSH_CHANNEL_REQ_STATE_ACCEPTED ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_REQUEST_FAILURE: // 82 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * - session->global_req_state == SSH_CHANNEL_REQ_STATE_PENDING ++ * ++ * Transitions: ++ * - session->global_req_state == SSH_CHANNEL_REQ_STATE_DENIED ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_OPEN: // 90 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: // 91 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - channel->state = SSH_CHANNEL_STATE_OPEN ++ * - channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_OPEN_FAILURE: // 92 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - channel->state = SSH_CHANNEL_STATE_OPEN_DENIED ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_WINDOW_ADJUST: // 93 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_DATA: // 94 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_EXTENDED_DATA: // 95 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_EOF: // 96 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - None ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_CLOSE: // 97 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - channel->state = SSH_CHANNEL_STATE_CLOSED ++ * - channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_REQUEST: // 98 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * ++ * Transitions: ++ * - Depends on the request ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_SUCCESS: // 99 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * - channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING ++ * ++ * Transitions: ++ * - channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ case SSH2_MSG_CHANNEL_FAILURE: // 100 ++ /* ++ * States required: ++ * - session_state == SSH_SESSION_STATE_AUTHENTICATED ++ * - channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING ++ * ++ * Transitions: ++ * - channel->request_state = SSH_CHANNEL_REQ_STATE_DENIED ++ * */ ++ ++ if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { ++ rc = SSH_PACKET_DENIED; ++ break; ++ } ++ ++ rc = SSH_PACKET_ALLOWED; ++ break; ++ default: ++ /* Unknown message, do not filter */ ++ rc = SSH_PACKET_UNKNOWN; ++ goto end; ++ } ++ ++end: ++#ifdef DEBUG_PACKET ++ if (rc == SSH_PACKET_DENIED) { ++ SSH_LOG(SSH_LOG_PACKET, "REJECTED packet type %d: ", ++ session->in_packet.type); ++ } ++ ++ if (rc == SSH_PACKET_UNKNOWN) { ++ SSH_LOG(SSH_LOG_PACKET, "UNKNOWN packet type %d", ++ session->in_packet.type); ++ } ++#endif ++ ++ return rc; ++} ++ + /* in nonblocking mode, socket_read will read as much as it can, and return */ + /* SSH_OK if it has read at least len bytes, otherwise, SSH_AGAIN. */ + /* in blocking mode, it will read at least len bytes and will block until it's ok. */ +@@ -153,6 +922,7 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) + uint32_t len, compsize, payloadsize; + uint8_t padding; + size_t processed = 0; /* number of byte processed from the callback */ ++ enum ssh_packet_filter_result_e filter_result; + + if(session->current_crypto != NULL) { + current_macsize = hmac_digest_len(session->current_crypto->in_hmac); +@@ -328,8 +1098,21 @@ int ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) + "packet: read type %hhd [len=%d,padding=%hhd,comp=%d,payload=%d]", + session->in_packet.type, len, padding, compsize, payloadsize); + +- /* Execute callbacks */ +- ssh_packet_process(session, session->in_packet.type); ++ /* Check if the packet is expected */ ++ filter_result = ssh_packet_incoming_filter(session); ++ ++ switch(filter_result) { ++ case SSH_PACKET_ALLOWED: ++ /* Execute callbacks */ ++ ssh_packet_process(session, session->in_packet.type); ++ break; ++ case SSH_PACKET_DENIED: ++ goto error; ++ case SSH_PACKET_UNKNOWN: ++ ssh_packet_send_unimplemented(session, session->recv_seq - 1); ++ break; ++ } ++ + session->packet_state = PACKET_STATE_INIT; + if (processed < receivedlen) { + /* Handle a potential packet left in socket buffer */ +-- +2.19.0 + + +From f1d57223dbc0dae99a783dd61fd679c56d7dfce5 Mon Sep 17 00:00:00 2001 +From: Anderson Toshiyuki Sasaki +Date: Wed, 19 Sep 2018 16:37:13 +0200 +Subject: [PATCH 8/8] CVE-2018-10933: Add tests for packet filtering + +Created the test torture_packet_filter.c which tests if packets are +being correctly filtered. + +Fixes T101 + +Signed-off-by: Anderson Toshiyuki Sasaki +--- + tests/unittests/CMakeLists.txt | 1 + + tests/unittests/torture_packet_filter.c | 500 ++++++++++++++++++++++++ + 2 files changed, 501 insertions(+) + create mode 100644 tests/unittests/torture_packet_filter.c + +diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt +index 21825978..80d7e604 100644 +--- a/tests/unittests/CMakeLists.txt ++++ b/tests/unittests/CMakeLists.txt +@@ -10,6 +10,7 @@ add_cmocka_test(torture_misc torture_misc.c ${TORTURE_LIBRARY}) + add_cmocka_test(torture_options torture_options.c ${TORTURE_LIBRARY}) + add_cmocka_test(torture_isipaddr torture_isipaddr.c ${TORTURE_LIBRARY}) + add_cmocka_test(torture_pki_ed25519 torture_pki_ed25519.c ${TORTURE_LIBRARY}) ++add_cmocka_test(torture_packet_filter torture_packet_filter.c ${TORTURE_LIBRARY}) + if (UNIX AND NOT WIN32) + # requires ssh-keygen + add_cmocka_test(torture_keyfiles torture_keyfiles.c ${TORTURE_LIBRARY}) +diff --git a/tests/unittests/torture_packet_filter.c b/tests/unittests/torture_packet_filter.c +new file mode 100644 +index 00000000..006be40a +--- /dev/null ++++ b/tests/unittests/torture_packet_filter.c +@@ -0,0 +1,500 @@ ++/* ++ * This file is part of the SSH Library ++ * ++ * Copyright (c) 2018 by Anderson Toshiyuki Sasaki ++ * ++ * The SSH Library is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as published by ++ * the Free Software Foundation; either version 2.1 of the License, or (at your ++ * option) any later version. ++ * ++ * The SSH Library is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY ++ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public ++ * License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with the SSH Library; see the file COPYING. If not, write to ++ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, ++ * MA 02111-1307, USA. ++ */ ++ ++/* ++ * This test checks if the messages accepted by the packet filter were intented ++ * to be accepted. ++ * ++ * The process consists in 2 steps: ++ * - Try the filter with a message type in an arbitrary state ++ * - If the message is accepted by the filter, check if the message is in the ++ * set of accepted states. ++ * ++ * Only the values selected by the flag (COMPARE_*) are considered. ++ * */ ++ ++#include "config.h" ++ ++#define LIBSSH_STATIC ++ ++#include "torture.h" ++#include "libssh/priv.h" ++#include "libssh/libssh.h" ++#include "libssh/session.h" ++#include "libssh/auth.h" ++#include "libssh/ssh2.h" ++#include "libssh/packet.h" ++ ++#include "packet.c" ++ ++#define COMPARE_SESSION_STATE 1 ++#define COMPARE_ROLE (1 << 1) ++#define COMPARE_DH_STATE (1 << 2) ++#define COMPARE_AUTH_STATE (1 << 3) ++#define COMPARE_GLOBAL_REQ_STATE (1 << 4) ++ ++#define SESSION_STATE_COUNT 11 ++#define DH_STATE_COUNT 4 ++#define AUTH_STATE_COUNT 14 ++#define GLOBAL_REQ_STATE_COUNT 5 ++#define MESSAGE_COUNT 100 // from 1 to 100 ++ ++#define ROLE_CLIENT 0 ++#define ROLE_SERVER 1 ++ ++/* ++ * This is the list of currently unfiltered message types. ++ * Only unrecognized types should be in this list. ++ * */ ++static uint8_t unfiltered[] = { ++ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, ++ 22, 23, 24, 25, 26, 27, 28, 29, ++ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ++ 54, 55, 56, 57, 58, 59, ++ 62, ++ 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, ++ 83, 84, 85, 86, 87, 88, 89, ++}; ++ ++typedef struct global_state_st { ++ /* If the bit in this flag is zero, the corresponding state is not ++ * considered, working as a wildcard (meaning any value is accepted) */ ++ uint32_t flags; ++ uint8_t role; ++ enum ssh_session_state_e session; ++ enum ssh_dh_state_e dh; ++ enum ssh_auth_state_e auth; ++ enum ssh_channel_request_state_e global_req; ++} global_state; ++ ++static int cmp_state(const void *e1, const void *e2) ++{ ++ global_state *s1 = (global_state *) e1; ++ global_state *s2 = (global_state *) e2; ++ ++ /* Compare role (client == 0 or server == 1)*/ ++ if (s1->role < s2->role) { ++ return -1; ++ } ++ else if (s1->role > s2->role) { ++ return 1; ++ } ++ ++ /* Compare session state */ ++ if (s1->session < s2->session) { ++ return -1; ++ } ++ else if (s1->session > s2->session) { ++ return 1; ++ } ++ ++ /* Compare DH state */ ++ if (s1->dh < s2->dh) { ++ return -1; ++ } ++ else if (s1->dh > s2->dh) { ++ return 1; ++ } ++ ++ /* Compare auth */ ++ if (s1->auth < s2->auth) { ++ return -1; ++ } ++ else if (s1->auth > s2->auth) { ++ return 1; ++ } ++ ++ /* Compare global_req */ ++ if (s1->global_req < s2->global_req) { ++ return -1; ++ } ++ else if (s1->global_req > s2->global_req) { ++ return 1; ++ } ++ ++ /* If all equal, they are equal */ ++ return 0; ++} ++ ++static int cmp_state_search(const void *key, const void *array_element) ++{ ++ global_state *s1 = (global_state *) key; ++ global_state *s2 = (global_state *) array_element; ++ ++ int result = 0; ++ ++ if (s2->flags & COMPARE_ROLE) { ++ /* Compare role (client == 0 or server == 1)*/ ++ if (s1->role < s2->role) { ++ return -1; ++ } ++ else if (s1->role > s2->role) { ++ return 1; ++ } ++ } ++ ++ if (s2->flags & COMPARE_SESSION_STATE) { ++ /* Compare session state */ ++ if (s1->session < s2->session) { ++ result = -1; ++ goto end; ++ } ++ else if (s1->session > s2->session) { ++ result = 1; ++ goto end; ++ } ++ } ++ ++ if (s2->flags & COMPARE_DH_STATE) { ++ /* Compare DH state */ ++ if (s1->dh < s2->dh) { ++ result = -1; ++ goto end; ++ } ++ else if (s1->dh > s2->dh) { ++ result = 1; ++ goto end; ++ } ++ } ++ ++ if (s2->flags & COMPARE_AUTH_STATE) { ++ /* Compare auth */ ++ if (s1->auth < s2->auth) { ++ result = -1; ++ goto end; ++ } ++ else if (s1->auth > s2->auth) { ++ result = 1; ++ goto end; ++ } ++ } ++ ++ if (s2->flags & COMPARE_GLOBAL_REQ_STATE) { ++ /* Compare global_req */ ++ if (s1->global_req < s2->global_req) { ++ result = -1; ++ goto end; ++ } ++ else if (s1->global_req > s2->global_req) { ++ result = 1; ++ goto end; ++ } ++ } ++ ++end: ++ return result; ++} ++ ++static int is_state_accepted(global_state *tested, global_state *accepted, ++ int accepted_len) ++{ ++ global_state *found = NULL; ++ ++ found = bsearch(tested, accepted, accepted_len, sizeof(global_state), ++ cmp_state_search); ++ ++ if (found != NULL) { ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static int cmp_uint8(const void *i, const void *j) ++{ ++ uint8_t e1 = *((uint8_t *)i); ++ uint8_t e2 = *((uint8_t *)j); ++ ++ if (e1 < e2) { ++ return -1; ++ } ++ else if (e1 > e2) { ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static int check_unfiltered(uint8_t msg_type) ++{ ++ uint8_t *found; ++ ++ found = bsearch(&msg_type, unfiltered, sizeof(unfiltered)/sizeof(uint8_t), ++ sizeof(uint8_t), cmp_uint8); ++ ++ if (found != NULL) { ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static void torture_packet_filter_check_unfiltered(void **state) ++{ ++ ssh_session session; ++ ++ int role_c; ++ int auth_c; ++ int session_c; ++ int dh_c; ++ int global_req_c; ++ ++ uint8_t msg_type; ++ ++ enum ssh_packet_filter_result_e rc; ++ int in_unfiltered; ++ ++ session = ssh_new(); ++ ++ for (msg_type = 1; msg_type <= MESSAGE_COUNT; msg_type++) { ++ session->in_packet.type = msg_type; ++ for (role_c = 0; role_c < 2; role_c++) { ++ session->server = role_c; ++ for (session_c = 0; session_c < SESSION_STATE_COUNT; session_c++) { ++ session->session_state = session_c; ++ for (dh_c = 0; dh_c < DH_STATE_COUNT; dh_c++) { ++ session->dh_handshake_state = dh_c; ++ for (auth_c = 0; auth_c < AUTH_STATE_COUNT; auth_c++) { ++ session->auth_state = auth_c; ++ for (global_req_c = 0; ++ global_req_c < GLOBAL_REQ_STATE_COUNT; ++ global_req_c++) ++ { ++ session->global_req_state = global_req_c; ++ ++ rc = ssh_packet_incoming_filter(session); ++ ++ if (rc == SSH_PACKET_UNKNOWN) { ++ in_unfiltered = check_unfiltered(msg_type); ++ ++ if (!in_unfiltered) { ++ fprintf(stderr, "Message type %d UNFILTERED " ++ "in state: role %d, session %d, dh %d, auth %d\n", ++ msg_type, role_c, session_c, dh_c, auth_c); ++ } ++ assert_int_equal(in_unfiltered, 1); ++ } ++ else { ++ in_unfiltered = check_unfiltered(msg_type); ++ ++ if (in_unfiltered) { ++ fprintf(stderr, "Message type %d NOT UNFILTERED " ++ "in state: role %d, session %d, dh %d, auth %d\n", ++ msg_type, role_c, session_c, dh_c, auth_c); ++ } ++ assert_int_equal(in_unfiltered, 0); ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ssh_free(session); ++} ++ ++static int check_message_in_all_states(global_state accepted[], ++ int accepted_count, uint8_t msg_type) ++{ ++ ssh_session session; ++ ++ int role_c; ++ int auth_c; ++ int session_c; ++ int dh_c; ++ int global_req_c; ++ ++ enum ssh_packet_filter_result_e rc; ++ int in_accepted; ++ ++ global_state key; ++ ++ session = ssh_new(); ++ ++ /* Sort the accepted array so that the elements can be searched using ++ * bsearch */ ++ qsort(accepted, accepted_count, sizeof(global_state), cmp_state); ++ ++ session->in_packet.type = msg_type; ++ ++ for (role_c = 0; role_c < 2; role_c++) { ++ session->server = role_c; ++ key.role = role_c; ++ for (session_c = 0; session_c < SESSION_STATE_COUNT; session_c++) { ++ session->session_state = session_c; ++ key.session = session_c; ++ for (dh_c = 0; dh_c < DH_STATE_COUNT; dh_c++) { ++ session->dh_handshake_state = dh_c; ++ key.dh = dh_c; ++ for (auth_c = 0; auth_c < AUTH_STATE_COUNT; auth_c++) { ++ session->auth_state = auth_c; ++ key.auth = auth_c; ++ for (global_req_c = 0; ++ global_req_c < GLOBAL_REQ_STATE_COUNT; ++ global_req_c++) ++ { ++ session->global_req_state = global_req_c; ++ key.global_req = global_req_c; ++ ++ rc = ssh_packet_incoming_filter(session); ++ ++ if (rc == SSH_PACKET_ALLOWED) { ++ in_accepted = is_state_accepted(&key, accepted, ++ accepted_count); ++ ++ if (!in_accepted) { ++ fprintf(stderr, "Message type %d ALLOWED " ++ "in state: role %d, session %d, dh %d, auth %d\n", ++ msg_type, role_c, session_c, dh_c, auth_c); ++ } ++ assert_int_equal(in_accepted, 1); ++ } ++ else if (rc == SSH_PACKET_DENIED) { ++ in_accepted = is_state_accepted(&key, accepted, accepted_count); ++ ++ if (in_accepted) { ++ fprintf(stderr, "Message type %d DENIED " ++ "in state: role %d, session %d, dh %d, auth %d\n", ++ msg_type, role_c, session_c, dh_c, auth_c); ++ } ++ assert_int_equal(in_accepted, 0); ++ } ++ else { ++ fprintf(stderr, "Message type %d UNFILTERED " ++ "in state: role %d, session %d, dh %d, auth %d\n", ++ msg_type, role_c, session_c, dh_c, auth_c); ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ ssh_free(session); ++ return 0; ++} ++ ++static void torture_packet_filter_check_auth_success(void **state) ++{ ++ int rc; ++ ++ global_state accepted[] = { ++ { ++ .flags = (COMPARE_SESSION_STATE | ++ COMPARE_ROLE | ++ COMPARE_AUTH_STATE | ++ COMPARE_DH_STATE), ++ .role = ROLE_CLIENT, ++ .session = SSH_SESSION_STATE_AUTHENTICATING, ++ .dh = DH_STATE_FINISHED, ++ .auth = SSH_AUTH_STATE_PUBKEY_AUTH_SENT, ++ }, ++ { ++ .flags = (COMPARE_SESSION_STATE | ++ COMPARE_ROLE | ++ COMPARE_AUTH_STATE | ++ COMPARE_DH_STATE), ++ .role = ROLE_CLIENT, ++ .session = SSH_SESSION_STATE_AUTHENTICATING, ++ .dh = DH_STATE_FINISHED, ++ .auth = SSH_AUTH_STATE_PASSWORD_AUTH_SENT, ++ }, ++ { ++ .flags = (COMPARE_SESSION_STATE | ++ COMPARE_ROLE | ++ COMPARE_AUTH_STATE | ++ COMPARE_DH_STATE), ++ .role = ROLE_CLIENT, ++ .session = SSH_SESSION_STATE_AUTHENTICATING, ++ .dh = DH_STATE_FINISHED, ++ .auth = SSH_AUTH_STATE_GSSAPI_MIC_SENT, ++ }, ++ { ++ .flags = (COMPARE_SESSION_STATE | ++ COMPARE_ROLE | ++ COMPARE_AUTH_STATE | ++ COMPARE_DH_STATE), ++ .role = ROLE_CLIENT, ++ .session = SSH_SESSION_STATE_AUTHENTICATING, ++ .dh = DH_STATE_FINISHED, ++ .auth = SSH_AUTH_STATE_KBDINT_SENT, ++ }, ++ { ++ .flags = (COMPARE_SESSION_STATE | ++ COMPARE_ROLE | ++ COMPARE_AUTH_STATE | ++ COMPARE_DH_STATE), ++ .role = ROLE_CLIENT, ++ .session = SSH_SESSION_STATE_AUTHENTICATING, ++ .dh = DH_STATE_FINISHED, ++ .auth = SSH_AUTH_STATE_AUTH_NONE_SENT, ++ } ++ }; ++ ++ int accepted_count = 5; ++ ++ /* Unused */ ++ (void) state; ++ ++ rc = check_message_in_all_states(accepted, accepted_count, ++ SSH2_MSG_USERAUTH_SUCCESS); ++ ++ assert_int_equal(rc, 0); ++} ++ ++static void torture_packet_filter_check_channel_open(void **state) ++{ ++ int rc; ++ ++ /* The only condition to accept a CHANNEL_OPEN is to be authenticated */ ++ global_state accepted[] = { ++ { ++ .flags = COMPARE_SESSION_STATE, ++ .session = SSH_SESSION_STATE_AUTHENTICATED, ++ } ++ }; ++ ++ int accepted_count = 1; ++ ++ /* Unused */ ++ (void) state; ++ ++ rc = check_message_in_all_states(accepted, accepted_count, ++ SSH2_MSG_CHANNEL_OPEN); ++ ++ assert_int_equal(rc, 0); ++} ++ ++int torture_run_tests(void) ++{ ++ int rc; ++ UnitTest tests[] = { ++ unit_test(torture_packet_filter_check_auth_success), ++ unit_test(torture_packet_filter_check_channel_open), ++ unit_test(torture_packet_filter_check_unfiltered), ++ }; ++ ++ ssh_init(); ++ torture_filter_tests(tests); ++ rc = run_tests(tests); ++ ssh_finalize(); ++ return rc; ++} +-- +2.19.0 + diff --git a/SOURCES/libssh-SHA256.patch b/SOURCES/libssh-SHA256.patch new file mode 100644 index 0000000..57f71d7 --- /dev/null +++ b/SOURCES/libssh-SHA256.patch @@ -0,0 +1,363 @@ +From f3f140e65f0e58fc37b04dbe4173d6ecda0127ac Mon Sep 17 00:00:00 2001 +From: Jan-Niklas Burfeind +Date: Thu, 9 Aug 2018 11:00:00 +0200 +Subject: dh: Add SSH_PUBLICKEY_HASH_SHA256 to ssh_get_publickey_hash() + +Signed-off-by: Jan-Niklas Burfeind +Reviewed-by: Andreas Schneider +(cherry picked from commit 1499b38aef17beac8b438522535daf428600d529) +--- + include/libssh/libssh.h | 3 ++- + src/dh.c | 23 +++++++++++++++++++++++ + 2 files changed, 25 insertions(+), 1 deletion(-) + +diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h +index 37214898..320dc032 100644 +--- a/include/libssh/libssh.h ++++ b/include/libssh/libssh.h +@@ -444,7 +444,8 @@ LIBSSH_API int ssh_get_publickey(ssh_session session, ssh_key *key); + + enum ssh_publickey_hash_type { + SSH_PUBLICKEY_HASH_SHA1, +- SSH_PUBLICKEY_HASH_MD5 ++ SSH_PUBLICKEY_HASH_MD5, ++ SSH_PUBLICKEY_HASH_SHA256 + }; + LIBSSH_API int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, +diff --git a/src/dh.c b/src/dh.c +index d27b66eb..bf1ade8b 100644 +--- a/src/dh.c ++++ b/src/dh.c +@@ -1039,6 +1039,29 @@ int ssh_get_publickey_hash(const ssh_key key, + *hlen = SHA_DIGEST_LEN; + } + break; ++ case SSH_PUBLICKEY_HASH_SHA256: ++ { ++ SHA256CTX ctx; ++ ++ h = malloc(SHA256_DIGEST_LEN); ++ if (h == NULL) { ++ rc = -1; ++ goto out; ++ } ++ ++ ctx = sha256_init(); ++ if (ctx == NULL) { ++ free(h); ++ rc = -1; ++ goto out; ++ } ++ ++ sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); ++ sha256_final(h, ctx); ++ ++ *hlen = SHA256_DIGEST_LEN; ++ } ++ break; + case SSH_PUBLICKEY_HASH_MD5: + { + MD5CTX ctx; +-- +cgit v1.2.1 + +From 9c62d6dfcd798d28895f5dd1b76a28524bcf18d3 Mon Sep 17 00:00:00 2001 +From: Jan-Niklas Burfeind +Date: Thu, 9 Aug 2018 11:00:00 +0200 +Subject: dh: Add ssh_print_hash() function which can deal with sha256 + +Signed-off-by: Jan-Niklas Burfeind +Reviewed-by: Andreas Schneider +(cherry picked from commit f32cb706752d8dc35ad53a64f51e432cc0bc41cd) +--- + include/libssh/libssh.h | 1 + + src/dh.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 81 insertions(+) + +diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h +index 320dc032..f6cce1e4 100644 +--- a/include/libssh/libssh.h ++++ b/include/libssh/libssh.h +@@ -564,6 +564,7 @@ LIBSSH_API int ssh_pki_export_pubkey_file(const ssh_key key, + + LIBSSH_API const char *ssh_pki_key_ecdsa_name(const ssh_key key); + ++LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char *hash, size_t len); + LIBSSH_API void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len); + LIBSSH_API int ssh_send_ignore (ssh_session session, const char *data); + LIBSSH_API int ssh_send_debug (ssh_session session, const char *message, int always_display); +diff --git a/src/dh.c b/src/dh.c +index bf1ade8b..66a0e704 100644 +--- a/src/dh.c ++++ b/src/dh.c +@@ -1097,6 +1097,38 @@ out: + return rc; + } + ++/** ++ * @internal ++ * ++ * @brief Convert a buffer into an unpadded base64 string. ++ * The caller has to free the memory. ++ * ++ * @param hash What should be converted to a base64 string. ++ * ++ * @param len Length of the buffer to convert. ++ * ++ * @return The base64 string or NULL on error. ++ * ++ * @see ssh_string_free_char() ++ */ ++static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) ++{ ++ char *b64_padded = NULL; ++ char *b64_unpadded = NULL; ++ size_t k; ++ ++ b64_padded = (char *)bin_to_base64(hash, (int)len); ++ if (b64_padded == NULL) { ++ return NULL; ++ } ++ for (k = strlen(b64_padded); k != 0 && b64_padded[k-1] == '='; k--); ++ ++ b64_unpadded = strndup(b64_padded, k); ++ SAFE_FREE(b64_padded); ++ ++ return b64_unpadded; ++} ++ + /** + * @brief Convert a buffer into a colon separated hex string. + * The caller has to free the memory. +@@ -1134,6 +1166,54 @@ char *ssh_get_hexa(const unsigned char *what, size_t len) { + return hexa; + } + ++/** ++ * @brief Print a hash as a human-readable hex- or base64-string. ++ * ++ * This function prints hex strings if the given hash is a md5 sum. ++ * But prints unpadded base64 strings for sha sums. ++ * Either way, the output is prepended by the hash-type. ++ * ++ * @param type Which sort of hash is given. ++ * ++ * @param hash What should be converted to a base64 string. ++ * ++ * @param len Length of the buffer to convert. ++ */ ++void ssh_print_hash(enum ssh_publickey_hash_type type, ++ unsigned char *hash, ++ size_t len) { ++ const char *prefix = "UNKNOWN"; ++ char *fingerprint = NULL; ++ ++ switch (type) { ++ case SSH_PUBLICKEY_HASH_SHA1: ++ case SSH_PUBLICKEY_HASH_SHA256: ++ fingerprint = ssh_get_b64_unpadded(hash, len); ++ break; ++ case SSH_PUBLICKEY_HASH_MD5: ++ fingerprint = ssh_get_hexa(hash, len); ++ break; ++ } ++ if (fingerprint == NULL) { ++ return; ++ } ++ ++ switch (type) { ++ case SSH_PUBLICKEY_HASH_MD5: ++ prefix = "MD5"; ++ break; ++ case SSH_PUBLICKEY_HASH_SHA1: ++ prefix = "SHA1"; ++ break; ++ case SSH_PUBLICKEY_HASH_SHA256: ++ prefix = "SHA256"; ++ break; ++ } ++ fprintf(stderr, "%s:%s\n", prefix, fingerprint); ++ ++ SAFE_FREE(fingerprint); ++} ++ + /** + * @brief Print a buffer as colon separated hex string. + * +-- +cgit v1.2.1 + +From 7a7c0a54bc24391fcff0aaccd983de621cb3e60d Mon Sep 17 00:00:00 2001 +From: Andreas Schneider +Date: Sun, 2 Sep 2018 15:45:41 +0200 +Subject: dh: Add ssh_get_fingerprint_hash() + +Signed-off-by: Andreas Schneider +(cherry picked from commit bbed139ecab26cb46b0bb3a21fa4cd2a4f12dadd) +--- + include/libssh/libssh.h | 3 ++ + src/dh.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 76 insertions(+) + +diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h +index f6cce1e4..7f59abe4 100644 +--- a/include/libssh/libssh.h ++++ b/include/libssh/libssh.h +@@ -564,6 +564,9 @@ LIBSSH_API int ssh_pki_export_pubkey_file(const ssh_key key, + + LIBSSH_API const char *ssh_pki_key_ecdsa_name(const ssh_key key); + ++LIBSSH_API char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, ++ unsigned char *hash, ++ size_t len); + LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char *hash, size_t len); + LIBSSH_API void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len); + LIBSSH_API int ssh_send_ignore (ssh_session session, const char *data); +diff --git a/src/dh.c b/src/dh.c +index 66a0e704..38298b2d 100644 +--- a/src/dh.c ++++ b/src/dh.c +@@ -1166,6 +1166,79 @@ char *ssh_get_hexa(const unsigned char *what, size_t len) { + return hexa; + } + ++/** ++ * @brief Get a hash as a human-readable hex- or base64-string. ++ * ++ * This gets an allocated fingerprint hash. It is a hex strings if the given ++ * hash is a md5 sum. If it is a SHA sum, it will return an unpadded base64 ++ * strings. Either way, the output is prepended by the hash-type. ++ * ++ * @param type Which sort of hash is given. ++ * ++ * @param hash What should be converted to a base64 string. ++ * ++ * @param len Length of the buffer to convert. ++ * ++ * @return Returns the allocated fingerprint hash or NULL on error. ++ * ++ * @see ssh_string_free_char() ++ */ ++char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, ++ unsigned char *hash, ++ size_t len) ++{ ++ const char *prefix = "UNKNOWN"; ++ char *fingerprint = NULL; ++ char *str = NULL; ++ size_t str_len; ++ int rc; ++ ++ switch (type) { ++ case SSH_PUBLICKEY_HASH_SHA1: ++ case SSH_PUBLICKEY_HASH_SHA256: ++ fingerprint = ssh_get_b64_unpadded(hash, len); ++ break; ++ case SSH_PUBLICKEY_HASH_MD5: ++ fingerprint = ssh_get_hexa(hash, len); ++ break; ++ } ++ if (fingerprint == NULL) { ++ return NULL; ++ } ++ ++ switch (type) { ++ case SSH_PUBLICKEY_HASH_MD5: ++ prefix = "MD5"; ++ break; ++ case SSH_PUBLICKEY_HASH_SHA1: ++ prefix = "SHA1"; ++ break; ++ case SSH_PUBLICKEY_HASH_SHA256: ++ prefix = "SHA256"; ++ break; ++ } ++ ++ str_len = strlen(prefix); ++ if (str_len + 1 + strlen(fingerprint) + 1 < str_len) { ++ SAFE_FREE(fingerprint); ++ return NULL; ++ } ++ str_len += 1 + strlen(fingerprint) + 1; ++ ++ str = malloc(str_len); ++ if (str == NULL) { ++ SAFE_FREE(fingerprint); ++ return NULL; ++ } ++ rc = snprintf(str, str_len, "%s:%s", prefix, fingerprint); ++ SAFE_FREE(fingerprint); ++ if (rc < 0 || rc < (int)(str_len - 1)) { ++ SAFE_FREE(str); ++ } ++ ++ return str; ++} ++ + /** + * @brief Print a hash as a human-readable hex- or base64-string. + * +-- +cgit v1.2.1 + +From e765c1400a724cc5009fd03395ef54b28de9c296 Mon Sep 17 00:00:00 2001 +From: Andreas Schneider +Date: Sun, 2 Sep 2018 15:47:41 +0200 +Subject: dh: Use ssh_get_fingerprint_hash() in ssh_print_hash() + +Signed-off-by: Andreas Schneider +(cherry picked from commit 92aa2cf4963b714d0f30d4fb0f9e609200224f7a) +--- + src/dh.c | 29 ++++++----------------------- + 1 file changed, 6 insertions(+), 23 deletions(-) + +diff --git a/src/dh.c b/src/dh.c +index 38298b2d..d7182798 100644 +--- a/src/dh.c ++++ b/src/dh.c +@@ -1254,35 +1254,18 @@ char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, + */ + void ssh_print_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, +- size_t len) { +- const char *prefix = "UNKNOWN"; ++ size_t len) ++{ + char *fingerprint = NULL; + +- switch (type) { +- case SSH_PUBLICKEY_HASH_SHA1: +- case SSH_PUBLICKEY_HASH_SHA256: +- fingerprint = ssh_get_b64_unpadded(hash, len); +- break; +- case SSH_PUBLICKEY_HASH_MD5: +- fingerprint = ssh_get_hexa(hash, len); +- break; +- } ++ fingerprint = ssh_get_fingerprint_hash(type, ++ hash, ++ len); + if (fingerprint == NULL) { + return; + } + +- switch (type) { +- case SSH_PUBLICKEY_HASH_MD5: +- prefix = "MD5"; +- break; +- case SSH_PUBLICKEY_HASH_SHA1: +- prefix = "SHA1"; +- break; +- case SSH_PUBLICKEY_HASH_SHA256: +- prefix = "SHA256"; +- break; +- } +- fprintf(stderr, "%s:%s\n", prefix, fingerprint); ++ fprintf(stderr, "%s\n", fingerprint); + + SAFE_FREE(fingerprint); + } +-- +cgit v1.2.1 + diff --git a/SOURCES/libssh-fix-kbdint.patch b/SOURCES/libssh-fix-kbdint.patch new file mode 100644 index 0000000..dadad67 --- /dev/null +++ b/SOURCES/libssh-fix-kbdint.patch @@ -0,0 +1,27 @@ +From 4ea46eecce9f4e676150fe27fec34e1570b70ace Mon Sep 17 00:00:00 2001 +From: Meng Tan +Date: Wed, 17 Oct 2018 14:50:08 +0200 +Subject: server: Set correct state after sending INFO_REQUEST (Kbd + Interactive) + +Signed-off-by: Meng Tan +Reviewed-by: Andreas Schneider +--- + src/server.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/server.c b/src/server.c +index e14636ec..84cc4f7a 100644 +--- a/src/server.c ++++ b/src/server.c +@@ -1039,6 +1039,7 @@ int ssh_message_auth_interactive_request(ssh_message msg, const char *name, + msg->session->kbdint->prompts = NULL; + msg->session->kbdint->echo = NULL; + } ++ msg->session->auth_state = SSH_AUTH_STATE_INFO; + + return rc; + } +-- +cgit v1.2.1 + diff --git a/SPECS/libssh.spec b/SPECS/libssh.spec index 2457def..c3f23db 100644 --- a/SPECS/libssh.spec +++ b/SPECS/libssh.spec @@ -2,7 +2,7 @@ Name: libssh Version: 0.7.1 -Release: 3%{?dist} +Release: 7%{?dist} Summary: A library implementing the SSH protocol License: LGPLv2+ URL: http://www.libssh.org @@ -13,6 +13,9 @@ Source0: https://red.libssh.org/attachments/download/154/libssh-0.7.1.tar Patch1: libssh-CVE-2016-0739.patch Patch2: libssh-0.7.1-fix_agent_bigendian.patch +Patch3: libssh-CVE-2018-10933.patch +Patch4: libssh-SHA256.patch +Patch5: libssh-fix-kbdint.patch BuildRequires: cmake BuildRequires: doxygen @@ -43,6 +46,9 @@ applications that use %{name}. %setup -q %patch1 -p1 %patch2 -p1 -b .libssh-0.7.2-fix_agent_bigendian.patch +%patch3 -p1 -b .libssh-CVE-2018-10933.patch +%patch4 -p1 -b .libssh-SHA256.patch +%patch5 -p1 -b .libssh-fix-kbdint.patch # Remove examples, they are not packaged and do not build on EPEL 5 sed -i -e 's|add_subdirectory(examples)||g' CMakeLists.txt @@ -97,6 +103,21 @@ rm -rf %{buildroot} %{_libdir}/libssh_threads.so %changelog +* Sun Oct 28 2018 Martin Pitt - 0.7.1-7 +- resolves: #1637182 - Add SHA256 fingerprint support +- Fix regression with keyboard interactive authentication introduced in + previous update + +* Tue Oct 09 2018 Andreas Schneider - 0.7.1-6 +- resolves: #1637257 - Fix CVE-2018-10933 + +* Thu May 03 2018 - Lokesh Mandvekar - 0.7.1-5 +- correct bogus date annoyance + +* Thu May 03 2018 - Lokesh Mandvekar - 0.7.1-4 +- Resolves: #1574670 - bump release tag to have build shipped to +client/workstation repos + * Wed Mar 22 2017 - Dominik Perpeet - 0.7.1-3 - Fix agent auth on big endian machines