Parcourir la source

[master] Finished merge of trac3618 (truncated packet/option/vendor-option)

Francis Dupont il y a 9 ans
Parent
commit
f4066793c5

+ 6 - 0
ChangeLog

@@ -1,3 +1,9 @@
+962.	[func]		fdupont
+	Make the parsing of options and vendor options more consistent
+	between v4 and v6. In addition make the parsing more robust
+	against malformed packets.
+	(Trac #3618, git xxx)
+
 961.	[func]		fdupont
 	Improved error messages when handling invalid or malformed
 	configuration file. File and line number are printed, when

+ 144 - 114
src/lib/dhcp/libdhcp++.cc

@@ -249,6 +249,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
                                size_t* relay_msg_len /* = 0 */) {
     size_t offset = 0;
     size_t length = buf.size();
+    size_t last_offset = 0;
 
     // Get the list of standard option definitions.
     OptionDefContainer option_defs;
@@ -265,7 +266,17 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
 
     // The buffer being read comprises a set of options, each starting with
     // a two-byte type code and a two-byte length field.
-    while (offset + 4 <= length) {
+    while (offset < length) {
+        // Save the current offset for backtracking
+        last_offset = offset;
+
+        // Check if there is room for another option
+        if (offset + 4 > length) {
+            // Still something but smaller than an option
+            return (last_offset);
+        }
+
+        // Parse the option header
         uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
         offset += 2;
 
@@ -273,13 +284,15 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
         offset += 2;
 
         if (offset + opt_len > length) {
-            // @todo: consider throwing exception here.
-
-            // We peeked at the option header of the next option, but discovered
-            // that it would end up beyond buffer end, so the option is
-            // truncated. Hence we can't parse it. Therefore we revert
-            // back by those four bytes (as if we never parsed them).
-            return (offset - 4);
+            // We peeked at the option header of the next option, but
+            // discovered that it would end up beyond buffer end, so
+            // the option is truncated. Hence we can't parse
+            // it. Therefore we revert back by those bytes (as if
+            // we never parsed them).
+            //
+            // @note it is the responsibility of the caller to throw
+            // an exception on partial parsing
+            return (last_offset);
         }
 
         if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
@@ -294,10 +307,10 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
 
         if (opt_type == D6O_VENDOR_OPTS) {
             if (offset + 4 > length) {
-                // Truncated vendor-option. There is expected at least 4 bytes
-                // long enterprise-id field. Let's roll back option code + option
-                // length (4 bytes) and return.
-                return (offset - 4);
+                // Truncated vendor-option. We expect at least
+                // 4 bytes for the enterprise-id field. Let's roll back
+                // option code + option length (4 bytes) and return.
+                return (last_offset);
             }
 
             // Parse this as vendor option
@@ -309,11 +322,11 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
             continue;
         }
 
-
-        // Get all definitions with the particular option code. Note that option
-        // code is non-unique within this container however at this point we
-        // expect to get one option definition with the particular code. If more
-        // are returned we report an error.
+        // Get all definitions with the particular option code. Note
+        // that option code is non-unique within this container
+        // however at this point we expect to get one option
+        // definition with the particular code. If more are returned
+        // we report an error.
         const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
         // Get the number of returned option definitions for the option code.
         size_t num_defs = distance(range.first, range.second);
@@ -321,16 +334,18 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
         OptionPtr opt;
         if (num_defs > 1) {
             // Multiple options of the same code are not supported right now!
-            isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
-                      " for option type " << opt_type << " returned. Currently it is not"
-                      " supported to initialize multiple option definitions"
-                      " for the same option code. This will be supported once"
-                      " support for option spaces is implemented");
+            isc_throw(isc::Unexpected, "Internal error: multiple option"
+                      " definitions for option type " << opt_type <<
+                      " returned. Currently it is not supported to initialize"
+                      " multiple option definitions for the same option code."
+                      " This will be supported once support for option spaces"
+                      " is implemented");
         } else if (num_defs == 0) {
-            // @todo Don't crash if definition does not exist because only a few
-            // option definitions are initialized right now. In the future
-            // we will initialize definitions for all options and we will
-            // remove this elseif. For now, return generic option.
+            // @todo Don't crash if definition does not exist because
+            // only a few option definitions are initialized right
+            // now. In the future we will initialize definitions for
+            // all options and we will remove this elseif. For now,
+            // return generic option.
             opt = OptionPtr(new Option(Option::V6, opt_type,
                                        buf.begin() + offset,
                                        buf.begin() + offset + opt_len));
@@ -355,8 +370,9 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
                                const std::string& option_space,
                                isc::dhcp::OptionCollection& options) {
     size_t offset = 0;
+    size_t last_offset = 0;
 
-    // Get the list of stdandard option definitions.
+    // Get the list of standard option definitions.
     OptionDefContainer option_defs;
     if (option_space == "dhcp4") {
         option_defs = LibDHCP::getOptionDefs(Option::V4);
@@ -371,12 +387,20 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
 
     // The buffer being read comprises a set of options, each starting with
     // a one-byte type code and a one-byte length field.
-    while (offset + 1 <= buf.size()) {
+    while (offset < buf.size()) {
+        // Save the current offset for backtracking
+        last_offset = offset;
+
+        // Get the option type
         uint8_t opt_type = buf[offset++];
 
         // DHO_END is a special, one octet long option
-        if (opt_type == DHO_END)
-            return (offset); // just return. Don't need to add DHO_END option
+        if (opt_type == DHO_END) {
+            // just return. Don't need to add DHO_END option
+            // Don't return offset because it makes this condition
+            // and partial parsing impossible to recognize.
+            return (last_offset);
+        }
 
         // DHO_PAD is just a padding after DHO_END. Let's continue parsing
         // in case we receive a message without DHO_END.
@@ -384,30 +408,30 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
             continue;
 
         if (offset + 1 > buf.size()) {
-            // opt_type must be cast to integer so as it is not treated as
-            // unsigned char value (a number is presented in error message).
-            isc_throw(OutOfRange, "Attempt to parse truncated option "
-                      << static_cast<int>(opt_type));
+            // We peeked at the option header of the next option, but
+            // discovered that it would end up beyond buffer end, so
+            // the option is truncated. Hence we can't parse
+            // it. Therefore we revert back (as if we never parsed it).
+            //
+            // @note it is the responsibility of the caller to throw
+            // an exception on partial parsing
+            return (last_offset);
         }
 
         uint8_t opt_len =  buf[offset++];
         if (offset + opt_len > buf.size()) {
-
-            // We peeked at the option header of the next option, but discovered
-            // that it would end up beyond buffer end, so the option is
-            // truncated. Hence we can't parse it. Therefore we revert
-            // back by two bytes (as if we never parsed them).
-            return (offset - 2);
-
-            // isc_throw(OutOfRange, "Option parse failed. Tried to parse "
-            //          << offset + opt_len << " bytes from " << buf.size()
-            //          << "-byte long buffer.");
+            // We peeked at the option header of the next option, but
+            // discovered that it would end up beyond buffer end, so
+            // the option is truncated. Hence we can't parse
+            // it. Therefore we revert back (as if we never parsed it).
+            return (last_offset);
         }
 
-        // Get all definitions with the particular option code. Note that option code
-        // is non-unique within this container however at this point we expect
-        // to get one option definition with the particular code. If more are
-        // returned we report an error.
+        // Get all definitions with the particular option code. Note
+        // that option code is non-unique within this container
+        // however at this point we expect to get one option
+        // definition with the particular code. If more are returned
+        // we report an error.
         const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
         // Get the number of returned option definitions for the option code.
         size_t num_defs = distance(range.first, range.second);
@@ -415,12 +439,13 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
         OptionPtr opt;
         if (num_defs > 1) {
             // Multiple options of the same code are not supported right now!
-            isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
-                      " for option type " << static_cast<int>(opt_type)
-                      << " returned. Currently it is not supported to initialize"
-                      << " multiple option definitions for the same option code."
-                      << " This will be supported once support for option spaces"
-                      << " is implemented");
+            isc_throw(isc::Unexpected, "Internal error: multiple option"
+                      " definitions for option type " <<
+                      static_cast<int>(opt_type) <<
+                      " returned. Currently it is not supported to initialize"
+                      " multiple option definitions for the same option code."
+                      " This will be supported once support for option spaces"
+                      " is implemented");
         } else if (num_defs == 0) {
             opt = OptionPtr(new Option(Option::V4, opt_type,
                                        buf.begin() + offset,
@@ -448,7 +473,8 @@ size_t LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
     size_t length = buf.size();
 
     // Get the list of option definitions for this particular vendor-id
-    const OptionDefContainer* option_defs = LibDHCP::getVendorOption6Defs(vendor_id);
+    const OptionDefContainer* option_defs =
+        LibDHCP::getVendorOption6Defs(vendor_id);
 
     // Get the search index #1. It allows to search for option definitions
     // using option code. If there's no such vendor-id space, we're out of luck
@@ -460,7 +486,12 @@ size_t LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
 
     // The buffer being read comprises a set of options, each starting with
     // a two-byte type code and a two-byte length field.
-    while (offset + 4 <= length) {
+    while (offset < length) {
+        if (offset + 4 > length) {
+            isc_throw(OutOfRange,
+                      "Vendor option parse failed: truncated header");
+        }
+
         uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
         offset += 2;
 
@@ -468,13 +499,9 @@ size_t LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
         offset += 2;
 
         if (offset + opt_len > length) {
-            // @todo: consider throwing exception here.
-
-            // We peeked at the option header of the next option, but discovered
-            // that it would end up beyond buffer end, so the option is
-            // truncated. Hence we can't parse it. Therefore we revert
-            // back by those four bytes (as if we never parsed them).
-            return (offset - 4);
+            isc_throw(OutOfRange, "Vendor option parse failed. Tried to parse "
+                          << offset + opt_len << " bytes from " << length
+                          << "-byte long buffer.");
         }
 
         OptionPtr opt;
@@ -482,20 +509,25 @@ size_t LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
 
         // If there is a definition for such a vendor option...
         if (idx) {
-            // Get all definitions with the particular option code. Note that option
-            // code is non-unique within this container however at this point we
-            // expect to get one option definition with the particular code. If more
-            // are returned we report an error.
-            const OptionDefContainerTypeRange& range = idx->equal_range(opt_type);
-            // Get the number of returned option definitions for the option code.
+            // Get all definitions with the particular option
+            // code. Note that option code is non-unique within this
+            // container however at this point we expect to get one
+            // option definition with the particular code. If more are
+            // returned we report an error.
+            const OptionDefContainerTypeRange& range =
+                idx->equal_range(opt_type);
+            // Get the number of returned option definitions for the
+            // option code.
             size_t num_defs = distance(range.first, range.second);
 
             if (num_defs > 1) {
-                // Multiple options of the same code are not supported right now!
-                isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
-                          " for option type " << opt_type << " returned. Currently it is not"
-                          " supported to initialize multiple option definitions"
-                          " for the same option code. This will be supported once"
+                // Multiple options of the same code are not supported
+                // right now!
+                isc_throw(isc::Unexpected, "Internal error: multiple option"
+                          " definitions for option type " << opt_type <<
+                          " returned. Currently it is not supported to"
+                          " initialize multiple option definitions for the"
+                          " same option code. This will be supported once"
                           " support for option spaces is implemented");
             } else if (num_defs == 1) {
                 // The option definition has been found. Use it to create
@@ -510,7 +542,9 @@ size_t LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
 
         // This can happen in one of 2 cases:
         // 1. we do not have definitions for that vendor-space
-        // 2. we do have definitions, but that particular option was not defined
+        // 2. we do have definitions, but that particular option was
+        //    not defined
+
         if (!opt) {
             opt = OptionPtr(new Option(Option::V6, opt_type,
                                        buf.begin() + offset,
@@ -532,7 +566,8 @@ size_t LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffe
     size_t offset = 0;
 
     // Get the list of stdandard option definitions.
-    const OptionDefContainer* option_defs = LibDHCP::getVendorOption4Defs(vendor_id);
+    const OptionDefContainer* option_defs =
+        LibDHCP::getVendorOption4Defs(vendor_id);
     // Get the search index #1. It allows to search for option definitions
     // using option code.
     const OptionDefContainerTypeIndex* idx = NULL;
@@ -542,47 +577,36 @@ size_t LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffe
 
     // The buffer being read comprises a set of options, each starting with
     // a one-byte type code and a one-byte length field.
-    while (offset + 1 <= buf.size()) {
-
-        // Note that Vendor-Specific info option (RFC3925) has a different option
-        // format than Vendor-Spec info for DHCPv6. (there's additional layer of
-        // data-length
+    while (offset < buf.size()) {
+        // Note that Vendor-Specific info option (RFC3925) has a
+        // different option format than Vendor-Spec info for
+        // DHCPv6. (there's additional layer of data-length)
         uint8_t data_len = buf[offset++];
 
         if (offset + data_len > buf.size()) {
             // The option is truncated.
-
-            // We peeked at the data_len, but discovered that it would end up
-            // beyond buffer end, so the data block is truncated. Hence we can't
-            // parse it. Therefore we revert back by one byte (as if we never
-            // parsed it).
-            return (offset - 1);
+            isc_throw(OutOfRange, "Attempt to parse truncated vendor option");
         }
 
         uint8_t offset_end = offset + data_len;
 
         // beginning of data-chunk parser
-        while (offset + 1 <= offset_end) {
+        while (offset < offset_end) {
             uint8_t opt_type = buf[offset++];
 
-            // DHO_END is a special, one octet long option
-            if (opt_type == DHO_END)
-                return (offset); // just return. Don't need to add DHO_END option
-
-            // DHO_PAD is just a padding after DHO_END. Let's continue parsing
-            // in case we receive a message without DHO_END.
-            if (opt_type == DHO_PAD)
-                continue;
+            // No DHO_END or DHO_PAD in vendor options
 
-            if (offset + 1 > buf.size()) {
-                // opt_type must be cast to integer so as it is not treated as
-                // unsigned char value (a number is presented in error message).
-                isc_throw(OutOfRange, "Attempt to parse truncated vendor option "
+            if (offset + 1 > offset_end) {
+                // opt_type must be cast to integer so as it is not
+                // treated as unsigned char value (a number is
+                // presented in error message).
+                isc_throw(OutOfRange,
+                          "Attempt to parse truncated vendor option "
                           << static_cast<int>(opt_type));
             }
 
             uint8_t opt_len =  buf[offset++];
-            if (offset + opt_len > buf.size()) {
+            if (offset + opt_len > offset_end) {
                 isc_throw(OutOfRange, "Option parse failed. Tried to parse "
                           << offset + opt_len << " bytes from " << buf.size()
                           << "-byte long buffer.");
@@ -592,22 +616,27 @@ size_t LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffe
             opt.reset();
 
             if (idx) {
-                // Get all definitions with the particular option code. Note that option code
-                // is non-unique within this container however at this point we expect
-                // to get one option definition with the particular code. If more are
-                // returned we report an error.
-                const OptionDefContainerTypeRange& range = idx->equal_range(opt_type);
-                // Get the number of returned option definitions for the option code.
+                // Get all definitions with the particular option
+                // code. Note that option code is non-unique within
+                // this container however at this point we expect to
+                // get one option definition with the particular
+                // code. If more are returned we report an error.
+                const OptionDefContainerTypeRange& range =
+                    idx->equal_range(opt_type);
+                // Get the number of returned option definitions for
+                // the option code.
                 size_t num_defs = distance(range.first, range.second);
 
                 if (num_defs > 1) {
-                    // Multiple options of the same code are not supported right now!
-                    isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
-                              " for option type " << static_cast<int>(opt_type)
-                              << " returned. Currently it is not supported to initialize"
-                              << " multiple option definitions for the same option code."
-                              << " This will be supported once support for option spaces"
-                              << " is implemented");
+                    // Multiple options of the same code are not
+                    // supported right now!
+                    isc_throw(isc::Unexpected, "Internal error: multiple"
+                              " option definitions for option type "
+                              << opt_type << " returned. Currently it is"
+                              " not supported to initialize multiple option"
+                              " definitions for the same option code."
+                              " This will be supported once support for"
+                              " option spaces is implemented");
                 } else if (num_defs == 1) {
                     // The option definition has been found. Use it to create
                     // the option instance from the provided buffer chunk.
@@ -630,6 +659,7 @@ size_t LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffe
 
         } // end of data-chunk
 
+        break; // end of the vendor block.
     }
     return (offset);
 }

+ 52 - 32
src/lib/dhcp/libdhcp++.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -137,48 +137,60 @@ public:
     static void packOptions(isc::util::OutputBuffer& buf,
                             const isc::dhcp::OptionCollection& options);
 
-    /// @brief Parses provided buffer as DHCPv4 options and creates Option objects.
-    ///
-    /// Parses provided buffer and stores created Option objects
-    /// in options container.
-    ///
-    /// @param buf Buffer to be parsed.
-    /// @param option_space A name of the option space which holds definitions
-    /// of to be used to parse options in the packets.
-    /// @param options Reference to option container. Options will be
-    ///        put here.
-    /// @return offset to the first byte after the last successfully parsed option
-    static size_t unpackOptions4(const OptionBuffer& buf,
-                                 const std::string& option_space,
-                                 isc::dhcp::OptionCollection& options);
-
-    /// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
-    ///
-    /// Parses provided buffer and stores created Option objects in options
-    /// container. The last two parameters are optional and are used in
-    /// relay parsing. If they are specified, relay-msg option is not created,
-    /// but rather those two parameters are specified to point out where
-    /// the relay-msg option resides and what is its length. This is perfromance
-    /// optimization that avoids unnecessary copying of potentially large
-    /// relay-msg option. It is not used for anything, except in the next
+    /// @brief Parses provided buffer as DHCPv6 options and creates
+    /// Option objects.
+    ///
+    /// Parses provided buffer and stores created Option objects in
+    /// options container. The last two parameters are optional and
+    /// are used in relay parsing. If they are specified, relay-msg
+    /// option is not created, but rather those two parameters are
+    /// specified to point out where the relay-msg option resides and
+    /// what is its length. This is a performance optimization that
+    /// avoids unnecessary copying of potentially large relay-msg
+    /// option. It is not used for anything, except in the next
     /// iteration its content will be treated as buffer to be parsed.
     ///
     /// @param buf Buffer to be parsed.
     /// @param option_space A name of the option space which holds definitions
-    /// of to be used to parse options in the packets.
+    ///        to be used to parse options in the packets.
     /// @param options Reference to option container. Options will be
     ///        put here.
     /// @param relay_msg_offset reference to a size_t structure. If specified,
     ///        offset to beginning of relay_msg option will be stored in it.
     /// @param relay_msg_len reference to a size_t structure. If specified,
     ///        length of the relay_msg option will be stored in it.
-    /// @return offset to the first byte after the last successfully parsed option
+    /// @return offset to the first byte after the last successfully
+    /// parsed option
+    ///
+    /// @note This function throws when an option type is defined more
+    /// than once, and it calls option building routines which can throw.
+    /// Partial parsing does not throw: it is the responsibility of the
+    /// caller to handle this condition.
     static size_t unpackOptions6(const OptionBuffer& buf,
                                  const std::string& option_space,
                                  isc::dhcp::OptionCollection& options,
                                  size_t* relay_msg_offset = 0,
                                  size_t* relay_msg_len = 0);
 
+    /// @brief Parses provided buffer as DHCPv4 options and creates
+    /// Option objects.
+    ///
+    /// Parses provided buffer and stores created Option objects
+    /// in options container.
+    ///
+    /// @param buf Buffer to be parsed.
+    /// @param option_space A name of the option space which holds definitions
+    ///        to be used to parse options in the packets.
+    /// @param options Reference to option container. Options will be
+    ///        put here.
+    /// @return offset to the first byte after the last successfully
+    /// parsed option or the offset of the DHO_END option type.
+    ///
+    /// The unpackOptions6 note applies too.
+    static size_t unpackOptions4(const OptionBuffer& buf,
+                                 const std::string& option_space,
+                                 isc::dhcp::OptionCollection& options);
+
     /// Registers factory method that produces options of specific option types.
     ///
     /// @throw isc::BadValue if provided the type is already registered, has
@@ -215,9 +227,13 @@ public:
     ///
     /// @param vendor_id enterprise-id of the vendor
     /// @param buf Buffer to be parsed.
-    /// @param options Reference to option container. Options will be
+    /// @param options Reference to option container. Suboptions will be
     ///        put here.
-    /// @return offset to the first byte after the last successfully parsed option
+    /// @return offset to the first byte after the last successfully
+    /// parsed suboption
+    ///
+    /// @note unpackVendorOptions6 throws when it fails to parse a suboption
+    /// so the return value is currently always the buffer length.
     static size_t unpackVendorOptions6(const uint32_t vendor_id,
                                        const OptionBuffer& buf,
                                        isc::dhcp::OptionCollection& options);
@@ -230,10 +246,14 @@ public:
     ///
     /// @param vendor_id enterprise-id of the vendor
     /// @param buf Buffer to be parsed.
-    /// @param options Reference to option container. Options will be
+    /// @param options Reference to option container. Suboptions will be
     ///        put here.
-    /// @return offset to the first byte after the last successfully parsed option
-    static size_t unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffer& buf,
+    /// @return offset to the first byte after the last successfully
+    /// parsed suboption
+    ///
+    /// The unpackVendorOptions6 note applies
+    static size_t unpackVendorOptions4(const uint32_t vendor_id,
+                                       const OptionBuffer& buf,
                                        isc::dhcp::OptionCollection& options);
 
 private:

+ 1 - 1
src/lib/dhcp/option_vendor.cc

@@ -58,7 +58,7 @@ void OptionVendor::unpack(OptionBufferConstIter begin,
 
     vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end));
 
-    OptionBuffer vendor_buffer(begin +4, end);
+    OptionBuffer vendor_buffer(begin + 4, end);
 
     if (universe_ == Option::V6) {
         LibDHCP::unpackVendorOptions6(vendor_id_, vendor_buffer, options_);

+ 5 - 4
src/lib/dhcp/pkt4.cc

@@ -225,9 +225,10 @@ Pkt4::unpack() {
         offset = callback_(opts_buffer, "dhcp4", options_, NULL, NULL);
     }
 
-    // If offset is not equal to the size, then something is wrong here. We
-    // either parsed past input buffer (bug in our code) or we haven't parsed
-    // everything (received trailing garbage or truncated option).
+    // If offset is not equal to the size and there is no DHO_END,
+    // then something is wrong here. We either parsed past input
+    // buffer (bug in our code) or we haven't parsed everything
+    // (received trailing garbage or truncated option).
     //
     // Invoking Jon Postel's law here: be conservative in what you send, and be
     // liberal in what you accept. There's no easy way to log something from
@@ -235,7 +236,7 @@ Pkt4::unpack() {
     // bytes. We also need to quell compiler warning about unused offset
     // variable.
     //
-    // if (offset != size) {
+    // if ((offset != size) && (opts_buffer[offset] != DHO_END)) {
     //        isc_throw(BadValue, "Received DHCPv6 buffer of size " << size
     //                  << ", were able to parse " << offset << " bytes.");
     // }

+ 1 - 1
src/lib/dhcp/pkt6.cc

@@ -256,7 +256,7 @@ void
 Pkt6::packTCP() {
     /// TODO Implement this function.
     isc_throw(NotImplemented, "DHCPv6 over TCP (bulk leasequery and failover)"
-              "not implemented yet.");
+              " not implemented yet.");
 }
 
 void

+ 1 - 1
src/lib/dhcp/pkt_filter_inet.cc

@@ -237,7 +237,7 @@ PktFilterInet::send(const Iface&, uint16_t sockfd,
     struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);
     memset(pktinfo, 0, sizeof(struct in_pktinfo));
     pktinfo->ipi_ifindex = pkt->getIndex();
-        pktinfo->ipi_spec_dst.s_addr = htonl(pkt->getLocalAddr()); // set the source IP address
+    pktinfo->ipi_spec_dst.s_addr = htonl(pkt->getLocalAddr()); // set the source IP address
     m.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
 #endif
 

+ 22 - 3
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -950,11 +950,19 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
         3, 1, 2, 3  // first byte is opaque data length, the rest is opaque data
     };
     std::vector<uint8_t> vivco_buf(vivco_data, vivco_data + sizeof(vivco_data));
+    const char vivsio_data[] = {
+        1, 2, 3, 4, // enterprise id
+        4,          // first byte is vendor block length
+        1, 2, 3, 4  // option type=1 length=2
+    };
+    std::vector<uint8_t> vivsio_buf(vivsio_data, vivsio_data + sizeof(vivsio_data));
+
     LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, vivco_buf.begin(),
                                     vivco_buf.end(), typeid(OptionVendorClass));
 
-    LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, begin, end,
-                                    typeid(OptionVendor));
+
+    LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, vivsio_buf.begin(),
+                                    vivsio_buf.end(), typeid(OptionVendor));
 }
 
 // Test that definitions of standard options have been initialized
@@ -983,6 +991,16 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
     // Initialize a vector with the FQDN data.
     std::vector<uint8_t> fqdn_buf(data, data + sizeof(data));
 
+    // Prepare buffer holding a vendor option
+    const char vopt_data[] = {
+        1, 2, 3, 4,                               // enterprise=0x1020304
+        0, 100,                                   // type=100
+        0, 6,                                     // length=6
+        102, 111, 111, 98, 97, 114                // data="foobar"
+    };
+    // Initialize a vector with the suboption data.
+    std::vector<uint8_t> vopt_buf(vopt_data, vopt_data + sizeof(vopt_data));
+
     // The CLIENT_FQDN holds a uint8_t value and FQDN. We have
     // to add the uint8_t value to it and then append the buffer
     // holding some valid FQDN.
@@ -1039,7 +1057,8 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
                                     vclass_buf.end(),
                                     typeid(OptionVendorClass));
 
-    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, begin, end,
+    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, vopt_buf.begin(),
+                                    vopt_buf.end(),
                                     typeid(OptionVendor),
                                     "vendor-opts-space");
 

+ 105 - 0
src/lib/dhcp/tests/pkt4_unittest.cc

@@ -654,6 +654,111 @@ TEST_F(Pkt4Test, unpackOptions) {
     verifyParsedOptions(pkt);
 }
 
+// Checks if the code is able to handle a malformed option
+TEST_F(Pkt4Test, unpackMalformed) {
+
+    vector<uint8_t> orig = generateTestPacket2();
+
+    orig.push_back(0x63);
+    orig.push_back(0x82);
+    orig.push_back(0x53);
+    orig.push_back(0x63);
+
+    orig.push_back(53); // Message Type 
+    orig.push_back(1); // length=1
+    orig.push_back(2); // type=2
+
+    orig.push_back(12); // Hostname
+    orig.push_back(3); // length=3
+    orig.push_back(102); // data="foo"
+    orig.push_back(111);
+    orig.push_back(111);
+
+    // That's our original content. It should be sane.
+    Pkt4Ptr success(new Pkt4(&orig[0], orig.size()));
+    EXPECT_NO_THROW(success->unpack());
+
+    // With the exception of END and PAD an option must have a length byte
+    vector<uint8_t> nolength = orig;
+    nolength.resize(orig.size() - 4);
+    Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size()));
+    EXPECT_NO_THROW(no_length_pkt->unpack());
+
+    // The unpack() operation doesn't throw but there is no option 12
+    EXPECT_FALSE(no_length_pkt->getOption(12));
+
+    // Truncated data is not accepted too but doesn't throw
+    vector<uint8_t> shorty = orig;
+    shorty.resize(orig.size() - 1);
+    Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size()));
+    EXPECT_NO_THROW(too_short_pkt->unpack());
+
+    // The unpack() operation doesn't throw but there is no option 12
+    EXPECT_FALSE(no_length_pkt->getOption(12));
+}
+
+// Checks if the code is able to handle a malformed vendor option
+TEST_F(Pkt4Test, unpackVendorMalformed) {
+
+    vector<uint8_t> orig = generateTestPacket2();
+
+    orig.push_back(0x63);
+    orig.push_back(0x82);
+    orig.push_back(0x53);
+    orig.push_back(0x63);
+
+    orig.push_back(53); // Message Type 
+    orig.push_back(1); // length=1
+    orig.push_back(2); // type=2
+
+    orig.push_back(125); // vivso suboptions
+    size_t full_len_index = orig.size();
+    orig.push_back(15); // length=15
+    orig.push_back(1); // vendor_id=0x1020304
+    orig.push_back(2);
+    orig.push_back(3);
+    orig.push_back(4);
+    size_t data_len_index = orig.size();
+    orig.push_back(10); // data-len=10
+    orig.push_back(128); // suboption type=128
+    orig.push_back(3); // suboption length=3
+    orig.push_back(102); // data="foo"
+    orig.push_back(111);
+    orig.push_back(111);
+    orig.push_back(129); // suboption type=129
+    orig.push_back(3); // suboption length=3
+    orig.push_back(99); // data="bar"
+    orig.push_back(98);
+    orig.push_back(114);
+
+    // That's our original content. It should be sane.
+    Pkt4Ptr success(new Pkt4(&orig[0], orig.size()));
+    EXPECT_NO_THROW(success->unpack());
+
+    // Data-len must match
+    vector<uint8_t> baddatalen = orig;
+    baddatalen.resize(orig.size() - 5);
+    baddatalen[full_len_index] = 10;
+    Pkt4Ptr bad_data_len_pkt(new Pkt4(&baddatalen[0], baddatalen.size()));
+    EXPECT_THROW(bad_data_len_pkt->unpack(), InvalidOptionValue);
+
+    // A suboption must have a length byte
+    vector<uint8_t> nolength = orig;
+    nolength.resize(orig.size() - 4);
+    nolength[full_len_index] = 11;
+    nolength[data_len_index] = 6;
+    Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size()));
+    EXPECT_THROW(no_length_pkt->unpack(), InvalidOptionValue);
+
+    // Truncated data is not accepted either
+    vector<uint8_t> shorty = orig;
+    shorty.resize(orig.size() - 1);
+    shorty[full_len_index] = 14;
+    shorty[data_len_index] = 9;
+    Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size()));
+    EXPECT_THROW(too_short_pkt->unpack(), InvalidOptionValue);
+}
+
 // This test verifies that it is possible to specify custom implementation of
 // the option parsing algorithm by installing a callback function.
 TEST_F(Pkt4Test, unpackOptionsWithCallback) {

+ 74 - 2
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -345,8 +345,8 @@ TEST_F(Pkt6Test, unpackMalformed) {
     Pkt6Ptr too_short_pkt(new Pkt6(&shorty[0], shorty.size()));
     EXPECT_THROW(too_short_pkt->unpack(), isc::BadValue);
 
-    // The code should complain about remaining bytes that can't
-    // be parsed.
+    // The code should complain about remaining bytes that can't be parsed
+    // but doesn't do so yet.
     Pkt6Ptr trailing_garbage(new Pkt6(&malform1[0], malform1.size()));
     EXPECT_NO_THROW(trailing_garbage->unpack());
 
@@ -369,6 +369,78 @@ TEST_F(Pkt6Test, unpackMalformed) {
 
     // ... but there should be no option 123 as it was malformed.
     EXPECT_FALSE(trunc_option->getOption(123));
+
+    // Check with truncated length field
+    Pkt6Ptr trunc_length(new Pkt6(&malform2[0], malform2.size() - 1));
+    EXPECT_NO_THROW(trunc_length->unpack());
+    EXPECT_FALSE(trunc_length->getOption(123));
+
+    // Check with missing length field
+    Pkt6Ptr no_length(new Pkt6(&malform2[0], malform2.size() - 2));
+    EXPECT_NO_THROW(no_length->unpack());
+    EXPECT_FALSE(no_length->getOption(123));
+
+    // Check with truncated type field
+    Pkt6Ptr trunc_type(new Pkt6(&malform2[0], malform2.size() - 3));
+    EXPECT_NO_THROW(trunc_type->unpack());
+    EXPECT_FALSE(trunc_type->getOption(123));
+}
+
+// Checks if the code is able to handle a malformed vendor option
+TEST_F(Pkt6Test, unpackVendorMalformed) {
+    // Get a packet. We're really interested in its on-wire
+    // representation only.
+    scoped_ptr<Pkt6> donor(capture1());
+
+    // Add a vendor option
+    OptionBuffer orig = donor->data_;
+
+    orig.push_back(0); // vendor options
+    orig.push_back(17);
+    orig.push_back(0);
+    size_t len_index = orig.size();
+    orig.push_back(18); // length=18
+    orig.push_back(1); // vendor_id=0x1020304
+    orig.push_back(2);
+    orig.push_back(3);
+    orig.push_back(4);
+    orig.push_back(1); // suboption type=0x101
+    orig.push_back(1); 
+    orig.push_back(0); // suboption length=3
+    orig.push_back(3);
+    orig.push_back(102); // data="foo"
+    orig.push_back(111);
+    orig.push_back(111);
+    orig.push_back(1); // suboption type=0x102
+    orig.push_back(2);
+    orig.push_back(0); // suboption length=3
+    orig.push_back(3);
+    orig.push_back(99); // data="bar'
+    orig.push_back(98);
+    orig.push_back(114);
+
+    Pkt6Ptr success(new Pkt6(&orig[0], orig.size()));
+    EXPECT_NO_THROW(success->unpack());
+
+    // Truncated vendor option is not accepted but doesn't throw
+    vector<uint8_t> shortv = orig;
+    shortv[len_index] = 20;
+    Pkt6Ptr too_short_vendor_pkt(new Pkt6(&shortv[0], shortv.size()));
+    EXPECT_NO_THROW(too_short_vendor_pkt->unpack());
+    
+    // Truncated option header is not accepted
+    vector<uint8_t> shorth = orig;
+    shorth.resize(orig.size() - 4);
+    shorth[len_index] = 12;
+    Pkt6Ptr too_short_header_pkt(new Pkt6(&shorth[0], shorth.size()));
+    EXPECT_THROW(too_short_header_pkt->unpack(), OutOfRange);
+
+    // Truncated option data is not accepted
+    vector<uint8_t> shorto = orig;
+    shorto.resize(orig.size() - 2);
+    shorto[len_index] = 16;
+    Pkt6Ptr too_short_option_pkt(new Pkt6(&shorto[0], shorto.size()));
+    EXPECT_THROW(too_short_option_pkt->unpack(), OutOfRange);
 }
 
 // This test verifies that it is possible to specify custom implementation of