]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4048] Backport CVE-2025-40779 fix
authorThomas Markwalder <tmark@isc.org>
Thu, 7 Aug 2025 18:14:27 +0000 (14:14 -0400)
committerRazvan Becheriu <razvan@isc.org>
Wed, 27 Aug 2025 17:54:32 +0000 (20:54 +0300)
new file:   changelog_unreleased/CVE-2025-40779-Kea-crash-on-108-in-PRL
modified:   src/bin/dhcp4/dhcp4_srv.cc
modified:   src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
modified:   src/bin/dhcp4/tests/dhcp4_test_utils.h

changelog_unreleased/CVE-2025-40779-Kea-crash-on-108-in-PRL [new file with mode: 0644]
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
src/bin/dhcp4/tests/dhcp4_test_utils.h

diff --git a/changelog_unreleased/CVE-2025-40779-Kea-crash-on-108-in-PRL b/changelog_unreleased/CVE-2025-40779-Kea-crash-on-108-in-PRL
new file mode 100644 (file)
index 0000000..22a04cc
--- /dev/null
@@ -0,0 +1,7 @@
+[sec]          tmark
+       Corrected an issue in kea-dhcp4 that caused
+       the server to exit after receiving a broadcast
+       query that requests option 108 and fails to
+       match a subnet.
+       CVE:2025-40779
+       (Gitlab #4048)
index 730afa3fe49babcc6bc785b87a3e7501d0963090..0701ed41e9f559279ad09ed5c6df17cd387e3fcb 100644 (file)
@@ -775,7 +775,6 @@ Dhcpv4Srv::shutdown() {
 isc::dhcp::ConstSubnet4Ptr
 Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query, bool& drop,
                         bool sanity_only, bool allow_answer_park) {
-
     // DHCPv4-over-DHCPv6 is a special (and complex) case
     if (query->isDhcp4o6()) {
         return (selectSubnet4o6(query, drop, sanity_only, allow_answer_park));
@@ -2969,21 +2968,23 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
     // allocation.
     bool fake_allocation = (query->getType() == DHCPDISCOVER);
 
-    // Check if IPv6-Only Preferred was requested.
-    OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
-        OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
-    if (option_prl) {
-        auto const& requested_opts = option_prl->getValues();
-        if ((std::find(requested_opts.cbegin(), requested_opts.cend(),
-                       DHO_V6_ONLY_PREFERRED) != requested_opts.cend()) &&
-            assignZero(subnet, query->getClasses())) {
-            ex.setIPv6OnlyPreferred(true);
-            ctx->subnet_ = subnet;
-            resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
-            if (!fake_allocation) {
-                resp->setCiaddr(query->getCiaddr());
+    if (subnet) {
+        // Check if IPv6-Only Preferred was requested.
+        OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
+            OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
+        if (option_prl) {
+            auto const& requested_opts = option_prl->getValues();
+            if ((std::find(requested_opts.cbegin(), requested_opts.cend(),
+                           DHO_V6_ONLY_PREFERRED) != requested_opts.cend()) &&
+                assignZero(subnet, query->getClasses())) {
+                ex.setIPv6OnlyPreferred(true);
+                ctx->subnet_ = subnet;
+                resp->setYiaddr(IOAddress::IPV4_ZERO_ADDRESS());
+                if (!fake_allocation) {
+                    resp->setCiaddr(query->getCiaddr());
+                }
+                return;
             }
-            return;
         }
     }
 
index 43bd2aa9473ad15e44122dc9673209c970bfd5ec..1310406e92640aee71d2c741929a76425626526b 100644 (file)
@@ -6265,6 +6265,38 @@ TEST_F(Dhcpv4SrvTest, v6OnlyPreferredRequestError) {
     EXPECT_FALSE(srv_->processRequest(req));
 }
 
+// Verify that a discover requesting v6-only-preferred
+// is handled gracefully when there is no matching subnet.
+TEST_F(Dhcpv4SrvTest, v6OnlyPreferredNoSelectedSubnet) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+    // Sort source address to non-matching subnet.
+    dis->setRemoteAddr(IOAddress("192.0.99.1"));
+    dis->setIface("eth1");
+    dis->setIndex(ETH1_INDEX);
+
+    // Add a PRL with v6-only-preferred.
+    OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+                                                 DHO_DHCP_PARAMETER_REQUEST_LIST));
+    ASSERT_TRUE(prl);
+    prl->addValue(DHO_V6_ONLY_PREFERRED);
+    dis->addOption(prl);
+
+    // Should not have gotten a response.
+    Pkt4Ptr resp = srv_->processDiscover(dis);
+    ASSERT_FALSE(resp);
+
+    // Should have logged a NAK_0001.
+    EXPECT_EQ(1, countFile("DHCP4_PACKET_NAK_0001 [hwtype=1 ], cid=[64:65:66:67],"
+                           " tid=0x4d2: failed to select a subnet for incoming packet,"
+                           " src 192.0.99.1, type DHCPDISCOVER"));
+
+}
+
 /// @brief Test fixture for recoverStashedAgentOption.
 class StashAgentOptionTest : public Dhcpv4SrvTest {
 public:
index c4867adcd10f2a08141936d354a2e0b06b485093..24223413698910a6e28dee28b22fc03fb0168a3e 100644 (file)
@@ -30,6 +30,7 @@
 #include <config/command_mgr.h>
 #include <config/unix_command_mgr.h>
 #include <util/multi_threading_mgr.h>
+#include <testutils/log_utils.h>
 #include <list>
 
 #include <boost/shared_ptr.hpp>
@@ -333,7 +334,7 @@ class Dhcp4Client;
 /// Currently it configures the test data path directory in
 /// the @c CfgMgr. When the object is destroyed, the original
 /// path is reverted.
-class BaseServerTest : public ::testing::Test {
+class BaseServerTest : public LogContentTest {
 public:
 
     /// @brief Constructor.