From nobody Wed Apr 15 15:06:20 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4fwkxZ0x3bz6Z1Dd for ; Wed, 15 Apr 2026 15:06:26 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R12" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4fwkxY5S7Wz3x6N for ; Wed, 15 Apr 2026 15:06:25 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1776265585; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=DeCpI7k7GVH6zB/mdC9F2b9VPmJPk6oNWzdFn24TlI4=; b=CcVswgCB4gaFCZfQAZKT8m5xE18F9Edntw78VZEQX5R1d/W3yUOP0hvM1L61QwjXEvUxKP iBuetyCqK2WhoP6TrgeqOkB+OJMInTXujMVvjSda/dcrh+zm/YTRETveQn9LFUpOi5zWj2 Bx4qoxDH9NJz1TGHGCtap12eMwCAHXBudbmfC76sMlYPZ8Y23z6nd62W2jjiFQ0cV+kA5+ /yx2ANE3HRpQsAlyUEehjsofd8GANOvGrL2+zGM3pfJh5XfvP2BhaVXSNqrKNsgu8Bt0bq 7gWrankT9AEA4Is+jMlXMsF3Z7oEWuQ9BzKqTY/SUcJycj0td7D2eryq/V3Yvg== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1776265585; a=rsa-sha256; cv=none; b=KKlA9pD6EKQr0wFQ9An3+UBUgdqKdch5g5pf7l77Ze28js/Txh6kcDI5hIvtVIXBK3ygrf kivETgnxGhesw1JN1Ai2vxzeDwHVR8YFkJyPlFUxfm4AdLi+pnS0lmm72pjv/d29DrXr0R xkGu9zeOaQL0muS5BCUgMWojnZtjtE64DorYi2FcEk9539jYHoblMMvzxzYxvf+w+7buey 35nHjIwwTjtFdNKvd3s+DOqoEYl15fUN66HxvKSJA9gXu1ExIsRUYoDOlE0vVJSX5LU4sU bjTwMsfNyzfC43lSk2lkx1/GXx1RTK66z48Ez6/I87nCNPNt3GacfjiP24leAQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1776265585; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=DeCpI7k7GVH6zB/mdC9F2b9VPmJPk6oNWzdFn24TlI4=; b=o/7NU92kGlK/WkczjilTNKUncrBWXKbox5D7tJJ+Y9SOyiGb/IVWX4BB28J7tSRyDhs9aH mcpRGonjfzkAZwNgsw7cjMV63HWZpwVsRdUjQS4XhntB1gYEb+XAYlMHCTIn7kiY6nGQbQ eqmGrZzYK/dkJsI7pdcq9+JCFmaye/7Nseq4iK7tX/Uwi8S4/HjpkbRfSaq6ly7URm1QTL EKC1WF+52Icb/jjw4cFbqOKRa0CutJBCtO16kjIUVguBvj3a2hVpwBPk5WwPzFgZcberiB 1ZMsh0EhulVTLyCrjK/CcKZItPyiRN/x+kg3M69MjmkKqOFoJdcVRgJPmYvkFQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4fwkxY4qdjz9hR for ; Wed, 15 Apr 2026 15:06:25 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 1c4b3 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Wed, 15 Apr 2026 15:06:20 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Mark Johnston Subject: git: 18b7115cba2f - main - ip_mroute: Fix a lock leak in X_ip_mforward() List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: markj X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 18b7115cba2f698909a4801dc2cc1b04b1f4f210 Auto-Submitted: auto-generated Date: Wed, 15 Apr 2026 15:06:20 +0000 Message-Id: <69dfa96c.1c4b3.13d03ab2@gitrepo.freebsd.org> The branch main has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=18b7115cba2f698909a4801dc2cc1b04b1f4f210 commit 18b7115cba2f698909a4801dc2cc1b04b1f4f210 Author: Mark Johnston AuthorDate: 2026-04-15 15:01:58 +0000 Commit: Mark Johnston CommitDate: 2026-04-15 15:06:05 +0000 ip_mroute: Fix a lock leak in X_ip_mforward() If a FIB does not have a router configured, X_ip_mforward() would leak a lock. Plug the leak. The IPv6 counterpart did not have such a check. It wouldn't send an upcall to a non-existent router anyway due to the router_ver check, but we should verify that a router is present anyway. Add regression test cases to exercise these code paths. Reported by: Claude Opus 4.6 Fixes: 0bb9c2b665d9 ("ip6_mroute: FIBify") Sponsored by: Klara, Inc. Sponsored by: Stormshield --- sys/netinet/ip_mroute.c | 4 +- sys/netinet6/ip6_mroute.c | 4 ++ tests/sys/netinet/ip_mroute.py | 153 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 147 insertions(+), 14 deletions(-) diff --git a/sys/netinet/ip_mroute.c b/sys/netinet/ip_mroute.c index 350f5db947af..359755b19e95 100644 --- a/sys/netinet/ip_mroute.c +++ b/sys/netinet/ip_mroute.c @@ -1385,8 +1385,10 @@ X_ip_mforward(struct ip *ip, struct ifnet *ifp, struct mbuf *m, * BEGIN: MCAST ROUTING HOT PATH */ MRW_RLOCK(); - if (__predict_false(mfct->router == NULL)) + if (__predict_false(mfct->router == NULL)) { + MRW_RUNLOCK(); return (EADDRNOTAVAIL); + } if (imo && ((vifi = imo->imo_multicast_vif) < mfct->numvifs)) { if (ip->ip_ttl < MAXTTL) diff --git a/sys/netinet6/ip6_mroute.c b/sys/netinet6/ip6_mroute.c index 9ae0d699ca31..0e8a0aa6f1ea 100644 --- a/sys/netinet6/ip6_mroute.c +++ b/sys/netinet6/ip6_mroute.c @@ -1191,6 +1191,10 @@ X_ip6_mforward(struct ip6_hdr *ip6, struct ifnet *ifp, struct mbuf *m) mfct = &V_mfctables[M_GETFIB(m)]; MFC6_LOCK(); + if (__predict_false(mfct->router == NULL)) { + MFC6_UNLOCK(); + return (EADDRNOTAVAIL); + } /* * Determine forwarding mifs from the forwarding cache table diff --git a/tests/sys/netinet/ip_mroute.py b/tests/sys/netinet/ip_mroute.py index 5416d824d3c2..c79b046445e5 100644 --- a/tests/sys/netinet/ip_mroute.py +++ b/tests/sys/netinet/ip_mroute.py @@ -21,7 +21,9 @@ class MRouteTestTemplate(VnetTestTemplate): COORD_SOCK = "coord.sock" @staticmethod - def _msgwait(sock: socket.socket, expected: bytes): + def _msgwait(sock: socket.socket, expected: bytes, timeout=None): + if timeout is not None: + sock.settimeout(timeout) msg = sock.recv(1024) assert msg == expected @@ -196,12 +198,7 @@ class Test1RBasicINET(MRouteINETTestTemplate): self.waittest() -class Test1RCrissCrossINET(MRouteINETTestTemplate): - """ - Test a router connected to four hosts, with pairs of interfaces - in different FIBs. - """ - +class MRouteINETCrissCrossTestTemplate(MRouteINETTestTemplate): TOPOLOGY = { "vnet_router": {"ifaces": ["if1", "if2", "if3", "if4"]}, "vnet_host1": {"ifaces": ["if1"]}, @@ -231,6 +228,14 @@ class Test1RCrissCrossINET(MRouteINETTestTemplate): } MULTICAST_ADDR = "239.0.0.1" + + +class Test1RCrissCrossINET(MRouteINETCrissCrossTestTemplate): + """ + Test a router connected to four hosts, with pairs of interfaces + in different FIBs. + """ + def setup_method(self, method): # Create VNETs and start the handlers. super().setup_method(method) @@ -288,6 +293,65 @@ class Test1RCrissCrossINET(MRouteINETTestTemplate): self.waittest() +class Test1RCrissCrossINETMissingRouter(MRouteINETCrissCrossTestTemplate): + """ + Test what happens when a router is configured for some FIBs but not others. + """ + + def setup_method(self, method): + # Create VNETs and start the handlers. + super().setup_method(method) + + # Only start a pimd instance in FIB 0. + ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]] + self.pimd0 = self.run_pimd("test0", ifaces, "127.0.0.1", + self.MULTICAST_ADDR + "/32", fib=0) + + time.sleep(3) # Give pimd a bit of time to get itself together. + + def vnet_host1_handler(self, vnet): + self.jointest(vnet) + + self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345) + self._msgwait(self.sock, b"Hello, Multicast on FIB 0!") + self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name, + b"Goodbye, Multicast on FIB 0!") + self.donetest() + + def vnet_host2_handler(self, vnet): + self.jointest(vnet) + self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345) + self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name, + b"Hello, Multicast on FIB 0!") + self._msgwait(self.sock, b"Hello, Multicast on FIB 0!") + self._msgwait(self.sock, b"Goodbye, Multicast on FIB 0!") + self.donetest() + + def vnet_host3_handler(self, vnet): + self.jointest(vnet) + self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345) + timedout = False + try: + self._msgwait(self.sock, b"Hello, Multicast on FIB 1!", timeout=5) + except socket.timeout: + timedout = True + assert timedout, "Received a message when we shouldn't have" + self.donetest() + + def vnet_host4_handler(self, vnet): + self.jointest(vnet) + self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345) + self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name, + b"Hello, Multicast on FIB 1!") + self.donetest() + + @pytest.mark.require_user("root") + @pytest.mark.require_progs(["pimd"]) + @pytest.mark.timeout(30) + def test(self): + self.starttest(["vnet_host1", "vnet_host2", "vnet_host3", "vnet_host4"]) + self.waittest() + class Test1RBasicINET6(MRouteINET6TestTemplate): """Basic multicast routing setup with 2 hosts connected via a router.""" @@ -343,12 +407,7 @@ class Test1RBasicINET6(MRouteINET6TestTemplate): self.waittest() -class Test1RCrissCrossINET6(MRouteINET6TestTemplate): - """ - Test a router connected to four hosts, with pairs of interfaces - in different FIBs. - """ - +class MRouteINET6CrissCrossTestTemplate(MRouteINET6TestTemplate): TOPOLOGY = { "vnet_router": {"ifaces": ["if1", "if2", "if3", "if4"]}, "vnet_host1": {"ifaces": ["if1"]}, @@ -374,6 +433,74 @@ class Test1RCrissCrossINET6(MRouteINET6TestTemplate): } MULTICAST_ADDR = "ff05::1" + +class Test1RCrissCrossINET6MissingRouter(MRouteINET6CrissCrossTestTemplate): + """ + Test what happens when a router is configured for some FIBs but not others. + """ + + def setup_method(self, method): + # Create VNETs and start the handlers. + super().setup_method(method) + + # Only start an ip6_mrouted instance in FIB 0. + ifaces = [self.vnet.iface_alias_map[i].name for i in ["if1", "if2"]] + self.mrouted0 = self.run_ip6_mrouted("test0", ifaces, fib=0) + time.sleep(1) # Give ip6_mrouted a bit of time to get itself together. + + def vnet_host1_handler(self, vnet): + self.jointest(vnet) + + self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name) + self._msgwait(self.sock, b"Hello, Multicast on FIB 0!") + self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name, + b"Goodbye, Multicast on FIB 0!") + self.donetest() + + def vnet_host2_handler(self, vnet): + self.jointest(vnet) + self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name) + self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name, + b"Hello, Multicast on FIB 0!") + self._msgwait(self.sock, b"Hello, Multicast on FIB 0!") + self._msgwait(self.sock, b"Goodbye, Multicast on FIB 0!") + self.donetest() + + def vnet_host3_handler(self, vnet): + self.jointest(vnet) + + self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, + vnet.ifaces[0].name) + timedout = False + try: + self._msgwait(self.sock, b"Hello, Multicast on FIB 1!", timeout=5) + except socket.timeout: + timedout = True + assert timedout, "Received a message when we shouldn't have" + self.donetest() + + def vnet_host4_handler(self, vnet): + self.jointest(vnet) + + self.sock = self.mcast_join(self.MULTICAST_ADDR, 12345, + vnet.ifaces[0].name) + self.mcast_sendto(self.MULTICAST_ADDR, 12345, vnet.ifaces[0].name, + b"Hello, Multicast on FIB 1!") + self.donetest() + + @pytest.mark.require_user("root") + @pytest.mark.timeout(30) + def test(self): + self.starttest(["vnet_host1", "vnet_host2", "vnet_host3", "vnet_host4"]) + self.waittest() + + +class Test1RCrissCrossINET6(MRouteINET6CrissCrossTestTemplate): + """ + Test a router connected to four hosts, with pairs of interfaces + in different FIBs. + """ + def setup_method(self, method): # Create VNETs and start the handlers. super().setup_method(method)