diff options
| author | Ross Burton <ross.burton@arm.com> | 2025-11-03 14:21:46 +0000 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2025-11-06 15:09:32 +0000 |
| commit | 1db7c5487bb9c20b40efef7c31d2a0ec31620d0d (patch) | |
| tree | 63a777f7f1107cb5ec06da015e65783962048e68 | |
| parent | 310183b813dea5898aff8d425b5d5c5063af354a (diff) | |
| download | poky-1db7c5487bb9c20b40efef7c31d2a0ec31620d0d.tar.gz | |
kea: fix CVE-2025-11232
Backport a patch from upstream to resolve CVE-2025-11232:
Invalid characters cause assert
To trigger the issue, three configuration parameters must have
specific settings: "hostname-char-set" must be left at the default
setting, which is "[^A-Za-z0-9.-]"; "hostname-char-replacement" must
be empty (the default); and "ddns-qualifying-suffix" must NOT be empty
(the default is empty). DDNS updates do not need to be enabled for
this issue to manifest. A client that sends certain option content
would then cause kea-dhcp4 to exit unexpectedly.
(From OE-Core rev: f9331b42fd8b0df64517969a794a93d41624bd96)
Signed-off-by: Ross Burton <ross.burton@arm.com>
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
| -rw-r--r-- | meta/recipes-connectivity/kea/files/CVE-2025-11232.patch | 474 | ||||
| -rw-r--r-- | meta/recipes-connectivity/kea/kea_3.0.1.bb | 1 |
2 files changed, 475 insertions, 0 deletions
diff --git a/meta/recipes-connectivity/kea/files/CVE-2025-11232.patch b/meta/recipes-connectivity/kea/files/CVE-2025-11232.patch new file mode 100644 index 0000000000..659627deba --- /dev/null +++ b/meta/recipes-connectivity/kea/files/CVE-2025-11232.patch | |||
| @@ -0,0 +1,474 @@ | |||
| 1 | From 92b65b2345e07d826b56ffd65cf47538f1c7a271 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Thomas Markwalder <tmark@isc.org> | ||
| 3 | Date: Tue, 7 Oct 2025 14:41:16 -0400 | ||
| 4 | Subject: [PATCH] [#4155] Backport #4142 to v3_0 | ||
| 5 | |||
| 6 | Invalid characters cause assert | ||
| 7 | |||
| 8 | To trigger the issue, three configuration parameters must have | ||
| 9 | specific settings: "hostname-char-set" must be left at the default | ||
| 10 | setting, which is "[^A-Za-z0-9.-]"; "hostname-char-replacement" must | ||
| 11 | be empty (the default); and "ddns-qualifying-suffix" must NOT be empty | ||
| 12 | (the default is empty). DDNS updates do not need to be enabled for | ||
| 13 | this issue to manifest. A client that sends certain option content | ||
| 14 | would then cause kea-dhcp4 to exit unexpectedly. | ||
| 15 | |||
| 16 | CVE: CVE-2025-11232 | ||
| 17 | Upstream-Status: Backport [https://github.com/isc-projects/kea/commit/92b65b2345e07d826b56ffd65cf47538f1c7a271] | ||
| 18 | Signed-off-by: Ross Burton <ross.burton@arm.com> | ||
| 19 | |||
| 20 | new file: changelog_unreleased/CVE-2025-11232-catch-empty-sanitized-hostname | ||
| 21 | modified: src/bin/dhcp4/dhcp4_messages.cc | ||
| 22 | modified: src/bin/dhcp4/dhcp4_messages.h | ||
| 23 | modified: src/bin/dhcp4/dhcp4_messages.mes | ||
| 24 | modified: src/bin/dhcp4/dhcp4_srv.cc | ||
| 25 | modified: src/bin/dhcp4/tests/fqdn_unittest.cc | ||
| 26 | modified: src/bin/dhcp6/dhcp6_messages.cc | ||
| 27 | modified: src/bin/dhcp6/dhcp6_messages.h | ||
| 28 | modified: src/bin/dhcp6/dhcp6_messages.mes | ||
| 29 | modified: src/bin/dhcp6/dhcp6_srv.cc | ||
| 30 | modified: src/bin/dhcp6/tests/fqdn_unittest.cc | ||
| 31 | modified: src/lib/dhcpsrv/d2_client_mgr.cc | ||
| 32 | modified: src/lib/dhcpsrv/d2_client_mgr.h | ||
| 33 | modified: src/lib/dhcpsrv/tests/d2_client_unittest.cc | ||
| 34 | --- | ||
| 35 | ...-2025-11232-catch-empty-sanitized-hostname | 6 +++ | ||
| 36 | src/bin/dhcp4/dhcp4_messages.cc | 4 ++ | ||
| 37 | src/bin/dhcp4/dhcp4_messages.h | 2 + | ||
| 38 | src/bin/dhcp4/dhcp4_messages.mes | 14 +++++ | ||
| 39 | src/bin/dhcp4/dhcp4_srv.cc | 21 ++++++-- | ||
| 40 | src/bin/dhcp4/tests/fqdn_unittest.cc | 54 ++++++++++++++++++- | ||
| 41 | src/bin/dhcp6/dhcp6_messages.cc | 2 + | ||
| 42 | src/bin/dhcp6/dhcp6_messages.h | 1 + | ||
| 43 | src/bin/dhcp6/dhcp6_messages.mes | 7 +++ | ||
| 44 | src/bin/dhcp6/dhcp6_srv.cc | 9 +++- | ||
| 45 | src/bin/dhcp6/tests/fqdn_unittest.cc | 23 ++++++++ | ||
| 46 | src/lib/dhcpsrv/d2_client_mgr.cc | 9 +++- | ||
| 47 | src/lib/dhcpsrv/d2_client_mgr.h | 19 ++++++- | ||
| 48 | src/lib/dhcpsrv/tests/d2_client_unittest.cc | 42 +++++++++++++++ | ||
| 49 | 14 files changed, 205 insertions(+), 8 deletions(-) | ||
| 50 | create mode 100644 changelog_unreleased/CVE-2025-11232-catch-empty-sanitized-hostname | ||
| 51 | |||
| 52 | diff --git a/src/bin/dhcp4/dhcp4_messages.cc b/src/bin/dhcp4/dhcp4_messages.cc | ||
| 53 | index e06ce6a121..5c6a334bad 100644 | ||
| 54 | --- a/src/bin/dhcp4/dhcp4_messages.cc | ||
| 55 | +++ b/src/bin/dhcp4/dhcp4_messages.cc | ||
| 56 | @@ -26,9 +26,11 @@ extern const isc::log::MessageID DHCP4_CLASS_UNCONFIGURED = "DHCP4_CLASS_UNCONFI | ||
| 57 | extern const isc::log::MessageID DHCP4_CLIENTID_IGNORED_FOR_LEASES = "DHCP4_CLIENTID_IGNORED_FOR_LEASES"; | ||
| 58 | extern const isc::log::MessageID DHCP4_CLIENT_FQDN_DATA = "DHCP4_CLIENT_FQDN_DATA"; | ||
| 59 | extern const isc::log::MessageID DHCP4_CLIENT_FQDN_PROCESS = "DHCP4_CLIENT_FQDN_PROCESS"; | ||
| 60 | +extern const isc::log::MessageID DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY = "DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY"; | ||
| 61 | extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_DATA = "DHCP4_CLIENT_HOSTNAME_DATA"; | ||
| 62 | extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_MALFORMED = "DHCP4_CLIENT_HOSTNAME_MALFORMED"; | ||
| 63 | extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_PROCESS = "DHCP4_CLIENT_HOSTNAME_PROCESS"; | ||
| 64 | +extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY = "DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY"; | ||
| 65 | extern const isc::log::MessageID DHCP4_CLIENT_NAME_PROC_FAIL = "DHCP4_CLIENT_NAME_PROC_FAIL"; | ||
| 66 | extern const isc::log::MessageID DHCP4_CONFIG_COMPLETE = "DHCP4_CONFIG_COMPLETE"; | ||
| 67 | extern const isc::log::MessageID DHCP4_CONFIG_LOAD_FAIL = "DHCP4_CONFIG_LOAD_FAIL"; | ||
| 68 | @@ -206,9 +208,11 @@ const char* values[] = { | ||
| 69 | "DHCP4_CLIENTID_IGNORED_FOR_LEASES", "%1: not using client identifier for lease allocation for subnet %2", | ||
| 70 | "DHCP4_CLIENT_FQDN_DATA", "%1: Client sent FQDN option: %2", | ||
| 71 | "DHCP4_CLIENT_FQDN_PROCESS", "%1: processing Client FQDN option", | ||
| 72 | + "DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY", "%1: sanitizing client's FQDN option '%2' yielded an empty string", | ||
| 73 | "DHCP4_CLIENT_HOSTNAME_DATA", "%1: client sent Hostname option: %2", | ||
| 74 | "DHCP4_CLIENT_HOSTNAME_MALFORMED", "%1: client hostname option malformed: %2", | ||
| 75 | "DHCP4_CLIENT_HOSTNAME_PROCESS", "%1: processing client's Hostname option", | ||
| 76 | + "DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY", "%1: sanitizing client's Hostname option '%2' yielded an empty string", | ||
| 77 | "DHCP4_CLIENT_NAME_PROC_FAIL", "%1: failed to process the fqdn or hostname sent by a client: %2", | ||
| 78 | "DHCP4_CONFIG_COMPLETE", "DHCPv4 server has completed configuration: %1", | ||
| 79 | "DHCP4_CONFIG_LOAD_FAIL", "configuration error using file: %1, reason: %2", | ||
| 80 | diff --git a/src/bin/dhcp4/dhcp4_messages.h b/src/bin/dhcp4/dhcp4_messages.h | ||
| 81 | index 9a4d0cda21..6e45c63053 100644 | ||
| 82 | --- a/src/bin/dhcp4/dhcp4_messages.h | ||
| 83 | +++ b/src/bin/dhcp4/dhcp4_messages.h | ||
| 84 | @@ -27,9 +27,11 @@ extern const isc::log::MessageID DHCP4_CLASS_UNCONFIGURED; | ||
| 85 | extern const isc::log::MessageID DHCP4_CLIENTID_IGNORED_FOR_LEASES; | ||
| 86 | extern const isc::log::MessageID DHCP4_CLIENT_FQDN_DATA; | ||
| 87 | extern const isc::log::MessageID DHCP4_CLIENT_FQDN_PROCESS; | ||
| 88 | +extern const isc::log::MessageID DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY; | ||
| 89 | extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_DATA; | ||
| 90 | extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_MALFORMED; | ||
| 91 | extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_PROCESS; | ||
| 92 | +extern const isc::log::MessageID DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY; | ||
| 93 | extern const isc::log::MessageID DHCP4_CLIENT_NAME_PROC_FAIL; | ||
| 94 | extern const isc::log::MessageID DHCP4_CONFIG_COMPLETE; | ||
| 95 | extern const isc::log::MessageID DHCP4_CONFIG_LOAD_FAIL; | ||
| 96 | diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes | ||
| 97 | index 1deb2e6074..b359d09616 100644 | ||
| 98 | --- a/src/bin/dhcp4/dhcp4_messages.mes | ||
| 99 | +++ b/src/bin/dhcp4/dhcp4_messages.mes | ||
| 100 | @@ -164,6 +164,20 @@ This debug message is issued when the server starts processing the Hostname | ||
| 101 | option sent in the client's query. The argument includes the client and | ||
| 102 | transaction identification information. | ||
| 103 | |||
| 104 | +% DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY %1: sanitizing client's Hostname option '%2' yielded an empty string | ||
| 105 | +Logged at debug log level 50. | ||
| 106 | +This debug message is issued when the result of sanitizing the | ||
| 107 | +hostname option(12) sent by the client is an empty string. When this occurs | ||
| 108 | +the server will ignore the hostname option. The arguments include the | ||
| 109 | +client and the hostname option it sent. | ||
| 110 | + | ||
| 111 | +% DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY %1: sanitizing client's FQDN option '%2' yielded an empty string | ||
| 112 | +Logged at debug log level 50. | ||
| 113 | +This debug message is issued when the result of sanitizing the | ||
| 114 | +FQDN option(81) sent by the client is an empty string. When this occurs | ||
| 115 | +the server will ignore the FQDN option. The arguments include the | ||
| 116 | +client and the FQDN option it sent. | ||
| 117 | + | ||
| 118 | % DHCP4_CLIENT_NAME_PROC_FAIL %1: failed to process the fqdn or hostname sent by a client: %2 | ||
| 119 | Logged at debug log level 55. | ||
| 120 | This debug message is issued when the DHCP server was unable to process the | ||
| 121 | diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc | ||
| 122 | index 0701ed41e9..a6be662889 100644 | ||
| 123 | --- a/src/bin/dhcp4/dhcp4_srv.cc | ||
| 124 | +++ b/src/bin/dhcp4/dhcp4_srv.cc | ||
| 125 | @@ -2714,8 +2714,15 @@ Dhcpv4Srv::processClientFqdnOption(Dhcpv4Exchange& ex) { | ||
| 126 | } else { | ||
| 127 | // Adjust the domain name based on domain name value and type sent by the | ||
| 128 | // client and current configuration. | ||
| 129 | - d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp, | ||
| 130 | - *(ex.getContext()->getDdnsParams())); | ||
| 131 | + try { | ||
| 132 | + d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp, | ||
| 133 | + *(ex.getContext()->getDdnsParams())); | ||
| 134 | + } catch (const FQDNScrubbedEmpty& scrubbed) { | ||
| 135 | + LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY) | ||
| 136 | + .arg(ex.getQuery()->getLabel()) | ||
| 137 | + .arg(scrubbed.what()); | ||
| 138 | + return; | ||
| 139 | + } | ||
| 140 | } | ||
| 141 | |||
| 142 | // Add FQDN option to the response message. Note that, there may be some | ||
| 143 | @@ -2857,7 +2864,15 @@ Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) { | ||
| 144 | ex.getContext()->getDdnsParams()->getHostnameSanitizer(); | ||
| 145 | |||
| 146 | if (sanitizer) { | ||
| 147 | - hostname = sanitizer->scrub(hostname); | ||
| 148 | + auto tmp = sanitizer->scrub(hostname); | ||
| 149 | + if (tmp.empty()) { | ||
| 150 | + LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL, DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY) | ||
| 151 | + .arg(ex.getQuery()->getLabel()) | ||
| 152 | + .arg(hostname); | ||
| 153 | + return; | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + hostname = tmp; | ||
| 157 | } | ||
| 158 | |||
| 159 | // Convert hostname to lower case. | ||
| 160 | diff --git a/src/bin/dhcp4/tests/fqdn_unittest.cc b/src/bin/dhcp4/tests/fqdn_unittest.cc | ||
| 161 | index a5d3e4c21a..18e4c6d4b9 100644 | ||
| 162 | --- a/src/bin/dhcp4/tests/fqdn_unittest.cc | ||
| 163 | +++ b/src/bin/dhcp4/tests/fqdn_unittest.cc | ||
| 164 | @@ -2253,7 +2253,7 @@ TEST_F(NameDhcpv4SrvTest, sanitizeHostDefault) { | ||
| 165 | }, | ||
| 166 | { | ||
| 167 | "qualified host name with nuls", | ||
| 168 | - std::string("four-ok-host\000.other.org",23), | ||
| 169 | + std::string("four-ok-host\000.other.org", 23), | ||
| 170 | "four-ok-host.other.org" | ||
| 171 | } | ||
| 172 | }; | ||
| 173 | @@ -3203,4 +3203,56 @@ TEST_F(NameDhcpv4SrvTest, poolDdnsParametersTest) { | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | +// Verifies that when the FQDN option is scrubbed empty it is logged | ||
| 178 | +// and ignored. | ||
| 179 | +TEST_F(NameDhcpv4SrvTest, hostnameScrubbedEmpty) { | ||
| 180 | + Dhcp4Client client(srv_, Dhcp4Client::SELECTING); | ||
| 181 | + | ||
| 182 | + // Configure DHCP server. | ||
| 183 | + configure(CONFIGS[2], *client.getServer()); | ||
| 184 | + | ||
| 185 | + // Set the hostname option. | ||
| 186 | + ASSERT_NO_THROW(client.includeHostname("___")); | ||
| 187 | + | ||
| 188 | + // Send the DHCPDISCOVER and make sure that the server responded. | ||
| 189 | + ASSERT_NO_THROW(client.doDiscover()); | ||
| 190 | + auto resp = client.getContext().response_; | ||
| 191 | + ASSERT_TRUE(resp); | ||
| 192 | + ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType())); | ||
| 193 | + | ||
| 194 | + // Should have logged that it was scrubbed empty. | ||
| 195 | + std::string log = "DHCP4_CLIENT_HOSTNAME_SCRUBBED_EMPTY"; | ||
| 196 | + EXPECT_EQ(1, countFile(log)); | ||
| 197 | + | ||
| 198 | + // Hostname should not be in the response. | ||
| 199 | + ASSERT_FALSE(resp->getOption(DHO_HOST_NAME)); | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +// Verifies that when the FQDN option is scrubbed empty it is logged | ||
| 203 | +// and ignored. | ||
| 204 | +TEST_F(NameDhcpv4SrvTest, fqdnScrubbedEmpty) { | ||
| 205 | + Dhcp4Client client(srv_, Dhcp4Client::SELECTING); | ||
| 206 | + | ||
| 207 | + // Configure DHCP server. | ||
| 208 | + configure(CONFIGS[2], *client.getServer()); | ||
| 209 | + | ||
| 210 | + // Include the Client FQDN option. | ||
| 211 | + ASSERT_NO_THROW(client.includeFQDN(Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, | ||
| 212 | + "___", Option4ClientFqdn::PARTIAL)); | ||
| 213 | + | ||
| 214 | + // Send the DHCPDISCOVER and make sure that the server responded. | ||
| 215 | + ASSERT_NO_THROW(client.doDiscover()); | ||
| 216 | + auto resp = client.getContext().response_; | ||
| 217 | + ASSERT_TRUE(resp); | ||
| 218 | + ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType())); | ||
| 219 | + | ||
| 220 | + // Should have logged that it was scrubbed empty. | ||
| 221 | + std::string log = "DHCP4_CLIENT_FQDN_SCRUBBED_EMPTY"; | ||
| 222 | + EXPECT_EQ(1, countFile(log)); | ||
| 223 | + | ||
| 224 | + // Hostname should not be in the response. | ||
| 225 | + ASSERT_FALSE(resp->getOption(DHO_FQDN)); | ||
| 226 | +} | ||
| 227 | + | ||
| 228 | + | ||
| 229 | } // end of anonymous namespace | ||
| 230 | diff --git a/src/bin/dhcp6/dhcp6_messages.cc b/src/bin/dhcp6/dhcp6_messages.cc | ||
| 231 | index 229ba74450..9619481aba 100644 | ||
| 232 | --- a/src/bin/dhcp6/dhcp6_messages.cc | ||
| 233 | +++ b/src/bin/dhcp6/dhcp6_messages.cc | ||
| 234 | @@ -27,6 +27,7 @@ extern const isc::log::MessageID DHCP6_CLASSES_ASSIGNED = "DHCP6_CLASSES_ASSIGNE | ||
| 235 | extern const isc::log::MessageID DHCP6_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION = "DHCP6_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION"; | ||
| 236 | extern const isc::log::MessageID DHCP6_CLASS_ASSIGNED = "DHCP6_CLASS_ASSIGNED"; | ||
| 237 | extern const isc::log::MessageID DHCP6_CLASS_UNCONFIGURED = "DHCP6_CLASS_UNCONFIGURED"; | ||
| 238 | +extern const isc::log::MessageID DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY = "DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY"; | ||
| 239 | extern const isc::log::MessageID DHCP6_CONFIG_COMPLETE = "DHCP6_CONFIG_COMPLETE"; | ||
| 240 | extern const isc::log::MessageID DHCP6_CONFIG_LOAD_FAIL = "DHCP6_CONFIG_LOAD_FAIL"; | ||
| 241 | extern const isc::log::MessageID DHCP6_CONFIG_PACKET_QUEUE = "DHCP6_CONFIG_PACKET_QUEUE"; | ||
| 242 | @@ -203,6 +204,7 @@ const char* values[] = { | ||
| 243 | "DHCP6_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION", "%1: client packet has been assigned to the following classes: %2", | ||
| 244 | "DHCP6_CLASS_ASSIGNED", "%1: client packet has been assigned to the following class: %2", | ||
| 245 | "DHCP6_CLASS_UNCONFIGURED", "%1: client packet belongs to an unconfigured class: %2", | ||
| 246 | + "DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY", "%1: sanitizing client's FQDN option '%2' yielded an empty string", | ||
| 247 | "DHCP6_CONFIG_COMPLETE", "DHCPv6 server has completed configuration: %1", | ||
| 248 | "DHCP6_CONFIG_LOAD_FAIL", "configuration error using file: %1, reason: %2", | ||
| 249 | "DHCP6_CONFIG_PACKET_QUEUE", "DHCPv6 packet queue info after configuration: %1", | ||
| 250 | diff --git a/src/bin/dhcp6/dhcp6_messages.h b/src/bin/dhcp6/dhcp6_messages.h | ||
| 251 | index 186f7d557a..7af56e716a 100644 | ||
| 252 | --- a/src/bin/dhcp6/dhcp6_messages.h | ||
| 253 | +++ b/src/bin/dhcp6/dhcp6_messages.h | ||
| 254 | @@ -28,6 +28,7 @@ extern const isc::log::MessageID DHCP6_CLASSES_ASSIGNED; | ||
| 255 | extern const isc::log::MessageID DHCP6_CLASSES_ASSIGNED_AFTER_SUBNET_SELECTION; | ||
| 256 | extern const isc::log::MessageID DHCP6_CLASS_ASSIGNED; | ||
| 257 | extern const isc::log::MessageID DHCP6_CLASS_UNCONFIGURED; | ||
| 258 | +extern const isc::log::MessageID DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY; | ||
| 259 | extern const isc::log::MessageID DHCP6_CONFIG_COMPLETE; | ||
| 260 | extern const isc::log::MessageID DHCP6_CONFIG_LOAD_FAIL; | ||
| 261 | extern const isc::log::MessageID DHCP6_CONFIG_PACKET_QUEUE; | ||
| 262 | diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes | ||
| 263 | index fff50ed367..79fc984ff5 100644 | ||
| 264 | --- a/src/bin/dhcp6/dhcp6_messages.mes | ||
| 265 | +++ b/src/bin/dhcp6/dhcp6_messages.mes | ||
| 266 | @@ -1167,3 +1167,10 @@ such modification. The clients will remember previous server-id, and will | ||
| 267 | use it to extend their leases. As a result, they will have to go through | ||
| 268 | a rebinding phase to re-acquire their leases and associate them with a | ||
| 269 | new server id. | ||
| 270 | + | ||
| 271 | +% DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY %1: sanitizing client's FQDN option '%2' yielded an empty string | ||
| 272 | +Logged at debug log level 50. | ||
| 273 | +This debug message is issued when the result of sanitizing the | ||
| 274 | +FQDN option(39) sent by the client is an empty string. When this occurs | ||
| 275 | +the server will ignore the FQDN option. The arguments include the | ||
| 276 | +client and the FQDN option it sent. | ||
| 277 | diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc | ||
| 278 | index 417960b126..f999c3178f 100644 | ||
| 279 | --- a/src/bin/dhcp6/dhcp6_srv.cc | ||
| 280 | +++ b/src/bin/dhcp6/dhcp6_srv.cc | ||
| 281 | @@ -2332,7 +2332,14 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer, | ||
| 282 | } else { | ||
| 283 | // Adjust the domain name based on domain name value and type sent by | ||
| 284 | // the client and current configuration. | ||
| 285 | - d2_mgr.adjustDomainName<Option6ClientFqdn>(*fqdn, *fqdn_resp, *ddns_params); | ||
| 286 | + try { | ||
| 287 | + d2_mgr.adjustDomainName<Option6ClientFqdn>(*fqdn, *fqdn_resp, *ddns_params); | ||
| 288 | + } catch(const FQDNScrubbedEmpty& scrubbed) { | ||
| 289 | + LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY) | ||
| 290 | + .arg(question->getLabel()) | ||
| 291 | + .arg(scrubbed.what()); | ||
| 292 | + return; | ||
| 293 | + } | ||
| 294 | } | ||
| 295 | |||
| 296 | // Once we have the FQDN setup to use it for the lease hostname. This | ||
| 297 | diff --git a/src/bin/dhcp6/tests/fqdn_unittest.cc b/src/bin/dhcp6/tests/fqdn_unittest.cc | ||
| 298 | index ca51856e67..7891c1f5e6 100644 | ||
| 299 | --- a/src/bin/dhcp6/tests/fqdn_unittest.cc | ||
| 300 | +++ b/src/bin/dhcp6/tests/fqdn_unittest.cc | ||
| 301 | @@ -2425,4 +2425,27 @@ TEST_F(FqdnDhcpv6SrvTest, poolDdnsParametersTest) { | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | +// Verify an FQDN with all invalid chars is ignored. | ||
| 306 | +TEST_F(FqdnDhcpv6SrvTest, fqdnScrubbedEmpty) { | ||
| 307 | + // Create the query. | ||
| 308 | + Pkt6Ptr question = generateMessage(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, | ||
| 309 | + "___" , Option6ClientFqdn::FULL, true); | ||
| 310 | + ASSERT_TRUE(getClientFqdnOption(question)); | ||
| 311 | + subnet_->setHostnameCharReplacement(""); | ||
| 312 | + | ||
| 313 | + // Create the response with an "assigned" lease. | ||
| 314 | + // Set the selected subnet so ddns params get returned correctly. | ||
| 315 | + AllocEngine::ClientContext6 ctx; | ||
| 316 | + ctx.subnet_ = subnet_; | ||
| 317 | + Pkt6Ptr answer = generateMessageWithIds(DHCPV6_ADVERTISE); | ||
| 318 | + addIA(1234, IOAddress("2001:db8:1::1"), answer, ctx); | ||
| 319 | + | ||
| 320 | + // Process the client's FQDN. | ||
| 321 | + ASSERT_NO_THROW(srv_->processClientFqdn(question, answer, ctx)); | ||
| 322 | + | ||
| 323 | + // Should not have an FQDN option in the answer. | ||
| 324 | + EXPECT_FALSE(answer->getOption(D6O_CLIENT_FQDN)); | ||
| 325 | + countFile("DHCP6_CLIENT_FQDN_SCRUBBED_EMPTY"); | ||
| 326 | +} | ||
| 327 | + | ||
| 328 | } // end of anonymous namespace | ||
| 329 | diff --git a/src/lib/dhcpsrv/d2_client_mgr.cc b/src/lib/dhcpsrv/d2_client_mgr.cc | ||
| 330 | index 84ee11d9fb..54c815176e 100644 | ||
| 331 | --- a/src/lib/dhcpsrv/d2_client_mgr.cc | ||
| 332 | +++ b/src/lib/dhcpsrv/d2_client_mgr.cc | ||
| 333 | @@ -186,10 +186,15 @@ std::string | ||
| 334 | D2ClientMgr::qualifyName(const std::string& partial_name, | ||
| 335 | const DdnsParams& ddns_params, | ||
| 336 | const bool trailing_dot) const { | ||
| 337 | + if (partial_name.empty()) { | ||
| 338 | + isc_throw(BadValue, "D2ClientMgr::qualifyName" | ||
| 339 | + " - partial_name cannot be an empty string"); | ||
| 340 | + } | ||
| 341 | + | ||
| 342 | std::ostringstream gen_name; | ||
| 343 | gen_name << partial_name; | ||
| 344 | std::string suffix = ddns_params.getQualifyingSuffix(); | ||
| 345 | - if (!suffix.empty() && partial_name.back() != '.') { | ||
| 346 | + if (!suffix.empty() && (partial_name.back() != '.')) { | ||
| 347 | bool suffix_present = true; | ||
| 348 | std::string str = gen_name.str(); | ||
| 349 | auto suffix_rit = suffix.rbegin(); | ||
| 350 | @@ -241,7 +246,7 @@ D2ClientMgr::qualifyName(const std::string& partial_name, | ||
| 351 | // If the trailing dot should not be appended but it is present, | ||
| 352 | // remove it. | ||
| 353 | if ((len > 0) && (str[len - 1] == '.')) { | ||
| 354 | - gen_name.str(str.substr(0,len-1)); | ||
| 355 | + gen_name.str(str.substr(0, len-1)); | ||
| 356 | } | ||
| 357 | |||
| 358 | } | ||
| 359 | diff --git a/src/lib/dhcpsrv/d2_client_mgr.h b/src/lib/dhcpsrv/d2_client_mgr.h | ||
| 360 | index 7344f19a40..238fd0a415 100644 | ||
| 361 | --- a/src/lib/dhcpsrv/d2_client_mgr.h | ||
| 362 | +++ b/src/lib/dhcpsrv/d2_client_mgr.h | ||
| 363 | @@ -30,6 +30,14 @@ | ||
| 364 | namespace isc { | ||
| 365 | namespace dhcp { | ||
| 366 | |||
| 367 | +/// @brief Exception thrown when host name sanitizing reduces | ||
| 368 | +/// the domain name to an empty string. | ||
| 369 | +class FQDNScrubbedEmpty : public Exception { | ||
| 370 | +public: | ||
| 371 | + FQDNScrubbedEmpty(const char* file, size_t line, const char* what) : | ||
| 372 | + isc::Exception(file, line, what) { } | ||
| 373 | +}; | ||
| 374 | + | ||
| 375 | /// @brief Defines the type for D2 IO error handler. | ||
| 376 | /// This callback is invoked when a send to kea-dhcp-ddns completes with a | ||
| 377 | /// failed status. This provides the application layer (Kea) with a means to | ||
| 378 | @@ -197,6 +205,7 @@ class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler, | ||
| 379 | /// suffix itself is empty (i.e. ""). | ||
| 380 | /// | ||
| 381 | /// @return std::string containing the qualified name. | ||
| 382 | + /// @throw BadValue if partial_name is empty. | ||
| 383 | std::string qualifyName(const std::string& partial_name, | ||
| 384 | const DdnsParams& ddns_params, | ||
| 385 | const bool trailing_dot) const; | ||
| 386 | @@ -264,6 +273,9 @@ class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler, | ||
| 387 | /// @param ddns_params DDNS behavioral configuration parameters | ||
| 388 | /// @tparam T FQDN Option class containing the FQDN data such as | ||
| 389 | /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn | ||
| 390 | + /// | ||
| 391 | + /// @throw FQDNScrubbedEmpty if hostname sanitizing reduces the input domain | ||
| 392 | + /// name to an empty string. | ||
| 393 | template <class T> | ||
| 394 | void adjustDomainName(const T& fqdn, T& fqdn_resp, | ||
| 395 | const DdnsParams& ddns_params); | ||
| 396 | @@ -515,7 +527,12 @@ D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp, const DdnsParams& ddn | ||
| 397 | ss << sanitizer->scrub(label); | ||
| 398 | } | ||
| 399 | |||
| 400 | - client_name = ss.str(); | ||
| 401 | + std::string clean_name = ss.str(); | ||
| 402 | + if (clean_name.empty() || clean_name == ".") { | ||
| 403 | + isc_throw(FQDNScrubbedEmpty, client_name); | ||
| 404 | + } | ||
| 405 | + | ||
| 406 | + client_name = clean_name; | ||
| 407 | } | ||
| 408 | |||
| 409 | // If the supplied name is partial, qualify it by adding the suffix. | ||
| 410 | diff --git a/src/lib/dhcpsrv/tests/d2_client_unittest.cc b/src/lib/dhcpsrv/tests/d2_client_unittest.cc | ||
| 411 | index 68ad2189d6..00375d0066 100644 | ||
| 412 | --- a/src/lib/dhcpsrv/tests/d2_client_unittest.cc | ||
| 413 | +++ b/src/lib/dhcpsrv/tests/d2_client_unittest.cc | ||
| 414 | @@ -9,6 +9,7 @@ | ||
| 415 | #include <dhcp/option6_client_fqdn.h> | ||
| 416 | #include <dhcpsrv/d2_client_mgr.h> | ||
| 417 | #include <testutils/test_to_element.h> | ||
| 418 | +#include <testutils/gtest_utils.h> | ||
| 419 | #include <exceptions/exceptions.h> | ||
| 420 | #include <util/str.h> | ||
| 421 | |||
| 422 | @@ -627,6 +628,10 @@ TEST_F(D2ClientMgrParamsTest, qualifyName) { | ||
| 423 | qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_not_dot); | ||
| 424 | EXPECT_EQ("somehost.suffix.com", qualified_name); | ||
| 425 | |||
| 426 | + // Verify that an empty name throws. | ||
| 427 | + partial_name = ""; | ||
| 428 | + ASSERT_THROW(mgr.qualifyName(partial_name, *ddns_params_, do_not_dot), BadValue); | ||
| 429 | + | ||
| 430 | // Verify that an empty suffix and false flag, does not change the name | ||
| 431 | subnet_->setDdnsQualifyingSuffix(""); | ||
| 432 | partial_name = "somehost"; | ||
| 433 | @@ -1257,4 +1262,41 @@ TEST_F(D2ClientMgrParamsTest, sanitizeFqdnV6) { | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | +/// @brief Tests adjustDomainName template method with Option4ClientFqdn | ||
| 438 | +/// when sanitizing scrubs input name empty. | ||
| 439 | +TEST_F(D2ClientMgrParamsTest, adjustDomainNameV4ScrubbedEmpty) { | ||
| 440 | + D2ClientMgr mgr; | ||
| 441 | + | ||
| 442 | + // Create enabled configuration | ||
| 443 | + subnet_->setDdnsSendUpdates(false); | ||
| 444 | + subnet_->setDdnsQualifyingSuffix("suffix.com"); | ||
| 445 | + subnet_->setHostnameCharSet("[^A-Za-z0-9.-]"); | ||
| 446 | + subnet_->setHostnameCharReplacement(""); | ||
| 447 | + | ||
| 448 | + Option4ClientFqdn request(0, Option4ClientFqdn::RCODE_CLIENT(), | ||
| 449 | + "___", Option4ClientFqdn::FULL); | ||
| 450 | + | ||
| 451 | + Option4ClientFqdn response(request); | ||
| 452 | + ASSERT_THROW_MSG(mgr.adjustDomainName<Option4ClientFqdn>(request, response, *ddns_params_), | ||
| 453 | + FQDNScrubbedEmpty, "___."); | ||
| 454 | +} | ||
| 455 | + | ||
| 456 | +/// @brief Tests adjustDomainName template method with Option4ClientFqdn | ||
| 457 | +/// when sanitizing scrubs input name empty. | ||
| 458 | +TEST_F(D2ClientMgrParamsTest, adjustDomainNameV6ScrubbedEmpty) { | ||
| 459 | + D2ClientMgr mgr; | ||
| 460 | + | ||
| 461 | + // Create enabled configuration | ||
| 462 | + subnet_->setDdnsSendUpdates(false); | ||
| 463 | + subnet_->setDdnsQualifyingSuffix("suffix.com"); | ||
| 464 | + subnet_->setHostnameCharSet("[^A-Za-z0-9.-]"); | ||
| 465 | + subnet_->setHostnameCharReplacement(""); | ||
| 466 | + | ||
| 467 | + Option6ClientFqdn request(0, "___", Option6ClientFqdn::FULL); | ||
| 468 | + | ||
| 469 | + Option6ClientFqdn response(request); | ||
| 470 | + ASSERT_THROW_MSG(mgr.adjustDomainName<Option6ClientFqdn>(request, response, *ddns_params_), | ||
| 471 | + FQDNScrubbedEmpty, "___."); | ||
| 472 | +} | ||
| 473 | + | ||
| 474 | } // end of anonymous namespace | ||
diff --git a/meta/recipes-connectivity/kea/kea_3.0.1.bb b/meta/recipes-connectivity/kea/kea_3.0.1.bb index 4a6623f94a..8729b1162e 100644 --- a/meta/recipes-connectivity/kea/kea_3.0.1.bb +++ b/meta/recipes-connectivity/kea/kea_3.0.1.bb | |||
| @@ -21,6 +21,7 @@ SRC_URI = "http://ftp.isc.org/isc/kea/${PV}/${BP}.tar.xz \ | |||
| 21 | file://0001-meson-use-a-runtime-safe-interpreter-string.patch \ | 21 | file://0001-meson-use-a-runtime-safe-interpreter-string.patch \ |
| 22 | file://0001-mk_cfgrpt.sh-strip-prefixes.patch \ | 22 | file://0001-mk_cfgrpt.sh-strip-prefixes.patch \ |
| 23 | file://0001-d2-dhcp-46-radius-dhcpsrv-Avoid-Boost-lexical_cast-o.patch \ | 23 | file://0001-d2-dhcp-46-radius-dhcpsrv-Avoid-Boost-lexical_cast-o.patch \ |
| 24 | file://CVE-2025-11232.patch \ | ||
| 24 | " | 25 | " |
| 25 | SRC_URI[sha256sum] = "ec84fec4bb7f6b9d15a82e755a571e9348eb4d6fbc62bb3f6f1296cd7a24c566" | 26 | SRC_URI[sha256sum] = "ec84fec4bb7f6b9d15a82e755a571e9348eb4d6fbc62bb3f6f1296cd7a24c566" |
| 26 | 27 | ||
