Browse Source

ospf6d: CVE-2011-3323 (fortify packet reception)

This vulnerability (CERT-FI #514840) was reported by CROSS project.

ospf6d processes IPv6 prefix structures in incoming packets without
verifying that the declared prefix length is valid. This leads to a
crash
caused by out of bounds memory access.

* ospf6_abr.h: new macros for size/alignment validation
* ospf6_asbr.h: idem
* ospf6_intra.h: idem
* ospf6_lsa.h: idem
* ospf6_message.h: idem
* ospf6_proto.h: idem
* ospf6_message.c
  * ospf6_packet_minlen: helper array for ospf6_packet_examin()
  * ospf6_lsa_minlen: helper array for ospf6_lsa_examin()
  * ospf6_hello_recv(): do not call ospf6_header_examin(), let upper
    layer verify the input data
  * ospf6_dbdesc_recv(): idem
  * ospf6_lsreq_recv(): idem
  * ospf6_lsupdate_recv(): idem
  * ospf6_lsack_recv(): idem
  * ospf6_prefixes_examin(): new function, implements A.4.1
  * ospf6_lsa_examin(): new function, implements A.4
  * ospf6_lsaseq_examin(): new function, an interface to above
  * ospf6_packet_examin(): new function, implements A.3
  * ospf6_rxpacket_examin(): new function, replaces
    ospf6_header_examin()
  * ospf6_header_examin(): sayonara
  * ospf6_receive(): perform passive interface check earliest possible,
    employ ospf6_rxpacket_examin()
Denis Ovsienko 8 years ago
parent
commit
abc7ef44ca
7 changed files with 492 additions and 73 deletions
  1. 2 0
      ospf6d/ospf6_abr.h
  2. 1 0
      ospf6d/ospf6_asbr.h
  3. 6 0
      ospf6d/ospf6_intra.h
  4. 1 0
      ospf6d/ospf6_lsa.h
  5. 474 73
      ospf6d/ospf6_message.c
  6. 7 0
      ospf6d/ospf6_message.h
  7. 1 0
      ospf6d/ospf6_proto.h

+ 2 - 0
ospf6d/ospf6_abr.h

@@ -35,6 +35,7 @@ extern unsigned char conf_debug_ospf6_abr;
   (conf_debug_ospf6_abr)
 
 /* Inter-Area-Prefix-LSA */
+#define OSPF6_INTER_PREFIX_LSA_MIN_SIZE        4U /* w/o IPv6 prefix */
 struct ospf6_inter_prefix_lsa
 {
   u_int32_t metric;
@@ -42,6 +43,7 @@ struct ospf6_inter_prefix_lsa
 };
 
 /* Inter-Area-Router-LSA */
+#define OSPF6_INTER_ROUTER_LSA_FIX_SIZE       12U
 struct ospf6_inter_router_lsa
 {
   u_char mbz;

+ 1 - 0
ospf6d/ospf6_asbr.h

@@ -44,6 +44,7 @@ struct ospf6_external_info
 };
 
 /* AS-External-LSA */
+#define OSPF6_AS_EXTERNAL_LSA_MIN_SIZE         4U /* w/o IPv6 prefix */
 struct ospf6_as_external_lsa
 {
   u_int32_t bits_metric;

+ 6 - 0
ospf6d/ospf6_intra.h

@@ -69,6 +69,7 @@ extern u_int32_t conf_debug_ospf6_brouter_specific_area_id;
    conf_debug_ospf6_brouter_specific_area_id == (area_id))
 
 /* Router-LSA */
+#define OSPF6_ROUTER_LSA_MIN_SIZE              4U
 struct ospf6_router_lsa
 {
   u_char bits;
@@ -77,6 +78,7 @@ struct ospf6_router_lsa
 };
 
 /* Link State Description in Router-LSA */
+#define OSPF6_ROUTER_LSDESC_FIX_SIZE          16U
 struct ospf6_router_lsdesc
 {
   u_char    type;
@@ -105,6 +107,7 @@ struct ospf6_router_lsdesc
   (((struct ospf6_router_lsdesc *)(x))->neighbor_router_id)
 
 /* Network-LSA */
+#define OSPF6_NETWORK_LSA_MIN_SIZE             4U
 struct ospf6_network_lsa
 {
   u_char reserved;
@@ -113,6 +116,7 @@ struct ospf6_network_lsa
 };
 
 /* Link State Description in Router-LSA */
+#define OSPF6_NETWORK_LSDESC_FIX_SIZE          4U
 struct ospf6_network_lsdesc
 {
   u_int32_t router_id;
@@ -121,6 +125,7 @@ struct ospf6_network_lsdesc
   (((struct ospf6_network_lsdesc *)(x))->router_id)
 
 /* Link-LSA */
+#define OSPF6_LINK_LSA_MIN_SIZE               24U /* w/o 1st IPv6 prefix */
 struct ospf6_link_lsa
 {
   u_char          priority;
@@ -131,6 +136,7 @@ struct ospf6_link_lsa
 };
 
 /* Intra-Area-Prefix-LSA */
+#define OSPF6_INTRA_PREFIX_LSA_MIN_SIZE       12U /* w/o 1st IPv6 prefix */
 struct ospf6_intra_prefix_lsa
 {
   u_int16_t prefix_num;

+ 1 - 0
ospf6d/ospf6_lsa.h

@@ -79,6 +79,7 @@
   (ntohs (type) & OSPF6_LSTYPE_SCOPE_MASK)
 
 /* LSA Header */
+#define OSPF6_LSA_HEADER_SIZE                 20U
 struct ospf6_lsa_header
 {
   u_int16_t age;        /* LS age */

+ 474 - 73
ospf6d/ospf6_message.c

@@ -39,6 +39,11 @@
 #include "ospf6_neighbor.h"
 #include "ospf6_interface.h"
 
+/* for structures and macros ospf6_lsa_examin() needs */
+#include "ospf6_abr.h"
+#include "ospf6_asbr.h"
+#include "ospf6_intra.h"
+
 #include "ospf6_flood.h"
 #include "ospf6d.h"
 
@@ -46,6 +51,34 @@ unsigned char conf_debug_ospf6_message[6] = {0x03, 0, 0, 0, 0, 0};
 const char *ospf6_message_type_str[] =
   { "Unknown", "Hello", "DbDesc", "LSReq", "LSUpdate", "LSAck" };
 
+/* Minimum (besides the standard OSPF packet header) lengths for OSPF
+   packets of particular types, offset is the "type" field. */
+const u_int16_t ospf6_packet_minlen[OSPF6_MESSAGE_TYPE_ALL] =
+{
+  0,
+  OSPF6_HELLO_MIN_SIZE,
+  OSPF6_DB_DESC_MIN_SIZE,
+  OSPF6_LS_REQ_MIN_SIZE,
+  OSPF6_LS_UPD_MIN_SIZE,
+  OSPF6_LS_ACK_MIN_SIZE
+};
+
+/* Minimum (besides the standard LSA header) lengths for LSAs of particular
+   types, offset is the "LSA function code" portion of "LSA type" field. */
+const u_int16_t ospf6_lsa_minlen[OSPF6_LSTYPE_SIZE] =
+{
+  0,
+  /* 0x2001 */ OSPF6_ROUTER_LSA_MIN_SIZE,
+  /* 0x2002 */ OSPF6_NETWORK_LSA_MIN_SIZE,
+  /* 0x2003 */ OSPF6_INTER_PREFIX_LSA_MIN_SIZE,
+  /* 0x2004 */ OSPF6_INTER_ROUTER_LSA_FIX_SIZE,
+  /* 0x4005 */ OSPF6_AS_EXTERNAL_LSA_MIN_SIZE,
+  /* 0x2006 */ 0,
+  /* 0x2007 */ OSPF6_AS_EXTERNAL_LSA_MIN_SIZE,
+  /* 0x0008 */ OSPF6_LINK_LSA_MIN_SIZE,
+  /* 0x2009 */ OSPF6_INTRA_PREFIX_LSA_MIN_SIZE
+};
+
 /* print functions */
 
 static void
@@ -224,52 +257,6 @@ ospf6_lsack_print (struct ospf6_header *oh)
     zlog_debug ("Trailing garbage exists");
 }
 
-/* Receive function */
-static int
-ospf6_header_examin (struct in6_addr *src, struct in6_addr *dst,
-                     struct ospf6_interface *oi, struct ospf6_header *oh)
-{
-  u_char type;
-  type = OSPF6_MESSAGE_TYPE_CANONICAL (oh->type);
-
-  /* version check */
-  if (oh->version != OSPFV3_VERSION)
-    {
-      if (IS_OSPF6_DEBUG_MESSAGE (type, RECV))
-        zlog_debug ("Message with unknown version");
-      return MSG_NG;
-    }
-
-  /* Area-ID check */
-  if (oh->area_id != oi->area->area_id)
-    {
-      if (oh->area_id == BACKBONE_AREA_ID)
-        {
-          if (IS_OSPF6_DEBUG_MESSAGE (type, RECV))
-            zlog_debug ("Message may be via Virtual Link: not supported");
-          return MSG_NG;
-        }
-
-      if (IS_OSPF6_DEBUG_MESSAGE (type, RECV))
-        zlog_debug ("Area-ID mismatch");
-      return MSG_NG;
-    }
-
-  /* Instance-ID check */
-  if (oh->instance_id != oi->instance_id)
-    {
-      if (IS_OSPF6_DEBUG_MESSAGE (type, RECV))
-        zlog_debug ("Instance-ID mismatch");
-      return MSG_NG;
-    }
-
-  /* Router-ID check */
-  if (oh->router_id == oi->area->ospf6->router_id)
-    zlog_warn ("Detect duplicate Router-ID");
-
-  return MSG_OK;
-}
-
 static void
 ospf6_hello_recv (struct in6_addr *src, struct in6_addr *dst,
                   struct ospf6_interface *oi, struct ospf6_header *oh)
@@ -281,9 +268,6 @@ ospf6_hello_recv (struct in6_addr *src, struct in6_addr *dst,
   int neighborchange = 0;
   int backupseen = 0;
 
-  if (ospf6_header_examin (src, dst, oi, oh) != MSG_OK)
-    return;
-
   hello = (struct ospf6_hello *)
     ((caddr_t) oh + sizeof (struct ospf6_header));
 
@@ -815,9 +799,6 @@ ospf6_dbdesc_recv (struct in6_addr *src, struct in6_addr *dst,
   struct ospf6_neighbor *on;
   struct ospf6_dbdesc *dbdesc;
 
-  if (ospf6_header_examin (src, dst, oi, oh) != MSG_OK)
-    return;
-
   on = ospf6_neighbor_lookup (oh->router_id, oi);
   if (on == NULL)
     {
@@ -867,9 +848,6 @@ ospf6_lsreq_recv (struct in6_addr *src, struct in6_addr *dst,
   struct ospf6_lsdb *lsdb = NULL;
   struct ospf6_lsa *lsa;
 
-  if (ospf6_header_examin (src, dst, oi, oh) != MSG_OK)
-    return;
-
   on = ospf6_neighbor_lookup (oh->router_id, oi);
   if (on == NULL)
     {
@@ -944,6 +922,433 @@ ospf6_lsreq_recv (struct in6_addr *src, struct in6_addr *dst,
     thread_add_event (master, ospf6_lsupdate_send_neighbor, on, 0);
 }
 
+/* Verify, that the specified memory area contains exactly N valid IPv6
+   prefixes as specified by RFC5340, A.4.1. */
+static unsigned
+ospf6_prefixes_examin
+(
+  struct ospf6_prefix *current, /* start of buffer    */
+  unsigned length,
+  const u_int32_t req_num_pfxs  /* always compared with the actual number of prefixes */
+)
+{
+  u_char requested_pfx_bytes;
+  u_int32_t real_num_pfxs = 0;
+
+  while (length)
+  {
+    if (length < OSPF6_PREFIX_MIN_SIZE)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: undersized IPv6 prefix header", __func__);
+      return MSG_NG;
+    }
+    /* safe to look deeper */
+    if (current->prefix_length > IPV6_MAX_BITLEN)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: invalid PrefixLength (%u bits)", __func__, current->prefix_length);
+      return MSG_NG;
+    }
+    /* covers both fixed- and variable-sized fields */
+    requested_pfx_bytes = OSPF6_PREFIX_MIN_SIZE + OSPF6_PREFIX_SPACE (current->prefix_length);
+    if (requested_pfx_bytes > length)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: undersized IPv6 prefix", __func__);
+      return MSG_NG;
+    }
+    /* next prefix */
+    length -= requested_pfx_bytes;
+    current = (struct ospf6_prefix *) ((caddr_t) current + requested_pfx_bytes);
+    real_num_pfxs++;
+  }
+  if (real_num_pfxs != req_num_pfxs)
+  {
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: IPv6 prefix number mismatch (%u required, %u real)",
+                  __func__, req_num_pfxs, real_num_pfxs);
+    return MSG_NG;
+  }
+  return MSG_OK;
+}
+
+/* Verify an LSA to have a valid length and dispatch further (where
+   appropriate) to check if the contents, including nested IPv6 prefixes,
+   is properly sized/aligned within the LSA. Note that this function gets
+   LSA type in network byte order, uses in host byte order and passes to
+   ospf6_lstype_name() in network byte order again. */
+static unsigned
+ospf6_lsa_examin (struct ospf6_lsa_header *lsah, const u_int16_t lsalen, const u_char headeronly)
+{
+  struct ospf6_intra_prefix_lsa *intra_prefix_lsa;
+  struct ospf6_as_external_lsa *as_external_lsa;
+  struct ospf6_link_lsa *link_lsa;
+  unsigned exp_length;
+  u_int8_t ltindex;
+  u_int16_t lsatype;
+
+  /* In case an additional minimum length constraint is defined for current
+     LSA type, make sure that this constraint is met. */
+  lsatype = ntohs (lsah->type);
+  ltindex = lsatype & OSPF6_LSTYPE_FCODE_MASK;
+  if
+  (
+    ltindex < OSPF6_LSTYPE_SIZE &&
+    ospf6_lsa_minlen[ltindex] &&
+    lsalen < ospf6_lsa_minlen[ltindex] + OSPF6_LSA_HEADER_SIZE
+  )
+  {
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: undersized (%u B) LSA", __func__, lsalen);
+    return MSG_NG;
+  }
+  switch (lsatype)
+  {
+  case OSPF6_LSTYPE_ROUTER:
+    /* RFC5340 A.4.3, LSA header + OSPF6_ROUTER_LSA_MIN_SIZE bytes followed
+       by N>=0 interface descriptions. */
+    if ((lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_ROUTER_LSA_MIN_SIZE) % OSPF6_ROUTER_LSDESC_FIX_SIZE)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: interface description alignment error", __func__);
+      return MSG_NG;
+    }
+    break;
+  case OSPF6_LSTYPE_NETWORK:
+    /* RFC5340 A.4.4, LSA header + OSPF6_NETWORK_LSA_MIN_SIZE bytes
+       followed by N>=0 attached router descriptions. */
+    if ((lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_NETWORK_LSA_MIN_SIZE) % OSPF6_NETWORK_LSDESC_FIX_SIZE)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: router description alignment error", __func__);
+      return MSG_NG;
+    }
+    break;
+  case OSPF6_LSTYPE_INTER_PREFIX:
+    /* RFC5340 A.4.5, LSA header + OSPF6_INTER_PREFIX_LSA_MIN_SIZE bytes
+       followed by 3-4 fields of a single IPv6 prefix. */
+    if (headeronly)
+      break;
+    return ospf6_prefixes_examin
+    (
+      (struct ospf6_prefix *) ((caddr_t) lsah + OSPF6_LSA_HEADER_SIZE + OSPF6_INTER_PREFIX_LSA_MIN_SIZE),
+      lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_INTER_PREFIX_LSA_MIN_SIZE,
+      1
+    );
+  case OSPF6_LSTYPE_INTER_ROUTER:
+    /* RFC5340 A.4.6, fixed-size LSA. */
+    if (lsalen > OSPF6_LSA_HEADER_SIZE + OSPF6_INTER_ROUTER_LSA_FIX_SIZE)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: oversized (%u B) LSA", __func__, lsalen);
+      return MSG_NG;
+    }
+    break;
+  case OSPF6_LSTYPE_AS_EXTERNAL: /* RFC5340 A.4.7, same as A.4.8. */
+  case OSPF6_LSTYPE_TYPE_7:
+    /* RFC5340 A.4.8, LSA header + OSPF6_AS_EXTERNAL_LSA_MIN_SIZE bytes
+       followed by 3-4 fields of IPv6 prefix and 3 conditional LSA fields:
+       16 bytes of forwarding address, 4 bytes of external route tag,
+       4 bytes of referenced link state ID. */
+    if (headeronly)
+      break;
+    as_external_lsa = (struct ospf6_as_external_lsa *) ((caddr_t) lsah + OSPF6_LSA_HEADER_SIZE);
+    exp_length = OSPF6_LSA_HEADER_SIZE + OSPF6_AS_EXTERNAL_LSA_MIN_SIZE;
+    /* To find out if the last optional field (Referenced Link State ID) is
+       assumed in this LSA, we need to access fixed fields of the IPv6
+       prefix before ospf6_prefix_examin() confirms its sizing. */
+    if (exp_length + OSPF6_PREFIX_MIN_SIZE > lsalen)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: undersized (%u B) LSA header", __func__, lsalen);
+      return MSG_NG;
+    }
+    /* forwarding address */
+    if (CHECK_FLAG (as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F))
+      exp_length += 16;
+    /* external route tag */
+    if (CHECK_FLAG (as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T))
+      exp_length += 4;
+    /* referenced link state ID */
+    if (as_external_lsa->prefix.u._prefix_referenced_lstype)
+      exp_length += 4;
+    /* All the fixed-size fields (mandatory and optional) must fit. I.e.,
+       this check does not include any IPv6 prefix fields. */
+    if (exp_length > lsalen)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: undersized (%u B) LSA header", __func__, lsalen);
+      return MSG_NG;
+    }
+    /* The last call completely covers the remainder (IPv6 prefix). */
+    return ospf6_prefixes_examin
+    (
+      (struct ospf6_prefix *) ((caddr_t) as_external_lsa + OSPF6_AS_EXTERNAL_LSA_MIN_SIZE),
+      lsalen - exp_length,
+      1
+    );
+  case OSPF6_LSTYPE_LINK:
+    /* RFC5340 A.4.9, LSA header + OSPF6_LINK_LSA_MIN_SIZE bytes followed
+       by N>=0 IPv6 prefix blocks (with N declared beforehand). */
+    if (headeronly)
+      break;
+    link_lsa = (struct ospf6_link_lsa *) ((caddr_t) lsah + OSPF6_LSA_HEADER_SIZE);
+    return ospf6_prefixes_examin
+    (
+      (struct ospf6_prefix *) ((caddr_t) link_lsa + OSPF6_LINK_LSA_MIN_SIZE),
+      lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_LINK_LSA_MIN_SIZE,
+      ntohl (link_lsa->prefix_num) /* 32 bits */
+    );
+  case OSPF6_LSTYPE_INTRA_PREFIX:
+  /* RFC5340 A.4.10, LSA header + OSPF6_INTRA_PREFIX_LSA_MIN_SIZE bytes
+     followed by N>=0 IPv6 prefixes (with N declared beforehand). */
+    if (headeronly)
+      break;
+    intra_prefix_lsa = (struct ospf6_intra_prefix_lsa *) ((caddr_t) lsah + OSPF6_LSA_HEADER_SIZE);
+    return ospf6_prefixes_examin
+    (
+      (struct ospf6_prefix *) ((caddr_t) intra_prefix_lsa + OSPF6_INTRA_PREFIX_LSA_MIN_SIZE),
+      lsalen - OSPF6_LSA_HEADER_SIZE - OSPF6_INTRA_PREFIX_LSA_MIN_SIZE,
+      ntohs (intra_prefix_lsa->prefix_num) /* 16 bits */
+    );
+  }
+  /* No additional validation is possible for unknown LSA types, which are
+     themselves valid in OPSFv3, hence the default decision is to accept. */
+  return MSG_OK;
+}
+
+/* Verify if the provided input buffer is a valid sequence of LSAs. This
+   includes verification of LSA blocks length/alignment and dispatching
+   of deeper-level checks. */
+static unsigned
+ospf6_lsaseq_examin
+(
+  struct ospf6_lsa_header *lsah, /* start of buffered data */
+  size_t length,
+  const u_char headeronly,
+  /* When declared_num_lsas is not 0, compare it to the real number of LSAs
+     and treat the difference as an error. */
+  const u_int32_t declared_num_lsas
+)
+{
+  u_int32_t counted_lsas = 0;
+
+  while (length)
+  {
+    u_int16_t lsalen;
+    if (length < OSPF6_LSA_HEADER_SIZE)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: undersized (%u B) trailing (#%u) LSA header",
+                    __func__, length, counted_lsas);
+      return MSG_NG;
+    }
+    /* save on ntohs() calls here and in the LSA validator */
+    lsalen = OSPF6_LSA_SIZE (lsah);
+    if (lsalen < OSPF6_LSA_HEADER_SIZE)
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: malformed LSA header #%u, declared length is %u B",
+                    __func__, counted_lsas, lsalen);
+      return MSG_NG;
+    }
+    if (headeronly)
+    {
+      /* less checks here and in ospf6_lsa_examin() */
+      if (MSG_OK != ospf6_lsa_examin (lsah, lsalen, 1))
+      {
+        if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+          zlog_debug ("%s: anomaly in header-only %s LSA #%u", __func__,
+                      ospf6_lstype_name (lsah->type), counted_lsas);
+        return MSG_NG;
+      }
+      lsah = (struct ospf6_lsa_header *) ((caddr_t) lsah + OSPF6_LSA_HEADER_SIZE);
+      length -= OSPF6_LSA_HEADER_SIZE;
+    }
+    else
+    {
+      /* make sure the input buffer is deep enough before further checks */
+      if (lsalen > length)
+      {
+        if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+          zlog_debug ("%s: anomaly in %s LSA #%u: declared length is %u B, buffered length is %u B",
+                      __func__, ospf6_lstype_name (lsah->type), counted_lsas, lsalen, length);
+        return MSG_NG;
+      }
+      if (MSG_OK != ospf6_lsa_examin (lsah, lsalen, 0))
+      {
+        if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+          zlog_debug ("%s: anomaly in %s LSA #%u", __func__,
+                      ospf6_lstype_name (lsah->type), counted_lsas);
+        return MSG_NG;
+      }
+      lsah = (struct ospf6_lsa_header *) ((caddr_t) lsah + lsalen);
+      length -= lsalen;
+    }
+    counted_lsas++;
+  }
+
+  if (declared_num_lsas && counted_lsas != declared_num_lsas)
+  {
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: #LSAs declared (%u) does not match actual (%u)",
+                  __func__, declared_num_lsas, counted_lsas);
+    return MSG_NG;
+  }
+  return MSG_OK;
+}
+
+/* Verify a complete OSPF packet for proper sizing/alignment. */
+static unsigned
+ospf6_packet_examin (struct ospf6_header *oh, const unsigned bytesonwire)
+{
+  struct ospf6_lsupdate *lsupd;
+  unsigned test;
+
+  /* length, 1st approximation */
+  if (bytesonwire < OSPF6_HEADER_SIZE)
+  {
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: undersized (%u B) packet", __func__, bytesonwire);
+    return MSG_NG;
+  }
+  /* Now it is safe to access header fields. */
+  if (bytesonwire != ntohs (oh->length))
+  {
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: packet length error (%u real, %u declared)",
+                  __func__, bytesonwire, ntohs (oh->length));
+    return MSG_NG;
+  }
+  /* version check */
+  if (oh->version != OSPFV3_VERSION)
+  {
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: invalid (%u) protocol version", __func__, oh->version);
+    return MSG_NG;
+  }
+  /* length, 2nd approximation */
+  if
+  (
+    oh->type < OSPF6_MESSAGE_TYPE_ALL &&
+    ospf6_packet_minlen[oh->type] &&
+    bytesonwire < OSPF6_HEADER_SIZE + ospf6_packet_minlen[oh->type]
+  )
+  {
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: undersized (%u B) %s packet", __func__,
+                  bytesonwire, ospf6_message_type_str[oh->type]);
+    return MSG_NG;
+  }
+  /* type-specific deeper validation */
+  switch (oh->type)
+  {
+  case OSPF6_MESSAGE_TYPE_HELLO:
+    /* RFC5340 A.3.2, packet header + OSPF6_HELLO_MIN_SIZE bytes followed
+       by N>=0 router-IDs. */
+    if (0 == (bytesonwire - OSPF6_HEADER_SIZE - OSPF6_HELLO_MIN_SIZE) % 4)
+      return MSG_OK;
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: alignment error in %s packet",
+                  __func__, ospf6_message_type_str[oh->type]);
+    return MSG_NG;
+  case OSPF6_MESSAGE_TYPE_DBDESC:
+    /* RFC5340 A.3.3, packet header + OSPF6_DB_DESC_MIN_SIZE bytes followed
+       by N>=0 header-only LSAs. */
+    test = ospf6_lsaseq_examin
+    (
+      (struct ospf6_lsa_header *) ((caddr_t) oh + OSPF6_HEADER_SIZE + OSPF6_DB_DESC_MIN_SIZE),
+      bytesonwire - OSPF6_HEADER_SIZE - OSPF6_DB_DESC_MIN_SIZE,
+      1,
+      0
+    );
+    break;
+  case OSPF6_MESSAGE_TYPE_LSREQ:
+    /* RFC5340 A.3.4, packet header + N>=0 LS description blocks. */
+    if (0 == (bytesonwire - OSPF6_HEADER_SIZE - OSPF6_LS_REQ_MIN_SIZE) % OSPF6_LSREQ_LSDESC_FIX_SIZE)
+      return MSG_OK;
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: alignment error in %s packet",
+                  __func__, ospf6_message_type_str[oh->type]);
+    return MSG_NG;
+  case OSPF6_MESSAGE_TYPE_LSUPDATE:
+    /* RFC5340 A.3.5, packet header + OSPF6_LS_UPD_MIN_SIZE bytes followed
+       by N>=0 full LSAs (with N declared beforehand). */
+    lsupd = (struct ospf6_lsupdate *) ((caddr_t) oh + OSPF6_HEADER_SIZE);
+    test = ospf6_lsaseq_examin
+    (
+      (struct ospf6_lsa_header *) ((caddr_t) lsupd + OSPF6_LS_UPD_MIN_SIZE),
+      bytesonwire - OSPF6_HEADER_SIZE - OSPF6_LS_UPD_MIN_SIZE,
+      0,
+      ntohl (lsupd->lsa_number) /* 32 bits */
+    );
+    break;
+  case OSPF6_MESSAGE_TYPE_LSACK:
+    /* RFC5340 A.3.6, packet header + N>=0 header-only LSAs. */
+    test = ospf6_lsaseq_examin
+    (
+      (struct ospf6_lsa_header *) ((caddr_t) oh + OSPF6_HEADER_SIZE + OSPF6_LS_ACK_MIN_SIZE),
+      bytesonwire - OSPF6_HEADER_SIZE - OSPF6_LS_ACK_MIN_SIZE,
+      1,
+      0
+    );
+    break;
+  default:
+    if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+      zlog_debug ("%s: invalid (%u) message type", __func__, oh->type);
+    return MSG_NG;
+  }
+  if (test != MSG_OK && IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+    zlog_debug ("%s: anomaly in %s packet", __func__, ospf6_message_type_str[oh->type]);
+  return test;
+}
+
+/* Verify particular fields of otherwise correct received OSPF packet to
+   meet the requirements of RFC. */
+static int
+ospf6_rxpacket_examin (struct ospf6_interface *oi, struct ospf6_header *oh, const unsigned bytesonwire)
+{
+  char buf[2][INET_ADDRSTRLEN];
+
+  if (MSG_OK != ospf6_packet_examin (oh, bytesonwire))
+    return MSG_NG;
+
+  /* Area-ID check */
+  if (oh->area_id != oi->area->area_id)
+  {
+    if (IS_OSPF6_DEBUG_MESSAGE (oh->type, RECV))
+    {
+      if (oh->area_id == BACKBONE_AREA_ID)
+        zlog_debug ("%s: Message may be via Virtual Link: not supported", __func__);
+      else
+        zlog_debug
+        (
+          "%s: Area-ID mismatch (my %s, rcvd %s)", __func__,
+          inet_ntop (AF_INET, &oi->area->area_id, buf[0], INET_ADDRSTRLEN),
+          inet_ntop (AF_INET, &oh->area_id, buf[1], INET_ADDRSTRLEN)
+         );
+    }
+    return MSG_NG;
+  }
+
+  /* Instance-ID check */
+  if (oh->instance_id != oi->instance_id)
+  {
+    if (IS_OSPF6_DEBUG_MESSAGE (oh->type, RECV))
+      zlog_debug ("%s: Instance-ID mismatch (my %u, rcvd %u)", __func__, oi->instance_id, oh->instance_id);
+    return MSG_NG;
+  }
+
+  /* Router-ID check */
+  if (oh->router_id == oi->area->ospf6->router_id)
+  {
+    zlog_warn ("%s: Duplicate Router-ID (%s)", __func__, inet_ntop (AF_INET, &oh->router_id, buf[0], INET_ADDRSTRLEN));
+    return MSG_NG;
+  }
+  return MSG_OK;
+}
+
 static void
 ospf6_lsupdate_recv (struct in6_addr *src, struct in6_addr *dst,
                      struct ospf6_interface *oi, struct ospf6_header *oh)
@@ -953,9 +1358,6 @@ ospf6_lsupdate_recv (struct in6_addr *src, struct in6_addr *dst,
   unsigned long num;
   char *p;
 
-  if (ospf6_header_examin (src, dst, oi, oh) != MSG_OK)
-    return;
-
   on = ospf6_neighbor_lookup (oh->router_id, oi);
   if (on == NULL)
     {
@@ -1033,8 +1435,6 @@ ospf6_lsack_recv (struct in6_addr *src, struct in6_addr *dst,
   struct ospf6_lsdb *lsdb = NULL;
 
   assert (oh->type == OSPF6_MESSAGE_TYPE_LSACK);
-  if (ospf6_header_examin (src, dst, oi, oh) != MSG_OK)
-    return;
 
   on = ospf6_neighbor_lookup (oh->router_id, oi);
   if (on == NULL)
@@ -1217,11 +1617,6 @@ ospf6_receive (struct thread *thread)
       zlog_err ("Excess message read");
       return 0;
     }
-  else if (len < sizeof (struct ospf6_header))
-    {
-      zlog_err ("Deficient message read");
-      return 0;
-    }
 
   oi = ospf6_interface_lookup_by_ifindex (ifindex);
   if (oi == NULL || oi->area == NULL)
@@ -1229,8 +1624,22 @@ ospf6_receive (struct thread *thread)
       zlog_debug ("Message received on disabled interface");
       return 0;
     }
+  if (CHECK_FLAG (oi->flag, OSPF6_INTERFACE_PASSIVE))
+    {
+      if (IS_OSPF6_DEBUG_MESSAGE (OSPF6_MESSAGE_TYPE_UNKNOWN, RECV))
+        zlog_debug ("%s: Ignore message on passive interface %s",
+                    __func__, oi->interface->name);
+      return 0;
+    }
 
   oh = (struct ospf6_header *) recvbuf;
+  if (ospf6_rxpacket_examin (oi, oh, len) != MSG_OK)
+    return 0;
+
+  /* Being here means, that no sizing/alignment issues were detected in
+     the input packet. This renders the additional checks performed below
+     and also in the type-specific dispatching functions a dead code,
+     which can be dismissed in a cleanup-focused review round later. */
 
   /* Log */
   if (IS_OSPF6_DEBUG_MESSAGE (oh->type, RECV))
@@ -1267,14 +1676,6 @@ ospf6_receive (struct thread *thread)
         }
     }
 
-  if (CHECK_FLAG (oi->flag, OSPF6_INTERFACE_PASSIVE))
-    {
-      if (IS_OSPF6_DEBUG_MESSAGE (oh->type, RECV))
-        zlog_debug ("Ignore message on passive interface %s",
-                   oi->interface->name);
-      return 0;
-    }
-
   switch (oh->type)
     {
       case OSPF6_MESSAGE_TYPE_HELLO:

+ 7 - 0
ospf6d/ospf6_message.h

@@ -52,6 +52,7 @@ extern const char *ospf6_message_type_str[];
   (ospf6_message_type_str[ OSPF6_MESSAGE_TYPE_CANONICAL (T) ])
 
 /* OSPFv3 packet header */
+#define OSPF6_HEADER_SIZE                     16U
 struct ospf6_header
 {
   u_char    version;
@@ -67,6 +68,7 @@ struct ospf6_header
 #define OSPF6_MESSAGE_END(H) ((caddr_t) (H) + ntohs ((H)->length))
 
 /* Hello */
+#define OSPF6_HELLO_MIN_SIZE                  20U
 struct ospf6_hello
 {
   u_int32_t interface_id;
@@ -80,6 +82,7 @@ struct ospf6_hello
 };
 
 /* Database Description */
+#define OSPF6_DB_DESC_MIN_SIZE                12U
 struct ospf6_dbdesc
 {
   u_char    reserved1;
@@ -96,7 +99,9 @@ struct ospf6_dbdesc
 #define OSPF6_DBDESC_IBIT  (0x04) /* initial bit */
 
 /* Link State Request */
+#define OSPF6_LS_REQ_MIN_SIZE                  0U
 /* It is just a sequence of entries below */
+#define OSPF6_LSREQ_LSDESC_FIX_SIZE           12U
 struct ospf6_lsreq_entry
 {
   u_int16_t reserved;     /* Must Be Zero */
@@ -106,6 +111,7 @@ struct ospf6_lsreq_entry
 };
 
 /* Link State Update */
+#define OSPF6_LS_UPD_MIN_SIZE                  4U
 struct ospf6_lsupdate
 {
   u_int32_t lsa_number;
@@ -113,6 +119,7 @@ struct ospf6_lsupdate
 };
 
 /* Link State Acknowledgement */
+#define OSPF6_LS_ACK_MIN_SIZE                  0U
 /* It is just a sequence of LSA Headers */
 
 /* Function definition */

+ 1 - 0
ospf6d/ospf6_proto.h

@@ -73,6 +73,7 @@
 #define OSPF6_OPT_V6 (1 << 0)   /* IPv6 forwarding Capability */
 
 /* OSPF6 Prefix */
+#define OSPF6_PREFIX_MIN_SIZE                  4U /* .length == 0 */
 struct ospf6_prefix
 {
   u_int8_t prefix_length;