(Fernando Arnaboldi, IOActive) Found and fixed several DoS/code execution vulns for...
authorMichael Rash <mbr@cipherdyne.org>
Sat, 25 Aug 2012 02:12:19 +0000 (22:12 -0400)
committerMichael Rash <mbr@cipherdyne.org>
Sat, 25 Aug 2012 02:12:19 +0000 (22:12 -0400)
- [server] Fernando Arnaboldi from IOActive found several DoS/code
execution vulnerabilities for malicious fwknop clients that manage to
get past the authentication stage (so a such a client must be in
possession of a valid access.conf encryption key).  These vulnerbilities
manifested themselves in the handling of malformed access requests, and
both the fwknopd server code along with libfko now perform stronger input
validation of access request data.  These vulnerabilities affect
pre-2.0.3 fwknop releases.
- [test suite] Added a new fuzzing capability to ensure proper server-side
input validation.  Fuzzing data is constructed with modified fwknop
client code that is designed to emulate malicious behavior.

CREDITS
ChangeLog
Makefile.am
lib/fko_message.c
lib/fko_message.h
server/access.c
server/access.h
server/fw_util_iptables.c
test/conf/disable_aging_fwknopd.conf [new file with mode: 0644]
test/test-fwknop.pl

diff --git a/CREDITS b/CREDITS
index ab6edd0..71f0fa8 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -53,3 +53,8 @@ Hank Leininger
     - For iptables firewalls, suggested a check for the 'comment' match to
       ensure the local environment will properly support fwknopd operations.
       The result is the new ENABLE_IPT_COMMENT_CHECK functionality.
+
+Fernando Arnaboldi (IOActive)
+    - Found important buffer overflow conditions for authenticated SPA clients
+      in the fwknopd server (pre-2.0.3).  These findings enabled fixes to be
+      developed along with a new fuzzing capability in the test suite.
index 1568a1a..00f3a37 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+fwknop-2.0.3 (08//2012):
+    - [server] Fernando Arnaboldi from IOActive found several DoS/code
+      execution vulnerabilities for malicious fwknop clients that manage to
+      get past the authentication stage (so a such a client must be in
+      possession of a valid access.conf encryption key).  These vulnerbilities
+      manifested themselves in the handling of malformed access requests, and
+      both the fwknopd server code along with libfko now perform stronger input
+      validation of access request data.  These vulnerabilities affect
+      pre-2.0.3 fwknop releases.
+    - [test suite] Added a new fuzzing capability to ensure proper server-side
+      input validation.  Fuzzing data is constructed with modified fwknop
+      client code that is designed to emulate malicious behavior.
+
 fwknop-2.0.2 (08/18/2012):
     - [server] For GPG mode, added a new access.conf variable
       "GPG_ALLOW_NO_PW" to make it possible to leverage a server-side GPG key
index 9df6222..7306bbc 100644 (file)
@@ -149,6 +149,7 @@ EXTRA_DIST = \
     test/conf/require_user_access.conf \
     test/conf/subnet_source_match_access.conf \
     test/conf/local_nat_fwknopd.conf \
+    test/conf/disable_aging_fwknopd.conf \
     test/hardening-check \
     test/local_spa.key \
     test/test-fwknop.pl \
index 061bb73..9148c2d 100644 (file)
@@ -200,6 +200,8 @@ validate_access_msg(const char *msg)
     do {
         ndx++;
         res = validate_proto_port_spec(ndx);
+        if(res != FKO_SUCCESS)
+            break;
     } while((ndx = strchr(ndx, ',')));
 
     return(res);
@@ -208,14 +210,13 @@ validate_access_msg(const char *msg)
 int
 validate_proto_port_spec(const char *msg)
 {
-    int     startlen    = strnlen(msg, MAX_SPA_MESSAGE_SIZE);
+    int     startlen    = strnlen(msg, MAX_SPA_MESSAGE_SIZE), port_str_len = 0;
     const char   *ndx   = msg;
 
     if(startlen == MAX_SPA_MESSAGE_SIZE)
         return(FKO_ERROR_INVALID_DATA);
 
-    /* Now check for proto/port string.  Currenly we only allow protos
-     * 'tcp', 'udp', and 'icmp'.
+    /* Now check for proto/port string.
     */
     if(strncmp(ndx, "tcp", 3)
       && strncmp(ndx, "udp", 3)
@@ -224,19 +225,25 @@ validate_proto_port_spec(const char *msg)
         return(FKO_ERROR_INVALID_SPA_ACCESS_MSG);
 
     ndx = strchr(ndx, '/');
-    if(ndx == NULL || (1+(ndx - msg)) >= startlen)
+    if(ndx == NULL || ((1+(ndx - msg)) > MAX_PROTO_STR_LEN))
         return(FKO_ERROR_INVALID_SPA_ACCESS_MSG);
 
-    /* Skip over the ',' and make sure we only have digits.
+    /* Skip over the '/' and make sure we only have digits.
     */
     ndx++;
-    while(*ndx != '\0')
+
+    /* Must have at least one digit for the port number
+    */
+    if(isdigit(*ndx) == 0)
+        return(FKO_ERROR_INVALID_SPA_ACCESS_MSG);
+
+    while(*ndx != '\0' && *ndx != ',')
     {
-        if(isdigit(*ndx) == 0)
+        port_str_len++;
+        if((isdigit(*ndx) == 0) || (port_str_len > MAX_PORT_STR_LEN))
             return(FKO_ERROR_INVALID_SPA_ACCESS_MSG);
         ndx++;
     }
-
     return(FKO_SUCCESS);
 }
 
index f56e33f..8350460 100644 (file)
@@ -32,6 +32,9 @@
 #ifndef FKO_MESSAGE_H
 #define FKO_MESSAGE_H 1
 
+#define MAX_PROTO_STR_LEN   4  /* tcp, udp, icmp for now */
+#define MAX_PORT_STR_LEN    5
+
 /* SPA message format validation functions.
 */
 int validate_cmd_msg(const char *msg);
index c81fb93..91feb0b 100644 (file)
@@ -168,7 +168,7 @@ add_acc_force_nat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *v
 /* Take an IP or Subnet/Mask and convert it to mask for later
  * comparisons of incoming source IPs against this mask.
 */
-static void
+static int
 add_source_mask(acc_stanza_t *acc, const char *ip)
 {
     char                *ndx;
@@ -237,7 +237,7 @@ add_source_mask(acc_stanza_t *acc, const char *ip)
             free(new_sle);
             new_sle = NULL;
 
-            return;
+            return 0;
         }
 
         /* Store our mask converted from CIDR to a 32-bit value.
@@ -249,15 +249,17 @@ add_source_mask(acc_stanza_t *acc, const char *ip)
         */
         new_sle->maddr = ntohl(in.s_addr) & new_sle->mask;
     }
+    return 1;
 }
 
 /* Expand the access SOURCE string to a list of masks.
 */
-void
+static int
 expand_acc_source(acc_stanza_t *acc)
 {
     char           *ndx, *start;
-    char            buf[32];
+    char            buf[ACCESS_BUF_LEN];
+    int             res = 1;
 
     start = acc->source;
 
@@ -270,8 +272,13 @@ expand_acc_source(acc_stanza_t *acc)
             while(isspace(*start))
                 start++;
 
+            if(((ndx-start)+1) >= ACCESS_BUF_LEN)
+                return 0;
+
             strlcpy(buf, start, (ndx-start)+1);
-            add_source_mask(acc, buf);
+            res = add_source_mask(acc, buf);
+            if(res == 0)
+                return res;
             start = ndx+1;
         }
     }
@@ -281,15 +288,20 @@ expand_acc_source(acc_stanza_t *acc)
     while(isspace(*start))
         start++;
 
+    if(((ndx-start)+1) >= ACCESS_BUF_LEN)
+        return 0;
+
     strlcpy(buf, start, (ndx-start)+1);
-    add_source_mask(acc, buf);
+    res = add_source_mask(acc, buf);
+
+    return res;
 }
 
 static int
 parse_proto_and_port(char *pstr, int *proto, int *port)
 {
     char    *ndx;
-    char    proto_str[32];
+    char    proto_str[ACCESS_BUF_LEN];
 
     /* Parse the string into its components.
     */
@@ -301,10 +313,24 @@ parse_proto_and_port(char *pstr, int *proto, int *port)
         return(-1);
     }
 
-    strlcpy(proto_str, pstr,  (ndx - pstr)+1);
+    if(((ndx - pstr)+1) >= ACCESS_BUF_LEN)
+    {
+        log_msg(LOG_ERR,
+            "Parse error on access port entry: %s", pstr);
+        return(-1);
+    }
+
+    strlcpy(proto_str, pstr, (ndx - pstr)+1);
 
     *port = atoi(ndx+1);
 
+    if((*port < 0) || (*port > MAX_PORT))
+    {
+        log_msg(LOG_ERR,
+            "Invalid port in access request: %s", pstr);
+        return(-1);
+    }
+
     if(strcasecmp(proto_str, "tcp") == 0)
         *proto = PROTO_TCP;
     else if(strcasecmp(proto_str, "udp") == 0)
@@ -313,7 +339,6 @@ parse_proto_and_port(char *pstr, int *proto, int *port)
     {
         log_msg(LOG_ERR,
             "Invalid protocol in access port entry: %s", pstr);
-
         return(-1);
     }
 
@@ -416,15 +441,15 @@ add_string_list_ent(acc_string_list_t **stlist, const char *str_str)
 
 /* Expand a proto/port access string to a list of access proto-port struct.
 */
-void
+int
 expand_acc_port_list(acc_port_list_t **plist, char *plist_str)
 {
     char           *ndx, *start;
-    char            buf[32];
+    char            buf[ACCESS_BUF_LEN];
 
     start = plist_str;
 
-    for(ndx = start; *ndx; ndx++)
+    for(ndx = start; *ndx != '\0'; ndx++)
     {
         if(*ndx == ',')
         {
@@ -433,6 +458,9 @@ expand_acc_port_list(acc_port_list_t **plist, char *plist_str)
             while(isspace(*start))
                 start++;
 
+            if(((ndx-start)+1) >= ACCESS_BUF_LEN)
+                return 0;
+
             strlcpy(buf, start, (ndx-start)+1);
             add_port_list_ent(plist, buf);
             start = ndx+1;
@@ -444,9 +472,14 @@ expand_acc_port_list(acc_port_list_t **plist, char *plist_str)
     while(isspace(*start))
         start++;
 
+    if(((ndx-start)+1) >= ACCESS_BUF_LEN)
+        return 0;
+
     strlcpy(buf, start, (ndx-start)+1);
 
     add_port_list_ent(plist, buf);
+
+    return 1;
 }
 
 /* Expand a comma-separated string into a simple acc_string_list.
@@ -602,7 +635,11 @@ expand_acc_ent_lists(fko_srv_options_t *opts)
     {
         /* Expand the source string to 32-bit integer masks foreach entry.
         */
-        expand_acc_source(acc);
+        if(expand_acc_source(acc) == 0)
+        {
+            acc = acc->next;
+            continue;
+        }
 
         /* Now expand the open_ports string.
         */
@@ -1086,9 +1123,9 @@ compare_port_list(acc_port_list_t *in, acc_port_list_t *ac, const int match_any)
 int
 acc_check_port_access(acc_stanza_t *acc, char *port_str)
 {
-    int             res     = 1;
+    int             res = 1, ctr = 0;
 
-    char            buf[32];
+    char            buf[ACCESS_BUF_LEN];
     char           *ndx, *start;
 
     acc_port_list_t *o_pl   = acc->oport_list;
@@ -1101,14 +1138,34 @@ acc_check_port_access(acc_stanza_t *acc, char *port_str)
     /* Create our own internal port_list from the incoming SPA data
      * for comparison.
     */
-    for(ndx = start; *ndx; ndx++)
+    for(ndx = start; *ndx != '\0'; ndx++)
     {
         if(*ndx == ',')
         {
+            if((ctr >= ACCESS_BUF_LEN)
+                    || (((ndx-start)+1) >= ACCESS_BUF_LEN))
+            {
+                log_msg(LOG_ERR,
+                    "Unable to create acc_port_list from incoming data: %s",
+                    port_str
+                );
+                return(0);
+            }
             strlcpy(buf, start, (ndx-start)+1);
             add_port_list_ent(&in_pl, buf);
             start = ndx+1;
+            ctr = 0;
         }
+        ctr++;
+    }
+    if((ctr >= ACCESS_BUF_LEN)
+            || (((ndx-start)+1) >= ACCESS_BUF_LEN))
+    {
+        log_msg(LOG_ERR,
+            "Unable to create acc_port_list from incoming data: %s",
+            port_str
+        );
+        return(0);
     }
     strlcpy(buf, start, (ndx-start)+1);
     add_port_list_ent(&in_pl, buf);
index b650101..4ecae19 100644 (file)
@@ -34,6 +34,8 @@
 #define PROTO_TCP   6
 #define PROTO_UDP   17
 
+#define ACCESS_BUF_LEN  32
+
 /* Function Prototypes
 */
 void parse_access_file(fko_srv_options_t *opts);
@@ -41,7 +43,7 @@ int compare_addr_list(acc_int_list_t *source_list, const uint32_t ip);
 int acc_check_port_access(acc_stanza_t *acc, char *port_str);
 int acc_check_gpg_remote_id(acc_stanza_t *acc, const char *gpg_id);
 void dump_access_list(const fko_srv_options_t *opts);
-void expand_acc_port_list(acc_port_list_t **plist, char *plist_str);
+int expand_acc_port_list(acc_port_list_t **plist, char *plist_str);
 void free_acc_stanzas(fko_srv_options_t *opts);
 void free_acc_port_list(acc_port_list_t *plist);
 
index ad1648a..e1b3988 100644 (file)
@@ -582,7 +582,8 @@ process_spa_request(const fko_srv_options_t *opts, const acc_stanza_t *acc, spa_
 
     /* Parse and expand our access message.
     */
-    expand_acc_port_list(&port_list, spadat->spa_message_remain);
+    if(expand_acc_port_list(&port_list, spadat->spa_message_remain) != 1)
+        return res;
 
     /* Start at the top of the proto-port list...
     */
diff --git a/test/conf/disable_aging_fwknopd.conf b/test/conf/disable_aging_fwknopd.conf
new file mode 100644 (file)
index 0000000..195a85b
--- /dev/null
@@ -0,0 +1,5 @@
+#
+# The default fwknopd.conf contains only comments since defaults are defined in
+# code and modified via the config file
+#
+ENABLE_SPA_PACKET_AGING         N;
index 0fd8adf..2f78bb4 100755 (executable)
@@ -48,6 +48,7 @@ my %cf = (
     'multi_src_access'     => "$conf_dir/multi_source_match_access.conf",
     'ip_src_match'         => "$conf_dir/ip_source_match_access.conf",
     'subnet_src_match'     => "$conf_dir/ip_source_match_access.conf",
+    'disable_aging'        => "$conf_dir/disable_aging_fwknopd.conf",
 );
 
 my $default_digest_file = "$run_dir/digest.cache";
@@ -508,7 +509,7 @@ my @tests = (
     {
         'category' => 'basic operations',
         'subcategory' => 'client',
-        'detail'   => '-A <proto>/<port> specification',
+        'detail'   => '-A <proto>/<port> specification (proto)',
         'err_msg'  => 'permitted invalid -A <proto>/<port>',
         'function' => \&generic_exec,
         'positive_output_matches' => [qr/Invalid\sSPA\saccess\smessage/i],
@@ -520,6 +521,19 @@ my @tests = (
     {
         'category' => 'basic operations',
         'subcategory' => 'client',
+        'detail'   => '-A <proto>/<port> specification (port)',
+        'err_msg'  => 'permitted invalid -A <proto>/<port>',
+        'function' => \&generic_exec,
+        'positive_output_matches' => [qr/Invalid\sSPA\saccess\smessage/i],
+        'exec_err' => $YES,
+        'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopCmd -A tcp/600001 -a $fake_ip -D $loopback_ip",
+        'fatal'    => $NO
+    },
+
+    {
+        'category' => 'basic operations',
+        'subcategory' => 'client',
         'detail'   => 'generate SPA packet',
         'err_msg'  => 'could not generate SPA packet',
         'function' => \&client_send_spa_packet,
@@ -1072,6 +1086,52 @@ my @tests = (
     {
         'category' => 'Rijndael SPA',
         'subcategory' => 'client+server',
+        'detail'   => 'complete cycle (tcp/60001)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopCmd -A tcp/60001 -a $fake_ip -D $loopback_ip --get-key " .
+            "$local_key_file --verbose --verbose",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd $default_server_conf_args $intf_str",
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => 'multi port (tcp/60001,udp/60001)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopCmd -A tcp/60001,udp/60001 -a $fake_ip -D $loopback_ip --get-key " .
+            "$local_key_file --verbose --verbose",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd $default_server_conf_args $intf_str",
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
+        'detail'   => 'multi port (tcp/22,udp/53,tcp/1234)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopCmd -A tcp/22,udp/53,tcp/1234 -a $fake_ip -D $loopback_ip --get-key " .
+            "$local_key_file --verbose --verbose",
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd $default_server_conf_args $intf_str",
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'client+server',
         'detail'   => 'complete cycle (udp/53 dns)',
         'err_msg'  => 'could not complete SPA cycle',
         'function' => \&spa_cycle,
@@ -1154,6 +1214,64 @@ my @tests = (
         'replay_positive_output_matches' => [qr/Data\sis\snot\sa\svalid\sSPA\smessage\sformat/],
         'fatal'    => $NO
     },
+
+    ### fuzzing tests
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'FUZZING',
+        'detail'   => 'overly long port value',
+        'err_msg'  => 'server crashed or did not detect error condition',
+        'function' => \&overly_long_port,
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $cf{'disable_aging'} -a $cf{'def_access'} " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'FUZZING',
+        'detail'   => 'overly long proto value',
+        'err_msg'  => 'server crashed or did not detect error condition',
+        'function' => \&overly_long_proto,
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $cf{'disable_aging'} -a $cf{'def_access'} " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'FUZZING',
+        'detail'   => 'negative port value',
+        'err_msg'  => 'server crashed or did not detect error condition',
+        'function' => \&negative_port,
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $cf{'disable_aging'} -a $cf{'def_access'} " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'FUZZING',
+        'detail'   => 'null port value',
+        'err_msg'  => 'server crashed or did not detect error condition',
+        'function' => \&null_port,
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $cf{'disable_aging'} -a $cf{'def_access'} " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'fatal'    => $NO
+    },
+    {
+        'category' => 'Rijndael SPA',
+        'subcategory' => 'FUZZING',
+        'detail'   => 'null proto value',
+        'err_msg'  => 'server crashed or did not detect error condition',
+        'function' => \&null_proto,
+        'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopdCmd -c $cf{'disable_aging'} -a $cf{'def_access'} " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'fatal'    => $NO
+    },
+
     {
         'category' => 'Rijndael SPA',
         'subcategory' => 'server',
@@ -1290,6 +1408,24 @@ my @tests = (
     {
         'category' => 'GPG (no pw) SPA',
         'subcategory' => 'client+server',
+        'detail'   => 'complete cycle (tcp/60001)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopCmd -A tcp/60001 -a $fake_ip -D $loopback_ip --get-key " .
+            "$local_key_file --verbose --verbose " .
+            "--gpg-recipient-key $gpg_server_key " .
+            "--gpg-signer-key $gpg_client_key " .
+            "--gpg-home-dir $gpg_client_home_dir_no_pw",
+        'fwknopd_cmdline'  => $default_server_gpg_args_no_pw,
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+
+    {
+        'category' => 'GPG (no pw) SPA',
+        'subcategory' => 'client+server',
         'detail'   => 'complete cycle (udp/53 dns)',
         'err_msg'  => 'could not complete SPA cycle',
         'function' => \&spa_cycle,
@@ -1453,6 +1589,24 @@ my @tests = (
     {
         'category' => 'GnuPG (GPG) SPA',
         'subcategory' => 'client+server',
+        'detail'   => 'complete cycle (tcp/60001)',
+        'err_msg'  => 'could not complete SPA cycle',
+        'function' => \&spa_cycle,
+        'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
+            "$fwknopCmd -A tcp/60001 -a $fake_ip -D $loopback_ip --get-key " .
+            "$local_key_file --verbose --verbose " .
+            "--gpg-recipient-key $gpg_server_key " .
+            "--gpg-signer-key $gpg_client_key " .
+            "--gpg-home-dir $gpg_client_home_dir",
+        'fwknopd_cmdline'  => $default_server_gpg_args,
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'fatal'    => $NO
+    },
+
+    {
+        'category' => 'GnuPG (GPG) SPA',
+        'subcategory' => 'client+server',
         'detail'   => 'complete cycle (udp/53 dns)',
         'err_msg'  => 'could not complete SPA cycle',
         'function' => \&spa_cycle,
@@ -2112,6 +2266,261 @@ sub altered_non_base64_spa_data() {
     return $rv;
 }
 
+sub null_proto() {
+    my $test_hr = shift;
+
+    my $rv = 1;
+    my $server_was_stopped = 0;
+    my $fw_rule_created = 0;
+    my $fw_rule_removed = 0;
+
+    ### this packet was generated with a modified fwknop client via the
+    ### following command line:
+    #
+    # LD_LIBRARY_PATH=../lib/.libs  ../client/.libs/fwknop -A /22 \
+    # -a 127.0.0.2 -D 127.0.0.1 --get-key local_spa.key \
+    # --verbose --verbose
+    #
+    my $spa_pkt =
+        '/JT14qxh9P4iy+CuUZahThaQjoEuL2zd46a+jL6sTrBZJSa6faUX4dH5fte/4ZJv+9f' .
+        'd/diWYKAUvdQ4DydPGlR7mwQa2W+obKpqrsTBz7D4054z6ATAOGpCtifakEVl1XRc2+' .
+        'hW04WpY8mdUNu9i+PrfPr7/KxqU';
+
+    my @packets = (
+        {
+            'proto'  => 'udp',
+            'port'   => $default_spa_port,
+            'dst_ip' => $loopback_ip,
+            'data'   => $spa_pkt,
+        },
+    );
+
+    ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
+        = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
+
+    $rv = 0 unless $server_was_stopped;
+
+    if ($fw_rule_created) {
+        &write_test_file("[-] new fw rule created.\n", $current_test_file);
+        $rv = 0;
+    } else {
+        &write_test_file("[+] new fw rule not created.\n", $current_test_file);
+    }
+
+    unless (&file_find_regex([qr/Args\scontain\sinvalid\sdata/],
+            $MATCH_ALL, $server_test_file)) {
+        $rv = 0;
+    }
+
+    return $rv;
+}
+
+sub null_port() {
+    my $test_hr = shift;
+
+    my $rv = 1;
+    my $server_was_stopped = 0;
+    my $fw_rule_created = 0;
+    my $fw_rule_removed = 0;
+
+    ### this packet was generated with a modified fwknop client via the
+    ### following command line:
+    #
+    # LD_LIBRARY_PATH=../lib/.libs  ../client/.libs/fwknop -A tcp/ \
+    # -a 127.0.0.2 -D 127.0.0.1 --get-key local_spa.key \
+    # --verbose --verbose
+    #
+    my $spa_pkt =
+        '94nu7hvq6V/3A27GzjHwfPnPCQfs44ySlraIFYHOAqy5YqjkrBS67nH35tX55N1BrYZ' .
+        '07zvcT03keUhLE1Uo7Wme1nE7BfTOG5stmIK1UQI85sL52//lDHu+xCqNcL7GUKbVRz' .
+        'ekw+EUscVvUkrsRcVtSvOm+fCNo';
+
+    my @packets = (
+        {
+            'proto'  => 'udp',
+            'port'   => $default_spa_port,
+            'dst_ip' => $loopback_ip,
+            'data'   => $spa_pkt,
+        },
+    );
+
+    ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
+        = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
+
+    $rv = 0 unless $server_was_stopped;
+
+    if ($fw_rule_created) {
+        &write_test_file("[-] new fw rule created.\n", $current_test_file);
+        $rv = 0;
+    } else {
+        &write_test_file("[+] new fw rule not created.\n", $current_test_file);
+    }
+
+    unless (&file_find_regex([qr/Args\scontain\sinvalid\sdata/],
+            $MATCH_ALL, $server_test_file)) {
+        $rv = 0;
+    }
+
+    return $rv;
+}
+
+sub negative_port() {
+    my $test_hr = shift;
+
+    my $rv = 1;
+    my $server_was_stopped = 0;
+    my $fw_rule_created = 0;
+    my $fw_rule_removed = 0;
+
+    ### this packet was generated with a modified fwknop client via the
+    ### following command line:
+    #
+    # LD_LIBRARY_PATH=../lib/.libs  ../client/.libs/fwknop -A \
+    # tcp/-33 -a 127.0.0.2 -D 127.0.0.1 --get-key local_spa.key \
+    # --verbose --verbose
+    #
+    my $spa_pkt =
+        '/weoc+pEuQknZo8ImWTQBB+/PwSJ2/TcrmFoSkxpRXX4+jlUxoJakHrioxh8rhLmAD9' .
+        '8E4lMnq+EbM2XYdhs2alpZ5bovAFojMsYRWwr/BvRO4Um4Fmo9z9sY3DR477TXNYXBR' .
+        'iGXWxSL4u+AWSSePK3qiiYoRQVw';
+
+    my @packets = (
+        {
+            'proto'  => 'udp',
+            'port'   => $default_spa_port,
+            'dst_ip' => $loopback_ip,
+            'data'   => $spa_pkt,
+        },
+    );
+
+    ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
+        = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
+
+    $rv = 0 unless $server_was_stopped;
+
+    if ($fw_rule_created) {
+        &write_test_file("[-] new fw rule created.\n", $current_test_file);
+        $rv = 0;
+    } else {
+        &write_test_file("[+] new fw rule not created.\n", $current_test_file);
+    }
+
+    unless (&file_find_regex([qr/Args\scontain\sinvalid\sdata/],
+            $MATCH_ALL, $server_test_file)) {
+        $rv = 0;
+    }
+
+    return $rv;
+}
+
+sub overly_long_proto() {
+    my $test_hr = shift;
+
+    my $rv = 1;
+    my $server_was_stopped = 0;
+    my $fw_rule_created = 0;
+    my $fw_rule_removed = 0;
+
+    ### this packet was generated with a modified fwknop client via the
+    ### following command line:
+    #
+    # LD_LIBRARY_PATH=../lib/.libs  ../client/.libs/fwknop -A \
+    # "tcp`perl -e '{print "A"x"28"}'`/1" -a 127.0.0.2 -D 127.0.0.1 \
+    # --get-key local_spa.key --verbose --verbose
+    #
+    # This problem was found by Fernando Arnaboldi of IOActive and exploits
+    # a buffer overflow in the fwknopd servers prior to 2.0.3 from
+    # authenticated clients.
+    #
+    my $spa_pkt =
+        '/im5MiJQmOdzqrdWXv+AjEtAm/HsLrdaTFcSw3ZskqpGOdDIrSCz3VXbFfv7qDkc5Y4' .
+        'q/k1mRXl9SGzpug87U5dZSyCdAr30z7/2kUFEPTGOQBi/x+L1t1pvdkm4xg13t09ldm' .
+        '5OD8KiV6qzqLOvN4ULJjvvJJWBZ9qvo/f2Q9Wf67g2KHiwS6EeCINAuMoUw/mNRQMa4' .
+        'oGnOXu3/DeWHJAwtSeh7EAr4';
+
+    my @packets = (
+        {
+            'proto'  => 'udp',
+            'port'   => $default_spa_port,
+            'dst_ip' => $loopback_ip,
+            'data'   => $spa_pkt,
+        },
+    );
+
+    ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
+        = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
+
+    $rv = 0 unless $server_was_stopped;
+
+    if ($fw_rule_created) {
+        &write_test_file("[-] new fw rule created.\n", $current_test_file);
+        $rv = 0;
+    } else {
+        &write_test_file("[+] new fw rule not created.\n", $current_test_file);
+    }
+
+    unless (&file_find_regex([qr/Args\scontain\sinvalid\sdata/],
+            $MATCH_ALL, $server_test_file)) {
+        $rv = 0;
+    }
+
+    return $rv;
+}
+
+sub overly_long_port() {
+    my $test_hr = shift;
+
+    my $rv = 1;
+    my $server_was_stopped = 0;
+    my $fw_rule_created = 0;
+    my $fw_rule_removed = 0;
+
+    ### this packet was generated with a modified fwknop client via the
+    ### following command line:
+    #
+    # LD_LIBRARY_PATH=../lib/.libs  ../client/.libs/fwknop -A \
+    # "tcp/`perl -e '{print "1"x"40"}'`" -a 127.0.0.2 -D 127.0.0.1 \
+    # --get-key local_spa.key --verbose --verbose
+    #
+    # This problem was found by Fernando Arnaboldi of IOActive and exploits
+    # a buffer overflow in the fwknopd servers prior to 2.0.3 from
+    # authenticated clients.
+    #
+    my $spa_pkt =
+        '+JzxeTGlc6lwwzbJSrYChKx8bonWBIPajwGfEtGOaoglcMLbTY/GGXo/nxqiN1LykFS' .
+        'lDFXgrkyx2emJ7NGzYqQPUYZxLdZRocR9aRIptvXLLIPBcIpJASi/TUiJlw7CDFMcj0' .
+        'ptSBJJUZi0tozpKHETp3AgqfzyOy5FNs38aZsV5/sDl3Pt+kF7fTZJ+YLbmYY4yCUz2' .
+        'ZUYoCaJ7X78ULyJTi5eT7nug';
+
+    my @packets = (
+        {
+            'proto'  => 'udp',
+            'port'   => $default_spa_port,
+            'dst_ip' => $loopback_ip,
+            'data'   => $spa_pkt,
+        },
+    );
+
+    ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
+        = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
+
+    $rv = 0 unless $server_was_stopped;
+
+    if ($fw_rule_created) {
+        &write_test_file("[-] new fw rule created.\n", $current_test_file);
+        $rv = 0;
+    } else {
+        &write_test_file("[+] new fw rule not created.\n", $current_test_file);
+    }
+
+    unless (&file_find_regex([qr/Args\scontain\sinvalid\sdata/],
+            $MATCH_ALL, $server_test_file)) {
+        $rv = 0;
+    }
+
+    return $rv;
+}
+
 sub altered_base64_spa_data() {
     my $test_hr = shift;
 
@@ -2357,7 +2766,7 @@ sub server_ignore_small_packets() {
 }
 
 sub client_server_interaction() {
-    my ($test_hr, $pkts_hr, $spa_client_flag, $fw_rules_flag) = @_;
+    my ($test_hr, $pkts_hr, $spa_client_flag) = @_;
 
     my $rv = 1;
     my $server_was_stopped = 1;