diff --git a/.gitignore b/.gitignore index 21d0e26..2a6e9b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -kea-3.0.1.tar.xz +kea-3.0.3.tar.xz keama-4.5.0.tar.gz diff --git a/CVE-2025-11232.patch b/CVE-2025-11232.patch deleted file mode 100644 index 07af123..0000000 --- a/CVE-2025-11232.patch +++ /dev/null @@ -1,180 +0,0 @@ -diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes -index 1deb2e6074..b359d09616 100644 ---- a/src/bin/dhcp4/dhcp4_messages.mes -+++ b/src/bin/dhcp4/dhcp4_messages.mes -@@ -164,6 +164,20 @@ This debug message is issued when the server starts processing the Hostname - option sent in the client's query. The argument includes the client and - transaction identification information. - -+% DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY %1: sanitizing client's Hostname option '%2' yielded an empty string -+Logged at debug log level 50. -+This debug message is issued when the result of sanitizing the -+hostname option(12) sent by the client is an empty string. When this occurs -+the server will ignore the hostname option. The arguments include the -+client and the hostname option it sent. -+ -+% DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY %1: sanitizing client's FQDN option '%2' yielded an empty string -+Logged at debug log level 50. -+This debug message is issued when the result of sanitizing the -+FQDN option(81) sent by the client is an empty string. When this occurs -+the server will ignore the FQDN option. The arguments include the -+client and the FQDN option it sent. -+ - % DHCP4_CLIENT_NAME_PROC_FAIL %1: failed to process the fqdn or hostname sent by a client: %2 - Logged at debug log level 55. - This debug message is issued when the DHCP server was unable to process the -diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc -index 0701ed41e9..a6be662889 100644 ---- a/src/bin/dhcp4/dhcp4_srv.cc -+++ b/src/bin/dhcp4/dhcp4_srv.cc -@@ -2714,8 +2714,15 @@ Dhcpv4Srv::processClientFqdnOption(Dhcpv4Exchange& ex) { - } else { - // Adjust the domain name based on domain name value and type sent by the - // client and current configuration. -- d2_mgr.adjustDomainName(*fqdn, *fqdn_resp, -- *(ex.getContext()->getDdnsParams())); -+ try { -+ d2_mgr.adjustDomainName(*fqdn, *fqdn_resp, -+ *(ex.getContext()->getDdnsParams())); -+ } catch (const FQDNScrubbedEmpty& scrubbed) { -+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY) -+ .arg(ex.getQuery()->getLabel()) -+ .arg(scrubbed.what()); -+ return; -+ } - } - - // Add FQDN option to the response message. Note that, there may be some -@@ -2857,7 +2864,15 @@ Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) { - ex.getContext()->getDdnsParams()->getHostnameSanitizer(); - - if (sanitizer) { -- hostname = sanitizer->scrub(hostname); -+ auto tmp = sanitizer->scrub(hostname); -+ if (tmp.empty()) { -+ LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY) -+ .arg(ex.getQuery()->getLabel()) -+ .arg(hostname); -+ return; -+ } -+ -+ hostname = tmp; - } - - // Convert hostname to lower case. -diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes -index fff50ed367..79fc984ff5 100644 ---- a/src/bin/dhcp6/dhcp6_messages.mes -+++ b/src/bin/dhcp6/dhcp6_messages.mes -@@ -1167,3 +1167,10 @@ such modification. The clients will remember previous server-id, and will - use it to extend their leases. As a result, they will have to go through - a rebinding phase to re-acquire their leases and associate them with a - new server id. -+ -+% DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY %1: sanitizing client's FQDN option '%2' yielded an empty string -+Logged at debug log level 50. -+This debug message is issued when the result of sanitizing the -+FQDN option(39) sent by the client is an empty string. When this occurs -+the server will ignore the FQDN option. The arguments include the -+client and the FQDN option it sent. -diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc -index 417960b126..f999c3178f 100644 ---- a/src/bin/dhcp6/dhcp6_srv.cc -+++ b/src/bin/dhcp6/dhcp6_srv.cc -@@ -2332,7 +2332,14 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer, - } else { - // Adjust the domain name based on domain name value and type sent by - // the client and current configuration. -- d2_mgr.adjustDomainName(*fqdn, *fqdn_resp, *ddns_params); -+ try { -+ d2_mgr.adjustDomainName(*fqdn, *fqdn_resp, *ddns_params); -+ } catch(const FQDNScrubbedEmpty& scrubbed) { -+ LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY) -+ .arg(question->getLabel()) -+ .arg(scrubbed.what()); -+ return; -+ } - } - - // Once we have the FQDN setup to use it for the lease hostname. This -diff --git a/src/lib/dhcpsrv/d2_client_mgr.cc b/src/lib/dhcpsrv/d2_client_mgr.cc -index 84ee11d9fb..54c815176e 100644 ---- a/src/lib/dhcpsrv/d2_client_mgr.cc -+++ b/src/lib/dhcpsrv/d2_client_mgr.cc -@@ -186,10 +186,15 @@ std::string - D2ClientMgr::qualifyName(const std::string& partial_name, - const DdnsParams& ddns_params, - const bool trailing_dot) const { -+ if (partial_name.empty()) { -+ isc_throw(BadValue, "D2ClientMgr::qualifyName" -+ " - partial_name cannot be an empty string"); -+ } -+ - std::ostringstream gen_name; - gen_name << partial_name; - std::string suffix = ddns_params.getQualifyingSuffix(); -- if (!suffix.empty() && partial_name.back() != '.') { -+ if (!suffix.empty() && (partial_name.back() != '.')) { - bool suffix_present = true; - std::string str = gen_name.str(); - auto suffix_rit = suffix.rbegin(); -@@ -241,7 +246,7 @@ D2ClientMgr::qualifyName(const std::string& partial_name, - // If the trailing dot should not be appended but it is present, - // remove it. - if ((len > 0) && (str[len - 1] == '.')) { -- gen_name.str(str.substr(0,len-1)); -+ gen_name.str(str.substr(0, len-1)); - } - - } -diff --git a/src/lib/dhcpsrv/d2_client_mgr.h b/src/lib/dhcpsrv/d2_client_mgr.h -index 7344f19a40..238fd0a415 100644 ---- a/src/lib/dhcpsrv/d2_client_mgr.h -+++ b/src/lib/dhcpsrv/d2_client_mgr.h -@@ -30,6 +30,14 @@ - namespace isc { - namespace dhcp { - -+/// @brief Exception thrown when host name sanitizing reduces -+/// the domain name to an empty string. -+class FQDNScrubbedEmpty : public Exception { -+public: -+ FQDNScrubbedEmpty(const char* file, size_t line, const char* what) : -+ isc::Exception(file, line, what) { } -+}; -+ - /// @brief Defines the type for D2 IO error handler. - /// This callback is invoked when a send to kea-dhcp-ddns completes with a - /// failed status. This provides the application layer (Kea) with a means to -@@ -197,6 +205,7 @@ public: - /// suffix itself is empty (i.e. ""). - /// - /// @return std::string containing the qualified name. -+ /// @throw BadValue if partial_name is empty. - std::string qualifyName(const std::string& partial_name, - const DdnsParams& ddns_params, - const bool trailing_dot) const; -@@ -264,6 +273,9 @@ public: - /// @param ddns_params DDNS behavioral configuration parameters - /// @tparam T FQDN Option class containing the FQDN data such as - /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn -+ /// -+ /// @throw FQDNScrubbedEmpty if hostname sanitizing reduces the input domain -+ /// name to an empty string. - template - void adjustDomainName(const T& fqdn, T& fqdn_resp, - const DdnsParams& ddns_params); -@@ -515,7 +527,12 @@ D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp, const DdnsParams& ddn - ss << sanitizer->scrub(label); - } - -- client_name = ss.str(); -+ std::string clean_name = ss.str(); -+ if (clean_name.empty() || clean_name == ".") { -+ isc_throw(FQDNScrubbedEmpty, client_name); -+ } -+ -+ client_name = clean_name; - } - - // If the supplied name is partial, qualify it by adding the suffix. diff --git a/CVE-2026-3608.patch b/CVE-2026-3608.patch deleted file mode 100644 index c98365b..0000000 --- a/CVE-2026-3608.patch +++ /dev/null @@ -1,1827 +0,0 @@ -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-3.0.1.tar.xz.asc b/kea-3.0.1.tar.xz.asc deleted file mode 100644 index 0d93514..0000000 --- a/kea-3.0.1.tar.xz.asc +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCgAdFiEEAlmjO19aOkRmzzRcel4ITKylGIQFAmil4+0ACgkQel4ITKyl -GIRxlg//f+A5yEQ6RKl0DOJfQIKKPZ9SL/2a04q6Gcay2ghUl8LZOiiO98RYicyV -PmrsY/5/nuJmLPRSPKt/pmosfgZUbWK9yuKOoBrfu0KZfQWMX+1ZJIfagY47PCvU -RKtT50+iPOsYZAtuRw8faO8g/rdgl1vMreSIjCPZTG1R4qZkQXNnwhNRV7O3pzsr -wSndDINLvjQoYYbklKpUszoBNppXzBCegzVFjcIjNOSta2U8xEPGQ7sv2JvKkaYf -bVjzKuoEVn0YkkAdf7C7vLotl4UZESNo/w+DcRbrbq/FYpT1R8YWzVAJtqJCVNFI -q0WqlK5/G2/4giAveSWzuX0dnk+OZ5kQrd323Ol6MlM/O4ymkHK7OJrcxLLrEyIC -OnRNWQqVzJddmUTOntvoLk90wJ9yF1jrdM+S3xTpOJzhnfRoFuioZ7njjfGTyskR -Nlt4DX3wGsg0quDQfQJAf1z1qk651/OIF27KThj5jNOPB5eWz4YjJBht9T+eHlcS -kOsNwnKtdZe+KiGeFCsfWU7wOR65w4kQXoH1ruFqVa44ZZKUvzDi4fiJYYfJLedJ -FfBx3c65B0COk+3kOWjAV1F+Zaf0PlrEnb75zbN0O4BrztPL12HhDtjF+CbAuOG2 -k4ZpogxZ0Q6MhiQjGFiFMs2PN2FlgiaL2zbKKG/KIjUzPnog60c= -=1ZFT ------END PGP SIGNATURE----- diff --git a/kea-3.0.3.tar.xz.asc b/kea-3.0.3.tar.xz.asc new file mode 100644 index 0000000..f521797 --- /dev/null +++ b/kea-3.0.3.tar.xz.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCgAdFiEE2mo1COZypJ3Tgq/ZW49NkbiO2QkFAmm6cKwACgkQW49NkbiO +2QmL/w/+O7Ijj50yoo0wXKJ/e9JCoyXXKzTGcUGjtUzhWy2pXM+mwm8rig85MBp+ +z073nVsez997lE40O77J1y+XjzL5HZrdlTtDxMnBr3CcHqUkW/jw4UbNo2QLOMXN +UQDx98Z7hTZdwSg527oaQr/zi13JcKtPt3KTJIoKX7SMXtLN8zDdIFXtSmS5Jz7L +QhXSmaXyKHGe16y5TMrN89Qync2fWhXo9pd91VN88yREhkUbPLHUek5JmpGnZyNz +L1wZI6PjdiqPd3ytMQrZ4Aop/CY44wE44gqiYBeoWpaRQCAZUFVssrQR1MURwL/b +9Mp2GmeG7sfhyD3pK1yRdfBBn64SPlV4Fx8GSapFYw2ifM1GJp20eIfqrOLJNM/o ++13hTivcPStVmpAcx2pzg1THHYK4/+XGq7ntrtWNyBEbxN6KhJDrhbSBmpp3J/nz +72kVHgg0vf6f611STEBlMvDirkDCzLmss6B8FL3sAIX/KNKqZX4qu6mzOMAjGmPw +GwRHiVoOpu4nrQataSLYr/qbpsblhKcLqQR2AolOAYIgPNUtFjFKGTiwfyjOoMCJ +zwQMyscCshcS8vFx2F5rxetwWsaqQVu4UEhEJRKl00ivG0QfBcmI0aoM1VvUt/Qz +IiwMJ+v3Jx8Nn+LYYDmT0WJFn18IZDfi0rWIysvUWDNUXOxPQZs= +=1b96 +-----END PGP SIGNATURE----- diff --git a/kea-ctrl-agent.service b/kea-ctrl-agent.service index 249cc8f..0cfcf16 100644 --- a/kea-ctrl-agent.service +++ b/kea-ctrl-agent.service @@ -6,13 +6,14 @@ After=network-online.target After=time-sync.target [Service] +Type=notify User=kea -Environment="KEA_PIDFILE_DIR=/run/kea" -Environment="KEA_LOCKFILE_DIR=/run/kea" +#Environment="KEA_PIDFILE_DIR=/var/run/kea" +#Environment="KEA_LOCKFILE_DIR=/var/run/kea" #Environment="KEA_LOGGER_DESTINATION=/var/log/kea/early-startup.log" -Environment="KEA_DHCP_DATA_DIR=/var/lib/kea" -Environment="KEA_LOG_FILE_DIR=/var/log/kea" -Environment="KEA_CONTROL_SOCKET_DIR=/run/kea" +#Environment="KEA_DHCP_DATA_DIR=/var/lib/kea" +#Environment="KEA_LOG_FILE_DIR=/var/log/kea" +#Environment="KEA_CONTROL_SOCKET_DIR=/var/run/kea" ConfigurationDirectory=kea ConfigurationDirectoryMode=0750 RuntimeDirectory=kea diff --git a/kea-dhcp-ddns.service b/kea-dhcp-ddns.service index 2f58a51..03ec228 100644 --- a/kea-dhcp-ddns.service +++ b/kea-dhcp-ddns.service @@ -6,14 +6,15 @@ After=network-online.target After=time-sync.target [Service] +Type=notify User=kea AmbientCapabilities=CAP_NET_BIND_SERVICE -Environment="KEA_PIDFILE_DIR=/run/kea" -Environment="KEA_LOCKFILE_DIR=/run/kea" +#Environment="KEA_PIDFILE_DIR=/var/run/kea" +#Environment="KEA_LOCKFILE_DIR=/var/run/kea" #Environment="KEA_LOGGER_DESTINATION=/var/log/kea/early-startup.log" -Environment="KEA_DHCP_DATA_DIR=/var/lib/kea" -Environment="KEA_LOG_FILE_DIR=/var/log/kea" -Environment="KEA_CONTROL_SOCKET_DIR=/run/kea" +#Environment="KEA_DHCP_DATA_DIR=/var/lib/kea" +#Environment="KEA_LOG_FILE_DIR=/var/log/kea" +#Environment="KEA_CONTROL_SOCKET_DIR=/var/run/kea" ConfigurationDirectory=kea ConfigurationDirectoryMode=0750 RuntimeDirectory=kea diff --git a/kea-dhcp4.service b/kea-dhcp4.service index dc7a57a..b3f3e88 100644 --- a/kea-dhcp4.service +++ b/kea-dhcp4.service @@ -9,12 +9,12 @@ After=time-sync.target Type=notify User=kea AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW -Environment="KEA_PIDFILE_DIR=/run/kea" -Environment="KEA_LOCKFILE_DIR=/run/kea" +#Environment="KEA_PIDFILE_DIR=/var/run/kea" +#Environment="KEA_LOCKFILE_DIR=/var/run/kea" #Environment="KEA_LOGGER_DESTINATION=/var/log/kea/early-startup.log" -Environment="KEA_DHCP_DATA_DIR=/var/lib/kea" -Environment="KEA_LOG_FILE_DIR=/var/log/kea" -Environment="KEA_CONTROL_SOCKET_DIR=/run/kea" +#Environment="KEA_DHCP_DATA_DIR=/var/lib/kea" +#Environment="KEA_LOG_FILE_DIR=/var/log/kea" +#Environment="KEA_CONTROL_SOCKET_DIR=/var/run/kea" ConfigurationDirectory=kea ConfigurationDirectoryMode=0750 RuntimeDirectory=kea diff --git a/kea-dhcp6.service b/kea-dhcp6.service index 35872dc..767043e 100644 --- a/kea-dhcp6.service +++ b/kea-dhcp6.service @@ -9,12 +9,12 @@ After=time-sync.target Type=notify User=kea AmbientCapabilities=CAP_NET_BIND_SERVICE -Environment="KEA_PIDFILE_DIR=/run/kea" -Environment="KEA_LOCKFILE_DIR=/run/kea" +#Environment="KEA_PIDFILE_DIR=/var/run/kea" +#Environment="KEA_LOCKFILE_DIR=/var/run/kea" #Environment="KEA_LOGGER_DESTINATION=/var/log/kea/early-startup.log" -Environment="KEA_DHCP_DATA_DIR=/var/lib/kea" -Environment="KEA_LOG_FILE_DIR=/var/log/kea" -Environment="KEA_CONTROL_SOCKET_DIR=/run/kea" +#Environment="KEA_DHCP_DATA_DIR=/var/lib/kea" +#Environment="KEA_LOG_FILE_DIR=/var/log/kea" +#Environment="KEA_CONTROL_SOCKET_DIR=/var/run/kea" ConfigurationDirectory=kea ConfigurationDirectoryMode=0750 RuntimeDirectory=kea diff --git a/kea-sd-daemon.patch b/kea-sd-daemon.patch index a3a9ea0..f059e87 100644 --- a/kea-sd-daemon.patch +++ b/kea-sd-daemon.patch @@ -36,7 +36,7 @@ index 42ccf28..cc6354a 100644 #mesondefine LIBC_MUSL diff --git a/meson.build b/meson.build -index 66e7fd0..dc86d89 100644 +index 8ed5b2d..df4f125 100644 --- a/meson.build +++ b/meson.build @@ -100,6 +100,7 @@ krb5_opt = get_option('krb5') @@ -61,7 +61,7 @@ index 66e7fd0..dc86d89 100644 # Google Test GTEST_DEP = dependency( 'gtest', -@@ -867,6 +875,11 @@ else +@@ -886,6 +894,11 @@ else report_conf_data.set('SYSREPOCPP_VERSION', 'no') report_conf_data.set('SYSREPOCPP_PREFIX', 'no') endif @@ -85,6 +85,110 @@ index 5c222d5..3ecd2e1 100644 # Options for enabling testing code (not real features). option( +diff --git a/src/bin/agent/ca_process.cc b/src/bin/agent/ca_process.cc +index f01dd97..4793067 100644 +--- a/src/bin/agent/ca_process.cc ++++ b/src/bin/agent/ca_process.cc +@@ -18,6 +18,10 @@ + #include + #include + ++#ifdef HAVE_LIBSYSTEMD_DAEMON ++#include ++#endif ++ + using namespace isc::asiolink; + using namespace isc::config; + using namespace isc::data; +@@ -42,7 +46,15 @@ CtrlAgentProcess::init() { + + void + CtrlAgentProcess::run() { ++ + LOG_INFO(agent_logger, CTRL_AGENT_STARTED).arg(VERSION); ++#ifdef HAVE_LIBSYSTEMD_DAEMON ++ // Notify systemd about the same ++ sd_notifyf(0, "READY=1\n" ++ "STATUS=Processing requests...\n" ++ "MAINPID=%lu", ++ (unsigned long) getpid()); ++#endif + + LOG_WARN(agent_logger, CTRL_AGENT_IS_DEPRECATED); + +diff --git a/src/bin/agent/meson.build b/src/bin/agent/meson.build +index c6afbfa..2d30179 100644 +--- a/src/bin/agent/meson.build ++++ b/src/bin/agent/meson.build +@@ -1,3 +1,8 @@ ++kea_ctrl_agent_dependencies = [CRYPTO_DEP] ++if SYSTEMD_DEP.found() ++ kea_ctrl_agent_dependencies += [SYSTEMD_DEP] ++endif ++ + agent_lib = static_library( + 'agent', + 'agent_lexer.cc', +@@ -17,7 +22,7 @@ agent_lib = static_library( + executable( + 'kea-ctrl-agent', + 'main.cc', +- dependencies: [CRYPTO_DEP], ++ dependencies: kea_ctrl_agent_dependencies, + include_directories: [include_directories('.')] + INCLUDES, + install: true, + install_dir: SBINDIR, +diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc +index 7db49a3..cdb9cef 100644 +--- a/src/bin/d2/d2_process.cc ++++ b/src/bin/d2/d2_process.cc +@@ -21,6 +21,10 @@ + #include + #include + ++#ifdef HAVE_LIBSYSTEMD_DAEMON ++#include ++#endif ++ + using namespace isc::asiolink; + using namespace isc::config; + using namespace isc::data; +@@ -95,6 +99,13 @@ D2Process::init() { + void + D2Process::run() { + LOG_INFO(d2_logger, DHCP_DDNS_STARTED).arg(VERSION); ++#ifdef HAVE_LIBSYSTEMD_DAEMON ++ // Notify systemd about the same ++ sd_notifyf(0, "READY=1\n" ++ "STATUS=Dispatching packets...\n" ++ "MAINPID=%lu", ++ (unsigned long) getpid()); ++#endif + + if (!PathChecker::shouldEnforceSecurity()) { + LOG_WARN(d2_logger, DHCP_DDNS_SECURITY_CHECKS_DISABLED); +diff --git a/src/bin/d2/meson.build b/src/bin/d2/meson.build +index 012b40d..3aff0c1 100644 +--- a/src/bin/d2/meson.build ++++ b/src/bin/d2/meson.build +@@ -1,3 +1,8 @@ ++kea_ddns_dependencies = [CRYPTO_DEP] ++if SYSTEMD_DEP.found() ++ kea_ddns_dependencies += [SYSTEMD_DEP] ++endif ++ + d2_lib = static_library( + 'd2', + 'check_exists_add.cc', +@@ -21,7 +26,7 @@ d2_lib = static_library( + executable( + 'kea-dhcp-ddns', + 'main.cc', +- dependencies: [CRYPTO_DEP], ++ dependencies: kea_ddns_dependencies, + include_directories: [include_directories('.')] + INCLUDES, + install: true, + install_dir: SBINDIR, diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc index 4f88e29..5581b7a 100644 --- a/src/bin/dhcp4/main.cc diff --git a/kea.spec b/kea.spec index f768c1a..d449afc 100644 --- a/kea.spec +++ b/kea.spec @@ -1,15 +1,15 @@ ## START: Set by rpmautospec -## (rpmautospec version 0.8.3) +## (rpmautospec version 0.8.1) ## RPMAUTOSPEC: autorelease, autochangelog %define autorelease(e:s:pb:n) %{?-p:0.}%{lua: - release_number = 3; + release_number = 1; base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}")); print(release_number + base_release_number - 1); }%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}} ## END: Set by rpmautospec Name: kea -Version: 3.0.1 +Version: 3.0.3 Release: %autorelease Summary: DHCPv4, DHCPv6 and DDNS server from ISC License: MPL-2.0 AND BSL-1.0 @@ -44,16 +44,6 @@ Source15: systemd-tmpfiles.conf 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 -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 @@ -188,8 +178,6 @@ export KEA_PKG_TYPE_IN_CONFIGURE="rpm" -D postgresql=enabled \ -D systemd=enabled -# Messages need to be regenerated by kea-msg-compiler for CVE-2025-11232.patch -%meson_build messages %meson_build %meson_build doc @@ -281,25 +269,6 @@ install -dm 0750 %{buildroot}%{_localstatedir}/log/kea/ %sysusers_create_compat %{S:16} %post -# Kea runs under kea user instead of root now, but if its files got altered, their new -# ownership&permissions won't get changed so fix them to prevent startup failures -[ "`stat --format '%U:%G' %{_rundir}/kea/logger_lockfile 2>&1 | grep root:root`" = "root:root" ] \ - && chown kea:kea %{_rundir}/kea/logger_lockfile -[ "`stat --format '%U:%G' %{_sharedstatedir}/kea/kea-leases4.csv* 2>&1 | grep root:root | head -1`" = "root:root" ] \ - && chown kea:kea %{_sharedstatedir}/kea/kea-leases4.csv* && chmod 0640 %{_sharedstatedir}/kea/kea-leases4.csv* -[ "`stat --format '%U:%G' %{_sharedstatedir}/kea/kea-leases6.csv* 2>&1 | grep root:root | head -1`" = "root:root" ] \ - && chown kea:kea %{_sharedstatedir}/kea/kea-leases6.csv* && chmod 0640 %{_sharedstatedir}/kea/kea-leases6.csv* -[ "`stat --format '%U:%G' %{_sharedstatedir}/kea/kea-dhcp6-serverid 2>&1 | grep root:root`" = "root:root" ] \ - && chown kea:kea %{_sharedstatedir}/kea/kea-dhcp6-serverid -[ "`stat --format '%U:%G' %{_sysconfdir}/kea/kea*.conf 2>&1 | grep root:root | head -1`" = "root:root" ] \ - && chown root:kea %{_sysconfdir}/kea/kea*.conf && chmod 0640 %{_sysconfdir}/kea/kea*.conf - -# Remove /tmp/ from socket-name for existing configurations to fix CVE-2025-32802 -for i in kea-ctrl-agent.conf kea-dhcp4.conf kea-dhcp6.conf kea-dhcp-ddns.conf; do - if [ -n "`grep '\"socket-name\": \"/tmp/' %{_sysconfdir}/kea/$i`" ]; then - sed -i.CVE-2025-32802.bak 's#\("socket-name": "/tmp/\)\(.*\)#"socket-name": "\2#g' %{_sysconfdir}/kea/$i - fi -done # Set a pseudo-random password for default config to secure fresh install and allow CA startup without user intervention if [[ ! -s %{_sysconfdir}/kea/kea-api-password && -n `grep '"password-file": "kea-api-password"' %{_sysconfdir}/kea/kea-ctrl-agent.conf` ]]; then (umask 0027; head -c 32 /dev/urandom | base64 > %{_sysconfdir}/kea/kea-api-password) @@ -431,7 +400,7 @@ fi %{_libdir}/libkea-database.so.76* %{_libdir}/libkea-dhcp_ddns.so.68* %{_libdir}/libkea-dhcp.so.109* -%{_libdir}/libkea-dhcpsrv.so.130* +%{_libdir}/libkea-dhcpsrv.so.131* %{_libdir}/libkea-dns.so.71* %{_libdir}/libkea-eval.so.84* %{_libdir}/libkea-exceptions.so.45* @@ -454,10 +423,25 @@ fi %changelog ## START: Generated by rpmautospec -* Fri Mar 27 2026 Martin Osvald - 3.0.1-3 +* Tue Jun 30 2026 Martin Osvald - 3.0.3-1 +- New version 3.0.3 - Fixes CVE-2026-3608 -* Thu Oct 30 2025 Martin Osvald - 3.0.1-2 +* Mon Jan 12 2026 Martin Osvald - 3.0.2-5 +- Do not set environmental variables to prevent clashes with compilation + time settings + +* Fri Jan 09 2026 Martin Osvald - 3.0.2-4 +- Create /var/lib/kea through systemd-tmpfiles + +* Fri Jan 09 2026 Martin Osvald - 3.0.2-3 +- Fix files owned by usbmon in image mode + +* Sun Dec 07 2025 Martin Osvald - 3.0.2-2 +- Support for sending startup notifications to systemd II + +* Thu Oct 30 2025 Martin Osvald - 3.0.2-1 +- New version 3.0.2 - Fixes CVE-2025-11232 * Tue Sep 02 2025 Martin Osvald - 3.0.1-1 diff --git a/sources b/sources index 78dfa08..c6eafce 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (kea-3.0.1.tar.xz) = 84e2164aa91c95b2e6e65994a2327fa1233c82b06af69312f55464119d4edc6151a4662a54f4a3ae83e0487dac7b25c5e59e60bcbed653fd30fb32ae7cacddf3 +SHA512 (kea-3.0.3.tar.xz) = 38dff3a089b76d043c0bd1cc1bbf480b71b627d101954eb2d8200bce507dcd43e8a1cbdb5b55b7f425424a156ec56e36af52b6c591ecf6c80c29b58875b5b585 SHA512 (keama-4.5.0.tar.gz) = 2e48987e21999718be7ceb5b604be672c84666b07dde9545285ff7146ab6825e81af1ec3b5a4b50f20e61b40ed11b0254e3705cc580bb85de24b77ee8cbca162 diff --git a/systemd-tmpfiles.conf b/systemd-tmpfiles.conf index 80257b0..9011f0d 100644 --- a/systemd-tmpfiles.conf +++ b/systemd-tmpfiles.conf @@ -2,3 +2,4 @@ # See tmpfiles.d(5) for details d /run/kea 0750 kea kea - +d /var/lib/kea 0750 kea kea -