From 92feb6b859473bdd909ff618c89ce4c89a6a867d 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 9731efd4..f5ab4a97 100644 --- 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; @@ -282,21 +286,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; @@ -598,7 +608,7 @@ int ssh_userauth_try_publickey(ssh_session session, goto fail; } - 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) { @@ -772,7 +782,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) { @@ -908,7 +918,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 e9729e647cf24bf26b48b04c008a1f2825b74336 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 f5ab4a97..4c7a8e30 100644 --- 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; @@ -1409,7 +1411,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 d42cf1bc656db3e335ed8e16b1252e7d51c3d6fc 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 4c7a8e30..375c2c85 100644 --- 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; @@ -444,7 +446,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 b5b9ae012501dbc4fc64442714e5612eed5e8841 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 7bd34479..8dbfad49 100644 --- a/src/gssapi.c +++ b/src/gssapi.c @@ -955,8 +955,8 @@ SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client){ ssh_string_free(token); } 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 0acc250ad094d9bfef04543ed87bd21de0931c55 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 ced9697a..2aa08322 100644 --- a/src/channels.c +++ b/src/channels.c @@ -171,6 +171,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 69e505bb8deb375486623117b9d1187e20e2e088 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 fix build for 0.6 --- src/channels.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/channels.c b/src/channels.c index 2aa08322..34207911 100644 --- a/src/channels.c +++ b/src/channels.c @@ -177,7 +177,8 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf){ "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION received in incorrect " "channel state %d", channel->state); - goto error; + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; } SSH_LOG(SSH_LOG_PROTOCOL, @@ -220,6 +221,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, @@ -227,6 +236,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 50cba69159e1c7f4b85f2e267d5f9e8c741ca964 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 513eaa81..80217f84 100644 --- a/include/libssh/packet.h +++ b/include/libssh/packet.h @@ -41,6 +41,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 535b6d55..4d6f4878 100644 --- a/src/packet.c +++ b/src/packet.c @@ -129,6 +129,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. */ @@ -155,6 +924,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 (data == NULL) { goto error; @@ -322,8 +1092,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 7030df59f04a5a59f008db8f0c9946ceb90cd89b 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 | 2 + tests/unittests/torture_packet_filter.c | 500 ++++++++++++++++++++++++ 2 files changed, 502 insertions(+) create mode 100644 tests/unittests/torture_packet_filter.c diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 38203991..2414a17a 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -7,6 +7,8 @@ add_cmocka_test(torture_list torture_list.c ${TORTURE_LIBRARY}) 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