Blob Blame History Raw
From e5f0e711b05c2ec4c2d016a6abaedae2959ddba2 Mon Sep 17 00:00:00 2001
From: Anderson Toshiyuki Sasaki <ansasaki@redhat.com>
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 <ansasaki@redhat.com>
---
 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 <ansasaki@redhat.com>
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 <ansasaki@redhat.com>
---
 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 <ansasaki@redhat.com>
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 <ansasaki@redhat.com>
---
 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 <ansasaki@redhat.com>
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 <ansasaki@redhat.com>
---
 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 <ansasaki@redhat.com>
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 <ansasaki@redhat.com>
---
 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 <ansasaki@redhat.com>
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 <ansasaki@redhat.com>
---
 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 <ansasaki@redhat.com>
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 <ansasaki@redhat.com>
---
 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 <ansasaki@redhat.com>
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 <ansasaki@redhat.com>
---
 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 <ansasaki@redhat.com>
+ *
+ * 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