]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4565] Checkpoint: built recursion chain
authorFrancis Dupont <fdupont@isc.org>
Mon, 8 Jun 2026 19:50:36 +0000 (21:50 +0200)
committerFrancis Dupont <fdupont@isc.org>
Tue, 16 Jun 2026 13:53:37 +0000 (15:53 +0200)
src/lib/dhcp/libdhcp++.cc
src/lib/dhcp/libdhcp++.h
src/lib/dhcp/option.cc
src/lib/dhcp/option.h
src/lib/dhcp/option_custom.cc
src/lib/dhcp/option_custom.h
src/lib/dhcp/option_definition.cc
src/lib/dhcp/option_definition.h

index 0789186e5f811d82d0e2670be1e55ebc89e3ef28..ea3fa7b72c3ed4ba28b5a8cf5c0d2a08a44ef90d 100644 (file)
@@ -313,11 +313,19 @@ LibDHCP::optionFactory(Option::Universe u,
     return (it->second(u, type, buf));
 }
 
+size_t
+LibDHCP::MAX_RECUSION_LEVEL = 10;
+
 size_t
 LibDHCP::unpackOptions6(const OptionBuffer& buf, const string& option_space,
                         OptionCollection& options,
                         size_t* relay_msg_offset /* = 0 */,
-                        size_t* relay_msg_len /* = 0 */) {
+                        size_t* relay_msg_len /* = 0 */,
+                        size_t rec_level /* = 0 */) {
+    ++rec_level;
+    if (rec_level >= MAX_RECUSION_LEVEL) {
+        isc_throw(isc::Unexpected, "Too deep recursion in unpacking options");
+    }
     size_t offset = 0;
     size_t length = buf.size();
     size_t last_offset = 0;
@@ -443,7 +451,8 @@ LibDHCP::unpackOptions6(const OptionBuffer& buf, const string& option_space,
                 isc_throw_assert(def);
                 opt = def->optionFactory(Option::V6, opt_type,
                                          buf.begin() + offset,
-                                         buf.begin() + offset + opt_len);
+                                         buf.begin() + offset + opt_len,
+                                         rec_level);
             } catch (const SkipThisOptionError&) {
                 opt.reset();
             } catch (const SkipRemainingOptionsError&) {
index 32ebcec121d1cbdd43084ea6867096e4cf482e25..cb61f8c488cee73b25e876219411a8cc5d8be216 100644 (file)
@@ -285,6 +285,7 @@ public:
     ///        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.
+    /// @param rec_level recursion level.
     /// @return offset to the first byte after the last successfully
     /// parsed option
     ///
@@ -296,7 +297,8 @@ public:
                                  const std::string& option_space,
                                  isc::dhcp::OptionCollection& options,
                                  size_t* relay_msg_offset = 0,
-                                 size_t* relay_msg_len = 0);
+                                 size_t* relay_msg_len = 0,
+                                 size_t rec_level = 0);
 
     /// @brief Extend vendor options from fused options in multiple OptionVendor
     /// or OptionVendorClass options and add respective suboptions or values.
@@ -467,6 +469,13 @@ public:
     /// @brief Get definition of D6O_IAADDR option.
     static const OptionDefinition& D6O_IAADDR_DEF();
 
+    /// @brief Maximum level of recursion unpacking options.
+    ///
+    /// @note: avoid to blow the stack when unpacking recursive
+    /// embedded v6 space options from a thread (so not using
+    /// system stack) on a TCP (so possibly large) received packet.
+    static size_t MAX_RECUSION_LEVEL;
+
 private:
 
     /// Initialize DHCP option definitions.
index 1b6fb639b34186094d6f7b41825a2ab6a76414d4..1897d77b62297897ebbcddad9362759ca9ab7b86 100644 (file)
@@ -152,7 +152,7 @@ void Option::unpack(OptionBufferConstIter begin,
 }
 
 void
-Option::unpackOptions(const OptionBuffer& buf) {
+Option::unpackOptions(const OptionBuffer& buf, size_t rec_level /* = 0 */) {
     list<uint16_t> deferred;
     switch (universe_) {
     case V4:
@@ -161,7 +161,8 @@ Option::unpackOptions(const OptionBuffer& buf) {
                                 getType() == DHO_VENDOR_ENCAPSULATED_OPTIONS);
         return;
     case V6:
-        LibDHCP::unpackOptions6(buf, getEncapsulatedSpace(), options_);
+        LibDHCP::unpackOptions6(buf, getEncapsulatedSpace(), options_,
+                                0, 0, rec_level);
         return;
     default:
         isc_throw(isc::BadValue, "Invalid universe type " << universe_);
index 68b422556f4da6b1abb0bad72e2ec61a8442e21b..5f491062c46a72c53de446f7d1a8efdd1134f91f 100644 (file)
@@ -549,12 +549,13 @@ protected:
     /// different exceptions when option assembly fails.
     ///
     /// @param buf buffer to be parsed.
+    /// @param rec_level recursion level.
     ///
     /// @todo The set of exceptions thrown by this function depend on
     /// exceptions thrown by unpack methods invoked on objects
     /// representing sub options. We should consider whether to aggregate
     /// those into one exception which can be documented here.
-    void unpackOptions(const OptionBuffer& buf);
+    void unpackOptions(const OptionBuffer& buf, size_t rec_level = 0);
 
     /// @brief Returns option header in the textual format.
     ///
index 29ad3cbcd6f8d0abea37abf0832230ac5bc09e81..2aaf0880f639f92255c3631a20b37f23bad2bb2a 100644 (file)
@@ -45,6 +45,17 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
     createBuffers(getData());
 }
 
+OptionCustom::OptionCustom(const OptionDefinition& def,
+                           Universe u,
+                           OptionBufferConstIter first,
+                           OptionBufferConstIter last,
+                           size_t rec_level)
+    : Option(u, def.getCode(), first, last),
+      definition_(def) {
+    setEncapsulatedSpace(def.getEncapsulatedSpace());
+    createBuffers(getData(), rec_level);
+}
+
 OptionPtr
 OptionCustom::clone() const {
     return (cloneInternal<OptionCustom>());
@@ -285,7 +296,7 @@ OptionCustom::bufferLength(const OptionDataType data_type, bool in_array,
 }
 
 void
-OptionCustom::createBuffers(const OptionBuffer& data_buf) {
+OptionCustom::createBuffers(const OptionBuffer& data_buf, size_t rec_level) {
     // Check that the option definition is correct as we are going
     // to use it to split the data_ buffer into set of sub buffers.
     definition_.validate();
@@ -335,7 +346,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
 
         // Unpack suboptions if any.
         else if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
-            unpackOptions(OptionBuffer(data, data_buf.end()));
+            unpackOptions(OptionBuffer(data, data_buf.end()), rec_level);
         }
 
     } else if (data_type != OPT_EMPTY_TYPE) {
@@ -390,13 +401,13 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
 
             // Unpack suboptions if any.
             if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
-                unpackOptions(OptionBuffer(data, data_buf.end()));
+                unpackOptions(OptionBuffer(data, data_buf.end()), rec_level);
             }
         }
     } else {
         // Unpack suboptions if any.
         if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
-            unpackOptions(OptionBuffer(data, data_buf.end()));
+            unpackOptions(OptionBuffer(data, data_buf.end()), rec_level);
         }
     }
     // If everything went ok we can replace old buffer set with new ones.
index e5f1167b74ac7cd2bd1c501eb0585f72cb994c3b..61c143befc36ce181dff803aec663f74bd2f5474 100644 (file)
@@ -80,6 +80,22 @@ public:
     OptionCustom(const OptionDefinition& def, Universe u,
                  OptionBufferConstIter first, OptionBufferConstIter last);
 
+    /// @brief Constructor, used for received options with limited recursion.
+    ///
+    /// @param def option definition.
+    /// @param u specifies universe (V4 or V6).
+    /// @param first iterator to the first element that should be copied.
+    /// @param last iterator to the next element after the last one
+    /// to be copied.
+    /// @param rec_level recursion level.
+    ///
+    /// @throw OutOfRange if option buffer is truncated.
+    ///
+    /// @todo list all exceptions thrown by ctor.
+    OptionCustom(const OptionDefinition& def, Universe u,
+                 OptionBufferConstIter first, OptionBufferConstIter last,
+                 size_t rec_level);
+
     /// @brief Copies this option and returns a pointer to the copy.
     virtual OptionPtr clone() const;
 
@@ -436,7 +452,8 @@ private:
     /// @brief Create collection of buffers representing data field values.
     ///
     /// @param data_buf a buffer to be parsed.
-    void createBuffers(const OptionBuffer& data_buf);
+    /// @param rec_level recursion level.
+    void createBuffers(const OptionBuffer& data_buf, size_t rec_level = 0);
 
     /// @brief Return a text representation of a data field.
     ///
index d3665e2b7b95121784b96924d81d4d248b22229d..29d47af5fa13a1625970e1081c116cb560b10a7d 100644 (file)
@@ -187,7 +187,8 @@ OptionDefinition::optionFactory(Option::Universe u,
                                 uint16_t type,
                                 OptionBufferConstIter begin,
                                 OptionBufferConstIter end,
-                                bool convenient_notation) const {
+                                bool convenient_notation,
+                                size_t rec_level) const {
 
     try {
         // Some of the options are represented by the specialized classes derived
@@ -206,7 +207,8 @@ OptionDefinition::optionFactory(Option::Universe u,
             if (getEncapsulatedSpace().empty()) {
                 return (factoryEmpty(u, type));
             } else {
-                return (OptionPtr(new OptionCustom(*this, u, begin, end)));
+                return (OptionPtr(new OptionCustom(*this, u, begin, end,
+                                                   rec_level)));
             }
 
         case OPT_BINARY_TYPE:
@@ -285,7 +287,7 @@ OptionDefinition::optionFactory(Option::Universe u,
             // Do nothing. We will return generic option a few lines down.
             ;
         }
-        return (OptionPtr(new OptionCustom(*this, u, begin, end)));
+        return (OptionPtr(new OptionCustom(*this, u, begin, end, rec_level)));
     } catch (const SkipThisOptionError&) {
         // We need to throw this one as is.
         throw;
index 558bd6af6b6b2024fcbf8bcb58a70f26e3b0afcc..3bbede8deebe9523a1940cb9731574b278dc2e12 100644 (file)
@@ -437,6 +437,7 @@ public:
     ///                            as a string formatted in user-friendly, convenient way.
     ///                            The flag is propagated to the option constructor, so that
     ///                            the data could be parsed properly. Defaults to false.
+    /// @param rec_level recursion level.
     ///
     /// @return instance of the DHCP option.
     /// @throw InvalidOptionValue if data for the option is invalid.
@@ -444,7 +445,8 @@ public:
                             uint16_t type,
                             OptionBufferConstIter begin,
                             OptionBufferConstIter end,
-                            bool convenient_notation = false) const;
+                            bool convenient_notation = false,
+                            size_t rec_level = 0) const;
 
     /// @brief Option factory.
     ///