From a103f432ea9e1eebcc836214510d8d5c8b0db10c Mon Sep 17 00:00:00 2001 From: AlmaLinux RelEng Bot Date: Fri, 10 Apr 2026 19:29:23 -0400 Subject: [PATCH] import Oracle_OSS kea-3.0.1-3.el10_1 --- CVE-2026-3608.patch | 1827 +++++++++++++++++++++++++++++++++++++++++++ kea.spec | 26 +- 2 files changed, 1845 insertions(+), 8 deletions(-) create mode 100644 CVE-2026-3608.patch diff --git a/CVE-2026-3608.patch b/CVE-2026-3608.patch new file mode 100644 index 0000000..c98365b --- /dev/null +++ b/CVE-2026-3608.patch @@ -0,0 +1,1827 @@ +commit 6ef02087bdf0f5158ad0654d9bdc30c166241e19 +Author: Razvan Becheriu +Date: Thu Mar 12 12:38:45 2026 +0200 + + [#4387] backport #4275, #4288 to v3_0 + +[Martin Osvald ] +NOTE: Tests were removed. + +diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc +index f48bed5e81..49a9359ae2 100644 +--- a/src/lib/cc/data.cc ++++ b/src/lib/cc/data.cc +@@ -1,4 +1,4 @@ +-// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") ++// Copyright (C) 2010-2026 Internet Systems Consortium, Inc. ("ISC") + // + // This Source Code Form is subject to the terms of the Mozilla Public + // License, v. 2.0. If a copy of the MPL was not distributed with this +@@ -17,6 +17,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -37,6 +38,8 @@ const char* const WHITESPACE = " \b\f\n\r\t"; + namespace isc { + namespace data { + ++constexpr unsigned Element::MAX_NESTING_LEVEL; ++ + std::string + Element::Position::str() const { + std::ostringstream ss; +@@ -50,6 +53,53 @@ operator<<(std::ostream& out, const Element::Position& pos) { + return (out); + } + ++void ++Element::removeEmptyContainersRecursively(unsigned level) { ++ if (level <= 0) { ++ // Cycles are by definition not empty so no need to throw. ++ return; ++ } ++ if (type_ == list || type_ == map) { ++ size_t s(size()); ++ for (size_t i = 0; i < s; ++i) { ++ // Get child. ++ ElementPtr child; ++ if (type_ == list) { ++ child = getNonConst(i); ++ } else if (type_ == map) { ++ std::string const key(get(i)->stringValue()); ++ // The ElementPtr - ConstElementPtr disparity between ++ // ListElement and MapElement is forcing a const cast here. ++ // It's undefined behavior to modify it after const casting. ++ // The options are limited. I've tried templating, moving ++ // this function from a member function to free-standing and ++ // taking the Element template as argument. I've tried ++ // making it a virtual function with overridden ++ // implementations in ListElement and MapElement. Nothing ++ // works. ++ child = boost::const_pointer_cast(get(key)); ++ } ++ ++ // Makes no sense to continue for non-container children. ++ if (child->getType() != list && child->getType() != map) { ++ continue; ++ } ++ ++ // Recurse if not empty. ++ if (!child->empty()){ ++ child->removeEmptyContainersRecursively(level - 1); ++ } ++ ++ // When returning from recursion, remove if empty. ++ if (child->empty()) { ++ remove(i); ++ --i; ++ --s; ++ } ++ } ++ } ++} ++ + std::string + Element::str() const { + std::stringstream ss; +@@ -600,7 +650,10 @@ fromStringstreamString(std::istream& in, const std::string& file, int& line, + + ElementPtr + fromStringstreamList(std::istream& in, const std::string& file, int& line, +- int& pos) { ++ int& pos, unsigned level) { ++ if (level == 0) { ++ isc_throw(JSONError, "fromJSON elements nested too deeply"); ++ } + int c = 0; + ElementPtr list = Element::createList(Element::Position(file, line, pos)); + ElementPtr cur_list_element; +@@ -608,7 +661,8 @@ fromStringstreamList(std::istream& in, const std::string& file, int& line, + skipChars(in, WHITESPACE, line, pos); + while (c != EOF && c != ']') { + if (in.peek() != ']') { +- cur_list_element = Element::fromJSON(in, file, line, pos); ++ cur_list_element = ++ Element::fromJSON(in, file, line, pos, level - 1); + list->add(cur_list_element); + c = skipTo(in, file, line, pos, ",]", WHITESPACE); + } else { +@@ -621,7 +675,10 @@ fromStringstreamList(std::istream& in, const std::string& file, int& line, + + ElementPtr + fromStringstreamMap(std::istream& in, const std::string& file, int& line, +- int& pos) { ++ int& pos, unsigned level) { ++ if (level == 0) { ++ isc_throw(JSONError, "fromJSON elements nested too deeply"); ++ } + ElementPtr map = Element::createMap(Element::Position(file, line, pos)); + skipChars(in, WHITESPACE, line, pos); + int c = in.peek(); +@@ -637,7 +694,8 @@ fromStringstreamMap(std::istream& in, const std::string& file, int& line, + skipTo(in, file, line, pos, ":", WHITESPACE); + // skip the : + +- ConstElementPtr value = Element::fromJSON(in, file, line, pos); ++ ConstElementPtr value = ++ Element::fromJSON(in, file, line, pos, level - 1); + map->set(key, value); + + c = skipTo(in, file, line, pos, ",}", WHITESPACE); +@@ -726,7 +784,10 @@ Element::fromJSON(std::istream& in, const std::string& file_name, bool preproc) + + ElementPtr + Element::fromJSON(std::istream& in, const std::string& file, int& line, +- int& pos) { ++ int& pos, unsigned level) { ++ if (level == 0) { ++ isc_throw(JSONError, "fromJSON elements nested too deeply"); ++ } + int c = 0; + ElementPtr element; + bool el_read = false; +@@ -773,11 +834,11 @@ Element::fromJSON(std::istream& in, const std::string& file, int& line, + el_read = true; + break; + case '[': +- element = fromStringstreamList(in, file, line, pos); ++ element = fromStringstreamList(in, file, line, pos, level); + el_read = true; + break; + case '{': +- element = fromStringstreamMap(in, file, line, pos); ++ element = fromStringstreamMap(in, file, line, pos, level); + el_read = true; + break; + case EOF: +@@ -831,17 +892,17 @@ Element::fromJSONFile(const std::string& file_name, bool preproc) { + // to JSON format + + void +-IntElement::toJSON(std::ostream& ss) const { ++IntElement::toJSON(std::ostream& ss, unsigned) const { + ss << intValue(); + } + + void +-BigIntElement::toJSON(std::ostream& ss) const { ++BigIntElement::toJSON(std::ostream& ss, unsigned) const { + ss << bigIntValue(); + } + + void +-DoubleElement::toJSON(std::ostream& ss) const { ++DoubleElement::toJSON(std::ostream& ss, unsigned) const { + // The default output for doubles nicely drops off trailing + // zeros, however this produces strings without decimal points + // for whole number values. When reparsed this will create +@@ -857,7 +918,7 @@ DoubleElement::toJSON(std::ostream& ss) const { + } + + void +-BoolElement::toJSON(std::ostream& ss) const { ++BoolElement::toJSON(std::ostream& ss, unsigned) const { + if (boolValue()) { + ss << "true"; + } else { +@@ -866,12 +927,12 @@ BoolElement::toJSON(std::ostream& ss) const { + } + + void +-NullElement::toJSON(std::ostream& ss) const { ++NullElement::toJSON(std::ostream& ss, unsigned) const { + ss << "null"; + } + + void +-StringElement::toJSON(std::ostream& ss) const { ++StringElement::toJSON(std::ostream& ss, unsigned) const { + ss << "\""; + const std::string& str = stringValue(); + for (size_t i = 0; i < str.size(); ++i) { +@@ -919,7 +980,11 @@ StringElement::toJSON(std::ostream& ss) const { + } + + void +-ListElement::toJSON(std::ostream& ss) const { ++ListElement::toJSON(std::ostream& ss, unsigned level) const { ++ if (level == 0) { ++ isc_throw(BadValue, "toJSON got infinite recursion: " ++ "arguments include cycles"); ++ } + ss << "[ "; + + const std::vector& v = listValue(); +@@ -930,13 +995,17 @@ ListElement::toJSON(std::ostream& ss) const { + } else { + first = false; + } +- it->toJSON(ss); ++ it->toJSON(ss, level - 1); + } + ss << " ]"; + } + + void +-MapElement::toJSON(std::ostream& ss) const { ++MapElement::toJSON(std::ostream& ss, unsigned level) const { ++ if (level == 0) { ++ isc_throw(BadValue, "toJSON got infinite recursion: " ++ "arguments include cycles"); ++ } + ss << "{ "; + + bool first = true; +@@ -948,7 +1017,7 @@ MapElement::toJSON(std::ostream& ss) const { + } + ss << "\"" << it.first << "\": "; + if (it.second) { +- it.second->toJSON(ss); ++ it.second->toJSON(ss, level - 1); + } else { + ss << "None"; + } +@@ -1024,9 +1093,9 @@ MapElement::find(const std::string& id, ConstElementPtr& t) const { + } + + bool +-IntElement::equals(const Element& other) const { ++IntElement::equals(const Element& other, unsigned) const { + // Let's not be very picky with constraining the integer types to be the +- // same. Equality is sometimes checked from high-up in the Element hierarcy. ++ // same. Equality is sometimes checked from high-up in the Element hierarchy. + // That is a context which, most of the time, does not have information on + // the type of integers stored on Elements lower in the hierarchy. So it + // would be difficult to differentiate between the integer types. +@@ -1035,9 +1104,9 @@ IntElement::equals(const Element& other) const { + } + + bool +-BigIntElement::equals(const Element& other) const { ++BigIntElement::equals(const Element& other, unsigned) const { + // Let's not be very picky with constraining the integer types to be the +- // same. Equality is sometimes checked from high-up in the Element hierarcy. ++ // same. Equality is sometimes checked from high-up in the Element hierarchy. + // That is a context which, most of the time, does not have information on + // the type of integers stored on Elements lower in the hierarchy. So it + // would be difficult to differentiate between the integer types. +@@ -1046,37 +1115,41 @@ BigIntElement::equals(const Element& other) const { + } + + bool +-DoubleElement::equals(const Element& other) const { ++DoubleElement::equals(const Element& other, unsigned) const { + return (other.getType() == Element::real) && + (fabs(d - other.doubleValue()) < 1e-14); + } + + bool +-BoolElement::equals(const Element& other) const { ++BoolElement::equals(const Element& other, unsigned) const { + return (other.getType() == Element::boolean) && + (b == other.boolValue()); + } + + bool +-NullElement::equals(const Element& other) const { ++NullElement::equals(const Element& other, unsigned) const { + return (other.getType() == Element::null); + } + + bool +-StringElement::equals(const Element& other) const { ++StringElement::equals(const Element& other, unsigned) const { + return (other.getType() == Element::string) && + (s == other.stringValue()); + } + + bool +-ListElement::equals(const Element& other) const { ++ListElement::equals(const Element& other, unsigned level) const { ++ if (level == 0) { ++ isc_throw(BadValue, "equals got infinite recursion: " ++ "arguments include cycles"); ++ } + if (other.getType() == Element::list) { + const size_t s = size(); + if (s != other.size()) { + return (false); + } + for (size_t i = 0; i < s; ++i) { +- if (!get(i)->equals(*other.get(i))) { ++ if (!get(i)->equals(*other.get(i), level - 1)) { + return (false); + } + } +@@ -1123,7 +1196,11 @@ ListElement::sort(std::string const& index /* = std::string() */) { + } + + bool +-MapElement::equals(const Element& other) const { ++MapElement::equals(const Element& other, unsigned level) const { ++ if (level == 0) { ++ isc_throw(BadValue, "equals got infinite recursion: " ++ "arguments include cycles"); ++ } + if (other.getType() == Element::map) { + if (size() != other.size()) { + return (false); +@@ -1131,7 +1208,7 @@ MapElement::equals(const Element& other) const { + for (auto const& kv : mapValue()) { + auto key = kv.first; + if (other.contains(key)) { +- if (!get(key)->equals(*other.get(key))) { ++ if (!get(key)->equals(*other.get(key), level - 1)) { + return (false); + } + } else { +@@ -1215,7 +1292,12 @@ merge(ElementPtr element, ConstElementPtr other) { + + void + mergeDiffAdd(ElementPtr& element, ElementPtr& other, +- HierarchyDescriptor& hierarchy, std::string key, size_t idx) { ++ HierarchyDescriptor& hierarchy, std::string key, size_t idx, ++ unsigned level) { ++ if (level == 0) { ++ isc_throw(BadValue, "mergeDiffAdd got infinite recursion: " ++ "arguments include cycles"); ++ } + if (element->getType() != other->getType()) { + isc_throw(TypeError, "mergeDiffAdd arguments not same type"); + } +@@ -1237,7 +1319,8 @@ mergeDiffAdd(ElementPtr& element, ElementPtr& other, + // entity. + if (f->second.match_(mutable_left, mutable_right)) { + found = true; +- mergeDiffAdd(mutable_left, mutable_right, hierarchy, key, idx); ++ mergeDiffAdd(mutable_left, mutable_right, hierarchy, ++ key, idx, level - 1); + } + } + if (!found) { +@@ -1263,7 +1346,8 @@ mergeDiffAdd(ElementPtr& element, ElementPtr& other, + (value->getType() == Element::map || + value->getType() == Element::list)) { + ElementPtr mutable_element = boost::const_pointer_cast(element->get(current_key)); +- mergeDiffAdd(mutable_element, value, hierarchy, current_key, idx + 1); ++ mergeDiffAdd(mutable_element, value, hierarchy, ++ current_key, idx + 1, level - 1); + } else { + element->set(current_key, value); + } +@@ -1276,7 +1360,12 @@ mergeDiffAdd(ElementPtr& element, ElementPtr& other, + + void + mergeDiffDel(ElementPtr& element, ElementPtr& other, +- HierarchyDescriptor& hierarchy, std::string key, size_t idx) { ++ HierarchyDescriptor& hierarchy, std::string key, size_t idx, ++ unsigned level) { ++ if (level == 0) { ++ isc_throw(BadValue, "mergeDiffDel got infinite recursion: " ++ "arguments include cycles"); ++ } + if (element->getType() != other->getType()) { + isc_throw(TypeError, "mergeDiffDel arguments not same type"); + } +@@ -1301,7 +1390,8 @@ mergeDiffDel(ElementPtr& element, ElementPtr& other, + element->remove(iter); + removed = true; + } else { +- mergeDiffDel(mutable_left, mutable_right, hierarchy, key, idx); ++ mergeDiffDel(mutable_left, mutable_right, ++ hierarchy, key, idx, level - 1); + if (mutable_left->empty()) { + element->remove(iter); + removed = true; +@@ -1332,7 +1422,8 @@ mergeDiffDel(ElementPtr& element, ElementPtr& other, + ElementPtr mutable_element = boost::const_pointer_cast(element->get(current_key)); + if (mutable_element->getType() == Element::map || + mutable_element->getType() == Element::list) { +- mergeDiffDel(mutable_element, value, hierarchy, current_key, idx + 1); ++ mergeDiffDel(mutable_element, value, hierarchy, ++ current_key, idx + 1, level - 1); + if (mutable_element->empty()) { + element->remove(current_key); + } +@@ -1367,7 +1458,12 @@ mergeDiffDel(ElementPtr& element, ElementPtr& other, + void + extend(const std::string& container, const std::string& extension, + ElementPtr& element, ElementPtr& other, HierarchyDescriptor& hierarchy, +- std::string key, size_t idx, bool alter) { ++ std::string key, size_t idx, bool alter, unsigned level) { ++ ++ if (level == 0) { ++ isc_throw(BadValue, "extend got infinite recursion: " ++ "arguments include cycles"); ++ } + if (element->getType() != other->getType()) { + isc_throw(TypeError, "extend arguments not same type"); + } +@@ -1386,7 +1482,7 @@ extend(const std::string& container, const std::string& extension, + } + if (f->second.match_(mutable_left, mutable_right)) { + extend(container, extension, mutable_left, mutable_right, +- hierarchy, key, idx, alter); ++ hierarchy, key, idx, alter, level - 1); + } + } + } +@@ -1406,7 +1502,8 @@ extend(const std::string& container, const std::string& extension, + if (container == key) { + alter = true; + } +- extend(container, extension, mutable_element, value, hierarchy, current_key, idx + 1, alter); ++ extend(container, extension, mutable_element, value, ++ hierarchy, current_key, idx + 1, alter, level - 1); + } else if (alter && current_key == extension) { + element->set(current_key, value); + } +@@ -1417,7 +1514,7 @@ extend(const std::string& container, const std::string& extension, + } + + ElementPtr +-copy(ConstElementPtr from, int level) { ++copy(ConstElementPtr from, unsigned level) { + if (!from) { + isc_throw(BadValue, "copy got a null pointer"); + } +@@ -1543,9 +1640,15 @@ isEquivalent(ConstElementPtr a, ConstElementPtr b) { + return (isEquivalent0(a, b, 100)); + } + ++namespace { ++ + void +-prettyPrint(ConstElementPtr element, std::ostream& out, +- unsigned indent, unsigned step) { ++prettyPrint0(ConstElementPtr element, std::ostream& out, ++ unsigned indent, unsigned step, unsigned level) { ++ if (level == 0) { ++ isc_throw(BadValue, "prettyPrint got infinite recursion: " ++ "arguments include cycles"); ++ } + if (!element) { + isc_throw(BadValue, "prettyPrint got a null pointer"); + } +@@ -1585,7 +1688,7 @@ prettyPrint(ConstElementPtr element, std::ostream& out, + out << std::string(indent + step, ' '); + } + // recursive call +- prettyPrint(it, out, indent + step, step); ++ prettyPrint0(it, out, indent + step, step, level - 1); + } + + // close the list +@@ -1620,7 +1723,7 @@ prettyPrint(ConstElementPtr element, std::ostream& out, + // add keyword: + out << "\"" << it.first << "\": "; + // recursive call +- prettyPrint(it.second, out, indent + step, step); ++ prettyPrint0(it.second, out, indent + step, step, level - 1); + } + + // close the map +@@ -1631,6 +1734,14 @@ prettyPrint(ConstElementPtr element, std::ostream& out, + } + } + ++} // end anonymous namespace ++ ++void ++prettyPrint(ConstElementPtr element, std::ostream& out, ++ unsigned indent, unsigned step) { ++ prettyPrint0(element, out, indent, step, Element::MAX_NESTING_LEVEL); ++} ++ + std::string + prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) { + std::stringstream ss; +@@ -1657,5 +1768,90 @@ void Element::preprocess(std::istream& in, std::stringstream& out) { + } + } + ++namespace { ++ ++// Type of arcs. ++typedef std::set Arc; ++ ++// Helper function walking on the supposed tree. ++bool ++IsCircular0(ConstElementPtr element, Arc arc) { ++ // Sanity check. ++ if (!element) { ++ return (false); ++ } ++ auto type = element->getType(); ++ // Container? ++ if ((type != Element::list) && (type != Element::map)) { ++ return (false); ++ } ++ // Empty? A cycle requires at least one element. ++ if (element->empty()) { ++ return (false); ++ } ++ // In the arc? ++ if (arc.count(element) > 0) { ++ return (true); ++ } ++ // This requires to work on a copy of the arc but it should be small. ++ arc.insert(element); ++ if (type == Element::list) { ++ for (auto const& it : element->listValue()) { ++ if (IsCircular0(it, arc)) { ++ return (true); ++ } ++ } ++ return (false); ++ } ++ // The argument is a map. ++ for (auto const& it : element->mapValue()) { ++ if (IsCircular0(it.second, arc)) { ++ return (true); ++ } ++ } ++ return (false); ++} ++ ++} // end anonymous namespace ++ ++bool ++IsCircular(ConstElementPtr element) { ++ return (IsCircular0(element, Arc())); ++} ++ ++unsigned ++getNestDepth(ConstElementPtr element, unsigned max_depth) { ++ if (max_depth == 0U) { ++ return (0U); ++ } ++ if (!element) { ++ return (0U); ++ } ++ unsigned ret = 1U; ++ if (element->getType() == Element::list) { ++ for (auto const& i : element->listValue()) { ++ unsigned sub = getNestDepth(i, max_depth - 1); ++ if (sub == max_depth - 1) { ++ return (max_depth); ++ } ++ if (sub + 1 > ret) { ++ ret = sub + 1; ++ } ++ } ++ } else if (element->getType() == Element::map) { ++ for (auto const& i : element->mapValue()) { ++ unsigned sub = getNestDepth(i.second, max_depth - 1); ++ if (sub == max_depth - 1) { ++ return (max_depth); ++ } ++ if (sub + 1 > ret) { ++ ret = sub + 1; ++ } ++ } ++ ++ } ++ return (ret); ++} ++ + } // end of isc::data namespace + } // end of isc namespace +diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h +index b93e2ec29e..e473f947b3 100644 +--- a/src/lib/cc/data.h ++++ b/src/lib/cc/data.h +@@ -21,7 +21,8 @@ + + #include + +-namespace isc { namespace data { ++namespace isc { ++namespace data { + + class Element; + // todo: describe the rationale behind ElementPtr? +@@ -70,8 +71,20 @@ public: + /// the type in question. + /// + class Element { +- + public: ++ /// @brief Maximum nesting level of Element objects. ++ /// ++ /// Many methods and functions perform a recursive walk on an element ++ /// containing lists or/and maps. This recursion is limited to using ++ /// an allowed level of nesting argument which is decremented at ++ /// each recursive call until it reaches 0. This was extended to ++ /// recursive parsing of a JSON text as stack overflows were reported ++ /// with excessive recursion on specially crafted input. ++ /// This constant is the default allowed level of nesting, its value ++ /// is arbitrary (but enough for all realistic cases) and used before ++ /// limiting recursion in *all* recursive methods/functions. ++ static constexpr unsigned MAX_NESTING_LEVEL = 100U; ++ + /// @brief Represents the position of the data element within a + /// configuration string. + /// +@@ -121,7 +134,7 @@ public: + /// + /// The object containing two zeros is a default for most of the + /// methods creating @c Element objects. The returned value is static +- /// so as it is not created everytime the function with the default ++ /// so as it is not created every time the function with the default + /// position argument is called. + static const Position& ZERO_POSITION() { + static Position position("", 0, 0); +@@ -171,37 +184,53 @@ protected: + + + public: +- // base class; make dtor virtual ++ // Base class; make destructor virtual. + virtual ~Element() {} + +- /// @return the type of this element +- types getType() const { return (type_); } ++ /// @return the type of this element. ++ types getType() const { ++ return (type_); ++ } + + /// @brief Returns position where the data element's value starts in a + /// configuration string. + /// + /// @warning The returned reference is valid as long as the object which + /// created it lives. +- const Position& getPosition() const { return (position_); } ++ /// @return The position. ++ const Position& getPosition() const { ++ return (position_); ++ } + +- /// Returns a string representing the Element and all its +- /// child elements; note that this is different from stringValue(), ++ /// @brief Returns a string representing the Element and all its ++ /// child elements ++ /// ++ /// @note: that this is different from stringValue(), + /// which only returns the single value of a StringElement + /// + /// The resulting string will contain the Element in JSON format. ++ /// Based on @ref toJSON. + /// +- /// @return std::string containing the string representation ++ /// @return std::string containing the string representation. + std::string str() const; + +- /// Returns the wireformat for the Element and all its child ++ /// @brief Returns the wireformat for the Element and all its child + /// elements. + /// ++ /// Based on @ref toJSON. ++ /// + /// @return std::string containing the element in wire format + std::string toWire() const; ++ ++ /// @brief Appends the wireformat for the Element to the stream. ++ /// ++ /// @param out The output stream where to append the wireformat. + void toWire(std::ostream& out) const; + + /// @brief Add the position to a TypeError message + /// should be used in place of isc_throw(TypeError, error) ++ /// ++ /// @param error The error message. + #define throwTypeError(error) \ + { \ + std::string msg_ = error; \ +@@ -213,15 +242,26 @@ public: + isc_throw(TypeError, msg_); \ + } + +- /// @name pure virtuals, every derived class must implement these ++ /// @name pure virtuals, every derived class must implement these. + ++ /// @brief Test equality. ++ /// ++ /// @param other The other element to compare with. ++ /// @param level The maximum level of recursion. + /// @return true if the other ElementPtr has the same value and the same + /// type (or a different and compatible type), false otherwise. +- virtual bool equals(const Element& other) const = 0; ++ /// @throw BadValue when nesting depth is more than level. ++ virtual bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const = 0; + +- /// Converts the Element to JSON format and appends it to +- /// the given stringstream. +- virtual void toJSON(std::ostream& ss) const = 0; ++ /// @brief Converts the Element to JSON format and appends it to ++ /// the given output stream. ++ /// ++ /// @param ss The output stream where to append the JSON format. ++ /// @param level The maximum level of recursion. ++ /// @throw BadValue when nesting depth is more than level. ++ virtual void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const = 0; + + /// @name Type-specific getters + /// +@@ -231,21 +271,38 @@ public: + /// If you want an exception-safe getter method, use + /// getValue() below + //@{ +- virtual int64_t intValue() const +- { throwTypeError("intValue() called on non-integer Element"); } ++ /// @brief Return the integer value. ++ virtual int64_t intValue() const { ++ throwTypeError("intValue() called on non-integer Element"); ++ } ++ ++ /// @brief Return the big integer value. + virtual isc::util::int128_t bigIntValue() const { + throwTypeError("bigIntValue() called on non-big-integer Element"); + } +- virtual double doubleValue() const +- { throwTypeError("doubleValue() called on non-double Element"); } +- virtual bool boolValue() const +- { throwTypeError("boolValue() called on non-Bool Element"); } +- virtual std::string stringValue() const +- { throwTypeError("stringValue() called on non-string Element"); } ++ ++ /// @brief Return the double value. ++ virtual double doubleValue() const { ++ throwTypeError("doubleValue() called on non-double Element"); ++ } ++ ++ /// @brief Return the boolean value. ++ virtual bool boolValue() const { ++ throwTypeError("boolValue() called on non-Bool Element"); ++ } ++ ++ /// @brief Return the string value. ++ virtual std::string stringValue() const { ++ throwTypeError("stringValue() called on non-string Element"); ++ } ++ ++ /// @brief Return the list value. + virtual const std::vector& listValue() const { + // replace with real exception or empty vector? + throwTypeError("listValue() called on non-list Element"); + } ++ ++ /// @brief Return the map value. + virtual const std::map& mapValue() const { + // replace with real exception or empty map? + throwTypeError("mapValue() called on non-map Element"); +@@ -261,11 +318,40 @@ public: + /// data to the given reference and returning true + /// + //@{ ++ /// @brief Get the integer value. ++ /// ++ /// @param t The reference to the integer. ++ /// @return false. + virtual bool getValue(int64_t& t) const; ++ ++ /// @brief Get the double value. ++ /// ++ /// @param t The reference to the double. ++ /// @return false. + virtual bool getValue(double& t) const; ++ ++ /// @brief Get the boolean value. ++ /// ++ /// @param t The reference to the boolean. ++ /// @return false. + virtual bool getValue(bool& t) const; ++ ++ /// @brief Get the string value. ++ /// ++ /// @param t The reference to the string. ++ /// @return false. + virtual bool getValue(std::string& t) const; ++ ++ /// @brief Get the list value. ++ /// ++ /// @param t The reference to the list. ++ /// @return false. + virtual bool getValue(std::vector& t) const; ++ ++ /// @brief Get the map value. ++ /// ++ /// @param t The reference to the map. ++ /// @return false. + virtual bool getValue(std::map& t) const; + //@} + +@@ -279,20 +365,70 @@ public: + /// Notes: Read notes of IntElement definition about the use of + /// long long int, long int and int. + //@{ ++ /// @brief Set the integer value. ++ /// ++ /// @param v The new integer value. ++ /// @return False. + virtual bool setValue(const long long int v); ++ ++ /// @brief Set the big integer value. ++ /// ++ /// @param v The new big integer value. ++ /// @return False. + virtual bool setValue(const isc::util::int128_t& v); +- bool setValue(const long int i) { return (setValue(static_cast(i))); } +- bool setValue(const int i) { return (setValue(static_cast(i))); } ++ ++ /// @brief Set the double value. ++ /// ++ /// @param v The new double value. ++ /// @return False. + virtual bool setValue(const double v); ++ ++ /// @brief Set the boolean value. ++ /// ++ /// @param t The new boolean value. ++ /// @return False. + virtual bool setValue(const bool t); ++ ++ /// @brief Set the string value. ++ /// ++ /// @param v The new string value. ++ /// @return False. + virtual bool setValue(const std::string& v); ++ ++ /// @brief Set the list value. ++ /// ++ /// @param v The new list value. ++ /// @return False. + virtual bool setValue(const std::vector& v); ++ ++ /// @brief Set the map value. ++ /// ++ /// @param v The new map value. ++ /// @return False. + virtual bool setValue(const std::map& v); ++ ++ /// @brief Set the integer value (long int overload). ++ /// ++ /// @param i The new integer value. ++ /// @return True (and set the value) when the Element type is integer, ++ /// false otherwise. ++ bool setValue(const long int i) { ++ return (setValue(static_cast(i))); ++ } ++ ++ /// @brief Set the integer value (int overload). ++ /// ++ /// @param i The new integer value. ++ /// @return True (and set the value) when the Element type is integer, ++ /// false otherwise. ++ bool setValue(const int i) { ++ return (setValue(static_cast(i))); ++ } + //@} + +- // Other functions for specific subtypes ++ // Other functions for specific subtypes. + +- /// @name ListElement functions ++ /// @name ListElement functions. + /// + /// @brief If the Element on which these functions are called are not + /// an instance of ListElement, a TypeError exception is thrown. +@@ -300,33 +436,34 @@ public: + /// Returns the ElementPtr at the given index. If the index is out + /// of bounds, this function throws an std::out_of_range exception. + /// @param i The position of the ElementPtr to return ++ /// @return specified element pointer. + virtual ConstElementPtr get(const int i) const; + +- /// @brief returns element as non-const pointer ++ /// @brief returns element as non-const pointer. + /// +- /// @param i The position of the ElementPtr to retrieve +- /// @return specified element pointer ++ /// @param i The position of the ElementPtr to retrieve. ++ /// @return specified element pointer. + virtual ElementPtr getNonConst(const int i) const; + +- /// Sets the ElementPtr at the given index. If the index is out ++ /// @brief Sets the ElementPtr at the given index. If the index is out + /// of bounds, this function throws an std::out_of_range exception. + /// @param i The position of the ElementPtr to set + /// @param element The ElementPtr to set at the position + virtual void set(const size_t i, ElementPtr element); + +- /// Adds an ElementPtr to the list ++ /// @brief Adds an ElementPtr to the list + /// @param element The ElementPtr to add + virtual void add(ElementPtr element); + +- /// Removes the element at the given position. If the index is out ++ /// @brief Removes the element at the given position. If the index is out + /// of nothing happens. + /// @param i The index of the element to remove. + virtual void remove(const int i); + +- /// Returns the number of elements in the list. ++ /// @brief Returns the number of elements in the list. + virtual size_t size() const; + +- /// Return true if there are no elements in the list. ++ /// @brief Return true if there are no elements in the list. + virtual bool empty() const; + //@} + +@@ -336,26 +473,26 @@ public: + /// @brief If the Element on which these functions are called are not + /// an instance of MapElement, a TypeError exception is thrown. + //@{ +- /// Returns the ElementPtr at the given key ++ /// @brief Returns the ElementPtr at the given key + /// @param name The key of the Element to return + /// @return The ElementPtr at the given key, or null if not present + virtual ConstElementPtr get(const std::string& name) const; + +- /// Sets the ElementPtr at the given key ++ /// @brief Sets the ElementPtr at the given key + /// @param name The key of the Element to set + /// @param element The ElementPtr to set at the given key. + virtual void set(const std::string& name, ConstElementPtr element); + +- /// Remove the ElementPtr at the given key ++ /// @brief Remove the ElementPtr at the given key + /// @param name The key of the Element to remove + virtual void remove(const std::string& name); + +- /// Checks if there is data at the given key ++ /// @brief Checks if there is data at the given key + /// @param name The key of the Element checked for existence + /// @return true if there is data at the key, false if not. + virtual bool contains(const std::string& name) const; + +- /// Recursively finds any data at the given identifier. The ++ /// @brief Recursively finds any data at the given identifier. The + /// identifier is a /-separated list of names of nested maps, with + /// the last name being the leaf that is returned. + /// +@@ -370,7 +507,7 @@ public: + /// Element::is_null(ElementPtr e). + virtual ConstElementPtr find(const std::string& identifier) const; + +- /// See @c Element::find() ++ /// @brief See @c Element::find() + /// @param identifier The identifier of the element to find + /// @param t Reference to store the resulting ElementPtr, if found. + /// @return true if the element was found, false if not. +@@ -396,25 +533,84 @@ public: + /// Notes: Read notes of IntElement definition about the use of + /// long long int, long int and int. + //@{ ++ /// @brief Create a NullElement. ++ /// ++ /// @param pos The position. ++ /// @return The NullElement at the position. + static ElementPtr create(const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create an IntElement. ++ /// ++ /// @param i The integer. ++ /// @param pos The position. ++ /// @return The IntElement with the argument at the position. + static ElementPtr create(const long long int i, + const Position& pos = ZERO_POSITION()); +- static ElementPtr create(const isc::util::int128_t& i, +- const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create an IntElement (int overload). ++ /// ++ /// @param i The integer. ++ /// @param pos The position. ++ /// @return The IntElement with the argument at the position. + static ElementPtr create(const int i, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create an IntElement (long int overload). ++ /// ++ /// @param i The integer. ++ /// @param pos The position. ++ /// @return The IntElement with the argument at the position. + static ElementPtr create(const long int i, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create an IntElement (int32_t overload). ++ /// ++ /// @param i The integer. ++ /// @param pos The position. ++ /// @return The IntElement with the argument at the position. + static ElementPtr create(const uint32_t i, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create a BigIntElement. ++ /// ++ /// @param i The big integer. ++ /// @param pos The position. ++ /// @return The BigIntElement with the argument at the position. ++ static ElementPtr create(const isc::util::int128_t& i, ++ const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create a DoubleElement. ++ /// ++ /// @param d The double. ++ /// @param pos The position. ++ /// @return The DoubleElement with the argument at the position. + static ElementPtr create(const double d, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create a BoolElement. ++ /// ++ /// @param b The boolean. ++ /// @param pos The position. ++ /// @return The BoolElement with the argument at the position. + static ElementPtr create(const bool b, + const Position& pos = ZERO_POSITION()); ++ ++ /// @brief Create a StringElement. ++ /// ++ /// @param s The string. ++ /// @param pos The position. ++ /// @return The StringElement with the argument at the position. + static ElementPtr create(const std::string& s, + const Position& pos = ZERO_POSITION()); ++ + // need both std:string and char *, since c++ will match + // bool before std::string when you pass it a char * ++ ++ /// @brief Create a StringElement (char* overload). ++ /// ++ /// @param s The string. ++ /// @param pos The position. ++ /// @return The StringElement with the argument at the position. + static ElementPtr create(const char *s, + const Position& pos = ZERO_POSITION()); + +@@ -438,7 +634,7 @@ public: + /// error, an exception of the type isc::data::JSONError is thrown. + + //@{ +- /// Creates an Element from the given JSON string ++ /// @brief Creates an Element from the given JSON string + /// @param in The string to parse the element from + /// @param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed +@@ -446,7 +642,7 @@ public: + /// in the given string. + static ElementPtr fromJSON(const std::string& in, bool preproc = false); + +- /// Creates an Element from the given input stream containing JSON ++ /// @brief Creates an Element from the given input stream containing JSON + /// formatted data. + /// + /// @param in The string to parse the element from +@@ -457,7 +653,7 @@ public: + /// in the given input stream. + static ElementPtr fromJSON(std::istream& in, bool preproc = false); + +- /// Creates an Element from the given input stream containing JSON ++ /// @brief Creates an Element from the given input stream containing JSON + /// formatted data. + /// + /// @param in The string to parse the element from +@@ -471,7 +667,7 @@ public: + static ElementPtr fromJSON(std::istream& in, const std::string& file_name, + bool preproc = false); + +- /// Creates an Element from the given input stream, where we keep ++ /// @brief Creates an Element from the given input stream, where we keep + /// track of the location in the stream for error reporting. + /// + /// @param in The string to parse the element from. +@@ -480,13 +676,14 @@ public: + /// track of the current line. + /// @param pos A reference to the int where the function keeps + /// track of the current position within the current line. +- /// @throw JSONError ++ /// @param level The maximum level of recursion. + /// @return An ElementPtr that contains the element(s) specified + /// in the given input stream. + // make this one private? + /// @throw JSONError + static ElementPtr fromJSON(std::istream& in, const std::string& file, +- int& line, int &pos); ++ int& line, int &pos, ++ unsigned level = MAX_NESTING_LEVEL); + + /// Reads contents of specified file and interprets it as JSON. + /// +@@ -499,23 +696,23 @@ public: + bool preproc = false); + //@} + +- /// @name Type name conversion functions ++ /// @name Type name conversion functions. + +- /// Returns the name of the given type as a string ++ /// @brief Returns the name of the given type as a string + /// +- /// @param type The type to return the name of ++ /// @param type The type to return the name of. + /// @return The name of the type, or "unknown" if the type + /// is not known. + static std::string typeToName(Element::types type); + +- /// Converts the string to the corresponding type ++ /// @brief Converts the string to the corresponding type + /// Throws a TypeError if the name is unknown. + /// +- /// @param type_name The name to get the type of ++ /// @param type_name The name to get the type of. + /// @return the corresponding type value + static Element::types nameToType(const std::string& type_name); + +- /// @brief input text preprocessor ++ /// @brief input text preprocessor. + /// + /// This method performs preprocessing of the input stream (which is + /// expected to contain a text version of to be parsed JSON). For now the +@@ -527,79 +724,43 @@ public: + /// the input stream, filters the content and returns the result in a + /// different stream. + /// +- /// @param in input stream to be preprocessed +- /// @param out output stream (filtered content will be written here) ++ /// @param in input stream to be preprocessed. ++ /// @param out output stream (filtered content will be written here). + static void preprocess(std::istream& in, std::stringstream& out); + + /// @name Wire format factory functions + +- /// These function pparse the wireformat at the given stringstream ++ /// These function parse the wireformat at the given stringstream + /// (of the given length). If there is a parse error an exception + /// of the type isc::cc::DecodeError is raised. + + //@{ +- /// Creates an Element from the wire format in the given ++ /// @brief Creates an Element from the wire format in the given + /// stringstream of the given length. +- /// Since the wire format is JSON, this is the same as ++ /// ++ /// @note: Since the wire format is JSON, this is the same as + /// fromJSON, and could be removed. + /// + /// @param in The input stringstream. +- /// @param length The length of the wireformat data in the stream ++ /// @param length The length of the wireformat data in the stream. + /// @return ElementPtr with the data that is parsed. + static ElementPtr fromWire(std::stringstream& in, int length); + +- /// Creates an Element from the wire format in the given string +- /// Since the wire format is JSON, this is the same as ++ /// @brief Creates an Element from the wire format in the given string. ++ /// ++ /// @note: Since the wire format is JSON, this is the same as + /// fromJSON, and could be removed. + /// +- /// @param s The input string ++ /// @param s The input string. + /// @return ElementPtr with the data that is parsed. + static ElementPtr fromWire(const std::string& s); + //@} + + /// @brief Remove all empty maps and lists from this Element and its + /// descendants. +- void removeEmptyContainersRecursively() { +- if (type_ == list || type_ == map) { +- size_t s(size()); +- for (size_t i = 0; i < s; ++i) { +- // Get child. +- ElementPtr child; +- if (type_ == list) { +- child = getNonConst(i); +- } else if (type_ == map) { +- std::string const key(get(i)->stringValue()); +- // The ElementPtr - ConstElementPtr disparity between +- // ListElement and MapElement is forcing a const cast here. +- // It's undefined behavior to modify it after const casting. +- // The options are limited. I've tried templating, moving +- // this function from a member function to free-standing and +- // taking the Element template as argument. I've tried +- // making it a virtual function with overridden +- // implementations in ListElement and MapElement. Nothing +- // works. +- child = boost::const_pointer_cast(get(key)); +- } +- +- // Makes no sense to continue for non-container children. +- if (child->getType() != list && child->getType() != map) { +- continue; +- } +- +- // Recurse if not empty. +- if (!child->empty()){ +- child->removeEmptyContainersRecursively(); +- } +- +- // When returning from recursion, remove if empty. +- if (child->empty()) { +- remove(i); +- --i; +- --s; +- } +- } +- } +- } ++ /// ++ /// @param level nesting level. ++ void removeEmptyContainersRecursively(unsigned level = MAX_NESTING_LEVEL); + }; + + /// Notes: IntElement type is changed to int64_t. +@@ -622,8 +783,10 @@ public: + bool getValue(int64_t& t) const { t = i; return (true); } + using Element::setValue; + bool setValue(long long int v) { i = v; return (true); } +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + /// @brief Wrapper over int128_t +@@ -655,13 +818,20 @@ public: + + /// @brief Converts the Element to JSON format and appends it to the given + /// stringstream. +- void toJSON(std::ostream& ss) const override; ++ /// ++ /// @param ss The output stream where to append the JSON format. ++ /// @param level The maximum level of recursion. Ignored. ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const override; + + /// @brief Checks whether the other Element is equal. + /// ++ /// @param other The other element to compare with. ++ /// @param level The maximum level of recursion. Ignored. + /// @return true if the other ElementPtr has the same value and the same + /// type (or a different and compatible type), false otherwise. +- bool equals(const Element& other) const override; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const override; + + private: + /// @brief the underlying stored value +@@ -679,8 +849,10 @@ public: + bool getValue(double& t) const { t = d; return (true); } + using Element::setValue; + bool setValue(const double v) { d = v; return (true); } +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + class BoolElement : public Element { +@@ -694,16 +866,20 @@ public: + bool getValue(bool& t) const { t = b; return (true); } + using Element::setValue; + bool setValue(const bool v) { b = v; return (true); } +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + class NullElement : public Element { + public: + NullElement(const Position& pos = ZERO_POSITION()) + : Element(null, pos) {} +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + class StringElement : public Element { +@@ -717,8 +893,10 @@ public: + bool getValue(std::string& t) const { t = s; return (true); } + using Element::setValue; + bool setValue(const std::string& v) { s = v; return (true); } +- void toJSON(std::ostream& ss) const; +- bool equals(const Element& other) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + }; + + class ListElement : public Element { +@@ -748,10 +926,12 @@ public: + void add(ElementPtr e) { l.push_back(e); } + using Element::remove; + void remove(int i) { l.erase(l.begin() + i); } +- void toJSON(std::ostream& ss) const; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const; + size_t size() const { return (l.size()); } + bool empty() const { return (l.empty()); } +- bool equals(const Element& other) const; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const; + + /// @brief Sorts the elements inside the list. + /// +@@ -820,7 +1000,8 @@ public: + bool contains(const std::string& s) const override { + return (m.find(s) != m.end()); + } +- void toJSON(std::ostream& ss) const override; ++ void toJSON(std::ostream& ss, ++ unsigned level = MAX_NESTING_LEVEL) const override; + + // we should name the two finds better... + // find the element at id; raises TypeError if one of the +@@ -842,46 +1023,60 @@ public: + return (m.size()); + } + +- bool equals(const Element& other) const override; ++ bool equals(const Element& other, ++ unsigned level = MAX_NESTING_LEVEL) const override; + + bool empty() const override { return (m.empty()); } + }; + +-/// Checks whether the given ElementPtr is a NULL pointer ++/// Checks whether the given ElementPtr is a null pointer + /// @param p The ElementPtr to check +-/// @return true if it is NULL, false if not. ++/// @return true if it is null, false if not. + bool isNull(ConstElementPtr p); + + /// + /// @brief Remove all values from the first ElementPtr that are + /// equal in the second. Both ElementPtrs MUST be MapElements ++/// + /// The use for this function is to end up with a MapElement that + /// only contains new and changed values (for ModuleCCSession and + /// configuration update handlers) +-/// Raises a TypeError if a or b are not MapElements ++/// ++/// @param a Pointer to the first element. ++/// @param b Pointer to the second element. ++/// @throw TypeError if a or b are not MapElements + void removeIdentical(ElementPtr a, ConstElementPtr b); + + /// @brief Create a new ElementPtr from the first ElementPtr, removing all + /// values that are equal in the second. Both ElementPtrs MUST be MapElements. +-/// The returned ElementPtr will be a MapElement that only contains new and +-/// changed values (for ModuleCCSession and configuration update handlers). +-/// Raises a TypeError if a or b are not MapElements ++/// ++/// @param a Pointer to the first element. ++/// @param b Pointer to the second element. ++/// @throw TypeError if a or b are not MapElements ++/// @return ElementPtr will be a MapElement that only contains new and changed ++/// values (for ModuleCCSession and configuration update handlers). + ConstElementPtr removeIdentical(ConstElementPtr a, ConstElementPtr b); + +-/// @brief Merges the data from other into element. (on the first level). Both +-/// elements must be MapElements. Every string, value pair in other is copied +-/// into element (the ElementPtr of value is copied, this is not a new object) +-/// Unless the value is a NullElement, in which case the key is removed from +-/// element, rather than setting the value to the given NullElement. ++/// @brief Merges the data from other into element. (on the first level). ++ ++/// Both elements must be MapElements. Every string, value pair in ++/// other is copied into element (the ElementPtr of value is copied, ++/// this is not a new object) Unless the value is a NullElement, in ++/// which case the key is removed from element, rather than setting ++/// the value to the given NullElement. + /// This way, we can remove values from for instance maps with configuration + /// data (which would then result in reverting back to the default). +-/// Raises a TypeError if either ElementPtr is not a MapElement ++/// ++/// @param element Pointer to the Element holding data. ++/// @param other Pointer to the other / from Element. ++/// @throw TypeError if either ElementPtr is not a MapElement + void merge(ElementPtr element, ConstElementPtr other); + + /// @brief Function used to check if two MapElements refer to the same +-/// configuration data. It can check if the two MapElements have the same or +-/// have equivalent value for some members. +-/// e.g. ++/// configuration data. ++/// ++/// It can check if the two MapElements have the same or have ++/// equivalent value for some members. e.g. + /// ( + /// left->get("prefix")->stringValue() == right->get("prefix")->stringValue() && + /// left->get("prefix-len")->intValue() == right->get("prefix-len")->intValue() && +@@ -925,7 +1120,7 @@ typedef std::vector HierarchyDescriptor; + + /// @brief Merges the diff data by adding the missing elements from 'other' + /// to 'element' (recursively). Both elements must be the same Element type. +-/// Raises a TypeError if elements are not the same Element type. ++/// + /// @note + /// for non map and list elements the values are updated with the new values + /// for maps: +@@ -941,13 +1136,15 @@ typedef std::vector HierarchyDescriptor; + /// identification keys. + /// @param key The container holding the current element. + /// @param idx The level inside the hierarchy the current element is located. ++/// @param level The maximum level of recursion. ++/// @throw TypeError if elements are not the same Element type. + void mergeDiffAdd(ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, +- size_t idx = 0); ++ size_t idx = 0, unsigned level = Element::MAX_NESTING_LEVEL); + + /// @brief Merges the diff data by removing the data present in 'other' from + /// 'element' (recursively). Both elements must be the same Element type. +-/// Raises a TypeError if elements are not the same Element type. ++//// + /// for non map and list elements the values are set to NullElement + /// for maps: + /// - non map and list elements are removed from the map +@@ -962,14 +1159,15 @@ void mergeDiffAdd(ElementPtr& element, ElementPtr& other, + /// identification keys. + /// @param key The container holding the current element. + /// @param idx The level inside the hierarchy the current element is located. ++/// @param level The maximum level of recursion. ++/// @throw TypeError if elements are not the same Element type. + void mergeDiffDel(ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, +- size_t idx = 0); ++ size_t idx = 0, unsigned level = Element::MAX_NESTING_LEVEL); + + /// @brief Extends data by adding the specified 'extension' elements from + /// 'other' inside the 'container' element (recursively). Both elements must be + /// the same Element type. +-/// Raises a TypeError if elements are not the same Element type. + /// + /// @param container The container holding the data that must be extended. + /// @param extension The name of the element that contains the data that must be +@@ -982,30 +1180,53 @@ void mergeDiffDel(ElementPtr& element, ElementPtr& other, + /// @param idx The level inside the hierarchy the current element is located. + /// @param alter The flag which indicates if the current element should be + /// updated. ++/// @param level The maximum level of recursion. ++/// @throw TypeError if elements are not the same Element type. + void extend(const std::string& container, const std::string& extension, + ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, size_t idx = 0, +- bool alter = false); ++ bool alter = false, unsigned level = Element::MAX_NESTING_LEVEL); + + /// @brief Copy the data up to a nesting level. + /// + /// The copy is a deep copy so nothing is shared if it is not + /// under the given nesting level. + /// +-/// @param from the pointer to the element to copy +-/// @param level nesting level (default is 100, 0 means shallow copy, +-/// negative means outbound and perhaps looping forever). +-/// @return a pointer to a fresh copy ++/// @note: copy is the ONLY method taking a level argument which make ++/// sense outside unit tests, and also which accepts the 0 value. ++/// ++/// @param from the pointer to the element to copy. ++/// @param level nesting level (default is 100, 0 means shallow copy). ++/// @return Pointer to a fresh copy + /// @throw raises a BadValue is a null pointer occurs. +-ElementPtr copy(ConstElementPtr from, int level = 100); ++ElementPtr copy(ConstElementPtr from, unsigned level = Element::MAX_NESTING_LEVEL); + +-/// @brief Compares the data with other using unordered lists ++/// @brief Compares the data with other using unordered lists. + /// + /// This comparison function handles lists (JSON arrays) as + /// unordered multi sets (multi means an item can occurs more + /// than once as soon as it occurs the same number of times). ++/// ++/// @param a Pointer to the first element. ++/// @param b Pointer to the second element. ++/// @return Result of loose comparison. + bool isEquivalent(ConstElementPtr a, ConstElementPtr b); + ++/// @brief Check if the data is circular. ++/// ++/// @param element The @c ConstElementPtr object to check. ++/// @return True if the argument is circular, false otherwise. ++bool IsCircular(ConstElementPtr element); ++ ++/// @brief Compute the nesting depth. ++/// ++/// @param element The @c ConstElementPtr object. ++/// @param max_depth Maximal nesting depth. ++/// @return The nesting depth or max_depth if the object has deeper nesting ++/// including being circular. ++unsigned getNestDepth(ConstElementPtr element, ++ unsigned max_depth = Element::MAX_NESTING_LEVEL); ++ + /// @brief Pretty prints the data into stream. + /// + /// This operator converts the @c ConstElementPtr into a string and +@@ -1013,23 +1234,23 @@ bool isEquivalent(ConstElementPtr a, ConstElementPtr b); + /// indentation @c indent and add at each level @c step spaces. + /// For maps if there is a comment property it is printed first. + /// +-/// @param element A @c ConstElementPtr to pretty print +-/// @param out A @c std::ostream on which the print operation is performed +-/// @param indent An initial number of spaces to add each new line +-/// @param step A number of spaces to add to indentation at a new level ++/// @param element A @c ConstElementPtr to pretty print. ++/// @param out A @c std::ostream on which the print operation is performed. ++/// @param indent An initial number of spaces to add each new line. ++/// @param step A number of spaces to add to indentation at a new level. + void prettyPrint(ConstElementPtr element, std::ostream& out, + unsigned indent = 0, unsigned step = 2); + +-/// @brief Pretty prints the data into string ++/// @brief Pretty prints the data into string. + /// + /// This operator converts the @c ConstElementPtr into a string with + /// an initial indentation @c indent and add at each level @c step spaces. + /// For maps if there is a comment property it is printed first. + /// +-/// @param element A @c ConstElementPtr to pretty print +-/// @param indent An initial number of spaces to add each new line +-/// @param step A number of spaces to add to indentation at a new level +-/// @return a string where element was pretty printed ++/// @param element A @c ConstElementPtr to pretty print. ++/// @param indent An initial number of spaces to add each new line. ++/// @param step A number of spaces to add to indentation at a new level. ++/// @return a string where element was pretty printed. + std::string prettyPrint(ConstElementPtr element, + unsigned indent = 0, unsigned step = 2); + +@@ -1061,8 +1282,31 @@ std::ostream& operator<<(std::ostream& out, const Element::Position& pos); + /// parameter @c out after the insertion operation. + std::ostream& operator<<(std::ostream& out, const Element& e); + ++/// @brief Test equality. ++/// ++/// @param a First element. ++/// @param b Second Element. ++/// @return True when the two elements are equal, false otherwise. + bool operator==(const Element& a, const Element& b); ++ ++/// @brief Test inequality. ++/// ++/// @param a First element. ++/// @param b Second Element. ++/// @return True when the two elements are not equal, false otherwise. + bool operator!=(const Element& a, const Element& b); ++ ++/// @brief Test less than. ++/// ++/// @note: both arguments must have the same supported type i.e. integer, ++/// double, boolean or string. ++/// ++/// @param a First element. ++/// @param b Second Element. ++/// @return True when the value of the first element is less than the value ++/// of the second element. ++/// @throw BadValue when arguments have different type or the type is not ++/// supported. + bool operator<(const Element& a, const Element& b); + + } // namespace data +diff --git a/src/lib/process/redact_config.cc b/src/lib/process/redact_config.cc +index 2127c724d4..10ae6fec73 100644 +--- a/src/lib/process/redact_config.cc ++++ b/src/lib/process/redact_config.cc +@@ -18,10 +18,14 @@ namespace { + + template + ElementPtrType +-redact(ElementPtrType const& element, list json_path, string obscure) { ++ redact(ElementPtrType const& element, list json_path, ++ string obscure, unsigned level) { + if (!element) { + isc_throw(BadValue, "redact() got a null pointer"); + } ++ if (level == 0) { ++ isc_throw(BadValue, "redact() elements nested too deeply"); ++ } + + string const next_key(json_path.empty() ? string() : json_path.front()); + ElementPtr result; +@@ -36,9 +40,9 @@ redact(ElementPtrType const& element, list json_path, string obscure) { + // Then redact all children. + result = Element::createList(); + for (ElementPtr const& child : element->listValue()) { +- result->add(redact(child, json_path, obscure)); ++ result->add(redact(child, json_path, obscure, level - 1)); + } +- return result; ++ return (result); + } + } else if (element->getType() == Element::map) { + // If we are looking for anything or if we have reached the end of a +@@ -64,23 +68,25 @@ redact(ElementPtrType const& element, list json_path, string obscure) { + result->set(key, value); + } else { + // We are looking for anything '*' so redact further. +- result->set(key, redact(value, json_path, obscure)); ++ result->set(key, redact(value, json_path, obscure, ++ level - 1)); + } + } + } +- return result; ++ return (result); + } else { + ConstElementPtr child(element->get(next_key)); + if (child) { +- result = isc::data::copy(element, 1); ++ result = isc::data::copy(element, 1U); + json_path.pop_front(); +- result->set(next_key, redact(child, json_path, obscure)); +- return result; ++ result->set(next_key, ++ redact(child, json_path, obscure, level - 1)); ++ return (result); + } + } + } + +- return element; ++ return (element); + } + + } // namespace +@@ -90,8 +96,8 @@ namespace process { + + ConstElementPtr + redactConfig(ConstElementPtr const& element, list const& json_path, +- string obscure) { +- return redact(element, json_path, obscure); ++ string obscure, unsigned max_nesting_depth) { ++ return (redact(element, json_path, obscure, max_nesting_depth)); + } + + } // namespace process +diff --git a/src/lib/process/redact_config.h b/src/lib/process/redact_config.h +index 0b66e22970..6ea5d3a06d 100644 +--- a/src/lib/process/redact_config.h ++++ b/src/lib/process/redact_config.h +@@ -26,13 +26,15 @@ namespace process { + /// configuration and smaller subtrees in recursive calls. + /// @param json_path JSON path to redact + /// @param obscure new value of secrets / passwords ++/// @param max_nesting_depth maximum nesting depth + /// + /// @return a copy of the config where passwords and secrets were replaced by + /// asterisks so it can be safely logged to an unprivileged place. + isc::data::ConstElementPtr + redactConfig(isc::data::ConstElementPtr const& element, + std::list const& json_path = {"*"}, +- std::string obscure = "*****"); ++ std::string obscure = "*****", ++ unsigned max_nesting_depth = isc::data::Element::MAX_NESTING_LEVEL); + + } // namespace process + } // namespace isc +commit 316e8d04aedf9392bf9a2edcb35bf2a719c17b13 +Author: Razvan Becheriu +Date: Thu Mar 12 14:56:47 2026 +0200 + + [#4388] backport #4365 to v3_0 + +[Martin Osvald ] +NOTE: Tests were removed. + +diff --git a/src/bin/agent/simple_parser.cc b/src/bin/agent/simple_parser.cc +index b4564fa5ce..182abcbf06 100644 +--- a/src/bin/agent/simple_parser.cc ++++ b/src/bin/agent/simple_parser.cc +@@ -16,6 +16,7 @@ + #include + + using namespace isc::data; ++using namespace isc::dhcp; + using namespace isc::asiolink; + using namespace isc::config; + +@@ -151,6 +152,10 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx, + if (ctrl_sockets) { + auto const& sockets_map = ctrl_sockets->mapValue(); + for (auto const& cs : sockets_map) { ++ if (!cs.second->get("socket-name")) { ++ isc_throw(DhcpConfigError, "missing parameter 'socket-name' from '" << cs.first ++ << "' service map (" << cs.second->getPosition() << ")"); ++ } + // Add a validated socket name so we can suppress it in + // toElement() but don't have to revalidate it every time we + // want to use it. +commit f1647a5b6d531c1cac4063bef22c4d97568a788e +Author: Razvan Becheriu +Date: Fri Mar 13 11:54:10 2026 +0200 + + [#4402] bump up library versions for 3.0.3 release + +[Martin Osvald ] +NOTE: Version and KEA_HOOKS_VERSION did not match. + +diff --git a/src/lib/cc/meson.build b/src/lib/cc/meson.build +index c22a1a1..b9924f7 100644 +--- a/src/lib/cc/meson.build ++++ b/src/lib/cc/meson.build +@@ -21,7 +21,7 @@ kea_cc_lib = shared_library( + install_rpath: INSTALL_RPATH, + build_rpath: BUILD_RPATH, + link_with: LIBS_BUILT_SO_FAR, +- version: '82.0.0', ++ version: '83.0.0', + ) + LIBS_BUILT_SO_FAR = [kea_cc_lib] + LIBS_BUILT_SO_FAR + subdir('tests') +diff --git a/src/lib/config/meson.build b/src/lib/config/meson.build +index 803cdfb..efd11a7 100644 +--- a/src/lib/config/meson.build ++++ b/src/lib/config/meson.build +@@ -20,7 +20,7 @@ kea_config_lib = shared_library( + install_rpath: INSTALL_RPATH, + build_rpath: BUILD_RPATH, + link_with: LIBS_BUILT_SO_FAR, +- version: '83.0.0', ++ version: '84.0.0', + cpp_args: [f'-DCONTROL_SOCKET_DIR="@RUNSTATEDIR_INSTALLED@"'], + ) + LIBS_BUILT_SO_FAR = [kea_config_lib] + LIBS_BUILT_SO_FAR +diff --git a/src/lib/hooks/hooks.h b/src/lib/hooks/hooks.h +index 27be442..222c2e5 100644 +--- a/src/lib/hooks/hooks.h ++++ b/src/lib/hooks/hooks.h +@@ -12,8 +12,15 @@ + + namespace { + +-// Version 30001 of the hooks framework, set for Kea 3.0.1 +-const int KEA_HOOKS_VERSION = 30001; ++// [Martin Osvald ] ++// NOTE: I am not sure how many custom hooks exist that compile different ++// code depending on the value of KEA_HOOKS_VERSION and expect ++// specific functionality to be present. ++// We have not backported the changes that led to the bump to 30002. ++// I am concerned that a SONAME version bump alone may not be ++// sufficient with changes fixing CVE-2026-3608. ++// Version 30003 of the hooks framework, set for Kea 3.0.3 ++const int KEA_HOOKS_VERSION = 30003; + + // Names of the framework functions. + const char* const LOAD_FUNCTION_NAME = "load"; +diff --git a/src/lib/hooks/meson.build b/src/lib/hooks/meson.build +index 51256d7..bc3b977 100644 +--- a/src/lib/hooks/meson.build ++++ b/src/lib/hooks/meson.build +@@ -23,7 +23,7 @@ kea_hooks_lib = shared_library( + install_rpath: INSTALL_RPATH, + build_rpath: BUILD_RPATH, + link_with: LIBS_BUILT_SO_FAR, +- version: '119.0.0', ++ version: '121.0.0', + ) + LIBS_BUILT_SO_FAR = [kea_hooks_lib] + LIBS_BUILT_SO_FAR + subdir('tests') +diff --git a/src/lib/process/meson.build b/src/lib/process/meson.build +index ce49503..7ffaa74 100644 +--- a/src/lib/process/meson.build ++++ b/src/lib/process/meson.build +@@ -22,7 +22,7 @@ kea_process_lib = shared_library( + install_rpath: INSTALL_RPATH, + build_rpath: BUILD_RPATH, + link_with: LIBS_BUILT_SO_FAR, +- version: '90.0.0', ++ version: '91.0.0', + ) + LIBS_BUILT_SO_FAR = [kea_process_lib] + LIBS_BUILT_SO_FAR + subdir('testutils') +diff --git a/src/lib/util/meson.build b/src/lib/util/meson.build +index d3e1efa..3a6d85d 100644 +--- a/src/lib/util/meson.build ++++ b/src/lib/util/meson.build +@@ -25,7 +25,7 @@ kea_util_lib = shared_library( + install_rpath: INSTALL_RPATH, + build_rpath: BUILD_RPATH, + link_with: LIBS_BUILT_SO_FAR, +- version: '101.0.0', ++ version: '102.0.0', + ) + subdir('io') + subdir('unittests') diff --git a/kea.spec b/kea.spec index 67c06fa..f768c1a 100644 --- a/kea.spec +++ b/kea.spec @@ -1,8 +1,8 @@ ## START: Set by rpmautospec -## (rpmautospec version 0.6.5) +## (rpmautospec version 0.8.3) ## RPMAUTOSPEC: autorelease, autochangelog %define autorelease(e:s:pb:n) %{?-p:0.}%{lua: - release_number = 2; + release_number = 3; base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}")); print(release_number + base_release_number - 1); }%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}} @@ -45,8 +45,15 @@ Source16: systemd-sysusers.conf Patch1: kea-sd-daemon.patch # https://issues.redhat.com/browse/RHEL-125048 -# Based on: https://gitlab.isc.org/isc-projects/kea/-/commit/92b65b2345e07d826b56ffd65cf47538f1c7a271 +# Based on: +# https://gitlab.isc.org/isc-projects/kea/-/commit/92b65b2345e07d826b56ffd65cf47538f1c7a271 Patch2: CVE-2025-11232.patch +# https://redhat.atlassian.net/browse/RHEL-159879 +# Based on: +# https://gitlab.isc.org/isc-projects/kea/-/commit/6ef02087bdf0f5158ad0654d9bdc30c166241e19 +# https://gitlab.isc.org/isc-projects/kea/-/commit/316e8d04aedf9392bf9a2edcb35bf2a719c17b13 +# https://gitlab.isc.org/isc-projects/kea/-/commit/f1647a5b6d531c1cac4063bef22c4d97568a788e +Patch3: CVE-2026-3608.patch BuildRequires: boost-devel # %%meson -D crypto=openssl @@ -416,9 +423,9 @@ fi # >=f41: find `rpm --eval %%{_topdir}`/BUILD/kea-*/BUILDROOT/usr/lib64/ -type f | grep /usr/lib64/libkea | sed -e 's#.*/usr/lib64\(.*\.so\.[0-9]\+\)\.[0-9]\+\.[0-9]\+#%%{_libdir}\1*#' | sort %{_libdir}/libkea-asiodns.so.62* %{_libdir}/libkea-asiolink.so.88* -%{_libdir}/libkea-cc.so.82* +%{_libdir}/libkea-cc.so.83* %{_libdir}/libkea-cfgrpt.so.3* -%{_libdir}/libkea-config.so.83* +%{_libdir}/libkea-config.so.84* %{_libdir}/libkea-cryptolink.so.64* %{_libdir}/libkea-d2srv.so.63* %{_libdir}/libkea-database.so.76* @@ -428,17 +435,17 @@ fi %{_libdir}/libkea-dns.so.71* %{_libdir}/libkea-eval.so.84* %{_libdir}/libkea-exceptions.so.45* -%{_libdir}/libkea-hooks.so.119* +%{_libdir}/libkea-hooks.so.121* %{_libdir}/libkea-http.so.87* %{_libdir}/libkea-log-interprocess.so.3* %{_libdir}/libkea-log.so.75* %{_libdir}/libkea-mysql.so.88* %{_libdir}/libkea-pgsql.so.88* -%{_libdir}/libkea-process.so.90* +%{_libdir}/libkea-process.so.91* %{_libdir}/libkea-stats.so.53* %{_libdir}/libkea-tcp.so.33* %{_libdir}/libkea-util-io.so.12* -%{_libdir}/libkea-util.so.101* +%{_libdir}/libkea-util.so.102* %files keama %license COPYING @@ -447,6 +454,9 @@ fi %changelog ## START: Generated by rpmautospec +* Fri Mar 27 2026 Martin Osvald - 3.0.1-3 +- Fixes CVE-2026-3608 + * Thu Oct 30 2025 Martin Osvald - 3.0.1-2 - Fixes CVE-2025-11232